253 lines
9.7 KiB
TypeScript
253 lines
9.7 KiB
TypeScript
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>
|
|
|
|
{/* Campo para alterar o status da tarefa conforme banco de dados */}
|
|
<select
|
|
{...register('status')}
|
|
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="PENDENTE">Pendente</option>
|
|
{/* Exibe 'Em andamento' sem underline */}
|
|
<option value="EM_ANDAMENTO">Em andamento</option>
|
|
<option value="CONCLUIDA">Concluída</option>
|
|
<option value="CANCELADA">Cancelada</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>
|
|
);
|
|
}; |