529 lines
24 KiB
TypeScript
529 lines
24 KiB
TypeScript
import React, { useState, useEffect, useCallback } from 'react'
|
|
import {
|
|
FileText,
|
|
Plus,
|
|
FileDown,
|
|
Sparkles,
|
|
Download,
|
|
Trash2,
|
|
Tag,
|
|
Copy,
|
|
Check,
|
|
Clock,
|
|
AlertCircle,
|
|
} from 'lucide-react'
|
|
import { useAuth } from '../contexts/AuthContext'
|
|
import {
|
|
reportService,
|
|
type ReportTemplateOutput,
|
|
type GeneratedReportOutput,
|
|
} from '../services/reportService'
|
|
import { projectService } from '../services/projectService'
|
|
import { categoryService } from '../services/categoryService'
|
|
import type { Project, Category } from '../types/task'
|
|
import { CreateTemplateModal } from '../components/reports/CreateTemplateModal'
|
|
import { GenerateReportModal } from '../components/reports/GenerateReportModal'
|
|
|
|
export const ReportsPage: React.FC = () => {
|
|
const { activeOrg } = useAuth()
|
|
const orgId = activeOrg?.id
|
|
|
|
const [activeTab, setActiveTab] = useState<'templates' | 'history' | 'guide'>('templates')
|
|
const [templates, setTemplates] = useState<ReportTemplateOutput[]>([])
|
|
const [history, setHistory] = useState<GeneratedReportOutput[]>([])
|
|
const [projects, setProjects] = useState<Project[]>([])
|
|
const [categories, setCategories] = useState<Category[]>([])
|
|
|
|
const [loading, setLoading] = useState(true)
|
|
const [error, setError] = useState<string | null>(null)
|
|
const [copiedTag, setCopiedTag] = useState<string | null>(null)
|
|
|
|
// Modais
|
|
const [isCreateOpen, setIsCreateOpen] = useState(false)
|
|
const [isGenerateOpen, setIsGenerateOpen] = useState(false)
|
|
const [generateInitialTemplateId, setGenerateInitialTemplateId] = useState<number | undefined>(undefined)
|
|
|
|
const loadData = useCallback(async () => {
|
|
if (!orgId) return
|
|
try {
|
|
setLoading(true)
|
|
setError(null)
|
|
const [tpls, hist, projs, cats] = await Promise.all([
|
|
reportService.listTemplates(orgId),
|
|
reportService.listHistory(orgId).catch(() => []),
|
|
projectService.listProjects(orgId).catch(() => []),
|
|
categoryService.listCategories(orgId).catch(() => []),
|
|
])
|
|
setTemplates(tpls)
|
|
setHistory(hist)
|
|
setProjects(projs)
|
|
setCategories(cats)
|
|
} catch (err: unknown) {
|
|
console.error(err)
|
|
setError('Falha ao carregar modelos e relatórios')
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}, [orgId])
|
|
|
|
useEffect(() => {
|
|
loadData()
|
|
}, [loadData])
|
|
|
|
const handleDeleteTemplate = async (templateId: number) => {
|
|
if (!orgId) return
|
|
if (!window.confirm('Tem certeza de que deseja excluir este modelo de relatório?')) return
|
|
try {
|
|
await reportService.deleteTemplate(orgId, templateId)
|
|
setTemplates((prev) => prev.filter((t) => t.id !== templateId))
|
|
} catch (err: unknown) {
|
|
console.error(err)
|
|
alert(err instanceof Error ? err.message : 'Erro ao excluir modelo')
|
|
}
|
|
}
|
|
|
|
const handleOpenGenerate = (templateId?: number) => {
|
|
setGenerateInitialTemplateId(templateId)
|
|
setIsGenerateOpen(true)
|
|
}
|
|
|
|
const handleCopyTag = (tag: string) => {
|
|
navigator.clipboard.writeText(tag)
|
|
setCopiedTag(tag)
|
|
setTimeout(() => setCopiedTag(null), 2000)
|
|
}
|
|
|
|
const standardTags = [
|
|
{ tag: '{{nome}}', desc: 'Nome do colaborador (pega automaticamente do usuário logado)' },
|
|
{ tag: '{{periodo}}', desc: 'Período formatado no padrão escolhido no modelo (ex.: 01/10/2026 até 31/10/2026)' },
|
|
{ tag: '{{data_inicio}}', desc: 'Data inicial do período escolhido (ex: 01/10/2026)' },
|
|
{ tag: '{{data_fim}}', desc: 'Data final do período escolhido (ex: 31/10/2026)' },
|
|
{ tag: '{{periodo_vigencia}}', desc: 'Intervalo formatado no modelo (ex: 01/10/2026 até 31/10/2026)' },
|
|
{ tag: '{{mes_ano}}', desc: 'Mês e ano por extenso (ex: Outubro/2026)' },
|
|
{ tag: '{{email_usuario}}', desc: 'E-mail do usuário logado' },
|
|
{ tag: '{{organizacao}}', desc: 'Nome da organização / workspace ativo' },
|
|
{ tag: '{{total_horas}}', desc: 'Total de horas trabalhadas no período (ex: 142h 30m)' },
|
|
{ tag: '{{data_emissao}}', desc: 'Data de geração do relatório (ex: 31/10/2026)' },
|
|
]
|
|
|
|
const tableItemTags = [
|
|
{ tag: '{{item.titulo}}', desc: 'Título da tarefa (em linhas repetidas de tabelas)' },
|
|
{ tag: '{{item.data}}', desc: 'Data da tarefa / conclusão' },
|
|
{ tag: '{{item.tempo}}', desc: 'Duração / horas trabalhadas' },
|
|
{ tag: '{{item.status}}', desc: 'Status da tarefa (Concluído, Em Andamento)' },
|
|
{ tag: '{{item.categoria}}', desc: 'Nome da categoria (ex: Reunião, Feature)' },
|
|
{ tag: '{{item.projeto}}', desc: 'Nome do projeto da tarefa' },
|
|
]
|
|
|
|
return (
|
|
<div className="space-y-6 max-w-7xl mx-auto">
|
|
{/* Top Banner */}
|
|
{error && (
|
|
<div className="p-3.5 rounded-2xl bg-red-500/10 border border-red-500/20 text-red-700 dark:text-red-400 text-xs flex items-center gap-2">
|
|
<AlertCircle className="w-4 h-4 shrink-0" />
|
|
<span>{error}</span>
|
|
</div>
|
|
)}
|
|
|
|
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4 pb-2">
|
|
<div className="space-y-1">
|
|
<div className="flex items-center gap-2 flex-wrap">
|
|
<div className="inline-flex items-center gap-1.5 px-3 py-1 rounded-full bg-brand/10 border border-brand/20 text-xs font-semibold text-brand">
|
|
<span className="w-1.5 h-1.5 rounded-full bg-brand animate-pulse" />
|
|
<span>Workspace: {activeOrg?.name || 'BeeTask'}</span>
|
|
</div>
|
|
<span className="px-2.5 py-0.5 rounded-full bg-accent/15 text-amber-700 dark:text-amber-300 border border-accent/25 text-[11px] font-bold">
|
|
Templates .DOCX
|
|
</span>
|
|
</div>
|
|
|
|
<h1 className="text-2xl sm:text-3xl font-extrabold tracking-tight text-foreground">
|
|
Central de Relatórios
|
|
</h1>
|
|
<p className="text-xs sm:text-sm text-muted">
|
|
Cadastre seus modelos corporativos em Word e gere relatórios de atividades e reuniões com preenchimento 100% automático.
|
|
</p>
|
|
</div>
|
|
|
|
{/* Action Buttons */}
|
|
<div className="flex items-center gap-2.5 flex-wrap">
|
|
<button
|
|
type="button"
|
|
onClick={() => setIsCreateOpen(true)}
|
|
className="inline-flex items-center gap-2 px-4 py-2.5 rounded-xl border border-border-soft hover:border-brand/40 bg-surface/80 hover:bg-surface-raised text-foreground text-xs font-bold transition-all shadow-xs cursor-pointer"
|
|
>
|
|
<Plus className="w-4 h-4 text-brand" />
|
|
<span>Novo Modelo .DOCX</span>
|
|
</button>
|
|
|
|
<button
|
|
type="button"
|
|
onClick={() => handleOpenGenerate()}
|
|
disabled={templates.length === 0}
|
|
className="inline-flex items-center gap-2 px-5 py-2.5 rounded-xl bg-gradient-to-r from-brand to-teal-700 hover:from-brand-hover hover:to-teal-800 text-white text-xs font-bold shadow-md shadow-brand/20 transition-all cursor-pointer disabled:opacity-50"
|
|
>
|
|
<FileDown className="w-4 h-4" />
|
|
<span>Gerar Relatório</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Tabs */}
|
|
<div className="border-b border-border-soft">
|
|
<nav className="flex space-x-6" aria-label="Tabs">
|
|
<button
|
|
type="button"
|
|
onClick={() => setActiveTab('templates')}
|
|
className={`py-3 px-1 border-b-2 font-bold text-xs sm:text-sm cursor-pointer transition-all flex items-center gap-2 ${
|
|
activeTab === 'templates'
|
|
? 'border-brand text-brand'
|
|
: 'border-transparent text-muted hover:text-foreground'
|
|
}`}
|
|
>
|
|
<FileText className="w-4 h-4" />
|
|
<span>Modelos de Relatório ({templates.length})</span>
|
|
</button>
|
|
|
|
<button
|
|
type="button"
|
|
onClick={() => setActiveTab('history')}
|
|
className={`py-3 px-1 border-b-2 font-bold text-xs sm:text-sm cursor-pointer transition-all flex items-center gap-2 ${
|
|
activeTab === 'history'
|
|
? 'border-brand text-brand'
|
|
: 'border-transparent text-muted hover:text-foreground'
|
|
}`}
|
|
>
|
|
<Clock className="w-4 h-4" />
|
|
<span>Histórico de Emissões ({history.length})</span>
|
|
</button>
|
|
|
|
<button
|
|
type="button"
|
|
onClick={() => setActiveTab('guide')}
|
|
className={`py-3 px-1 border-b-2 font-bold text-xs sm:text-sm cursor-pointer transition-all flex items-center gap-2 ${
|
|
activeTab === 'guide'
|
|
? 'border-brand text-brand'
|
|
: 'border-transparent text-muted hover:text-foreground'
|
|
}`}
|
|
>
|
|
<Tag className="w-4 h-4" />
|
|
<span>Guia de Tags do Word</span>
|
|
</button>
|
|
</nav>
|
|
</div>
|
|
|
|
{/* Tab 1: Modelos de Relatório */}
|
|
{activeTab === 'templates' && (
|
|
<div className="space-y-4">
|
|
{loading ? (
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
{[1, 2, 3].map((i) => (
|
|
<div
|
|
key={i}
|
|
className="h-44 rounded-3xl border border-border-soft bg-surface-sunken/40 animate-pulse p-6"
|
|
/>
|
|
))}
|
|
</div>
|
|
) : templates.length === 0 ? (
|
|
<div className="p-12 text-center rounded-3xl border border-dashed border-border-soft bg-surface/60 backdrop-blur-md space-y-4">
|
|
<div className="w-14 h-14 rounded-2xl bg-brand/10 text-brand flex items-center justify-center mx-auto">
|
|
<FileText className="w-7 h-7" />
|
|
</div>
|
|
<div className="max-w-md mx-auto space-y-1">
|
|
<h3 className="text-base font-bold text-foreground">
|
|
Nenhum modelo de relatório cadastrado
|
|
</h3>
|
|
<p className="text-xs text-muted">
|
|
Faça o upload do seu primeiro template em formato Word (.docx) com a identidade visual da sua organização.
|
|
</p>
|
|
</div>
|
|
<button
|
|
type="button"
|
|
onClick={() => setIsCreateOpen(true)}
|
|
className="inline-flex items-center gap-2 px-5 py-2.5 rounded-xl bg-brand hover:bg-brand-hover text-white text-xs font-bold shadow-md shadow-brand/20 transition-all cursor-pointer"
|
|
>
|
|
<Plus className="w-4 h-4" />
|
|
<span>Cadastrar Primeiro Modelo</span>
|
|
</button>
|
|
</div>
|
|
) : (
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
{templates.map((tpl) => (
|
|
<div
|
|
key={tpl.id}
|
|
className="p-5 rounded-3xl border border-border-soft bg-surface/90 backdrop-blur-md shadow-panel hover:-translate-y-0.5 hover:shadow-xl transition-all duration-200 flex flex-col justify-between space-y-4"
|
|
>
|
|
<div className="space-y-3">
|
|
<div className="flex items-start justify-between gap-3">
|
|
<div className="w-10 h-10 rounded-2xl bg-brand/10 border border-brand/20 text-brand flex items-center justify-center shrink-0">
|
|
<FileText className="w-5 h-5" />
|
|
</div>
|
|
<span className="px-2.5 py-0.5 rounded-full text-[10px] font-bold bg-surface-raised border border-border-soft text-muted">
|
|
{tpl.scope === 'PROJECT' && tpl.projectName
|
|
? `Projeto: ${tpl.projectName}`
|
|
: 'Organização'}
|
|
</span>
|
|
</div>
|
|
|
|
<div>
|
|
<h3 className="text-sm font-bold text-foreground line-clamp-1">{tpl.name}</h3>
|
|
{tpl.description && (
|
|
<p className="text-xs text-muted line-clamp-2 mt-1">{tpl.description}</p>
|
|
)}
|
|
</div>
|
|
|
|
{/* Informações adicionais se houver */}
|
|
{(tpl.managerName || tpl.customProjectName || tpl.projectName) && (
|
|
<div className="p-2.5 rounded-xl bg-surface-raised/60 border border-border-soft text-[11px] space-y-1">
|
|
{tpl.managerName && (
|
|
<div className="flex items-center justify-between gap-1 text-muted">
|
|
<span>Gestor:</span>
|
|
<span className="font-semibold text-foreground truncate">{tpl.managerName}</span>
|
|
</div>
|
|
)}
|
|
{(tpl.customProjectName || tpl.projectName) && (
|
|
<div className="flex items-center justify-between gap-1 text-muted">
|
|
<span>Projeto:</span>
|
|
<span className="font-semibold text-foreground truncate">{tpl.customProjectName || tpl.projectName}</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
{/* Tags configuradas */}
|
|
<div className="space-y-1.5 pt-1">
|
|
<span className="text-[10px] font-bold text-muted uppercase tracking-wider block">
|
|
Campos e Seções ({tpl.sections.length})
|
|
</span>
|
|
<div className="flex flex-wrap gap-1.5 max-h-20 overflow-y-auto">
|
|
{tpl.sections.map((s) => (
|
|
<span
|
|
key={s.sectionKey}
|
|
title={`${s.title} (${s.sectionType}${s.dateFormat ? ` — Formato: ${s.dateFormat}` : ''}${s.defaultValue ? ` — Valor fixo: ${s.defaultValue}` : ''})`}
|
|
className="px-2 py-0.5 rounded-md text-[10px] font-mono font-semibold bg-surface-raised border border-border-soft text-foreground/80 inline-flex items-center gap-1"
|
|
>
|
|
<span>{`{{${s.sectionKey}}}`}</span>
|
|
{s.sectionType === 'DATE' && (
|
|
<span className="text-[9px] text-brand font-sans font-bold">[{s.dateFormat || 'data'}]</span>
|
|
)}
|
|
{s.defaultValue && (
|
|
<span className="w-1.5 h-1.5 rounded-full bg-brand" title="Valor pré-preenchido" />
|
|
)}
|
|
</span>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Actions */}
|
|
<div className="flex items-center justify-between pt-3 border-t border-border-soft/60">
|
|
<button
|
|
type="button"
|
|
onClick={() => handleDeleteTemplate(tpl.id)}
|
|
className="p-1.5 rounded-xl text-muted hover:text-rose-500 hover:bg-rose-500/10 transition-colors cursor-pointer"
|
|
title="Excluir Modelo"
|
|
>
|
|
<Trash2 className="w-4 h-4" />
|
|
</button>
|
|
|
|
<div className="flex items-center gap-2">
|
|
<a
|
|
href={tpl.fileUrl}
|
|
target="_blank"
|
|
rel="noreferrer"
|
|
className="p-1.5 rounded-xl text-muted hover:text-foreground hover:bg-surface-raised transition-colors"
|
|
title="Baixar Template Original (.docx)"
|
|
>
|
|
<Download className="w-4 h-4" />
|
|
</a>
|
|
|
|
<button
|
|
type="button"
|
|
onClick={() => handleOpenGenerate(tpl.id)}
|
|
className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-xl bg-brand/10 hover:bg-brand text-brand hover:text-white text-xs font-bold transition-all cursor-pointer"
|
|
>
|
|
<FileDown className="w-3.5 h-3.5" />
|
|
<span>Gerar</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
{/* Tab 2: Histórico de Emissões */}
|
|
{activeTab === 'history' && (
|
|
<div className="rounded-3xl border border-border-soft bg-surface/90 backdrop-blur-md shadow-panel overflow-hidden">
|
|
{history.length === 0 ? (
|
|
<div className="p-12 text-center text-xs text-muted">
|
|
Nenhum relatório foi emitido neste workspace ainda.
|
|
</div>
|
|
) : (
|
|
<div className="overflow-x-auto">
|
|
<table className="w-full text-left border-collapse text-xs">
|
|
<thead>
|
|
<tr className="border-b border-border-soft bg-surface-raised/60 text-muted font-bold">
|
|
<th className="py-3 px-4">Relatório / Período</th>
|
|
<th className="py-3 px-4">Período de Vigência</th>
|
|
<th className="py-3 px-4">Emitido por</th>
|
|
<th className="py-3 px-4">Data de Emissão</th>
|
|
<th className="py-3 px-4 text-right">Download</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody className="divide-y divide-border-soft/60">
|
|
{history.map((rep) => (
|
|
<tr key={rep.id} className="hover:bg-surface-raised/40 transition-colors">
|
|
<td className="py-3.5 px-4 font-bold text-foreground flex items-center gap-2">
|
|
<FileText className="w-4 h-4 text-brand shrink-0" />
|
|
<span>{rep.title}</span>
|
|
</td>
|
|
<td className="py-3.5 px-4 text-muted font-mono">
|
|
{rep.startDate} até {rep.endDate}
|
|
</td>
|
|
<td className="py-3.5 px-4 text-foreground/90">
|
|
{rep.userName || 'Você'}
|
|
</td>
|
|
<td className="py-3.5 px-4 text-muted">
|
|
{new Date(rep.createdAt).toLocaleDateString('pt-BR')}
|
|
</td>
|
|
<td className="py-3.5 px-4 text-right">
|
|
<a
|
|
href={rep.fileUrl}
|
|
target="_blank"
|
|
rel="noreferrer"
|
|
className="inline-flex items-center gap-1.5 px-3 py-1 rounded-xl bg-brand/10 hover:bg-brand text-brand hover:text-white text-xs font-bold transition-all"
|
|
>
|
|
<Download className="w-3.5 h-3.5" />
|
|
<span>Baixar (.DOCX)</span>
|
|
</a>
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
{/* Tab 3: Guia de Tags do Word */}
|
|
{activeTab === 'guide' && (
|
|
<div className="space-y-6">
|
|
<div className="rounded-3xl border border-border-soft bg-surface/90 backdrop-blur-md p-6 shadow-panel space-y-4">
|
|
<div className="flex items-center gap-3">
|
|
<div className="w-10 h-10 rounded-2xl bg-amber-500/15 text-amber-600 dark:text-amber-400 border border-amber-500/25 flex items-center justify-center">
|
|
<Sparkles className="w-5 h-5" />
|
|
</div>
|
|
<div>
|
|
<h3 className="text-base font-extrabold text-foreground">
|
|
Como preparar seu Template no Microsoft Word (.docx)
|
|
</h3>
|
|
<p className="text-xs text-muted">
|
|
Basta abrir seu arquivo Word habitual e digitar as tags com chaves duplas nos locais onde deseja que as informações apareçam.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 pt-2">
|
|
{/* Tags Globais de Período e Usuário */}
|
|
<div className="p-4 rounded-2xl border border-border-soft bg-surface-raised/40 space-y-3">
|
|
<span className="text-xs font-bold text-foreground block">
|
|
Tags Globais de Período e Cabeçalho
|
|
</span>
|
|
<div className="space-y-2">
|
|
{standardTags.map((t) => (
|
|
<div
|
|
key={t.tag}
|
|
className="flex items-center justify-between gap-2 p-2 rounded-xl bg-surface border border-border-soft text-xs"
|
|
>
|
|
<div className="truncate">
|
|
<code className="text-brand font-mono font-bold">{t.tag}</code>
|
|
<p className="text-[11px] text-muted truncate">{t.desc}</p>
|
|
</div>
|
|
<button
|
|
type="button"
|
|
onClick={() => handleCopyTag(t.tag)}
|
|
className="p-1.5 rounded-lg text-muted hover:text-brand hover:bg-brand/10 transition-colors cursor-pointer shrink-0"
|
|
title="Copiar Tag"
|
|
>
|
|
{copiedTag === t.tag ? (
|
|
<Check className="w-3.5 h-3.5 text-emerald-500" />
|
|
) : (
|
|
<Copy className="w-3.5 h-3.5" />
|
|
)}
|
|
</button>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Tags em Tabelas Dinâmicas */}
|
|
<div className="p-4 rounded-2xl border border-border-soft bg-surface-raised/40 space-y-3">
|
|
<span className="text-xs font-bold text-foreground block">
|
|
Tags para Tabelas com Repetição de Linhas
|
|
</span>
|
|
<p className="text-[11px] text-muted leading-relaxed">
|
|
Crie uma tabela no Word com um cabeçalho e 1 linha contendo as tags abaixo. O BeeTask multiplicará as linhas para cada tarefa encontrada no período!
|
|
</p>
|
|
|
|
<div className="space-y-2">
|
|
{tableItemTags.map((t) => (
|
|
<div
|
|
key={t.tag}
|
|
className="flex items-center justify-between gap-2 p-2 rounded-xl bg-surface border border-border-soft text-xs"
|
|
>
|
|
<div className="truncate">
|
|
<code className="text-accent font-mono font-bold">{t.tag}</code>
|
|
<p className="text-[11px] text-muted truncate">{t.desc}</p>
|
|
</div>
|
|
<button
|
|
type="button"
|
|
onClick={() => handleCopyTag(t.tag)}
|
|
className="p-1.5 rounded-lg text-muted hover:text-brand hover:bg-brand/10 transition-colors cursor-pointer shrink-0"
|
|
title="Copiar Tag"
|
|
>
|
|
{copiedTag === t.tag ? (
|
|
<Check className="w-3.5 h-3.5 text-emerald-500" />
|
|
) : (
|
|
<Copy className="w-3.5 h-3.5" />
|
|
)}
|
|
</button>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Modais */}
|
|
{orgId && (
|
|
<>
|
|
<CreateTemplateModal
|
|
isOpen={isCreateOpen}
|
|
onClose={() => setIsCreateOpen(false)}
|
|
onSuccess={(newTemplate) => {
|
|
setTemplates((prev) => [newTemplate, ...prev])
|
|
}}
|
|
orgId={orgId}
|
|
projects={projects}
|
|
categories={categories}
|
|
/>
|
|
|
|
<GenerateReportModal
|
|
isOpen={isGenerateOpen}
|
|
onClose={() => setIsGenerateOpen(false)}
|
|
orgId={orgId}
|
|
templates={templates}
|
|
projects={projects}
|
|
initialTemplateId={generateInitialTemplateId}
|
|
/>
|
|
</>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|