feat: migrate to dedicated patch test management
This commit is contained in:
@@ -20,55 +20,27 @@
|
||||
patch_test_duration_hours: number;
|
||||
minimum_age_required: number;
|
||||
created_at: string;
|
||||
updated_at?: string;
|
||||
created_by?: string;
|
||||
updated_by?: string;
|
||||
};
|
||||
|
||||
// =============== State ===============
|
||||
let services = $state<Service[]>([]);
|
||||
let servicesLoading = $state(true);
|
||||
let servicesUpdating = $state<Record<string, boolean>>({});
|
||||
|
||||
// Service Creation State
|
||||
let showServiceModal = $state(false);
|
||||
let creatingService = $state(false);
|
||||
let newService = $state({
|
||||
let newService = $state<Omit<Service, 'id' | 'created_at' | 'created_by' | 'patch_test_duration_hours'>>({
|
||||
name: '',
|
||||
description: '',
|
||||
price: '',
|
||||
duration_minutes: 60,
|
||||
patch_test_duration_hours: 0,
|
||||
minimum_age_required: 0
|
||||
price: 0,
|
||||
duration_minutes: 0,
|
||||
is_active: true,
|
||||
minimum_age_required: 0,
|
||||
});
|
||||
let editingService = $state<Service | null>(null);
|
||||
let loading = $state(true);
|
||||
let serviceErrors = $state<Record<string, string>>({});
|
||||
|
||||
let serviceErrors = $state({
|
||||
name: '',
|
||||
price: '',
|
||||
duration_minutes: '',
|
||||
patch_test_duration_hours: '',
|
||||
minimum_age_required: ''
|
||||
});
|
||||
function validateDuration(val: any, field: string) {
|
||||
if (typeof val !== 'number' || val < 0) return 'Duration must be a positive number';
|
||||
return '';
|
||||
}
|
||||
|
||||
// =============== Validation ===============
|
||||
let isFormValid = $derived(
|
||||
newService.name.trim() !== '' &&
|
||||
/^\d+(\.\d{1,2})?$/.test(newService.price) &&
|
||||
parseFloat(newService.price) > 0 &&
|
||||
Number.isInteger(newService.duration_minutes) &&
|
||||
newService.duration_minutes > 0 &&
|
||||
Number.isInteger(newService.patch_test_duration_hours) &&
|
||||
newService.patch_test_duration_hours >= 0 &&
|
||||
Number.isInteger(newService.minimum_age_required) &&
|
||||
newService.minimum_age_required >= 0 &&
|
||||
newService.minimum_age_required <= 100
|
||||
);
|
||||
|
||||
function validatePrice(price: string): string {
|
||||
const validFormat = /^\d*\.?\d*$/.test(price);
|
||||
if (!validFormat) {
|
||||
return 'Price must be a valid number (e.g., 4.50)';
|
||||
}
|
||||
|
||||
const numPrice = parseFloat(price);
|
||||
if (isNaN(numPrice)) {
|
||||
@@ -143,12 +115,97 @@
|
||||
}
|
||||
|
||||
function validateDurationField() {
|
||||
serviceErrors.duration_minutes = validateDuration(
|
||||
newService.duration_minutes,
|
||||
'duration_minutes'
|
||||
);
|
||||
serviceErrors.duration_minutes = validateDuration(newService.duration_minutes, 'duration_minutes');
|
||||
}
|
||||
|
||||
function validateMinimumAgeField() {
|
||||
serviceErrors.minimum_age_required = validateDuration(newService.minimum_age_required, 'minimum_age_required');
|
||||
}
|
||||
|
||||
function validateNameField() {
|
||||
if (!newService.name) serviceErrors.name = 'Name is required';
|
||||
else serviceErrors.name = '';
|
||||
}
|
||||
|
||||
function validatePriceField() {
|
||||
if (typeof newService.price !== 'number' || newService.price <= 0) serviceErrors.price = 'Price must be greater than 0';
|
||||
else serviceErrors.price = '';
|
||||
}
|
||||
|
||||
let isFormValid = $derived(
|
||||
newService.name !== '' &&
|
||||
!serviceErrors.name &&
|
||||
!serviceErrors.price &&
|
||||
!serviceErrors.duration_minutes &&
|
||||
!serviceErrors.minimum_age_required
|
||||
);
|
||||
|
||||
let creatingService = $state(false);
|
||||
|
||||
async function createService() {
|
||||
creatingService = true;
|
||||
const loadingToast = toast.loading('Creating service...');
|
||||
|
||||
const payload = {
|
||||
...newService,
|
||||
price: Number(newService.price),
|
||||
duration_minutes: Number(newService.duration_minutes),
|
||||
minimum_age_required: Number(newService.minimum_age_required)
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/admin/services', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${authStore.currentToken}`
|
||||
},
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
await response.json();
|
||||
toast.success('Service created successfully!', { id: loadingToast });
|
||||
|
||||
resetServiceForm();
|
||||
showServiceModal = false;
|
||||
|
||||
await fetchServices();
|
||||
} else if (response.status === 409) {
|
||||
toast.error('A service with this name already exists', { id: loadingToast });
|
||||
} else if (response.status === 400) {
|
||||
const errorText = await response.text();
|
||||
toast.error(`Validation error: ${errorText}`, { id: loadingToast });
|
||||
} else {
|
||||
const errorText = await response.text();
|
||||
toast.error(`Failed to create service: ${errorText}`, { id: loadingToast });
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error creating service:', err);
|
||||
toast.error('Network error creating service', { id: loadingToast });
|
||||
} finally {
|
||||
creatingService = false;
|
||||
}
|
||||
}
|
||||
|
||||
function resetServiceForm() {
|
||||
newService = {
|
||||
name: '',
|
||||
description: '',
|
||||
price: 0,
|
||||
duration_minutes: 60,
|
||||
is_active: true,
|
||||
minimum_age_required: 0
|
||||
};
|
||||
serviceErrors = {
|
||||
name: '',
|
||||
price: '',
|
||||
duration_minutes: '',
|
||||
minimum_age_required: ''
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
function validatePatchTestField() {
|
||||
serviceErrors.patch_test_duration_hours = validateDuration(
|
||||
newService.patch_test_duration_hours,
|
||||
@@ -618,27 +675,6 @@
|
||||
|
||||
<!-- Patch Test and Minimum Age - Side by side on desktop -->
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<!-- Patch Test Duration -->
|
||||
<div class="space-y-2">
|
||||
<label for="patch-test-duration" class="text-sm font-medium"
|
||||
>Patch Test Duration (hours)</label
|
||||
>
|
||||
<Input
|
||||
id="patch-test-duration"
|
||||
type="number"
|
||||
min="0"
|
||||
step="1"
|
||||
placeholder="0"
|
||||
bind:value={newService.patch_test_duration_hours}
|
||||
onblur={validatePatchTestField}
|
||||
class="w-full border-red-500={serviceErrors.patch_test_duration_hours}"
|
||||
/>
|
||||
{#if serviceErrors.patch_test_duration_hours}
|
||||
<p class="text-sm text-red-600">{serviceErrors.patch_test_duration_hours}</p>
|
||||
{/if}
|
||||
<p class="text-xs text-gray-500">Hours required before service (0 for none)</p>
|
||||
</div>
|
||||
|
||||
<!-- Minimum Age -->
|
||||
<div class="space-y-2">
|
||||
<label for="minimum-age" class="text-sm font-medium">Minimum Age</label>
|
||||
|
||||
Reference in New Issue
Block a user