228 lines
6.9 KiB
TypeScript
228 lines
6.9 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useEffect, useMemo } from "react";
|
|
import { FolderKanban, RefreshCw, Plus } from "lucide-react";
|
|
|
|
import { Project } from "@/types/project";
|
|
import { getProjects, deleteProject } from "@/lib/projectsService";
|
|
import { DataTable } from "./data-table";
|
|
import { DataTableToolbar } from "./data-table-toolbar";
|
|
import { DataTableSkeleton } from "./data-table-skeleton";
|
|
import { EmptyState } from "./empty-state";
|
|
import { createProjectColumns } from "./columns";
|
|
import { ProjectFormSheet } from "@/components/project-form/project-form-sheet";
|
|
import { DeleteConfirmationDialog } from "@/components/ui/delete-confirmation-dialog";
|
|
import { Button } from "@/components/ui/button";
|
|
|
|
export function ProjectsTable() {
|
|
const [projects, setProjects] = useState<Project[]>([]);
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
// Filtros
|
|
const [searchValue, setSearchValue] = useState("");
|
|
const [statusFilter, setStatusFilter] = useState("all");
|
|
|
|
// Estados para crear/editar/eliminar
|
|
const [isCreateSheetOpen, setIsCreateSheetOpen] = useState(false);
|
|
const [isEditSheetOpen, setIsEditSheetOpen] = useState(false);
|
|
const [projectToEdit, setProjectToEdit] = useState<Project | null>(null);
|
|
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
|
|
const [projectToDelete, setProjectToDelete] = useState<Project | null>(null);
|
|
const [isDeleting, setIsDeleting] = useState(false);
|
|
|
|
// Cargar proyectos
|
|
const fetchProjects = async () => {
|
|
setIsLoading(true);
|
|
setError(null);
|
|
try {
|
|
const data = await getProjects();
|
|
setProjects(data);
|
|
} catch (err) {
|
|
console.error("Error fetching projects:", err);
|
|
setError("Error al cargar los proyectos. Por favor, intenta de nuevo.");
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
fetchProjects();
|
|
}, []);
|
|
|
|
// Handlers para CRUD
|
|
const handleProjectCreated = (newProject: Project) => {
|
|
setProjects(prev => [newProject, ...prev]);
|
|
};
|
|
|
|
const handleEditProject = (project: Project) => {
|
|
setProjectToEdit(project);
|
|
setIsEditSheetOpen(true);
|
|
};
|
|
|
|
const handleEditSuccess = (updatedProject: Project) => {
|
|
setProjects(prev => prev.map(p => p.id === updatedProject.id ? updatedProject : p));
|
|
setProjectToEdit(null);
|
|
};
|
|
|
|
const handleDeleteProject = (project: Project) => {
|
|
setProjectToDelete(project);
|
|
setIsDeleteDialogOpen(true);
|
|
};
|
|
|
|
const handleConfirmDelete = async () => {
|
|
if (!projectToDelete) return;
|
|
|
|
setIsDeleting(true);
|
|
try {
|
|
await deleteProject(projectToDelete.id);
|
|
setProjects(prev => prev.filter(p => p.id !== projectToDelete.id));
|
|
setIsDeleteDialogOpen(false);
|
|
setProjectToDelete(null);
|
|
} catch (error) {
|
|
console.error("Error deleting project:", error);
|
|
} finally {
|
|
setIsDeleting(false);
|
|
}
|
|
};
|
|
|
|
// Crear columnas con callbacks
|
|
const columns = useMemo(() => createProjectColumns({
|
|
onEdit: handleEditProject,
|
|
onDelete: handleDeleteProject,
|
|
}), []);
|
|
|
|
// Filtrar proyectos
|
|
const filteredProjects = useMemo(() => {
|
|
let result = [...projects];
|
|
|
|
// Filtro de búsqueda (nombre o referencia)
|
|
if (searchValue) {
|
|
const search = searchValue.toLowerCase();
|
|
result = result.filter(
|
|
(project) =>
|
|
project.name.toLowerCase().includes(search) ||
|
|
project.ref.toLowerCase().includes(search) ||
|
|
project.client.toLowerCase().includes(search)
|
|
);
|
|
}
|
|
|
|
// Filtro de estado
|
|
if (statusFilter !== "all") {
|
|
result = result.filter((project) => project.status === statusFilter);
|
|
}
|
|
|
|
return result;
|
|
}, [projects, searchValue, statusFilter]);
|
|
|
|
// Limpiar filtros
|
|
const handleClearFilters = () => {
|
|
setSearchValue("");
|
|
setStatusFilter("all");
|
|
};
|
|
|
|
// Contar seleccionados (placeholder - el DataTable lo maneja internamente)
|
|
const selectedCount = 0;
|
|
|
|
// Estado de error
|
|
if (error) {
|
|
return (
|
|
<div className="flex flex-col items-center justify-center p-8 text-center">
|
|
<div className="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-destructive/10">
|
|
<FolderKanban className="h-6 w-6 text-destructive" />
|
|
</div>
|
|
<h3 className="mt-4 text-lg font-semibold">Error al cargar</h3>
|
|
<p className="mt-2 text-sm text-muted-foreground max-w-sm">{error}</p>
|
|
<Button onClick={fetchProjects} variant="outline" className="mt-4">
|
|
<RefreshCw className="mr-2 h-4 w-4" />
|
|
Reintentar
|
|
</Button>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// Estado de carga
|
|
if (isLoading) {
|
|
return <DataTableSkeleton />;
|
|
}
|
|
|
|
// Estado vacío
|
|
if (projects.length === 0) {
|
|
return (
|
|
<>
|
|
<EmptyState
|
|
title="No hay proyectos"
|
|
description="Aún no tienes proyectos creados. Comienza creando tu primer proyecto."
|
|
actionLabel="Crear proyecto"
|
|
onAction={() => setIsCreateSheetOpen(true)}
|
|
/>
|
|
<ProjectFormSheet
|
|
open={isCreateSheetOpen}
|
|
onOpenChange={setIsCreateSheetOpen}
|
|
mode="create"
|
|
onSuccess={handleProjectCreated}
|
|
/>
|
|
</>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-4">
|
|
<div className="flex items-center justify-between">
|
|
<DataTableToolbar
|
|
searchValue={searchValue}
|
|
onSearchChange={setSearchValue}
|
|
statusFilter={statusFilter}
|
|
onStatusFilterChange={setStatusFilter}
|
|
selectedCount={selectedCount}
|
|
onClearFilters={handleClearFilters}
|
|
/>
|
|
<Button onClick={() => setIsCreateSheetOpen(true)} size="sm">
|
|
<Plus className="h-4 w-4 mr-2" />
|
|
Nuevo proyecto
|
|
</Button>
|
|
</div>
|
|
<DataTable
|
|
columns={columns}
|
|
data={filteredProjects}
|
|
searchKey="name"
|
|
searchValue={searchValue}
|
|
/>
|
|
|
|
{/* Sheet de creación */}
|
|
<ProjectFormSheet
|
|
open={isCreateSheetOpen}
|
|
onOpenChange={setIsCreateSheetOpen}
|
|
mode="create"
|
|
onSuccess={handleProjectCreated}
|
|
/>
|
|
|
|
{/* Sheet de edición */}
|
|
<ProjectFormSheet
|
|
open={isEditSheetOpen}
|
|
onOpenChange={(open) => {
|
|
setIsEditSheetOpen(open);
|
|
if (!open) setProjectToEdit(null);
|
|
}}
|
|
mode="edit"
|
|
project={projectToEdit}
|
|
onSuccess={handleEditSuccess}
|
|
/>
|
|
|
|
{/* Diálogo de confirmación de eliminación */}
|
|
<DeleteConfirmationDialog
|
|
open={isDeleteDialogOpen}
|
|
onOpenChange={(open) => {
|
|
setIsDeleteDialogOpen(open);
|
|
if (!open) setProjectToDelete(null);
|
|
}}
|
|
onConfirm={handleConfirmDelete}
|
|
title="¿Eliminar proyecto?"
|
|
itemName={projectToDelete?.name}
|
|
description={`Esta acción eliminará el proyecto "${projectToDelete?.name}" y todas sus tareas asociadas. Esta acción no se puede deshacer.`}
|
|
isDeleting={isDeleting}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|