import React, { useState, useEffect } from 'react' import { X, UploadCloud, Plus, Trash2, FileText, Tag, AlertCircle, Folder, Calendar, Sparkles, Info, Check, PenTool, } from 'lucide-react' import { CustomSelect } from '../ui/Select' import { reportService, type ReportTemplateOutput } from '../../services/reportService' import type { Project, Category } from '../../types/task' interface EditTemplateModalProps { isOpen: boolean onClose: () => void onSuccess: (updatedTemplate: ReportTemplateOutput) => void template: ReportTemplateOutput orgId: number projects: Project[] categories: Category[] } interface SectionFormItem { sectionKey: string title: string sectionType: 'FREE_TEXT' | 'FIXED_TEXT' | 'DATE' | 'TASK_LIST' | 'SIGNATURE' | 'AI_TEXT' defaultValue?: string dateFormat?: string aiPrompt?: string filterCategoryId?: number filterStatus?: 'ALL' | 'COMPLETED' | 'IN_PROGRESS' includeTime: boolean } export const EditTemplateModal: React.FC = ({ isOpen, onClose, onSuccess, template, orgId, projects, categories, }) => { const [name, setName] = useState(template.name) const [description, setDescription] = useState(template.description || '') const [scope, setScope] = useState<'ORGANIZATION' | 'PROJECT'>(template.scope) const [projectId, setProjectId] = useState(template.projectId) const [file, setFile] = useState(null) const [sections, setSections] = useState([]) const [loading, setLoading] = useState(false) const [error, setError] = useState(null) useEffect(() => { setName(template.name) setDescription(template.description || '') setScope(template.scope) setProjectId(template.projectId) setFile(null) setSections( template.sections?.map((s) => ({ sectionKey: s.sectionKey, title: s.title, sectionType: s.sectionType, defaultValue: s.defaultValue || '', dateFormat: s.dateFormat || 'DD/MM/AAAA', aiPrompt: s.aiPrompt || '', filterCategoryId: s.filterCategoryId, filterStatus: s.filterStatus || 'ALL', includeTime: s.includeTime ?? true, })) || [] ) }, [template]) if (!isOpen) return null const handleAddSection = () => { const nextIndex = sections.length + 1 setSections((prev) => [ ...prev, { sectionKey: `campo_${nextIndex}`, title: `Campo ${nextIndex}`, sectionType: 'FREE_TEXT', defaultValue: '', dateFormat: 'DD/MM/AAAA', includeTime: false, }, ]) } const handleRemoveSection = (index: number) => { setSections((prev) => prev.filter((_, i) => i !== index)) } const handleUpdateSection = (index: number, patch: Partial) => { setSections((prev) => prev.map((s, i) => (i === index ? { ...s, ...patch } : s)) ) } const handleFileChange = (e: React.ChangeEvent) => { if (e.target.files && e.target.files[0]) { const selected = e.target.files[0] if (!selected.name.toLowerCase().endsWith('.docx')) { setError('O modelo deve ser um arquivo com extensão .docx (Word)') return } setFile(selected) setError(null) } } const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() if (!name.trim()) { setError('Informe o nome do modelo de relatório') return } try { setLoading(true) setError(null) const formData = new FormData() if (file) { formData.append('file', file) } formData.append('name', name.trim()) formData.append('description', description.trim()) formData.append('scope', scope) if (scope === 'PROJECT' && projectId) { formData.append('projectId', String(projectId)) } // Sanitizar seções com chave segura, valor padrão e formato de data const sanitizedSections = sections.map((s, idx) => ({ sectionKey: s.sectionKey.trim().toLowerCase().replace(/[^a-z0-9_]/g, '_'), title: s.title.trim() || s.sectionKey, sectionType: s.sectionType, defaultValue: s.defaultValue ? s.defaultValue.trim() : '', dateFormat: s.sectionType === 'DATE' ? (s.dateFormat || 'DD/MM/AAAA') : undefined, aiPrompt: s.sectionType === 'AI_TEXT' ? (s.aiPrompt?.trim() || undefined) : undefined, filterCategoryId: s.filterCategoryId, filterStatus: s.filterStatus || 'ALL', includeTime: s.includeTime ?? true, orderIndex: idx, })) formData.append('sectionsJson', JSON.stringify(sanitizedSections)) const updated = await reportService.updateTemplate(orgId, template.id, formData) onSuccess(updated) onClose() } catch (err: unknown) { console.error(err) setError(err instanceof Error ? err.message : 'Falha ao atualizar modelo de relatório') } finally { setLoading(false) } } return (
{/* Header */}

Editar Modelo de Relatório (.DOCX)

Altere configurações, tags, campos ou substitua o arquivo Word do modelo.

{/* Form Body */}
{error && (
{error}
)} {/* Nome e Escopo */}
setName(e.target.value)} placeholder="Ex.: Relatório Mensal de Atividades P&D" className="w-full px-3.5 py-2.5 rounded-xl border border-border-soft bg-surface text-xs text-foreground focus:outline-hidden focus:border-brand focus:ring-1 focus:ring-brand shadow-xs" required />
setScope(val as 'ORGANIZATION' | 'PROJECT')} options={[ { value: 'ORGANIZATION', label: 'Organização' }, { value: 'PROJECT', label: 'Projeto Específico' }, ]} />
{/* Se for escopo de Projeto, selecionar qual */} {scope === 'PROJECT' && (
setProjectId(Number(val) === 0 ? undefined : Number(val))} placeholder="Selecione o Projeto..." leftIcon={} options={[ { value: 0, label: 'Selecione o Projeto...' }, ...projects.map((p) => ({ value: p.id, label: p.name, color: p.color || '#0f766e', })), ]} />
)} {/* Descrição */}
setDescription(e.target.value)} placeholder="Ex.: Modelo padrão exigido para prestação de contas mensal." className="w-full px-3.5 py-2 rounded-xl border border-border-soft bg-surface text-xs text-foreground focus:outline-hidden focus:border-brand shadow-xs" />
{/* Dica de Tags Nativas */}

Tags Nativas Automáticas Disponíveis no Word:

Você pode utilizar diretamente as tags{' '} {'{{nome}}'} (usuário logado),{' '} {'{{data_inicio}}'} e{' '} {'{{data_fim}}'} sem precisar cadastrar campos para elas. Para listagens de tarefas, cadastre uma seção do tipo Listagem de Tarefas definindo a tag desejada.

{/* Upload de Arquivo .DOCX (Substituição Opcional) */}
{/* Construtor de Campos e Seções */}

Campos e Seções do Relatório

Crie tags que serão substituídas pelo Word. Escolha se o campo é texto livre, texto fixo, data (com formato personalizado) ou listagem de tarefas.

{sections.length > 0 && ( {sections.length} {sections.length === 1 ? 'campo' : 'campos'} )}
{sections.length === 0 ? (

Nenhum campo personalizado cadastrado

Caso seu documento precise de variáveis como Gestor, Nome do Projeto, Data formatada, Resumo ou Lista de Tarefas, adicione campos abaixo.

) : (
{sections.map((section, idx) => (
Tag no Word:{' '} {`{{${section.sectionKey || 'tag'}}}`}
handleUpdateSection(idx, { sectionKey: e.target.value.toLowerCase().replace(/[^a-z0-9_]/g, '_'), }) } placeholder="ex: gestor ou periodo" className="w-full px-2.5 py-1.5 rounded-lg border border-border-soft bg-surface text-xs font-mono text-foreground focus:outline-hidden focus:border-brand" />
handleUpdateSection(idx, { title: e.target.value })} placeholder="ex: Período de Vigência" className="w-full px-2.5 py-1.5 rounded-lg border border-border-soft bg-surface text-xs text-foreground focus:outline-hidden focus:border-brand" />
handleUpdateSection(idx, { sectionType: val as 'FREE_TEXT' | 'FIXED_TEXT' | 'DATE' | 'TASK_LIST' | 'SIGNATURE' | 'AI_TEXT', }) } options={[ { value: 'FREE_TEXT', label: 'Texto Livre (Preenchido na emissão)' }, { value: 'FIXED_TEXT', label: 'Texto Fixo (Pré-preenchido no cadastro)' }, { value: 'DATE', label: 'Data (Com formato personalizado)' }, { value: 'SIGNATURE', label: '✍️ Assinatura Digital (Assinatura do usuário)' }, { value: 'AI_TEXT', label: '✨ Resumo / Texto com IA (Ollama)' }, { value: 'TASK_LIST', label: 'Lista de Tarefas do Período' }, ]} />
{/* Configuração de Campo de Tipo IA */} {section.sectionType === 'AI_TEXT' && (

Geração Inteligente com IA Local (Ollama / Qwen)

A IA lerá as tarefas do colaborador no período (título, descrição, categoria, projeto) e gerará o resumo conforme o prompt configurado abaixo. Na emissão do relatório, o usuário poderá clicar em "Gerar com IA" e editar o resultado antes de baixar o documento.