first commit

This commit is contained in:
rodrigo committed 2025-04-10 23:07:21 -03:00
commit c9fb67e751
423 files changed
+53287

No files matched your search

+241
View File
@@ -0,0 +1,241 @@
import React, { useState, useEffect } from 'react';
import { Dialog } from '@headlessui/react';
import { useForm } from 'react-hook-form';
import { useQuery } from 'react-query';
import { DatePicker } from '@mui/x-date-pickers';
import { Tarefa } from '../../types/entities';
import { XMarkIcon } from '@heroicons/react/24/outline';
import { SubtarefasList } from './SubtarefasList';
import { ComentariosList } from './ComentariosList';
import api from '../../services/api';
import { useTarefas } from '../../hooks/useTarefas';
interface TarefaModalProps {
isOpen: boolean;
onClose: () => void;
tarefa?: Tarefa | null;
}
export const TarefaModal: React.FC<TarefaModalProps> = ({ isOpen, onClose, tarefa }) => {
const [activeTab, setActiveTab] = useState('detalhes');
const { register, handleSubmit, reset, setValue, watch } = useForm();
const [loading, setLoading] = useState(false);
const { createTarefa, updateTarefa } = useTarefas();
// Buscar usuários para atribuição
const { data: usuarios = [] } = useQuery('usuarios', async () => {
const response = await api.get('/usuarios');
return response.data;
});
// Buscar processos ativos
const { data: processos = [] } = useQuery('processos', async () => {
const response = await api.get('/processos');
return response.data;
});
useEffect(() => {
if (tarefa) {
reset(tarefa);
} else {
reset({
status: 'PENDENTE',
prioridade: 'MEDIA',
progresso: 0,
etiquetas: [],
subtarefas: [],
comentarios: []
});
}
}, [tarefa, reset]);
useEffect(() => {
if (tarefa && isOpen) {
reset({
...tarefa,
dataInicio: new Date(tarefa.dataInicio),
dataVencimento: new Date(tarefa.dataVencimento)
});
}
}, [tarefa, isOpen, reset]);
const onSubmit = async (data: any) => {
try {
setLoading(true);
// Preparar dados removendo campos que não devem ser enviados
const dadosParaEnviar = {
...data,
responsavelId: data.responsavelId,
// Remover campos que não devem ser enviados
id: undefined,
criadoEm: undefined,
atualizadoEm: undefined
};
if (tarefa) {
// Para edição, enviar id separadamente
await updateTarefa.mutateAsync({
id: tarefa.id,
data: dadosParaEnviar
});
} else {
await createTarefa.mutateAsync(dadosParaEnviar);
}
onClose();
} catch (error) {
console.error('Erro ao salvar tarefa:', error);
alert('Erro ao salvar tarefa. Por favor, tente novamente.');
} finally {
setLoading(false);
}
};
const tabs = [
{ id: 'detalhes', label: 'Detalhes' },
{ id: 'subtarefas', label: 'Subtarefas' },
{ id: 'comentarios', label: 'Comentários' },
];
return (
<Dialog open={isOpen} onClose={onClose} className="relative z-50">
<div className="fixed inset-0 bg-black/30 backdrop-blur-sm" />
<div className="fixed inset-0 overflow-y-auto">
<div className="flex min-h-full items-center justify-center p-4">
<Dialog.Panel className="w-full max-w-4xl bg-white dark:bg-gray-800 rounded-xl shadow-xl">
{/* Header */}
<div className="border-b border-gray-200 dark:border-gray-700 p-4">
<div className="flex justify-between items-center">
<Dialog.Title className="text-lg font-semibold text-gray-900 dark:text-white">
{tarefa ? 'Editar Tarefa' : 'Nova Tarefa'}
</Dialog.Title>
<button onClick={onClose} className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200">
<XMarkIcon className="h-6 w-6" />
</button>
</div>
{/* Tabs */}
<div className="flex space-x-4 mt-4">
{tabs.map(tab => (
<button
key={tab.id}
onClick={() => setActiveTab(tab.id)}
className={`px-4 py-2 rounded-lg text-sm font-medium transition-colors
${activeTab === tab.id
? 'bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-200'
: 'text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200'
}`}
>
{tab.label}
</button>
))}
</div>
</div>
{/* Content */}
<form onSubmit={handleSubmit(onSubmit)} className="p-6">
{/* Conteúdo das tabs */}
<div className="space-y-6">
{activeTab === 'detalhes' && (
<div className="grid grid-cols-2 gap-4">
<div className="col-span-2">
<input
type="text"
{...register('titulo')}
placeholder="Título da tarefa"
className="w-full px-4 py-2 text-xl font-medium border-0 border-b-2 border-gray-200 dark:border-gray-700 bg-transparent text-gray-900 dark:text-white focus:ring-0 focus:border-blue-500"
/>
</div>
<div className="col-span-2">
<textarea
{...register('descricao')}
rows={3}
placeholder="Descrição detalhada"
className="w-full px-4 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
/>
</div>
<div className="space-y-4">
<select
{...register('prioridade')}
className="w-full p-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
>
<option value="BAIXA">Baixa</option>
<option value="MEDIA">Média</option>
<option value="ALTA">Alta</option>
<option value="URGENTE">Urgente</option>
</select>
<select
{...register('responsavelId')}
className="w-full p-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
>
<option value="">Selecione um responsável</option>
{usuarios.map((usuario: any) => (
<option key={usuario.id} value={usuario.id}>
{usuario.nome}
</option>
))}
</select>
</div>
<div className="space-y-4">
<DatePicker
label="Data de Início"
value={watch('dataInicio')}
onChange={(date) => setValue('dataInicio', date)}
className="w-full"
slotProps={{ textField: { size: 'small' } }}
/>
<DatePicker
label="Data de Vencimento"
value={watch('dataVencimento')}
onChange={(date) => setValue('dataVencimento', date)}
className="w-full"
slotProps={{ textField: { size: 'small' } }}
/>
</div>
</div>
)}
{activeTab === 'subtarefas' && (
<SubtarefasList
subtarefas={watch('subtarefas') || []}
onChange={(subtarefas) => setValue('subtarefas', subtarefas)}
/>
)}
{activeTab === 'comentarios' && (
<ComentariosList
comentarios={watch('comentarios') || []}
onChange={(comentarios) => setValue('comentarios', comentarios)}
/>
)}
</div>
<div className="mt-6 flex justify-end space-x-3">
<button
type="button"
onClick={onClose}
className="px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg"
>
Cancelar
</button>
<button
type="submit"
disabled={loading}
className="px-4 py-2 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-lg disabled:opacity-50"
>
{loading ? 'Salvando...' : tarefa ? 'Salvar' : 'Criar'}
</button>
</div>
</form>
</Dialog.Panel>
</div>
</div>
</Dialog>
);
};