fix(admin): pagination, patch tests, and per-page limits
Backend:
- Fix GetAllAdminBookingsHandler and SearchAdminBookingsHandler to
return totalPages in response
- Auto-record patch tests when booking status progresses to "completed"
- Add GET/POST /api/admin/users/{id}/patch-tests endpoints
Frontend:
- BookingsCard: proper pagination with 4 per page, prev/next buttons
- UsersCard, BookingCreateModal, WalkInCreateModal: per_page=4 for user
search
- Add PatchTestModal for manual patch test entry in UserModal
- Hide patch test section when user has no eligible services
Database:
- Add UNIQUE constraint on user_service_patch_tests(user_id, service_id)
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
import { toast } from 'svelte-sonner';
|
||||
import * as Modal from '$lib/components/ui/dialog';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import PatchTestModal from './PatchTestModal.svelte';
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
@@ -68,6 +69,9 @@
|
||||
let totalBookingPages = $state(1);
|
||||
let loadingBookings = $state(false);
|
||||
|
||||
let showPatchTestModal = $state(false);
|
||||
let hasEligiblePatchTests = $state(false);
|
||||
|
||||
async function fetchUserDetails() {
|
||||
if (!userId) return;
|
||||
|
||||
@@ -92,6 +96,32 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchEligiblePatchTests() {
|
||||
if (!userId) return;
|
||||
try {
|
||||
const response = await fetch(`/api/admin/users/${userId}/patch-tests/eligible`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${authStore.currentToken}`
|
||||
}
|
||||
});
|
||||
if (response.ok) {
|
||||
const services = await response.json();
|
||||
hasEligiblePatchTests = services.length > 0;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error fetching eligible patch tests:', err);
|
||||
}
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
if (userId) {
|
||||
fetchUserDetails();
|
||||
fetchEligiblePatchTests();
|
||||
}
|
||||
});
|
||||
|
||||
async function fetchUserBookings(page: number = 1) {
|
||||
if (!userId) return;
|
||||
|
||||
@@ -99,7 +129,7 @@
|
||||
try {
|
||||
const params = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
per_page: '5'
|
||||
per_page: '4'
|
||||
});
|
||||
|
||||
const response = await fetch(`/api/admin/bookings/user/${userId}?${params}`, {
|
||||
@@ -279,6 +309,24 @@
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if hasEligiblePatchTests}
|
||||
<!-- Patch Test Actions -->
|
||||
<div class="rounded-lg border border-gray-200 bg-gray-50 p-4">
|
||||
<h3 class="mb-3 text-sm font-semibold tracking-wide text-gray-600 uppercase">
|
||||
Patch Tests
|
||||
</h3>
|
||||
<p class="mb-3 text-sm text-gray-600">
|
||||
Record patch test completion to allow this user to book services requiring one.
|
||||
</p>
|
||||
<Button variant="outline" onclick={() => (showPatchTestModal = true)}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="mr-2 h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>
|
||||
</svg>
|
||||
Record Patch Test
|
||||
</Button>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Loyalty & Referrals -->
|
||||
<div class="rounded-lg border border-gray-200 bg-gray-50 p-4">
|
||||
<h3 class="mb-3 text-sm font-semibold tracking-wide text-gray-600 uppercase">
|
||||
@@ -454,3 +502,14 @@
|
||||
</Modal.Footer>
|
||||
</Modal.Content>
|
||||
</Modal.Root>
|
||||
|
||||
{#if selectedUser}
|
||||
<PatchTestModal
|
||||
bind:open={showPatchTestModal}
|
||||
userId={selectedUser.id}
|
||||
userName={selectedUser.fullName}
|
||||
onPatchTestAdded={() => {
|
||||
fetchUserDetails();
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user