Initial commit DBMaker - Oficiais e Funcionando
This commit is contained in:
272
src/pages/TemplateEdit.tsx
Normal file
272
src/pages/TemplateEdit.tsx
Normal file
@@ -0,0 +1,272 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useNavigate, useParams } from 'react-router-dom'
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import { ChevronLeft, ChevronRight } from 'lucide-react'
|
||||
import Button from '@/components/common/Button'
|
||||
import Input from '@/components/common/Input'
|
||||
import LoadingSpinner from '@/components/common/LoadingSpinner'
|
||||
import { TOPICOS_PADRAO } from '@/lib/constants'
|
||||
import { supabase } from '@/lib/supabase'
|
||||
import { updateTemplate } from '@/lib/mutations'
|
||||
import { toast } from '@/lib/toast'
|
||||
|
||||
export default function TemplateEdit() {
|
||||
const navigate = useNavigate()
|
||||
const { id } = useParams()
|
||||
const queryClient = useQueryClient()
|
||||
const [step, setStep] = useState(1)
|
||||
const [formData, setFormData] = useState({
|
||||
nome: '',
|
||||
tipo: 'novo' as 'novo' | 'derivado',
|
||||
template_pai_id: null as string | null,
|
||||
topicos_selecionados: [] as string[],
|
||||
descricao: '',
|
||||
})
|
||||
|
||||
const { data: template, isLoading } = useQuery({
|
||||
queryKey: ['template', id],
|
||||
queryFn: async () => {
|
||||
const { data, error } = await supabase
|
||||
.from('templates_customizados')
|
||||
.select('*')
|
||||
.eq('id', id)
|
||||
.single()
|
||||
|
||||
if (error) throw error
|
||||
return data as any
|
||||
},
|
||||
enabled: !!id,
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (template) {
|
||||
setFormData({
|
||||
nome: template.nome,
|
||||
tipo: template.tipo,
|
||||
template_pai_id: template.template_pai_id,
|
||||
topicos_selecionados: template.topicos_selecionados || [],
|
||||
descricao: template.descricao || '',
|
||||
})
|
||||
}
|
||||
}, [template])
|
||||
|
||||
const updateMutation = useMutation({
|
||||
mutationFn: () => updateTemplate(id!, {
|
||||
nome: formData.nome,
|
||||
topicos_selecionados: formData.topicos_selecionados,
|
||||
descricao: formData.descricao,
|
||||
}),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['templates'] })
|
||||
queryClient.invalidateQueries({ queryKey: ['template', id] })
|
||||
toast.success('Template atualizado com sucesso')
|
||||
navigate('/templates')
|
||||
},
|
||||
onError: () => {
|
||||
toast.error('Erro ao atualizar template')
|
||||
},
|
||||
})
|
||||
|
||||
const handleTopicoToggle = (numero: string) => {
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
topicos_selecionados: prev.topicos_selecionados.includes(numero)
|
||||
? prev.topicos_selecionados.filter(t => t !== numero)
|
||||
: [...prev.topicos_selecionados, numero]
|
||||
}))
|
||||
}
|
||||
|
||||
const handleSelectAll = () => {
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
topicos_selecionados: TOPICOS_PADRAO.map(t => t.numero)
|
||||
}))
|
||||
}
|
||||
|
||||
const handleSelectMinimo = () => {
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
topicos_selecionados: TOPICOS_PADRAO.filter(t => t.obrigatorio).map(t => t.numero)
|
||||
}))
|
||||
}
|
||||
|
||||
const handleSave = () => {
|
||||
updateMutation.mutate()
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center h-full">
|
||||
<LoadingSpinner size="lg" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto space-y-6">
|
||||
<div className="flex items-center gap-4">
|
||||
<Button variant="ghost" onClick={() => navigate('/templates')}>
|
||||
<ChevronLeft size={20} />
|
||||
</Button>
|
||||
<h1 className="text-3xl font-bold text-gray-900 dark:text-gray-100">Editar Template</h1>
|
||||
</div>
|
||||
|
||||
{/* Progress Steps */}
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
||||
<div className="flex items-center justify-between mb-8">
|
||||
{[1, 2, 3].map((s) => (
|
||||
<div key={s} className="flex items-center flex-1">
|
||||
<div className={`flex items-center justify-center w-10 h-10 rounded-full ${
|
||||
step >= s ? 'bg-primary text-white' : 'bg-gray-200 dark:bg-gray-700 text-gray-600 dark:text-gray-400'
|
||||
}`}>
|
||||
{s}
|
||||
</div>
|
||||
{s < 3 && (
|
||||
<div className={`flex-1 h-1 mx-4 ${
|
||||
step > s ? 'bg-primary' : 'bg-gray-200 dark:bg-gray-700'
|
||||
}`} />
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Step 1: Dados Básicos */}
|
||||
{step === 1 && (
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-xl font-semibold text-gray-900 dark:text-gray-100">Dados Básicos</h2>
|
||||
|
||||
<Input
|
||||
label="Nome do Template"
|
||||
value={formData.nome}
|
||||
onChange={(e) => setFormData(prev => ({ ...prev, nome: e.target.value }))}
|
||||
placeholder="Ex: Padrão Galpão Civil"
|
||||
required
|
||||
/>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Descrição (opcional)</label>
|
||||
<textarea
|
||||
value={formData.descricao}
|
||||
onChange={(e) => setFormData(prev => ({ ...prev, descricao: e.target.value }))}
|
||||
placeholder="Descreva o propósito deste template..."
|
||||
className="w-full px-3 py-2 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 text-gray-900 dark:text-gray-100 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary"
|
||||
rows={3}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end">
|
||||
<Button onClick={() => setStep(2)}>
|
||||
Próximo
|
||||
<ChevronRight size={20} className="ml-2" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Step 2: Seleção de Tópicos */}
|
||||
{step === 2 && (
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<h2 className="text-xl font-semibold text-gray-900 dark:text-gray-100">Selecionar Tópicos</h2>
|
||||
<div className="space-x-2">
|
||||
<Button size="sm" variant="outline" onClick={handleSelectAll}>
|
||||
Selecionar Todos
|
||||
</Button>
|
||||
<Button size="sm" variant="outline" onClick={handleSelectMinimo}>
|
||||
Apenas Obrigatórios
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400">
|
||||
Selecionados: {formData.topicos_selecionados.length} / {TOPICOS_PADRAO.length}
|
||||
</p>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3 max-h-96 overflow-y-auto">
|
||||
{TOPICOS_PADRAO.map((topico) => (
|
||||
<label
|
||||
key={topico.numero}
|
||||
className="flex items-start gap-3 p-3 border border-gray-200 dark:border-gray-600 rounded-lg hover:border-primary cursor-pointer bg-white dark:bg-gray-700 transition-colors"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={formData.topicos_selecionados.includes(topico.numero)}
|
||||
onChange={() => handleTopicoToggle(topico.numero)}
|
||||
className="mt-1 text-primary focus:ring-primary"
|
||||
/>
|
||||
<div className="flex-1">
|
||||
<div className="font-medium text-sm text-gray-900 dark:text-gray-100">
|
||||
{topico.numero} - {topico.titulo}
|
||||
{topico.obrigatorio && (
|
||||
<span className="ml-2 text-xs text-red-600 dark:text-red-400">*</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<Button variant="outline" onClick={() => setStep(1)}>
|
||||
<ChevronLeft size={20} className="mr-2" />
|
||||
Anterior
|
||||
</Button>
|
||||
<Button onClick={() => setStep(3)}>
|
||||
Próximo
|
||||
<ChevronRight size={20} className="ml-2" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Step 3: Revisar e Salvar */}
|
||||
{step === 3 && (
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-xl font-semibold text-gray-900 dark:text-gray-100">Revisar e Salvar</h2>
|
||||
|
||||
<div className="bg-gray-50 dark:bg-gray-700 rounded-lg p-6 space-y-4">
|
||||
<div>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400">Nome</p>
|
||||
<p className="font-medium text-gray-900 dark:text-gray-100">{formData.nome}</p>
|
||||
</div>
|
||||
{formData.descricao && (
|
||||
<div>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400">Descrição</p>
|
||||
<p className="font-medium text-gray-900 dark:text-gray-100">{formData.descricao}</p>
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400">Tipo</p>
|
||||
<p className="font-medium text-gray-900 dark:text-gray-100">{formData.tipo}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400">Total de Seções</p>
|
||||
<p className="font-medium text-gray-900 dark:text-gray-100">
|
||||
{formData.topicos_selecionados.length} de {TOPICOS_PADRAO.length}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400">Seções Obrigatórias</p>
|
||||
<p className="font-medium text-gray-900 dark:text-gray-100">
|
||||
{formData.topicos_selecionados.filter(t =>
|
||||
TOPICOS_PADRAO.find(tp => tp.numero === t)?.obrigatorio
|
||||
).length}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<Button variant="outline" onClick={() => setStep(2)}>
|
||||
<ChevronLeft size={20} className="mr-2" />
|
||||
Anterior
|
||||
</Button>
|
||||
<Button onClick={handleSave} isLoading={updateMutation.isPending}>
|
||||
Salvar Alterações
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user