mirror of
https://github.com/escalante29/WealthySmart.git
synced 2026-05-19 08:28:48 +02:00
Repoints imports at the relocated lib/api and src/contexts modules, and refreshes Layout + Login alongside the rest of the migration. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
166 lines
5.3 KiB
TypeScript
166 lines
5.3 KiB
TypeScript
import { type ColumnDef } from '@tanstack/react-table';
|
|
import { ArrowLeftRight, ArrowRightFromLine, Banknote, Pencil, Trash2, TrendingDown, TrendingUp } from 'lucide-react';
|
|
|
|
import { type Transaction } from '@/lib/api';
|
|
import { formatAmount } from '@/lib/format';
|
|
import { cn } from '@/lib/utils';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Badge } from '@/components/ui/badge';
|
|
import { DataTableColumnHeader } from '@/components/ui/data-table-column-header';
|
|
|
|
interface TransactionColumnOptions {
|
|
showCategory: boolean;
|
|
showSourceIcon?: boolean;
|
|
onEdit: (tx: Transaction) => void;
|
|
onDelete: (txId: number) => void;
|
|
onToggleDeferred?: (tx: Transaction) => void;
|
|
}
|
|
|
|
export function getTransactionColumns({
|
|
showCategory,
|
|
showSourceIcon,
|
|
onEdit,
|
|
onDelete,
|
|
onToggleDeferred,
|
|
}: TransactionColumnOptions): ColumnDef<Transaction, unknown>[] {
|
|
const columns: ColumnDef<Transaction, unknown>[] = [
|
|
{
|
|
accessorKey: 'date',
|
|
header: ({ column }) => <DataTableColumnHeader column={column} title="Date" />,
|
|
cell: ({ row }) => (
|
|
<span className="font-mono text-muted-foreground text-xs whitespace-nowrap">
|
|
{new Date(row.original.date).toLocaleDateString('en-US', {
|
|
month: 'short',
|
|
day: 'numeric',
|
|
year: 'numeric',
|
|
})}
|
|
</span>
|
|
),
|
|
},
|
|
{
|
|
accessorKey: 'merchant',
|
|
header: ({ column }) => <DataTableColumnHeader column={column} title="Merchant" />,
|
|
cell: ({ row }) => {
|
|
const tx = row.original;
|
|
return (
|
|
<div className="flex items-center gap-2">
|
|
<div
|
|
className={cn(
|
|
'w-6 h-6 rounded flex items-center justify-center shrink-0',
|
|
tx.transaction_type === 'COMPRA'
|
|
? 'bg-destructive/10 text-destructive'
|
|
: 'bg-primary/10 text-primary',
|
|
)}
|
|
>
|
|
{tx.transaction_type === 'COMPRA' ? (
|
|
<TrendingDown className="w-3 h-3" />
|
|
) : (
|
|
<TrendingUp className="w-3 h-3" />
|
|
)}
|
|
</div>
|
|
<span className="truncate">{tx.merchant}</span>
|
|
{showSourceIcon && tx.source === 'CASH' && (
|
|
<Banknote className="w-3.5 h-3.5 text-muted-foreground shrink-0 ml-1" />
|
|
)}
|
|
{showSourceIcon && tx.source === 'TRANSFER' && (
|
|
<ArrowLeftRight className="w-3.5 h-3.5 text-muted-foreground shrink-0 ml-1" />
|
|
)}
|
|
{tx.deferred_to_next_cycle && (
|
|
<Badge variant="outline" className="ml-1.5 text-[10px] px-1 py-0 shrink-0 text-amber-600 border-amber-300">
|
|
Diferida
|
|
</Badge>
|
|
)}
|
|
</div>
|
|
);
|
|
},
|
|
},
|
|
];
|
|
|
|
if (showCategory) {
|
|
columns.push({
|
|
accessorFn: (row) => row.category?.name ?? '',
|
|
id: 'category',
|
|
header: ({ column }) => <DataTableColumnHeader column={column} title="Category" />,
|
|
cell: ({ row }) => {
|
|
const category = row.original.category;
|
|
return category ? (
|
|
<Badge variant="secondary">{category.name}</Badge>
|
|
) : (
|
|
<span className="text-xs text-muted-foreground">—</span>
|
|
);
|
|
},
|
|
});
|
|
}
|
|
|
|
columns.push(
|
|
{
|
|
accessorKey: 'amount',
|
|
meta: { className: 'text-right' },
|
|
header: ({ column }) => (
|
|
<DataTableColumnHeader column={column} title="Amount" className="justify-end" />
|
|
),
|
|
cell: ({ row }) => {
|
|
const tx = row.original;
|
|
return (
|
|
<span
|
|
data-sensitive
|
|
className={cn(
|
|
'font-mono font-medium',
|
|
tx.transaction_type !== 'COMPRA' && 'text-primary',
|
|
)}
|
|
>
|
|
{tx.transaction_type === 'COMPRA' ? '-' : '+'}
|
|
{formatAmount(tx.amount, tx.currency)}
|
|
</span>
|
|
);
|
|
},
|
|
},
|
|
{
|
|
id: 'actions',
|
|
meta: { className: 'text-right' },
|
|
size: 80,
|
|
enableSorting: false,
|
|
cell: ({ row }) => {
|
|
const tx = row.original;
|
|
return (
|
|
<div className="flex items-center justify-end gap-1">
|
|
{onToggleDeferred && (
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
title={tx.deferred_to_next_cycle ? 'Quitar diferida' : 'Diferir al siguiente ciclo'}
|
|
aria-label={tx.deferred_to_next_cycle ? 'Remove deferred' : 'Defer to next cycle'}
|
|
onClick={() => onToggleDeferred(tx)}
|
|
className={cn(tx.deferred_to_next_cycle && 'text-amber-600')}
|
|
>
|
|
<ArrowRightFromLine className="w-4 h-4" />
|
|
</Button>
|
|
)}
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
title="Edit transaction"
|
|
aria-label="Edit transaction"
|
|
onClick={() => onEdit(tx)}
|
|
>
|
|
<Pencil className="w-4 h-4" />
|
|
</Button>
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
title="Delete transaction"
|
|
aria-label="Delete transaction"
|
|
onClick={() => onDelete(tx.id)}
|
|
className="hover:text-destructive"
|
|
>
|
|
<Trash2 className="w-4 h-4" />
|
|
</Button>
|
|
</div>
|
|
);
|
|
},
|
|
},
|
|
);
|
|
|
|
return columns;
|
|
}
|