feat: customer relationship view, idempotency keys, approval decline, seed payments, backlog cleanup

- #3: Wire ApprovalModal handleDecline to POST /api/admin/bookings/{id}/cancel
- #25: New GET /api/admin/users/{id}/relationship endpoint with spend, tips, visits, customer-for duration, top services
- #25: UserModal reorganized — Personal Info, Booking History, Customer Relationship, Loyalty, Patch Tests
- #36: Idempotency keys on user and admin booking creation (UUID header, duplicate detection)
- local-dev-2.sh: seed payments via PL/pgSQL for completed bookings (5 randomized scenarios)
- local-dev-2.sh: shrink guest/time-blocker output, add payments to summary
- Backlog: mark #3/#25/#35/#36/#49 done, plan #36/#45, remove #46/#48, update #45 with milestone campaigns
- Remove notes history table, avg visits/year metric, Account Information, Privacy & Consent from UserModal
This commit is contained in:
2026-05-04 12:20:03 +01:00
parent 88ee265603
commit bec4100e4d
15 changed files with 1688 additions and 887 deletions
@@ -67,6 +67,7 @@
>({});
let submitting = $state(false);
let idempotencyKey = $state<string>('');
// Countdown state
let reservationCountdown = $state<string>('');
@@ -262,6 +263,11 @@
submitting = true;
try {
// Generate idempotency key if not already set (reused on retry)
if (!idempotencyKey) {
idempotencyKey = crypto.randomUUID();
}
// Validate duration doesn't exceed available slot
if (maxSlotDuration > 0 && getTotalDuration() > maxSlotDuration) {
toast.error(
@@ -358,6 +364,7 @@
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Idempotency-Key': idempotencyKey,
Authorization: `Bearer ${authStore.currentToken}`
},
body: JSON.stringify(payload)