diff --git a/apps/app/src/components/shifts/AssignPersonDialog.vue b/apps/app/src/components/shifts/AssignPersonDialog.vue index da10db4e..745622dc 100644 --- a/apps/app/src/components/shifts/AssignPersonDialog.vue +++ b/apps/app/src/components/shifts/AssignPersonDialog.vue @@ -1,5 +1,6 @@ diff --git a/apps/app/src/components/shifts/ShiftDetailPanel.vue b/apps/app/src/components/shifts/ShiftDetailPanel.vue index 6395d4ca..911574ec 100644 --- a/apps/app/src/components/shifts/ShiftDetailPanel.vue +++ b/apps/app/src/components/shifts/ShiftDetailPanel.vue @@ -8,6 +8,7 @@ import { useAssignPersonToShift, } from '@/composables/api/useShiftAssignments' import AssignPersonDialog from '@/components/shifts/AssignPersonDialog.vue' +import { getApiErrorMessage } from '@/lib/apiErrors' import { useShiftDetailStore } from '@/stores/useShiftDetailStore' import { ShiftAssignmentStatus } from '@/types/shiftAssignment' import type { ShiftAssignment } from '@/types/shiftAssignment' @@ -75,12 +76,13 @@ async function executeReassign(assignment: ShiftAssignment) { shiftId: assignment.shift_id, personId: assignment.person_id, }) - successMessage.value = `${assignment.person?.full_name ?? 'Persoon'} opnieuw toegewezen` - showSuccess.value = true + flashFeedback(`${assignment.person?.full_name ?? 'Persoon'} opnieuw toegewezen`, 'success') } - catch { - successMessage.value = 'Fout bij opnieuw toewijzen' - showSuccess.value = true + catch (err: unknown) { + flashFeedback( + getApiErrorMessage(err, 'Fout bij opnieuw toewijzen'), + 'error', + ) } finally { reassigning.value = null @@ -177,17 +179,25 @@ function onToggleSelectAll() { } } -// Snackbar +// Snackbar (success + server errors) const showSuccess = ref(false) const successMessage = ref('') +const snackbarColor = ref<'success' | 'error'>('success') +const snackbarTimeout = ref(3000) + +function flashFeedback(message: string, variant: 'success' | 'error' = 'success') { + successMessage.value = message + snackbarColor.value = variant + snackbarTimeout.value = variant === 'error' ? 12_000 : 3000 + showSuccess.value = true +} // --- Actions --- function onApprove(assignment: ShiftAssignment) { approveAssignment(assignment.id, { onSuccess: () => { - successMessage.value = `${assignment.person?.full_name ?? 'Toewijzing'} goedgekeurd` - showSuccess.value = true + flashFeedback(`${assignment.person?.full_name ?? 'Toewijzing'} goedgekeurd`, 'success') }, }) } @@ -205,8 +215,7 @@ function onCancelExecute() { onSuccess: () => { isCancelDialogOpen.value = false cancellingAssignment.value = null - successMessage.value = `${name} geannuleerd` - showSuccess.value = true + flashFeedback(`${name} geannuleerd`, 'success') }, }) } @@ -236,8 +245,7 @@ function onRejectExecute() { isRejectDialogOpen.value = false rejectingAssignment.value = null rejectReason.value = '' - successMessage.value = `${name} afgewezen` - showSuccess.value = true + flashFeedback(`${name} afgewezen`, 'success') }, }, ) @@ -260,8 +268,7 @@ function onBulkApproveExecute() { bulkApprove(store.selectedAssignmentIds, { onSuccess: () => { isBulkApproveDialogOpen.value = false - successMessage.value = `${store.selectedAssignmentIds.length} toewijzingen goedgekeurd` - showSuccess.value = true + flashFeedback(`${store.selectedAssignmentIds.length} toewijzingen goedgekeurd`, 'success') store.clearSelection() }, }) @@ -271,8 +278,7 @@ function onBulkApproveExecute() { const isAssignDialogOpen = ref(false) function onPersonAssigned() { - successMessage.value = 'Persoon toegewezen' - showSuccess.value = true + flashFeedback('Persoon toegewezen', 'success') } // Fill rate color @@ -911,11 +917,13 @@ function fillRateColor(): string { @assigned="onPersonAssigned" /> - + {{ successMessage }} diff --git a/apps/app/src/lib/apiErrors.ts b/apps/app/src/lib/apiErrors.ts new file mode 100644 index 00000000..cf9a8258 --- /dev/null +++ b/apps/app/src/lib/apiErrors.ts @@ -0,0 +1,26 @@ +import { isAxiosError } from 'axios' + +/** + * Human-readable message from Laravel API validation / exception responses. + */ +export function getApiErrorMessage(error: unknown, fallback: string): string { + if (!isAxiosError(error)) return fallback + + const data = error.response?.data as Record | undefined + if (!data) return fallback + + const errors = data.errors + if (errors && typeof errors === 'object' && errors !== null && !Array.isArray(errors)) { + for (const value of Object.values(errors as Record)) { + if (Array.isArray(value) && value.length > 0 && typeof value[0] === 'string') + return value[0] + + if (typeof value === 'string') return value + } + } + + const message = data.message + if (typeof message === 'string' && message.trim() !== '') return message + + return fallback +}