trello_fake/components/projects/projects-table.tsx

245 lines
7.6 KiB
TypeScript
Raw Permalink Normal View History

"use client";
import { useState, useEffect, useMemo, useCallback } from "react";
import { FolderKanban, RefreshCw, Plus } from "lucide-react";
import { Project } from "@/types/project";
import { getProjects, deleteProject } from "@/lib/projectsService";
import { exportProjectsToPDF } from "@/lib/exportPdf";
import { DataTable, TableInfo } 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");
// Info de la tabla
const [tableInfo, setTableInfo] = useState<TableInfo<Project> | null>(null);
// 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);
}
};
// Handler para exportar PDF
const handleExport = useCallback((projectsToExport: Project[]) => {
if (projectsToExport.length === 0) return;
exportProjectsToPDF(projectsToExport);
}, []);
// Handler para cuando la tabla cambia
const handleTableChange = useCallback((info: TableInfo<Project>) => {
setTableInfo(info);
}, []);
// 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");
};
// 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 flex-col gap-3 lg:flex-row lg:items-center lg:justify-between">
<DataTableToolbar
searchValue={searchValue}
onSearchChange={setSearchValue}
statusFilter={statusFilter}
onStatusFilterChange={setStatusFilter}
selectedCount={tableInfo?.selectedCount ?? 0}
onClearFilters={handleClearFilters}
columns={tableInfo?.columns}
onExport={handleExport}
getSelectedProjects={tableInfo?.getSelectedRows}
getAllVisibleProjects={tableInfo?.getAllVisibleRows}
/>
<Button onClick={() => setIsCreateSheetOpen(true)} size="sm" className="shrink-0">
<Plus className="h-4 w-4 mr-2" />
Nuevo proyecto
</Button>
</div>
<DataTable
columns={columns}
data={filteredProjects}
searchKey="name"
searchValue={searchValue}
onTableChange={handleTableChange}
/>
{/* 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>
);
}