Compare commits
	
		
			1 Commits
		
	
	
		
			main
			...
			groups-tre
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| c9dab33e85 | 
@ -13,20 +13,22 @@ export class Guest implements Entity {
 | 
				
			|||||||
  groupId?: string;
 | 
					  groupId?: string;
 | 
				
			||||||
  color?: string;
 | 
					  color?: string;
 | 
				
			||||||
  status?: GuestStatus;
 | 
					  status?: GuestStatus;
 | 
				
			||||||
 | 
					  children?: Guest[];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constructor(id?: string, name?: string, group_name?: string, groupId?: string, color?: string, status?: GuestStatus) {
 | 
					  constructor(id?: string, name?: string, group_name?: string, groupId?: string, color?: string, status?: GuestStatus, children?: Guest[]) {
 | 
				
			||||||
    this.id = id;
 | 
					    this.id = id;
 | 
				
			||||||
    this.name = name;
 | 
					    this.name = name;
 | 
				
			||||||
    this.group_name = group_name;
 | 
					    this.group_name = group_name;
 | 
				
			||||||
    this.groupId = groupId;
 | 
					    this.groupId = groupId;
 | 
				
			||||||
    this.color = color;
 | 
					    this.color = color;
 | 
				
			||||||
    this.status = status;
 | 
					    this.status = status;
 | 
				
			||||||
 | 
					    this.children = children;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class GuestSerializer implements Serializable<Guest> {
 | 
					export class GuestSerializer implements Serializable<Guest> {
 | 
				
			||||||
  fromJson(data: any): Guest {
 | 
					  fromJson(data: any): Guest {
 | 
				
			||||||
    return new Guest(data.id, data.name, data.group_name, data.group_id, data.color, data.status);
 | 
					    return new Guest(data.id, data.name, data.group_name, data.group_id, data.color, data.status, data.children);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  toJson(guest: Guest): string {
 | 
					  toJson(guest: Guest): string {
 | 
				
			||||||
 | 
				
			|||||||
@ -4,8 +4,11 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import { Group, GroupSerializer } from '@/app/lib/group';
 | 
					import { Group, GroupSerializer } from '@/app/lib/group';
 | 
				
			||||||
import TableOfContents from '../components/table-of-contents';
 | 
					import TableOfContents from '../components/table-of-contents';
 | 
				
			||||||
import { PencilIcon, TrashIcon } from '@heroicons/react/24/outline';
 | 
					import { MapPinIcon, PencilIcon, TrashIcon } from '@heroicons/react/24/outline';
 | 
				
			||||||
import { AbstractApi } from '@/app/api/abstract-api';
 | 
					import { AbstractApi } from '@/app/api/abstract-api';
 | 
				
			||||||
 | 
					import { TreeTable } from 'primereact/treetable';
 | 
				
			||||||
 | 
					import { Column } from 'primereact/column';
 | 
				
			||||||
 | 
					import { TreeNode } from 'primereact/treenode';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default function GroupsTable({ groups, onUpdate, onEdit }: {
 | 
					export default function GroupsTable({ groups, onUpdate, onEdit }: {
 | 
				
			||||||
  groups: Group[],
 | 
					  groups: Group[],
 | 
				
			||||||
@ -16,45 +19,85 @@ export default function GroupsTable({ groups, onUpdate, onEdit }: {
 | 
				
			|||||||
  const api = new AbstractApi<Group>();
 | 
					  const api = new AbstractApi<Group>();
 | 
				
			||||||
  const serializer = new GroupSerializer();
 | 
					  const serializer = new GroupSerializer();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  const actions = (group: Group) => (
 | 
				
			||||||
    <TableOfContents
 | 
					 | 
				
			||||||
      headers={['Name', 'Color', 'Confirmed', 'Tentative', 'Pending', 'Declined', 'Considered', 'Total', 'Actions']}
 | 
					 | 
				
			||||||
      caption='Groups'
 | 
					 | 
				
			||||||
      elements={groups}
 | 
					 | 
				
			||||||
      rowRender={(group) => (
 | 
					 | 
				
			||||||
        <tr key={group.id} className="bg-white border-b odd:bg-white odd:dark:bg-gray-900 even:bg-gray-50 even:dark:bg-gray-800">
 | 
					 | 
				
			||||||
          <td scope="row" className="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">
 | 
					 | 
				
			||||||
            {group.name}
 | 
					 | 
				
			||||||
          </td>
 | 
					 | 
				
			||||||
          <td className="px-6">
 | 
					 | 
				
			||||||
            <div className="w-8 h-8 rounded-full" style={{ backgroundColor: group.color }}></div>
 | 
					 | 
				
			||||||
          </td>
 | 
					 | 
				
			||||||
          <td className="px-6 text-lg">
 | 
					 | 
				
			||||||
            {group.attendance?.confirmed}
 | 
					 | 
				
			||||||
          </td>
 | 
					 | 
				
			||||||
          <td className="px-6 text-sm">
 | 
					 | 
				
			||||||
            {group.attendance?.tentative}
 | 
					 | 
				
			||||||
          </td>
 | 
					 | 
				
			||||||
          <td className="px-6 text-sm">
 | 
					 | 
				
			||||||
            {group.attendance?.invited}
 | 
					 | 
				
			||||||
          </td>
 | 
					 | 
				
			||||||
          <td className="px-6 text-sm">
 | 
					 | 
				
			||||||
            {group.attendance?.declined}
 | 
					 | 
				
			||||||
          </td>
 | 
					 | 
				
			||||||
          <td className="px-6 text-sm">
 | 
					 | 
				
			||||||
            {group.attendance?.considered}
 | 
					 | 
				
			||||||
          </td>
 | 
					 | 
				
			||||||
          <td className="px-6 text-sm">
 | 
					 | 
				
			||||||
            {group.attendance?.total}
 | 
					 | 
				
			||||||
          </td>
 | 
					 | 
				
			||||||
          <td>
 | 
					 | 
				
			||||||
    <div className="flex flex-row items-center">
 | 
					    <div className="flex flex-row items-center">
 | 
				
			||||||
      <TrashIcon className='size-6 cursor-pointer' onClick={() => { api.destroy(serializer, group, onUpdate) }} />
 | 
					      <TrashIcon className='size-6 cursor-pointer' onClick={() => { api.destroy(serializer, group, onUpdate) }} />
 | 
				
			||||||
      <PencilIcon className='size-6 cursor-pointer' onClick={() => onEdit(group)} />
 | 
					      <PencilIcon className='size-6 cursor-pointer' onClick={() => onEdit(group)} />
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
          </td>
 | 
					  );
 | 
				
			||||||
        </tr>
 | 
					
 | 
				
			||||||
      )}
 | 
					  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>
 | 
				
			||||||
 | 
					    </>
 | 
				
			||||||
  )
 | 
					  )
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user