import { usePage } from '@inertiajs/react'; import { Cell, ColumnDef, ColumnFiltersState, flexRender, getCoreRowModel, getFilteredRowModel, getSortedRowModel, Row, SortingState, useReactTable, VisibilityState, } from '@tanstack/react-table'; import * as Icons from 'lucide-react'; import { MoreHorizontal } from 'lucide-react'; import { useMemo, useState } from 'react'; import { CreateAutomationRuleDialog } from '@/components/automation-rules/create-automation-rule-dialog'; import { DeleteAutomationRuleDialog } from '@/components/automation-rules/delete-automation-rule-dialog'; import { EditAutomationRuleDialog } from '@/components/automation-rules/edit-automation-rule-dialog'; import { LabelBadges } from '@/components/shared/label-combobox'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuLabel, ContextMenuTrigger, } from '@/components/ui/context-menu'; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; import { Input } from '@/components/ui/input'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table'; import { useEncryptionKey } from '@/contexts/encryption-key-context'; import { type AutomationRule, getRuleActions } from '@/types/automation-rule'; import { type Category, getCategoryColorClasses } from '@/types/category'; import { type Label } from '@/types/label'; import { __ } from '@/utils/i18n'; interface AutomationRulesDialogProps { open: boolean; onOpenChange: (open: boolean) => void; } function AutomationRuleActions({ rule, categories, labels, }: { rule: AutomationRule; categories: Category[]; labels: Label[]; }) { const [editOpen, setEditOpen] = useState(false); const [deleteOpen, setDeleteOpen] = useState(false); return ( <> {__('Actions')} setEditOpen(true)}> {__('Edit')} setDeleteOpen(true)} variant="destructive" > {__('Delete')} ); } function AutomationRuleRow({ row, categories, labels, }: { row: Row; categories: Category[]; labels: Label[]; }) { const rule = row.original; const [editOpen, setEditOpen] = useState(false); const [deleteOpen, setDeleteOpen] = useState(false); const [contextMenuOpen, setContextMenuOpen] = useState(false); return ( <> {row .getVisibleCells() .map((cell: Cell) => ( {flexRender( cell.column.columnDef.cell, cell.getContext(), )} ))} {__('Actions')} setEditOpen(true)}> {__('Edit')} setDeleteOpen(true)} variant="destructive" > {__('Delete')} ); } export function AutomationRulesDialog({ open, onOpenChange, }: AutomationRulesDialogProps) { const { isKeySet } = useEncryptionKey(); const { automationRules: rawRules } = usePage<{ automationRules: AutomationRule[]; }>().props; // Get categories and labels from globally shared Inertia data const categories = usePage().props.categories as Category[]; const labels = usePage().props.labels as Label[]; const rules = useMemo( () => rawRules.map((rule) => ({ ...rule, rules_json: typeof rule.rules_json === 'string' ? JSON.parse(rule.rules_json) : rule.rules_json, })), [rawRules], ); const [sorting, setSorting] = useState([ { id: 'priority', desc: false, }, ]); const [columnFilters, setColumnFilters] = useState([]); const [columnVisibility, setColumnVisibility] = useState( {}, ); const columns: ColumnDef[] = [ { accessorKey: 'priority', header: __('Priority'), cell: ({ row }) => { return (
{row.getValue('priority')}
); }, }, { accessorKey: 'title', header: __('Title'), cell: ({ row }) => { return (
{row.getValue('title')}
); }, }, { id: 'actions_display', header: __('Actions'), cell: ({ row }) => { const rule = row.original; const actions = getRuleActions(rule); if (actions.type === 'multiple') { return (
{actions.category && ( {(() => { const IconComponent = Icons[ actions.category .icon as keyof typeof Icons ] as Icons.LucideIcon; return IconComponent ? ( ) : null; })()} {actions.category.name} )} {actions.hasLabels && actions.labels && ( )} {actions.hasNote && ( + note )}
); } if (actions.type === 'category' && actions.category) { const colorClasses = getCategoryColorClasses( actions.category.color, ); const IconComponent = Icons[ actions.category.icon as keyof typeof Icons ] as Icons.LucideIcon; return ( {actions.category.name} ); } if (actions.type === 'labels' && actions.labels) { return ; } return ( {__('Add note')} ); }, }, { id: 'actions', enableHiding: false, cell: ({ row }) => ( ), }, ]; const table = useReactTable({ data: rules, columns, onSortingChange: setSorting, onColumnFiltersChange: setColumnFilters, getCoreRowModel: getCoreRowModel(), getSortedRowModel: getSortedRowModel(), getFilteredRowModel: getFilteredRowModel(), onColumnVisibilityChange: setColumnVisibility, state: { sorting, columnFilters, columnVisibility, }, }); return ( {__('Automation Rules')} {__( 'Manage your transaction automation rules. Rules will be applied to categorize transactions automatically.', )}
table .getColumn('title') ?.setFilterValue(event.target.value) } className="max-w-sm" />
{table.getHeaderGroups().map((headerGroup) => ( {headerGroup.headers.map((header) => { return ( {header.isPlaceholder ? null : flexRender( header.column .columnDef .header, header.getContext(), )} ); })} ))} {table.getRowModel().rows?.length ? ( table .getRowModel() .rows.map((row) => ( )) ) : ( {__('No automation rules found.')} )}
{table.getFilteredRowModel().rows.length}{' '} {__('rule(s) total.')}
); }