import React, { useState } from 'react' import { X, UploadCloud, Plus, Trash2, FileText, Tag, AlertCircle, Folder, Calendar, Sparkles, Info, } from 'lucide-react' import { CustomSelect } from '../ui/Select' import { reportService, type ReportTemplateOutput } from '../../services/reportService' import type { Project, Category } from '../../types/task' interface CreateTemplateModalProps { isOpen: boolean onClose: () => void onSuccess: (newTemplate: ReportTemplateOutput) => void orgId: number projects: Project[] categories: Category[] } interface SectionFormItem { sectionKey: string title: string sectionType: 'FREE_TEXT' | 'FIXED_TEXT' | 'TASK_LIST' defaultValue?: string filterCategoryId?: number filterStatus?: 'ALL' | 'COMPLETED' | 'IN_PROGRESS' includeTime: boolean } export const CreateTemplateModal: React.FC = ({ isOpen, onClose, onSuccess, orgId, projects, categories, }) => { const [name, setName] = useState('') const [description, setDescription] = useState('') const [scope, setScope] = useState<'ORGANIZATION' | 'PROJECT'>('ORGANIZATION') const [projectId, setProjectId] = useState(undefined) const [dateFormatPattern, setDateFormatPattern] = useState('DD/MM/AAAA até DD/MM/AAAA') const [file, setFile] = useState(null) const [sections, setSections] = useState([]) const [loading, setLoading] = useState(false) const [error, setError] = useState(null) if (!isOpen) return null const handleAddSection = () => { const nextIndex = sections.length + 1 setSections((prev) => [ ...prev, { sectionKey: `campo_${nextIndex}`, title: `Campo ${nextIndex}`, sectionType: 'FREE_TEXT', defaultValue: '', 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) if (!name) { setName(selected.name.replace(/\.[^/.]+$/, '').replace(/_/g, ' ')) } } } const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() if (!name.trim()) { setError('Informe o nome do modelo de relatório') return } if (!file) { setError('Faça o upload de um arquivo .docx para o modelo') return } try { setLoading(true) setError(null) const formData = new FormData() formData.append('file', file) formData.append('name', name.trim()) formData.append('description', description.trim()) formData.append('scope', scope) formData.append('dateFormatPattern', dateFormatPattern) if (scope === 'PROJECT' && projectId) { formData.append('projectId', String(projectId)) } // Sanitizar seções com chave segura e valor padrão 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() : '', filterCategoryId: s.filterCategoryId, filterStatus: s.filterStatus || 'ALL', includeTime: s.includeTime ?? true, orderIndex: idx, })) formData.append('sectionsJson', JSON.stringify(sanitizedSections)) const created = await reportService.createTemplate(orgId, formData) onSuccess(created) onClose() } catch (err: unknown) { console.error(err) setError(err instanceof Error ? err.message : 'Falha ao salvar modelo de relatório') } finally { setLoading(false) } } return (
{/* Header */}

Cadastrar Modelo de Relatório (.DOCX)

Envie seu arquivo Word e configure os campos dinâmicos e valores pré-preenchidos.

{/* 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 e Formato do Período */}
setDescription(e.target.value)} placeholder="Ex.: Modelo oficial para prestação de contas" 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" />
setDateFormatPattern(String(val))} options={[ { 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 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)' }, ]} />
{/* Dica de Tags Nativas */}

Tags Padrão Automáticas Disponíveis no Word:

Você pode utilizar diretamente as tags{' '} {'{{nome}}'} (usuário logado),{' '} {'{{periodo}}'} (período formatado),{' '} {'{{data_inicio}}'},{' '} {'{{data_fim}}'} e{' '} {'{{total_horas}}'} sem precisar cadastrar seções para elas.

{/* Upload de Arquivo .DOCX */}
{/* 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 pré-preenchido ou listagem de tarefas.

{sections.length === 0 ? (

Nenhum campo personalizado adicionado

Caso seu documento precise de variáveis como Gestor, Nome do Projeto, Resumo ou Lista de Tarefas, clique em Adicionar Campo / Seção.

) : (
{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 resumo" 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: Nome do Gestor" 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' | 'TASK_LIST', }) } options={[ { value: 'FREE_TEXT', label: 'Texto Livre (Preenchido na emissão)' }, { value: 'FIXED_TEXT', label: 'Texto Fixo (Pré-preenchido no cadastro)' }, { value: 'TASK_LIST', label: 'Lista de Tarefas do Período' }, ]} />
{/* 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, })), ]} />
)}
))}
)}
{/* Footer */}
) }