From ee1ee6f41dedd8f2a9fe4c786c6637a0284dfce2 Mon Sep 17 00:00:00 2001 From: "bert.hausmans" Date: Fri, 10 Apr 2026 15:04:36 +0200 Subject: [PATCH] feat(app): enhanced crowd list detail panel with person management Co-Authored-By: Claude Opus 4.6 (1M context) --- .../crowd-lists/CrowdListDetailPanel.vue | 737 +++++++++++++++--- apps/app/src/composables/api/useCrowdLists.ts | 50 ++ apps/app/src/types/person.ts | 50 +- 3 files changed, 720 insertions(+), 117 deletions(-) diff --git a/apps/app/src/components/crowd-lists/CrowdListDetailPanel.vue b/apps/app/src/components/crowd-lists/CrowdListDetailPanel.vue index b731d62d..1c69b564 100644 --- a/apps/app/src/components/crowd-lists/CrowdListDetailPanel.vue +++ b/apps/app/src/components/crowd-lists/CrowdListDetailPanel.vue @@ -1,9 +1,17 @@ @@ -66,40 +230,15 @@ function formatDate(iso: string) { v-model="modelValue" location="end" temporary - :width="520" + :width="560" > @@ -253,6 +708,68 @@ function formatDate(iso: string) { :crowd-list="crowdList" /> + + + + + Weet je zeker dat je {{ removingPerson?.name }} van de lijst + {{ crowdList?.name }} wilt verwijderen? +

+ + De persoon wordt niet verwijderd uit het evenement — alleen van deze lijst. + +
+ + + + Annuleren + + + Verwijderen + + +
+
+ + + + + + Weet je zeker dat je {{ pendingPersonIds.length }} + {{ pendingPersonIds.length === 1 ? 'persoon' : 'personen' }} wilt goedkeuren? + + + + + Annuleren + + + Goedkeuren + + + + + { success: boolean @@ -9,6 +10,17 @@ interface ApiResponse { message?: string } +interface PaginatedResponse { + data: T[] + links: Record + meta: { + current_page: number + per_page: number + total: number + last_page: number + } +} + export function useCrowdLists(eventId: Ref) { return useQuery({ queryKey: ['crowd-lists', eventId], @@ -23,6 +35,20 @@ export function useCrowdLists(eventId: Ref) { }) } +export function useCrowdListPersons(eventId: Ref, crowdListId: Ref) { + return useQuery({ + queryKey: ['crowd-lists', eventId, 'persons', crowdListId], + queryFn: async () => { + const { data } = await apiClient.get>( + `/events/${eventId.value}/crowd-lists/${crowdListId.value}/persons`, + ) + + return data + }, + enabled: () => !!eventId.value && !!crowdListId.value, + }) +} + export function useCreateCrowdList(eventId: Ref) { const queryClient = useQueryClient() @@ -104,3 +130,27 @@ export function useRemovePersonFromCrowdList(eventId: Ref) { }, }) } + +export function useApproveAllPending(eventId: Ref) { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: async (personIds: string[]) => { + const results = await Promise.allSettled( + personIds.map(id => + apiClient.post>( + `/events/${eventId.value}/persons/${id}/approve`, + ), + ), + ) + + const approved = results.filter(r => r.status === 'fulfilled').length + + return { approved, total: personIds.length } + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['crowd-lists', eventId.value] }) + queryClient.invalidateQueries({ queryKey: ['persons', eventId.value] }) + }, + }) +} diff --git a/apps/app/src/types/person.ts b/apps/app/src/types/person.ts index b8e3a6b8..bd98a40a 100644 --- a/apps/app/src/types/person.ts +++ b/apps/app/src/types/person.ts @@ -1,12 +1,45 @@ import type { Company, CrowdType } from '@/types/organisation' -export type PersonStatus = - | 'invited' - | 'applied' - | 'pending' - | 'approved' - | 'rejected' - | 'no_show' +export const PersonStatus = { + INVITED: 'invited', + APPLIED: 'applied', + PENDING: 'pending', + APPROVED: 'approved', + REJECTED: 'rejected', + NO_SHOW: 'no_show', +} as const +export type PersonStatus = (typeof PersonStatus)[keyof typeof PersonStatus] + +export interface PersonTag { + id: string + person_tag: { + id: string + name: string + category: string | null + icon: string | null + color: string | null + } + source: string + proficiency: string | null + notes: string | null + assigned_at: string +} + +export interface CrowdListPivot { + added_at: string + added_by_user_id: string | null +} + +export interface PendingIdentityMatch { + match_id: string + matched_user: { + id: string + name: string + email: string + } + matched_on: string + confidence: string +} export interface Person { id: string @@ -21,6 +54,9 @@ export interface Person { created_at: string crowd_type: CrowdType | null company: Company | null + pending_identity_match?: PendingIdentityMatch + crowd_list_pivot?: CrowdListPivot + tags?: PersonTag[] } export interface CreatePersonPayload {