trello_fake/components/task-form/task-form-sheet.tsx

328 lines
9.3 KiB
TypeScript
Raw Permalink Normal View History

"use client";
import { useState, useEffect } from "react";
import { Loader2 } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import {
Sheet,
SheetContent,
SheetDescription,
SheetFooter,
SheetHeader,
SheetTitle,
} from "@/components/ui/sheet";
import {
Task,
TaskStatus,
TaskPriority,
TaskFormData,
TASK_STATUS_CONFIG,
TASK_PRIORITY_CONFIG,
taskToFormData,
formDataToCreateTask,
formDataToUpdateTask,
} from "@/types/task";
import { createTask, updateTask } from "@/lib/tasksService";
interface TaskFormSheetProps {
open: boolean;
onOpenChange: (open: boolean) => void;
mode: "create" | "edit";
projectId: number;
task?: Task | null;
onSuccess?: (task: Task) => void;
}
const defaultFormData: TaskFormData = {
title: "",
description: "",
projectId: 0,
parentTaskId: null,
status: "0",
priority: "0",
progress: 0,
plannedHours: 0,
startDate: "",
endDate: "",
budget: 0,
};
export function TaskFormSheet({
open,
onOpenChange,
mode,
projectId,
task,
onSuccess,
}: TaskFormSheetProps) {
const [formData, setFormData] = useState<TaskFormData>({
...defaultFormData,
projectId,
});
const [isSubmitting, setIsSubmitting] = useState(false);
const [error, setError] = useState<string | null>(null);
// Initialize form data when task changes (for edit mode)
useEffect(() => {
if (mode === "edit" && task) {
setFormData(taskToFormData(task));
} else {
setFormData({
...defaultFormData,
projectId,
});
}
setError(null);
}, [mode, task, projectId, open]);
const handleInputChange = (
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
) => {
const { name, value, type } = e.target;
setFormData((prev) => ({
...prev,
[name]: type === "number" ? parseFloat(value) || 0 : value,
}));
};
const handleSelectChange = (name: string, value: string) => {
setFormData((prev) => ({
...prev,
[name]: value,
}));
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setIsSubmitting(true);
setError(null);
try {
let result: Task;
if (mode === "create") {
const createData = formDataToCreateTask(formData);
result = await createTask(createData);
} else {
if (!task) {
throw new Error("No hay tarea para editar");
}
const updateData = formDataToUpdateTask(formData);
result = await updateTask(task.id, updateData);
}
onSuccess?.(result);
onOpenChange(false);
} catch (err) {
console.error("Error submitting task:", err);
setError(
err instanceof Error ? err.message : "Error al guardar la tarea"
);
} finally {
setIsSubmitting(false);
}
};
const isFormValid = formData.title.trim().length > 0;
return (
<Sheet open={open} onOpenChange={onOpenChange}>
<SheetContent className="sm:max-w-lg overflow-y-auto">
<SheetHeader>
<SheetTitle>
{mode === "create" ? "Nueva Tarea" : "Editar Tarea"}
</SheetTitle>
<SheetDescription>
{mode === "create"
? "Crea una nueva tarea para este proyecto."
: "Modifica los detalles de la tarea."}
</SheetDescription>
</SheetHeader>
<form onSubmit={handleSubmit} className="mt-6 space-y-6">
{/* Título */}
<div className="space-y-2">
<Label htmlFor="title">
Título <span className="text-red-500">*</span>
</Label>
<Input
id="title"
name="title"
value={formData.title}
onChange={handleInputChange}
placeholder="Nombre de la tarea"
required
/>
</div>
{/* Descripción */}
<div className="space-y-2">
<Label htmlFor="description">Descripción</Label>
<Textarea
id="description"
name="description"
value={formData.description}
onChange={handleInputChange}
placeholder="Descripción detallada de la tarea..."
rows={3}
/>
</div>
{/* Estado y Prioridad - Grid de 2 columnas */}
<div className="grid grid-cols-2 gap-4">
{/* Estado */}
<div className="space-y-2">
<Label htmlFor="status">Estado</Label>
<Select
value={formData.status}
onValueChange={(value) => handleSelectChange("status", value)}
>
<SelectTrigger>
<SelectValue placeholder="Seleccionar estado" />
</SelectTrigger>
<SelectContent>
{(Object.keys(TASK_STATUS_CONFIG) as TaskStatus[]).map(
(status) => (
<SelectItem key={status} value={status}>
{TASK_STATUS_CONFIG[status].label}
</SelectItem>
)
)}
</SelectContent>
</Select>
</div>
{/* Prioridad */}
<div className="space-y-2">
<Label htmlFor="priority">Prioridad</Label>
<Select
value={formData.priority}
onValueChange={(value) => handleSelectChange("priority", value)}
>
<SelectTrigger>
<SelectValue placeholder="Seleccionar prioridad" />
</SelectTrigger>
<SelectContent>
{(Object.keys(TASK_PRIORITY_CONFIG) as TaskPriority[]).map(
(priority) => (
<SelectItem key={priority} value={priority}>
{TASK_PRIORITY_CONFIG[priority].label}
</SelectItem>
)
)}
</SelectContent>
</Select>
</div>
</div>
{/* Progreso */}
<div className="space-y-2">
<Label htmlFor="progress">Progreso (%)</Label>
<Input
id="progress"
name="progress"
type="number"
min="0"
max="100"
value={formData.progress}
onChange={handleInputChange}
placeholder="0"
/>
</div>
{/* Fechas - Grid de 2 columnas */}
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="startDate">Fecha inicio</Label>
<Input
id="startDate"
name="startDate"
type="date"
value={formData.startDate}
onChange={handleInputChange}
/>
</div>
<div className="space-y-2">
<Label htmlFor="endDate">Fecha fin</Label>
<Input
id="endDate"
name="endDate"
type="date"
value={formData.endDate}
onChange={handleInputChange}
/>
</div>
</div>
{/* Horas planificadas y Presupuesto - Grid de 2 columnas */}
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="plannedHours">Horas planificadas</Label>
<Input
id="plannedHours"
name="plannedHours"
type="number"
min="0"
step="0.5"
value={formData.plannedHours}
onChange={handleInputChange}
placeholder="0"
/>
</div>
<div className="space-y-2">
<Label htmlFor="budget">Presupuesto (€)</Label>
<Input
id="budget"
name="budget"
type="number"
min="0"
step="0.01"
value={formData.budget}
onChange={handleInputChange}
placeholder="0.00"
/>
</div>
</div>
{/* Error message */}
{error && (
<div className="p-3 text-sm text-red-600 bg-red-50 dark:bg-red-950/50 dark:text-red-400 rounded-md">
{error}
</div>
)}
{/* Submit button */}
<SheetFooter className="pt-4">
<Button
type="button"
variant="outline"
onClick={() => onOpenChange(false)}
disabled={isSubmitting}
>
Cancelar
</Button>
<Button type="submit" disabled={!isFormValid || isSubmitting}>
{isSubmitting && (
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
)}
{mode === "create" ? "Crear tarea" : "Guardar cambios"}
</Button>
</SheetFooter>
</form>
</SheetContent>
</Sheet>
);
}