feat: enriched edit request system with side-by-side snapshots, calendar preloading, and admin review UI

Backend:
- Add enriched response types (EditSnapshot, EnrichedEditRequest) with original vs proposed snapshots
- Add 4 new GET endpoints for viewing edit requests (user and admin scoped)
- Remove github.com/lib/pq dependency — use native PostgreSQL array scanning
- Clean up edit requests, time blockers, and notifications on booking cancellation
- Validate exceptional closed hours on admin approve (409 Conflict)
- Notification upsert on edit request replace (no duplicate admin notifications)

Frontend:
- New user EditRequestModal with time/services/both modes and lunch protection
- New admin EditRequestModal with side-by-side diff (date/time, services, notes)
- Integrate edit requests into PendingApprovals card and notifications page
- Preload 3 months of availability to prevent calendar snap-back
- Apply lunch protection to isDateUnavailable in BookingFlow and BookingCreateModal
- Fix accessibility: card list items use <button> instead of <div>

Dev & Docs:
- Seed edit requests in local-dev-2.sh
- Update all Obsidian manuals with enriched edit request documentation
- 42 new tests (438/441 passing)
This commit is contained in:
2026-05-26 11:59:07 +01:00
parent 3ccc017716
commit 8574bf2221
19 changed files with 5270 additions and 599 deletions
@@ -471,8 +471,9 @@
{#if showCardList}
<div class="space-y-2">
{#if canSaveCards}
<div
class="flex items-center justify-between rounded-lg border p-3 cursor-pointer hover:border-gray-300 transition-colors"
<button
type="button"
class="flex w-full items-center justify-between rounded-lg border p-3 hover:border-gray-300 transition-colors"
onclick={() => {
showNewCardForm = true;
showCardList = false;
@@ -483,11 +484,12 @@
<svg class="h-4 w-4 text-gray-400" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M9 18l6-6-6-6" />
</svg>
</div>
</button>
{/if}
{#each paymentMethods as method (method.id)}
<div
class="flex items-center justify-between rounded-lg border p-3 cursor-pointer {selectedPaymentMethod === method.id ? 'border-input bg-fuchsia-100' : 'border-input hover:bg-fuchsia-50'}"
<button
type="button"
class="flex w-full items-center justify-between rounded-lg border p-3 {selectedPaymentMethod === method.id ? 'border-input bg-fuchsia-100' : 'border-input hover:bg-fuchsia-50'}"
onclick={() => {
selectedPaymentMethod = method.id;
showNewCardForm = false;
@@ -508,7 +510,7 @@
{#if selectedPaymentMethod === method.id}
<span class="text-xs font-medium text-foreground">Selected</span>
{/if}
</div>
</button>
{/each}
</div>
{/if}