/* Copyright (C) 2024-2025 LibreWeddingPlanner contributors*/ 'use client'; import { AbstractApi } from '@/app/api/abstract-api'; import { Group, GroupSerializer } from '@/app/lib/group'; import { AdjustmentsHorizontalIcon, PencilIcon, TrashIcon } from '@heroicons/react/24/outline'; import { Column } from 'primereact/column'; import { TreeNode } from 'primereact/treenode'; import { TreeTable } from 'primereact/treetable'; export default function GroupsTable({ groups, onUpdate, onEdit, onEditAffinities }: { groups: Group[], onUpdate: () => void, onEdit: (group: Group) => void, onEditAffinities: (group: Group) => void, }) { const api = new AbstractApi<Group>(); const serializer = new GroupSerializer(); const actions = (group: Group) => ( <div className="flex flex-row items-center"> <TrashIcon className='size-6 cursor-pointer' onClick={() => { api.destroy(serializer, group, onUpdate) }} /> <PencilIcon className='size-6 cursor-pointer' onClick={() => onEdit(group)} /> <AdjustmentsHorizontalIcon className='size-6 cursor-pointer' onClick={() => onEditAffinities(group)} /> </div> ); const index = groups.reduce((acc, group) => { if (group.id) { acc.set(group.id, group) } return acc; }, new Map()); groups.forEach(group => group.children = []); groups.forEach(group => { if (group.parentId) { const parent = index.get(group.parentId); if (parent) { if (!parent.children) { parent.children = []; } parent.children.push(group); } } }); const renderTree = (group: Group): TreeNode => { const childrenAttendance = (group.children || []).reduce((acc, child) => { acc.confirmed += child.attendance?.confirmed || 0; acc.tentative += child.attendance?.tentative || 0; acc.invited += child.attendance?.invited || 0; acc.declined += child.attendance?.declined || 0; acc.considered += child.attendance?.considered || 0; acc.total += child.attendance?.total || 0; return acc; }, { confirmed: 0, tentative: 0, invited: 0, declined: 0, considered: 0, total: 0 }); return { id: group.id, key: group.id, label: group.name, data: { name: group.name, color: <div className="w-8 h-8 rounded-full" style={{ backgroundColor: group.color }} />, confirmed: childrenAttendance.confirmed + (group.attendance?.confirmed || 0), tentative: childrenAttendance.tentative + (group.attendance?.tentative || 0), pending: childrenAttendance.invited + (group.attendance?.invited || 0), declined: childrenAttendance.declined + (group.attendance?.declined || 0), considered: childrenAttendance.considered + (group.attendance?.considered || 0), total: childrenAttendance.total + (group.attendance?.total || 0), actions: actions(group), }, children: group.children?.map(renderTree), } } const nodes: TreeNode[] = groups .filter(group => !group.parentId) .map(renderTree) const headers = ['Name', 'Color', 'Confirmed', 'Tentative', 'Pending', 'Declined', 'Considered', 'Total', 'Actions']; const rowClassName = () => { return { 'border-b odd:bg-white even:bg-gray-50 hover:bg-gray-100': true }; } return ( <> <TreeTable value={nodes} rowClassName={rowClassName} className='py-4'> <Column expander field="name" header="Name" className='w-2/5' /> <Column field="color" header="Color" bodyClassName="text-sm" /> <Column field="confirmed" header="Confirmed" bodyClassName="text-sm" /> <Column field="tentative" header="Tentative" bodyClassName="text-sm" /> <Column field="pending" header="Pending" bodyClassName="text-sm" /> <Column field="declined" header="Declined" bodyClassName="text-sm" /> <Column field="considered" header="Considered" bodyClassName="text-sm" /> <Column field="total" header="Total" bodyClassName="text-sm" /> <Column field="actions" header="Actions" /> </TreeTable> </> ) }