trello_fake/components/projects/columns.tsx

280 lines
7.5 KiB
TypeScript
Raw Permalink Normal View History

"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 { Badge } from "@/components/ui/badge";
import { Checkbox } from "@/components/ui/checkbox";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Project, ProjectStatus, STATUS_CONFIG } from "@/types/project";
// Componente para el badge de estado
function StatusBadge({ status }: { status: ProjectStatus }) {
const config = STATUS_CONFIG[status];
const colorClasses: Record<ProjectStatus, string> = {
'0': 'bg-gray-100 text-gray-700 hover:bg-gray-100/80 dark:bg-gray-800 dark:text-gray-300',
'1': 'bg-blue-100 text-blue-700 hover:bg-blue-100/80 dark:bg-blue-900/30 dark:text-blue-400',
'2': 'bg-green-100 text-green-700 hover:bg-green-100/80 dark:bg-green-900/30 dark:text-green-400',
};
return (
<Badge variant="outline" className={colorClasses[status]}>
{config?.label || 'Desconocido'}
</Badge>
);
}
// 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-gray-200 dark:bg-gray-700 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 }) => <StatusBadge 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>
);
},
},
];