import React, { useState, useEffect } from 'react' import { X, UploadCloud, Plus, Trash2, FileText, Tag, AlertCircle, Folder, Calendar, Sparkles, Info, Check, } 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' defaultValue?: string dateFormat?: 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', 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, 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),{' '} {'{{periodo}}'} (período de vigência),{' '} {'{{data_inicio}}'},{' '} {'{{data_fim}}'} e{' '} {'{{total_horas}}'} sem precisar cadastrar campos para elas.

{/* 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', }) } 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: 'TASK_LIST', label: 'Lista de Tarefas do Período' }, ]} />
{/* Configuração de Campo de Tipo DATA */} {section.sectionType === 'DATE' && (
handleUpdateSection(idx, { dateFormat: String(val) })} options={[ { value: 'DD/MM/AAAA', label: 'DD/MM/AAAA (ex: 31/10/2026)' }, { value: 'DD-MM-AAAA', label: 'DD-MM-AAAA (ex: 31-10-2026)' }, { value: 'AAAA-MM-DD', label: 'AAAA-MM-DD (ex: 2026-10-31)' }, { value: 'DD/MM/AAAA até DD/MM/AAAA', label: 'DD/MM/AAAA até DD/MM/AAAA (ex: 01/10/2026 até 31/10/2026)' }, { value: 'DD-MM-AAAA até DD-MM-AAAA', label: 'DD-MM-AAAA até DD-MM-AAAA (ex: 01-10-2026 até 31-10-2026)' }, { value: 'DD/MM/AAAA a DD/MM/AAAA', label: 'DD/MM/AAAA a DD/MM/AAAA (ex: 01/10/2026 a 31/10/2026)' }, { value: 'DD-MM-AAAA a DD-MM-AAAA', label: 'DD-MM-AAAA a DD-MM-AAAA (ex: 01-10-2026 a 31-10-2026)' }, { value: 'DD de Mês de AAAA', label: 'DD de Mês de AAAA (ex: 31 de Outubro de 2026)' }, { value: 'DD de Mês de AAAA até DD de Mês de AAAA', label: '01 de Outubro de 2026 até 31 de Outubro de 2026' }, { value: 'MM/AAAA', label: 'MM/AAAA (ex: 10/2026)' }, { value: 'Mês de AAAA', label: 'Mês de AAAA (ex: Outubro de 2026)' }, { value: 'DD/MM - DD/MM/AAAA', label: 'DD/MM - DD/MM/AAAA (ex: 01/10 - 31/10/2026)' }, ]} />
)} {/* Campo de Valor Pré-preenchido (Fixo ou Sugerido) */} {(section.sectionType === 'FIXED_TEXT' || section.sectionType === 'FREE_TEXT') && (
handleUpdateSection(idx, { defaultValue: e.target.value })} placeholder={ section.sectionType === 'FIXED_TEXT' ? 'Ex.: Dr. Roberto Silva ou Nome do Projeto' : 'Ex.: Deixar uma observação inicial...' } 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" />
)} {/* Configurações de Lista de Tarefas */} {section.sectionType === 'TASK_LIST' && (
handleUpdateSection(idx, { filterCategoryId: Number(val) === 0 ? undefined : Number(val), }) } options={[ { value: 0, label: 'Todas as Categorias' }, ...categories.map((c) => ({ value: c.id, label: c.name, color: c.color, })), ]} />
)}
))}
)} {/* Botão de Adicionar Campo / Seção embaixo */}
{/* Footer */}
) }