diff --git a/src/hospitality-web/src/App.tsx b/src/hospitality-web/src/App.tsx index 326932b..4affffc 100644 --- a/src/hospitality-web/src/App.tsx +++ b/src/hospitality-web/src/App.tsx @@ -2,6 +2,7 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { BrowserRouter, Routes, Route, Link } from 'react-router-dom'; import EventsPage from './pages/admin/EventsPage'; import EventDetailPage from './pages/admin/EventDetailPage'; +import GroupDetailPage from './pages/admin/GroupDetailPage'; import ScannerPage from './pages/staff/ScannerPage'; import GuestQrPage from './pages/guest/GuestQrPage'; @@ -34,6 +35,7 @@ function App() { } /> } /> + } /> } /> } /> diff --git a/src/hospitality-web/src/hooks/useGroups.ts b/src/hospitality-web/src/hooks/useGroups.ts index 28d75ad..170624f 100644 --- a/src/hospitality-web/src/hooks/useGroups.ts +++ b/src/hospitality-web/src/hooks/useGroups.ts @@ -50,15 +50,16 @@ export const useCreatePerson = (groupId: string) => { }); }; -export const useAssignQuota = (personId: string) => { +export const useAssignQuota = () => { const queryClient = useQueryClient(); return useMutation({ - mutationFn: async (data: AssignQuotaRequest) => { + mutationFn: async ({ personId, data }: { personId: string; data: AssignQuotaRequest }) => { const response = await peopleApi.assignQuota(personId, data); return response.data; }, - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ['people', personId] }); + onSuccess: (_, variables) => { + queryClient.invalidateQueries({ queryKey: ['people', variables.personId] }); + queryClient.invalidateQueries({ queryKey: ['groups'] }); }, }); }; diff --git a/src/hospitality-web/src/pages/admin/EventDetailPage.tsx b/src/hospitality-web/src/pages/admin/EventDetailPage.tsx index 36144fb..128b4ac 100644 --- a/src/hospitality-web/src/pages/admin/EventDetailPage.tsx +++ b/src/hospitality-web/src/pages/admin/EventDetailPage.tsx @@ -1,18 +1,19 @@ import { useState } from 'react'; -import { useParams } from 'react-router-dom'; +import { useParams, useNavigate } from 'react-router-dom'; import { useEvent } from '../../hooks/useEvents'; -import { useGroups, useCreateGroup, useCreatePerson } from '../../hooks/useGroups'; +import { useGroups, useCreateGroup } from '../../hooks/useGroups'; import { useProducts, useCreateProduct } from '../../hooks/useProducts'; -import { ProductType, type CreateGroupRequest, type CreatePersonRequest, type CreateProductRequest } from '../../lib/types'; +import { ProductType, type CreateGroupRequest, type CreateProductRequest } from '../../lib/types'; export default function EventDetailPage() { const { id } = useParams<{ id: string }>(); + const navigate = useNavigate(); const { data: event } = useEvent(id!); const { data: groups } = useGroups(id!); const { data: products } = useProducts(id!); const createGroup = useCreateGroup(id!); const createProduct = useCreateProduct(id!); - + const [showGroupForm, setShowGroupForm] = useState(false); const [showProductForm, setShowProductForm] = useState(false); const [groupForm, setGroupForm] = useState({ name: '' }); @@ -91,7 +92,11 @@ export default function EventDetailPage() {
{groups?.map((group) => ( -
+
navigate(`/groups/${group.id}`)} + className="bg-white p-4 rounded-lg shadow cursor-pointer hover:shadow-lg transition-shadow" + >

{group.name}

{group.contactPersonName && (

{group.contactPersonName}

diff --git a/src/hospitality-web/src/pages/admin/GroupDetailPage.tsx b/src/hospitality-web/src/pages/admin/GroupDetailPage.tsx new file mode 100644 index 0000000..c597ca8 --- /dev/null +++ b/src/hospitality-web/src/pages/admin/GroupDetailPage.tsx @@ -0,0 +1,201 @@ +import { useState } from 'react'; +import { useParams } from 'react-router-dom'; +import { useGroup, useCreatePerson, useAssignQuota } from '../../hooks/useGroups'; +import { useProducts } from '../../hooks/useProducts'; +import type { CreatePersonRequest, Person } from '../../lib/types'; + +export default function GroupDetailPage() { + const { id } = useParams<{ id: string }>(); + const { data: group, refetch } = useGroup(id!); + const createPerson = useCreatePerson(id!); + const assignQuota = useAssignQuota(); + + const [showPersonForm, setShowPersonForm] = useState(false); + const [personForm, setPersonForm] = useState({ name: '' }); + const [selectedPerson, setSelectedPerson] = useState(null); + const [quotaProductId, setQuotaProductId] = useState(''); + const [quotaAmount, setQuotaAmount] = useState(1); + + // Get event ID from group data + const eventId = group?.eventId || ''; + const { data: products } = useProducts(eventId); + + const handleCreatePerson = async (e: React.FormEvent) => { + e.preventDefault(); + await createPerson.mutateAsync(personForm); + setPersonForm({ name: '' }); + setShowPersonForm(false); + refetch(); + }; + + const handleAssignQuota = async (personId: string) => { + if (!quotaProductId) return; + + try { + await assignQuota.mutateAsync({ + personId, + data: { + productId: quotaProductId, + initialAmount: quotaAmount, + }, + }); + alert('Quota assigned successfully!'); + setQuotaProductId(''); + setQuotaAmount(1); + refetch(); + } catch (error) { + alert('Failed to assign quota'); + } + }; + + if (!group) return
Loading...
; + + return ( +
+
+

{group.name}

+ {group.contactPersonName && ( +

Contact: {group.contactPersonName}

+ )} + {group.contactEmail && ( +

{group.contactEmail}

+ )} +
+ +
+

People ({group.people?.length || 0})

+ +
+ + {showPersonForm && ( +
+

Add New Person

+
+
+ + setPersonForm({ ...personForm, name: e.target.value })} + className="w-full px-3 py-2 border border-gray-300 rounded-lg" + /> +
+
+ + setPersonForm({ ...personForm, email: e.target.value })} + className="w-full px-3 py-2 border border-gray-300 rounded-lg" + /> +
+
+ + setPersonForm({ ...personForm, phoneNumber: e.target.value })} + className="w-full px-3 py-2 border border-gray-300 rounded-lg" + /> +
+ +
+
+ )} + +
+ {group.people?.map((person) => ( +
+
+
+

{person.name}

+ {person.email &&

{person.email}

} + {person.phoneNumber &&

{person.phoneNumber}

} +

QR: {person.qrCode}

+
+ +
+ + {person.quotas && person.quotas.length > 0 && ( +
+

Current Quotas

+
+ {person.quotas.map((quota) => ( +
+ {quota.productName} + {quota.remainingAmount}/{quota.initialAmount} +
+ ))} +
+
+ )} + + {selectedPerson?.id === person.id && ( +
+

Assign New Quota

+
+ + setQuotaAmount(parseInt(e.target.value))} + placeholder="Amount" + className="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm" + /> + +
+
+ )} +
+ ))} +
+ + {(!group.people || group.people.length === 0) && !showPersonForm && ( +
+

No people in this group yet.

+ +
+ )} +
+ ); +}