263 lines
6.9 KiB
TypeScript
263 lines
6.9 KiB
TypeScript
"use client";
|
|
|
|
import { ColumnDef } from "@tanstack/react-table";
|
|
import { ArrowUpDown, MoreHorizontal, Eye, Pencil, Trash2 } from "lucide-react";
|
|
import Link from "next/link";
|
|
|
|
import { Button } from "@/components/ui/button";
|
|
import { Checkbox } from "@/components/ui/checkbox";
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuLabel,
|
|
DropdownMenuSeparator,
|
|
DropdownMenuTrigger,
|
|
} from "@/components/ui/dropdown-menu";
|
|
import { ProjectStatusBadge } from "@/components/ui/project-status-badge";
|
|
import { Project, ProjectStatus } from "@/types/project";
|
|
|
|
// Componente para la barra de progreso
|
|
function ProgressBar({ progress }: { progress: number }) {
|
|
const getProgressColor = (value: number) => {
|
|
if (value >= 75) return 'bg-green-500';
|
|
if (value >= 50) return 'bg-blue-500';
|
|
if (value >= 25) return 'bg-yellow-500';
|
|
return 'bg-gray-400';
|
|
};
|
|
|
|
return (
|
|
<div className="flex items-center gap-2">
|
|
<div className="w-24 h-2 bg-muted rounded-full overflow-hidden">
|
|
<div
|
|
className={`h-full rounded-full transition-all ${getProgressColor(progress)}`}
|
|
style={{ width: `${Math.min(progress, 100)}%` }}
|
|
/>
|
|
</div>
|
|
<span className="text-sm text-muted-foreground w-10">
|
|
{progress.toFixed(0)}%
|
|
</span>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// Formateador de moneda
|
|
function formatCurrency(amount: number): string {
|
|
return new Intl.NumberFormat('es-ES', {
|
|
style: 'currency',
|
|
currency: 'EUR',
|
|
minimumFractionDigits: 0,
|
|
maximumFractionDigits: 0,
|
|
}).format(amount);
|
|
}
|
|
|
|
// Formateador de fecha
|
|
function formatDate(dateString: string): string {
|
|
if (!dateString) return '-';
|
|
try {
|
|
return new Date(dateString).toLocaleDateString('es-ES', {
|
|
day: '2-digit',
|
|
month: 'short',
|
|
year: 'numeric',
|
|
});
|
|
} catch {
|
|
return '-';
|
|
}
|
|
}
|
|
|
|
export const projectColumns: ColumnDef<Project>[] = [
|
|
// Columna de selección
|
|
{
|
|
id: "select",
|
|
header: ({ table }) => (
|
|
<Checkbox
|
|
checked={
|
|
table.getIsAllPageRowsSelected() ||
|
|
(table.getIsSomePageRowsSelected() && "indeterminate")
|
|
}
|
|
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
|
|
aria-label="Seleccionar todo"
|
|
className="translate-y-[2px]"
|
|
/>
|
|
),
|
|
cell: ({ row }) => (
|
|
<Checkbox
|
|
checked={row.getIsSelected()}
|
|
onCheckedChange={(value) => row.toggleSelected(!!value)}
|
|
aria-label="Seleccionar fila"
|
|
className="translate-y-[2px]"
|
|
/>
|
|
),
|
|
enableSorting: false,
|
|
enableHiding: false,
|
|
},
|
|
// Referencia
|
|
{
|
|
accessorKey: "ref",
|
|
header: ({ column }) => (
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
className="-ml-3 h-8"
|
|
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
|
|
>
|
|
Referencia
|
|
<ArrowUpDown className="ml-2 h-4 w-4" />
|
|
</Button>
|
|
),
|
|
cell: ({ row }) => (
|
|
<span className="font-mono text-sm text-muted-foreground">
|
|
{row.getValue("ref")}
|
|
</span>
|
|
),
|
|
},
|
|
// Nombre del proyecto
|
|
{
|
|
accessorKey: "name",
|
|
header: ({ column }) => (
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
className="-ml-3 h-8"
|
|
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
|
|
>
|
|
Nombre
|
|
<ArrowUpDown className="ml-2 h-4 w-4" />
|
|
</Button>
|
|
),
|
|
cell: ({ row }) => (
|
|
<div className="max-w-[250px]">
|
|
<Link
|
|
href={`/proyectos/${row.original.id}`}
|
|
className="font-medium hover:underline hover:text-primary transition-colors"
|
|
>
|
|
{row.getValue("name")}
|
|
</Link>
|
|
</div>
|
|
),
|
|
},
|
|
// Cliente
|
|
{
|
|
accessorKey: "client",
|
|
header: "Cliente",
|
|
cell: ({ row }) => (
|
|
<span className="text-sm">
|
|
{row.getValue("client")}
|
|
</span>
|
|
),
|
|
},
|
|
// Estado
|
|
{
|
|
accessorKey: "status",
|
|
header: "Estado",
|
|
cell: ({ row }) => <ProjectStatusBadge status={row.getValue("status")} />,
|
|
filterFn: (row, id, value) => {
|
|
return value.includes(row.getValue(id));
|
|
},
|
|
},
|
|
// Progreso
|
|
{
|
|
accessorKey: "progress",
|
|
header: ({ column }) => (
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
className="-ml-3 h-8"
|
|
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
|
|
>
|
|
Progreso
|
|
<ArrowUpDown className="ml-2 h-4 w-4" />
|
|
</Button>
|
|
),
|
|
cell: ({ row }) => <ProgressBar progress={row.getValue("progress")} />,
|
|
},
|
|
// Presupuesto
|
|
{
|
|
accessorKey: "budget",
|
|
header: ({ column }) => (
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
className="-ml-3 h-8"
|
|
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
|
|
>
|
|
Presupuesto
|
|
<ArrowUpDown className="ml-2 h-4 w-4" />
|
|
</Button>
|
|
),
|
|
cell: ({ row }) => (
|
|
<span className="font-medium tabular-nums">
|
|
{formatCurrency(row.getValue("budget"))}
|
|
</span>
|
|
),
|
|
},
|
|
// Fecha inicio
|
|
{
|
|
accessorKey: "startDate",
|
|
header: ({ column }) => (
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
className="-ml-3 h-8"
|
|
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
|
|
>
|
|
Inicio
|
|
<ArrowUpDown className="ml-2 h-4 w-4" />
|
|
</Button>
|
|
),
|
|
cell: ({ row }) => (
|
|
<span className="text-sm text-muted-foreground">
|
|
{formatDate(row.getValue("startDate"))}
|
|
</span>
|
|
),
|
|
},
|
|
// Fecha fin
|
|
{
|
|
accessorKey: "endDate",
|
|
header: "Fin",
|
|
cell: ({ row }) => (
|
|
<span className="text-sm text-muted-foreground">
|
|
{formatDate(row.getValue("endDate"))}
|
|
</span>
|
|
),
|
|
},
|
|
// Acciones
|
|
{
|
|
id: "actions",
|
|
enableHiding: false,
|
|
cell: ({ row }) => {
|
|
const project = row.original;
|
|
|
|
return (
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger asChild>
|
|
<Button variant="ghost" className="h-8 w-8 p-0">
|
|
<span className="sr-only">Abrir menú</span>
|
|
<MoreHorizontal className="h-4 w-4" />
|
|
</Button>
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent align="end">
|
|
<DropdownMenuLabel>Acciones</DropdownMenuLabel>
|
|
<DropdownMenuSeparator />
|
|
<DropdownMenuItem asChild>
|
|
<Link href={`/proyectos/${project.id}`} className="cursor-pointer">
|
|
<Eye className="mr-2 h-4 w-4" />
|
|
Ver detalles
|
|
</Link>
|
|
</DropdownMenuItem>
|
|
<DropdownMenuItem className="cursor-pointer">
|
|
<Pencil className="mr-2 h-4 w-4" />
|
|
Editar
|
|
</DropdownMenuItem>
|
|
<DropdownMenuSeparator />
|
|
<DropdownMenuItem className="cursor-pointer text-destructive focus:text-destructive">
|
|
<Trash2 className="mr-2 h-4 w-4" />
|
|
Eliminar
|
|
</DropdownMenuItem>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
);
|
|
},
|
|
},
|
|
];
|