103 lines
3.7 KiB
TypeScript
103 lines
3.7 KiB
TypeScript
/* Copyright (C) 2024 Manuel Bustillo*/
|
|
|
|
'use client';
|
|
|
|
import { AbstractApi } from '@/app/api/abstract-api';
|
|
import { Group, GroupSerializer } from '@/app/lib/group';
|
|
import { 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 }: {
|
|
groups: Group[],
|
|
onUpdate: () => void,
|
|
onEdit: (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)} />
|
|
</div>
|
|
);
|
|
|
|
const index = groups.reduce((acc, group) => {
|
|
if (group.id) {
|
|
acc.set(group.id, group)
|
|
}
|
|
|
|
return acc;
|
|
}, new Map());
|
|
|
|
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>
|
|
</>
|
|
)
|
|
}
|