All checks were successful
		
		
	
	Build Nginx-based docker image / build-static-assets (push) Successful in 2m0s
				
			Check usage of free licenses / build-static-assets (pull_request) Successful in 45s
				
			Add copyright notice / copyright_notice (pull_request) Successful in 59s
				
			Playwright Tests / test (pull_request) Successful in 3m44s
				
			
		
			
				
	
	
		
			180 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			180 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| /* Copyright (C) 2024-2025 LibreWeddingPlanner contributors*/
 | |
| 
 | |
| 'use client';
 | |
| 
 | |
| import { AbstractApi, } from '@/app/api/abstract-api';
 | |
| import { Group, GroupSerializer } from '@/app/lib/group';
 | |
| import { Guest, GuestSerializer } from '@/app/lib/guest';
 | |
| import { getCsrfToken, getSlug } from '@/app/lib/utils';
 | |
| import AffinitiesFormDialog from '@/app/ui/components/affinities-form-dialog';
 | |
| import { classNames } from '@/app/ui/components/button';
 | |
| import GroupFormDialog from '@/app/ui/components/group-form-dialog';
 | |
| import GuestFormDialog from '@/app/ui/components/guest-form-dialog';
 | |
| import GroupsTable from '@/app/ui/groups/table';
 | |
| import SkeletonTable from '@/app/ui/guests/skeleton-row';
 | |
| import GuestsTable from '@/app/ui/guests/table';
 | |
| import { TabPanel, TabView } from 'primereact/tabview';
 | |
| import { Toast } from 'primereact/toast';
 | |
| import { Suspense, useEffect, useRef, useState } from 'react';
 | |
| import InvitationsBoard from '@/app/ui/invitations/board';
 | |
| import { Invitation, InvitationSerializer } from '@/app/lib/invitation';
 | |
| import { Entity } from '@/app/lib/definitions';
 | |
| 
 | |
| 
 | |
| export default function Page() {
 | |
|   const [slug, setSlug] = useState<string>("default");
 | |
| 
 | |
|   useEffect(() => {
 | |
|     setSlug(getSlug());
 | |
|     refreshGroups();
 | |
|     refreshGuests();
 | |
|     refreshInvitations();
 | |
|   }, []);
 | |
| 
 | |
|   const toast = useRef<Toast>(null);
 | |
| 
 | |
|   function refreshGuests() {
 | |
|     new AbstractApi<Guest>().getAll(new GuestSerializer(), (objects: Guest[]) => {
 | |
|       setGuests(objects);
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   function refreshGroups() {
 | |
|     new AbstractApi<Group>().getAll(new GroupSerializer(), (objects: Group[]) => {
 | |
|       setGroups(objects);
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   function refreshInvitations() {
 | |
|     new AbstractApi<Invitation>().getAll(new InvitationSerializer(), (objects: Invitation[]) => {
 | |
|       setInvitations(objects);
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   function resetAffinities() {
 | |
|     fetch(`/api/${slug}/groups/affinities/reset`, {
 | |
|       method: 'POST',
 | |
|       headers: {
 | |
|         'Accept': 'application/json',
 | |
|         'X-CSRF-TOKEN': getCsrfToken(),
 | |
|       }
 | |
|     })
 | |
|       .then(response => {
 | |
|         if (response.ok) {
 | |
|           showAffinitiesResetSuccess();
 | |
|         } else {
 | |
|           console.error('Failed to reset affinities');
 | |
|         }
 | |
|       })
 | |
|       .catch(error => {
 | |
|         console.error('Error resetting affinities:', error);
 | |
|       });
 | |
|   }
 | |
| 
 | |
|   function showAffinitiesResetSuccess() {
 | |
|     toast.current?.show({
 | |
|       severity: 'success',
 | |
|       summary: 'Affinities reset',
 | |
|       detail: 'All affinities have been reset to default values.'
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   const [groups, setGroups] = useState<Array<Group>>([]);
 | |
|   const [groupBeingEdited, setGroupBeingEdited] = useState<Group | undefined>(undefined);
 | |
| 
 | |
|   const [groupAffinitiesBeingEditted, setGroupAffinitiesBeingEditted] = useState<Group | undefined>(undefined);
 | |
| 
 | |
|   const [guests, setGuests] = useState<Array<Guest>>([]);
 | |
|   const [guestBeingEdited, setGuestBeingEdited] = useState<Guest | undefined>(undefined);
 | |
| 
 | |
|   const [invitations, setInvitations] = useState<Array<Invitation>>([]);
 | |
| 
 | |
|   function updateList<T extends Entity>(originalList: T[], element: T): T[] {
 | |
|     {
 | |
|       const index = originalList.findIndex(g => g.id === element?.id);
 | |
|       if (index !== -1) {
 | |
|         // Replace existing element
 | |
|         return [
 | |
|           element!,
 | |
|           ...originalList.slice(0, index),
 | |
|           ...originalList.slice(index + 1)
 | |
|         ];
 | |
|       } else {
 | |
|         // Add new element at the start
 | |
|         return [element!, ...originalList];
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return (
 | |
|     <div className="w-full">
 | |
|       <TabView>
 | |
|         <TabPanel header="Guests" leftIcon="pi pi-users mx-2">
 | |
|           <div className="flex flex-col w-full items-center justify-between">
 | |
|             <button onClick={() => setGuestBeingEdited({})} className={classNames('primary')}>Add new</button>
 | |
|             <GuestFormDialog
 | |
|               key={guestBeingEdited?.id}
 | |
|               groups={groups}
 | |
|               onCreate={(newGuest) => {
 | |
|                 setGuests(guests => updateList(guests, newGuest));
 | |
|                 setGuestBeingEdited(undefined);
 | |
|               }}
 | |
|               guest={guestBeingEdited}
 | |
|               visible={guestBeingEdited !== undefined}
 | |
|               onHide={() => { setGuestBeingEdited(undefined) }}
 | |
|             />
 | |
|             <Suspense fallback={<SkeletonTable />}>
 | |
|               <GuestsTable
 | |
|                 guests={guests}
 | |
|                 onUpdate={refreshGuests}
 | |
|                 onEdit={(guest) => setGuestBeingEdited(guest)}
 | |
|               />
 | |
|             </Suspense>
 | |
|           </div>
 | |
|         </ TabPanel>
 | |
|         <TabPanel header="Groups" leftIcon="pi pi-sitemap mx-2">
 | |
|           <div className="flex flex-col w-full items-center justify-between">
 | |
| 
 | |
|             <div>
 | |
|               <Toast ref={toast} />
 | |
|               <button onClick={() => setGroupBeingEdited({})} className={classNames('primary')}>Add new</button>
 | |
|               <button onClick={resetAffinities} className={classNames('yellow')}>Reset affinities</button>
 | |
|             </div>
 | |
| 
 | |
|             <GroupFormDialog
 | |
|               key={groupBeingEdited?.id}
 | |
|               groups={groups}
 | |
|               onCreate={(newGroup) => {
 | |
|                 setGroups(groups => updateList(groups, newGroup));
 | |
|                 setGroupBeingEdited(undefined)
 | |
|               }}
 | |
|               group={groupBeingEdited}
 | |
|               visible={groupBeingEdited !== undefined}
 | |
|               onHide={() => { setGroupBeingEdited(undefined) }}
 | |
|             />
 | |
| 
 | |
|             <AffinitiesFormDialog
 | |
|               groups={groups}
 | |
|               group={groupAffinitiesBeingEditted}
 | |
|               visible={groupAffinitiesBeingEditted !== undefined}
 | |
|               onHide={() => { setGroupAffinitiesBeingEditted(undefined) }}
 | |
|             />
 | |
| 
 | |
|             <Suspense fallback={<SkeletonTable />}>
 | |
|               <GroupsTable
 | |
|                 groups={groups}
 | |
|                 onUpdate={refreshGroups}
 | |
|                 onEdit={(group) => setGroupBeingEdited(group)}
 | |
|                 onEditAffinities={(group) => setGroupAffinitiesBeingEditted(group)}
 | |
|               />
 | |
|             </Suspense>
 | |
|           </div>
 | |
|         </ TabPanel>
 | |
|         <TabPanel header="Invitations" leftIcon="pi pi-envelope mx-2">
 | |
|           <InvitationsBoard guests={guests} invitations={invitations} />
 | |
|         </TabPanel>
 | |
|       </ TabView>
 | |
|     </div>
 | |
| 
 | |
|   );
 | |
| } |