diff --git a/frontend/src/lib/components/admin/PatchTestsManagement.svelte b/frontend/src/lib/components/admin/PatchTestsManagement.svelte index 346aa97..ca65c87 100644 --- a/frontend/src/lib/components/admin/PatchTestsManagement.svelte +++ b/frontend/src/lib/components/admin/PatchTestsManagement.svelte @@ -7,6 +7,11 @@ import { Skeleton } from '$lib/components/ui/skeleton'; import * as Modal from '$lib/components/ui/dialog'; + type Service = { + id: string; + name: string; + }; + type PatchTest = { id: string; name: string; @@ -17,27 +22,153 @@ }; let patchTests = $state([]); + let availableServices = $state([]); let loading = $state(true); - let showModal = $state(false); + let isSubmitting = $state(false); - async function fetchPatchTests() { + let showModal = $state(false); + let isEditing = $state(false); + + let formData = $state({ + id: '', + name: '', + description: '', + notice_duration_hours: 48, + expiry_months: 6, + service_ids: [] as string[] + }); + + async function fetchData() { loading = true; try { - const response = await fetch('/api/admin/patch-tests', { - headers: { Authorization: `Bearer ${authStore.currentToken}` } - }); - if (response.ok) { - patchTests = await response.json(); + const [ptRes, svcRes] = await Promise.all([ + fetch('/api/admin/patch-tests', { + headers: { Authorization: `Bearer ${authStore.currentToken}` } + }), + fetch('/api/admin/services', { + headers: { Authorization: `Bearer ${authStore.currentToken}` } + }) + ]); + + if (ptRes.ok && svcRes.ok) { + patchTests = await ptRes.json(); + availableServices = await svcRes.json(); } else { - toast.error('Failed to load patch tests'); + toast.error('Failed to load patch test data'); } + } catch (err) { + console.error(err); + toast.error('Network error loading data'); } finally { loading = false; } } + function resetForm() { + formData = { + id: '', + name: '', + description: '', + notice_duration_hours: 48, + expiry_months: 6, + service_ids: [] + }; + isEditing = false; + } + + function openAddModal() { + resetForm(); + showModal = true; + } + + function openEditModal(pt: PatchTest) { + formData = { + id: pt.id, + name: pt.name, + description: pt.description || '', + notice_duration_hours: pt.notice_duration_hours, + expiry_months: pt.expiry_months, + service_ids: pt.service_ids || [] + }; + isEditing = true; + showModal = true; + } + + function toggleServiceSelection(serviceId: string) { + if (formData.service_ids.includes(serviceId)) { + formData.service_ids = formData.service_ids.filter((id) => id !== serviceId); + } else { + formData.service_ids = [...formData.service_ids, serviceId]; + } + } + + async function savePatchTest() { + if (!formData.name.trim()) { + toast.error('Patch test name is required'); + return; + } + + isSubmitting = true; + const method = isEditing ? 'PUT' : 'POST'; + const url = isEditing ? `/api/admin/patch-tests/${formData.id}` : '/api/admin/patch-tests'; + + const payload = { + name: formData.name.trim(), + description: formData.description.trim() || undefined, + notice_duration_hours: Number(formData.notice_duration_hours), + expiry_months: Number(formData.expiry_months), + service_ids: formData.service_ids + }; + + try { + const res = await fetch(url, { + method, + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${authStore.currentToken}` + }, + body: JSON.stringify(payload) + }); + + if (res.ok) { + toast.success(`Patch test ${isEditing ? 'updated' : 'created'} successfully!`); + showModal = false; + await fetchData(); + } else { + const errText = await res.text(); + toast.error(`Failed to save: ${errText}`); + } + } catch (err) { + console.error(err); + toast.error('Network error saving patch test'); + } finally { + isSubmitting = false; + } + } + + async function deletePatchTest(id: string) { + if (!confirm('Are you sure you want to delete this patch test requirement?')) return; + + try { + const res = await fetch(`/api/admin/patch-tests/${id}`, { + method: 'DELETE', + headers: { Authorization: `Bearer ${authStore.currentToken}` } + }); + + if (res.ok) { + toast.success('Patch test deleted'); + await fetchData(); + } else { + toast.error('Failed to delete patch test'); + } + } catch (err) { + console.error(err); + toast.error('Network error deleting patch test'); + } + } + $effect(() => { - fetchPatchTests(); + fetchData(); }); @@ -46,46 +177,225 @@
Patch Test Management - Manage patch test requirements. + + Create and manage patch test requirements and assign them to services. +
- +
- - {#if loading} - - {:else} -
- - - - - - - - - - {#each patchTests as pt} + + +
NameNotice (hrs)Expiry (months)
+ + + + + + + + + + + {#if loading} + {#each Array(2) as _, i (i)} - - - + + + + + {/each} - -
NameDescriptionNotice (hrs)Expiry (months)Actions
{pt.name}{pt.notice_duration_hours}{pt.expiry_months} +
+ + +
+
-
- {/if} + {:else} + {#each patchTests as pt (pt.id)} + + {pt.name} + + {pt.description || '—'} + + {pt.notice_duration_hours} + {pt.expiry_months} + +
+ + +
+ + + {/each} + {#if patchTests.length === 0} + + + No patch tests configured. Click "Add Patch Test" to create one. + + + {/if} + {/if} + + + + +
+ {#if loading} + {#each Array(2) as _, i (i)} +
+ + +
+ + +
+
+ {/each} + {:else} + {#each patchTests as pt (pt.id)} +
+
+

{pt.name}

+
+ {#if pt.description} +

{pt.description}

+ {/if} +
+
+ Notice: {pt.notice_duration_hours}h +
+
+ Expiry: {pt.expiry_months}m +
+
+
+ + +
+
+ {/each} + {#if patchTests.length === 0} +
+ No patch tests configured. +
+ {/if} + {/if} +
- + - Add Patch Test + {isEditing ? 'Edit' : 'Add'} Patch Test + + Configure the rules for this patch test requirement. + -
-

Patch test creation modal content goes here...

+ +
+
+ + +
+ +
+ + +
+ +
+
+ + +

Hours prior to booking required

+
+ +
+ + +

How long the test remains valid

+
+
+ +
+ +

Select which services require this patch test

+
+ {#if availableServices.length === 0} +

No services available.

+ {:else} + {#each availableServices as svc (svc.id)} + + {/each} + {/if} +
+
+ + + + +