fix: Improve SelectDropdown handling and valueset caching
This commit is contained in:
parent
4accf7b6d6
commit
4641668f78
@ -8,7 +8,7 @@ export async function fetchProvinces() {
|
|||||||
return get('/api/areageo/provinces');
|
return get('/api/areageo/provinces');
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchCities(provinceId = null) {
|
export async function fetchCities(provinceId = null) {
|
||||||
const query = provinceId ? `?province_id=${provinceId}` : '';
|
const query = provinceId ? `?province_id=${encodeURIComponent(provinceId)}` : '';
|
||||||
return get(`/api/areageo/cities${query}`);
|
return get(`/api/areageo/cities${query}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,6 +29,16 @@
|
|||||||
lg: 'modal-lg',
|
lg: 'modal-lg',
|
||||||
xl: 'modal-xl',
|
xl: 'modal-xl',
|
||||||
full: 'modal-full',
|
full: 'modal-full',
|
||||||
|
wide: 'modal-wide',
|
||||||
|
};
|
||||||
|
|
||||||
|
const widthStyles = {
|
||||||
|
sm: 'max-width: 400px;',
|
||||||
|
md: 'max-width: 500px;',
|
||||||
|
lg: 'max-width: 800px;',
|
||||||
|
xl: 'max-width: 1200px;',
|
||||||
|
full: 'max-width: 100%; width: 100%; height: 100%;',
|
||||||
|
wide: 'max-width: 90vw; width: 1200px;',
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -67,7 +77,7 @@
|
|||||||
<svelte:window onkeydown={handleKeydown} />
|
<svelte:window onkeydown={handleKeydown} />
|
||||||
|
|
||||||
<dialog class="modal {sizeClasses[size] || ''}" class:modal-open={open}>
|
<dialog class="modal {sizeClasses[size] || ''}" class:modal-open={open}>
|
||||||
<div class="modal-box" role="dialog" aria-modal="true" aria-labelledby={title ? 'modal-title' : undefined}>
|
<div class="modal-box" style={widthStyles[size] || ''} role="dialog" aria-modal="true" aria-labelledby={title ? 'modal-title' : undefined}>
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<div class="flex items-center justify-between mb-4">
|
<div class="flex items-center justify-between mb-4">
|
||||||
{#if title}
|
{#if title}
|
||||||
|
|||||||
@ -38,12 +38,20 @@
|
|||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
if (valueSetKey) {
|
if (valueSetKey) {
|
||||||
loading = true;
|
loading = true;
|
||||||
const items = await valueSets.load(valueSetKey);
|
try {
|
||||||
dropdownOptions = items.map((item) => ({
|
const items = await valueSets.load(valueSetKey);
|
||||||
value: item.Value,
|
if (!items || items.length === 0) {
|
||||||
label: item.Label,
|
console.warn('SelectDropdown: No items loaded for valueset:', valueSetKey);
|
||||||
}));
|
}
|
||||||
loading = false;
|
dropdownOptions = items.map((item) => ({
|
||||||
|
value: item.value || item.Value || item.code || item.Code || item.ItemCode || item.itemCode || '',
|
||||||
|
label: item.label || item.Label || item.description || item.Description || item.name || item.Name || item.value || item.Value || '',
|
||||||
|
}));
|
||||||
|
} catch (err) {
|
||||||
|
console.error('SelectDropdown error loading valueset:', err);
|
||||||
|
} finally {
|
||||||
|
loading = false;
|
||||||
|
}
|
||||||
} else if (options.length > 0) {
|
} else if (options.length > 0) {
|
||||||
dropdownOptions = options;
|
dropdownOptions = options;
|
||||||
}
|
}
|
||||||
@ -51,7 +59,7 @@
|
|||||||
|
|
||||||
// Watch for changes in manual options
|
// Watch for changes in manual options
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (!valueSetKey && options.length > 0) {
|
if (!valueSetKey) {
|
||||||
dropdownOptions = options;
|
dropdownOptions = options;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -11,6 +11,9 @@ import { error as toastError } from '$lib/utils/toast.js';
|
|||||||
function createValueSetsStore() {
|
function createValueSetsStore() {
|
||||||
const { subscribe, set, update } = writable({});
|
const { subscribe, set, update } = writable({});
|
||||||
|
|
||||||
|
// Keep track of in-flight requests to prevent duplicates
|
||||||
|
const inflightRequests = new Map();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
subscribe,
|
subscribe,
|
||||||
|
|
||||||
@ -20,68 +23,74 @@ function createValueSetsStore() {
|
|||||||
* @returns {Promise<Array>} - Array of value set items
|
* @returns {Promise<Array>} - Array of value set items
|
||||||
*/
|
*/
|
||||||
async load(key) {
|
async load(key) {
|
||||||
let result = [];
|
console.log('valuesets.load() called for key:', key);
|
||||||
|
|
||||||
update((cache) => {
|
// If there's already an in-flight request, return its promise
|
||||||
// If already loading, return current state
|
if (inflightRequests.has(key)) {
|
||||||
if (cache[key]?.loading) {
|
console.log('valuesets: returning existing in-flight request for:', key);
|
||||||
return cache;
|
return inflightRequests.get(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If already loaded, return cached items
|
// Check if already loaded in cache
|
||||||
if (cache[key]?.loaded) {
|
let cacheState = null;
|
||||||
result = cache[key].items;
|
subscribe((state) => { cacheState = state; })();
|
||||||
return cache;
|
|
||||||
}
|
if (cacheState?.[key]?.loaded) {
|
||||||
|
console.log('valuesets: returning cached items for:', key);
|
||||||
|
return cacheState[key].items;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the fetch promise
|
||||||
|
const fetchPromise = (async () => {
|
||||||
|
console.log('valuesets: starting fetch for:', key);
|
||||||
|
|
||||||
// Mark as loading
|
// Mark as loading
|
||||||
return {
|
update((cache) => ({
|
||||||
...cache,
|
...cache,
|
||||||
[key]: { items: [], loaded: false, loading: true, error: null },
|
[key]: { items: [], loaded: false, loading: true, error: null },
|
||||||
};
|
}));
|
||||||
});
|
|
||||||
|
|
||||||
// If already loaded, return immediately
|
try {
|
||||||
if (result.length > 0) {
|
console.log('valuesets: calling API for key:', key);
|
||||||
return result;
|
const response = await fetchValueSetByKey(key);
|
||||||
}
|
console.log('valuesets: API response for', key, ':', response);
|
||||||
|
|
||||||
// If already loading, wait a bit and retry
|
if (response.status === 'success' && response.data) {
|
||||||
const currentState = getCurrentState();
|
// Handle both response.data being an array or having an Items property
|
||||||
if (currentState[key]?.loading) {
|
let items = Array.isArray(response.data) ? response.data : (response.data.Items || []);
|
||||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
console.log('valuesets: items for', key, ':', items);
|
||||||
return this.load(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
// Sort by Sequence if available
|
||||||
const response = await fetchValueSetByKey(key);
|
items.sort((a, b) => (a.Sequence || 0) - (b.Sequence || 0));
|
||||||
|
|
||||||
if (response.status === 'success' && response.data) {
|
update((cache) => ({
|
||||||
const items = response.data.Items || [];
|
...cache,
|
||||||
|
[key]: { items, loaded: true, loading: false, error: null },
|
||||||
|
}));
|
||||||
|
|
||||||
// Sort by Sequence if available
|
console.log('valuesets: returning items for', key);
|
||||||
items.sort((a, b) => (a.Sequence || 0) - (b.Sequence || 0));
|
return items;
|
||||||
|
} else {
|
||||||
|
throw new Error(response.message || 'Failed to load value set');
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
const errorMessage = err.message || `Failed to load value set: ${key}`;
|
||||||
|
console.error('valuesets: error for', key, ':', err);
|
||||||
|
|
||||||
update((cache) => ({
|
update((cache) => ({
|
||||||
...cache,
|
...cache,
|
||||||
[key]: { items, loaded: true, loading: false, error: null },
|
[key]: { items: [], loaded: false, loading: false, error: errorMessage },
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return items;
|
toastError(errorMessage);
|
||||||
} else {
|
return [];
|
||||||
throw new Error(response.message || 'Failed to load value set');
|
} finally {
|
||||||
|
inflightRequests.delete(key);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
})();
|
||||||
const errorMessage = err.message || `Failed to load value set: ${key}`;
|
|
||||||
|
|
||||||
update((cache) => ({
|
inflightRequests.set(key, fetchPromise);
|
||||||
...cache,
|
return fetchPromise;
|
||||||
[key]: { items: [], loaded: false, loading: false, error: errorMessage },
|
|
||||||
}));
|
|
||||||
|
|
||||||
toastError(errorMessage);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -163,15 +172,6 @@ function createValueSetsStore() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper to get current state synchronously
|
|
||||||
function getCurrentState() {
|
|
||||||
let state = {};
|
|
||||||
valueSets.subscribe((s) => {
|
|
||||||
state = s;
|
|
||||||
})();
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const valueSets = createValueSetsStore();
|
export const valueSets = createValueSetsStore();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -2,10 +2,10 @@
|
|||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import {
|
import {
|
||||||
fetchPatients,
|
fetchPatients,
|
||||||
|
fetchPatient,
|
||||||
createPatient,
|
createPatient,
|
||||||
updatePatient,
|
updatePatient,
|
||||||
deletePatient,
|
deletePatient,
|
||||||
checkPatientExists,
|
|
||||||
} from '$lib/api/patients.js';
|
} from '$lib/api/patients.js';
|
||||||
import { fetchProvinces, fetchCities } from '$lib/api/geography.js';
|
import { fetchProvinces, fetchCities } from '$lib/api/geography.js';
|
||||||
import { success as toastSuccess, error as toastError } from '$lib/utils/toast.js';
|
import { success as toastSuccess, error as toastError } from '$lib/utils/toast.js';
|
||||||
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
// State
|
// State
|
||||||
let loading = $state(false);
|
let loading = $state(false);
|
||||||
|
let modalLoading = $state(false);
|
||||||
let patients = $state([]);
|
let patients = $state([]);
|
||||||
let provinces = $state([]);
|
let provinces = $state([]);
|
||||||
let cities = $state([]);
|
let cities = $state([]);
|
||||||
@ -26,6 +27,7 @@
|
|||||||
let deleteItem = $state(null);
|
let deleteItem = $state(null);
|
||||||
let currentStep = $state(1);
|
let currentStep = $state(1);
|
||||||
let formErrors = $state({});
|
let formErrors = $state({});
|
||||||
|
let previousProvince = $state('');
|
||||||
|
|
||||||
// Search and pagination
|
// Search and pagination
|
||||||
let searchQuery = $state('');
|
let searchQuery = $state('');
|
||||||
@ -69,7 +71,9 @@
|
|||||||
PatCom: '',
|
PatCom: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
// Dropdown options
|
|
||||||
|
|
||||||
|
// Dropdown options (prefix - valueset not available yet)
|
||||||
const prefixOptions = [
|
const prefixOptions = [
|
||||||
{ value: 'Mr', label: 'Mr' },
|
{ value: 'Mr', label: 'Mr' },
|
||||||
{ value: 'Mrs', label: 'Mrs' },
|
{ value: 'Mrs', label: 'Mrs' },
|
||||||
@ -78,35 +82,13 @@
|
|||||||
{ value: 'Prof', label: 'Prof' },
|
{ value: 'Prof', label: 'Prof' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const sexOptions = [
|
|
||||||
{ value: '1', label: 'Female' },
|
|
||||||
{ value: '2', label: 'Male' },
|
|
||||||
];
|
|
||||||
|
|
||||||
const maritalStatusOptions = [
|
|
||||||
{ value: 'A', label: 'Annulled' },
|
|
||||||
{ value: 'B', label: 'Separated' },
|
|
||||||
{ value: 'D', label: 'Divorced' },
|
|
||||||
{ value: 'M', label: 'Married' },
|
|
||||||
{ value: 'S', label: 'Single' },
|
|
||||||
{ value: 'W', label: 'Widowed' },
|
|
||||||
];
|
|
||||||
|
|
||||||
const identifierTypeOptions = [
|
|
||||||
{ value: 'KTP', label: 'KTP (National ID)' },
|
|
||||||
{ value: 'PASS', label: 'Passport' },
|
|
||||||
{ value: 'SSN', label: 'SSN' },
|
|
||||||
{ value: 'SIM', label: 'Driver License' },
|
|
||||||
{ value: 'KTAS', label: 'KTAS' },
|
|
||||||
];
|
|
||||||
|
|
||||||
// Derived values
|
// Derived values
|
||||||
const provinceOptions = $derived(
|
const provinceOptions = $derived(
|
||||||
provinces.map((p) => ({ value: p.AreaCode, label: p.AreaName }))
|
provinces.map((p) => ({ value: p.value, label: p.label }))
|
||||||
);
|
);
|
||||||
|
|
||||||
const cityOptions = $derived(
|
const cityOptions = $derived(
|
||||||
cities.map((c) => ({ value: c.AreaCode, label: c.AreaName }))
|
cities.map((c) => ({ value: c.value, label: c.label }))
|
||||||
);
|
);
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
@ -118,7 +100,10 @@
|
|||||||
];
|
];
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
await Promise.all([loadPatients(), loadProvinces()]);
|
await Promise.all([
|
||||||
|
loadPatients(),
|
||||||
|
loadProvinces(),
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
async function loadPatients() {
|
async function loadPatients() {
|
||||||
@ -164,7 +149,7 @@
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const response = await fetchCities(provinceCode);
|
const response = await fetchCities(provinceCode);
|
||||||
cities = Array.isArray(response.data) ? response.data : [];
|
cities = Array.isArray(response.data) ? response.data : (Array.isArray(response) ? response : []);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to load cities:', err);
|
console.error('Failed to load cities:', err);
|
||||||
cities = [];
|
cities = [];
|
||||||
@ -224,50 +209,62 @@
|
|||||||
modalOpen = true;
|
modalOpen = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function openEditModal(row) {
|
async function openEditModal(row) {
|
||||||
modalMode = 'edit';
|
modalMode = 'edit';
|
||||||
currentStep = 1;
|
currentStep = 1;
|
||||||
formErrors = {};
|
formErrors = {};
|
||||||
formData = {
|
modalLoading = true;
|
||||||
InternalPID: row.InternalPID,
|
|
||||||
PatientID: row.PatientID || '',
|
|
||||||
Prefix: row.Prefix || '',
|
|
||||||
NameFirst: row.NameFirst || '',
|
|
||||||
NameMiddle: row.NameMiddle || '',
|
|
||||||
NameLast: row.NameLast || '',
|
|
||||||
NameMaiden: row.NameMaiden || '',
|
|
||||||
Suffix: row.Suffix || '',
|
|
||||||
Sex: row.Sex || '',
|
|
||||||
Birthdate: row.Birthdate ? row.Birthdate.split('T')[0] : '',
|
|
||||||
PlaceOfBirth: row.PlaceOfBirth || '',
|
|
||||||
Citizenship: row.Citizenship || '',
|
|
||||||
Street_1: row.Street_1 || '',
|
|
||||||
Street_2: row.Street_2 || '',
|
|
||||||
Street_3: row.Street_3 || '',
|
|
||||||
ZIP: row.ZIP || '',
|
|
||||||
Province: row.Province || '',
|
|
||||||
City: row.City || '',
|
|
||||||
Country: row.Country || 'Indonesia',
|
|
||||||
Phone: row.Phone || '',
|
|
||||||
MobilePhone: row.MobilePhone || '',
|
|
||||||
EmailAddress1: row.EmailAddress1 || '',
|
|
||||||
EmailAddress2: row.EmailAddress2 || '',
|
|
||||||
PatIdt: row.PatIdt || { IdentifierType: '', Identifier: '' },
|
|
||||||
Race: row.Race || '',
|
|
||||||
MaritalStatus: row.MaritalStatus || '',
|
|
||||||
Religion: row.Religion || '',
|
|
||||||
Ethnic: row.Ethnic || '',
|
|
||||||
DeathIndicator: row.DeathIndicator || 'N',
|
|
||||||
TimeOfDeath: row.TimeOfDeath ? row.TimeOfDeath.split('T')[0] : '',
|
|
||||||
PatCom: row.PatCom || '',
|
|
||||||
};
|
|
||||||
|
|
||||||
// Load cities if province is set
|
try {
|
||||||
if (formData.Province) {
|
// Fetch full patient details including individual name fields
|
||||||
loadCities(formData.Province);
|
const response = await fetchPatient(row.InternalPID);
|
||||||
|
const patient = response.data || response;
|
||||||
|
|
||||||
|
formData = {
|
||||||
|
InternalPID: patient.InternalPID,
|
||||||
|
PatientID: patient.PatientID || '',
|
||||||
|
Prefix: patient.Prefix || '',
|
||||||
|
NameFirst: patient.NameFirst || '',
|
||||||
|
NameMiddle: patient.NameMiddle || '',
|
||||||
|
NameLast: patient.NameLast || '',
|
||||||
|
NameMaiden: patient.NameMaiden || '',
|
||||||
|
Suffix: patient.Suffix || '',
|
||||||
|
Sex: patient.Sex || '',
|
||||||
|
Birthdate: patient.Birthdate ? patient.Birthdate.split('T')[0] : '',
|
||||||
|
PlaceOfBirth: patient.PlaceOfBirth || '',
|
||||||
|
Citizenship: patient.Citizenship || '',
|
||||||
|
Street_1: patient.Street_1 || '',
|
||||||
|
Street_2: patient.Street_2 || '',
|
||||||
|
Street_3: patient.Street_3 || '',
|
||||||
|
ZIP: patient.ZIP || '',
|
||||||
|
Province: patient.Province || '',
|
||||||
|
City: patient.City || '',
|
||||||
|
Country: patient.Country || 'Indonesia',
|
||||||
|
Phone: patient.Phone || '',
|
||||||
|
MobilePhone: patient.MobilePhone || '',
|
||||||
|
EmailAddress1: patient.EmailAddress1 || '',
|
||||||
|
EmailAddress2: patient.EmailAddress2 || '',
|
||||||
|
PatIdt: patient.PatIdt || { IdentifierType: '', Identifier: '' },
|
||||||
|
Race: patient.Race || '',
|
||||||
|
MaritalStatus: patient.MaritalStatus || '',
|
||||||
|
Religion: patient.Religion || '',
|
||||||
|
Ethnic: patient.Ethnic || '',
|
||||||
|
DeathIndicator: patient.DeathIndicator || 'N',
|
||||||
|
TimeOfDeath: patient.TimeOfDeath ? patient.TimeOfDeath.split('T')[0] : '',
|
||||||
|
PatCom: patient.PatCom || '',
|
||||||
|
};
|
||||||
|
|
||||||
|
// Load cities if province is set
|
||||||
|
if (formData.Province) {
|
||||||
|
await loadCities(formData.Province);
|
||||||
|
}
|
||||||
|
|
||||||
|
modalOpen = true;
|
||||||
|
} catch (err) {
|
||||||
|
toastError(err.message || 'Failed to load patient details');
|
||||||
|
} finally {
|
||||||
|
modalLoading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
modalOpen = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateStep(step) {
|
function validateStep(step) {
|
||||||
@ -358,10 +355,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleProvinceChange() {
|
$effect(() => {
|
||||||
formData.City = '';
|
if (formData.Province !== previousProvince) {
|
||||||
loadCities(formData.Province);
|
previousProvince = formData.Province;
|
||||||
}
|
formData.City = '';
|
||||||
|
loadCities(formData.Province);
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="p-6">
|
<div class="p-6">
|
||||||
@ -401,10 +401,10 @@
|
|||||||
{columns}
|
{columns}
|
||||||
data={patients.map((p) => ({
|
data={patients.map((p) => ({
|
||||||
...p,
|
...p,
|
||||||
FullName: [p.Prefix, p.NameFirst, p.NameMiddle, p.NameLast, p.Suffix]
|
FullName: p.FullName || [p.Prefix, p.NameFirst, p.NameMiddle, p.NameLast, p.Suffix]
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.join(' '),
|
.join(' ') || '-',
|
||||||
SexLabel: p.Sex === '1' ? 'Female' : p.Sex === '2' ? 'Male' : '-',
|
SexLabel: p.SexLabel || (p.Sex === '1' ? 'Female' : p.Sex === '2' ? 'Male' : '-'),
|
||||||
BirthdateFormatted: p.Birthdate
|
BirthdateFormatted: p.Birthdate
|
||||||
? new Date(p.Birthdate).toLocaleDateString()
|
? new Date(p.Birthdate).toLocaleDateString()
|
||||||
: '-',
|
: '-',
|
||||||
@ -464,69 +464,64 @@
|
|||||||
<Modal
|
<Modal
|
||||||
bind:open={modalOpen}
|
bind:open={modalOpen}
|
||||||
title={modalMode === 'create' ? 'Add Patient' : 'Edit Patient'}
|
title={modalMode === 'create' ? 'Add Patient' : 'Edit Patient'}
|
||||||
size="xl"
|
size="lg"
|
||||||
>
|
>
|
||||||
<!-- Step Indicator -->
|
<!-- Tabs -->
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<div class="flex items-center justify-center gap-4">
|
<div class="flex items-center justify-center gap-2">
|
||||||
<button
|
<button
|
||||||
class="flex items-center gap-2 px-4 py-2 rounded-full transition-colors"
|
class="px-4 py-2 rounded-lg transition-colors"
|
||||||
class:bg-emerald-100={currentStep === 1}
|
class:bg-emerald-600={currentStep === 1}
|
||||||
class:text-emerald-700={currentStep === 1}
|
class:text-white={currentStep === 1}
|
||||||
class:bg-gray-100={currentStep !== 1}
|
class:bg-gray-100={currentStep !== 1}
|
||||||
class:text-gray-600={currentStep !== 1}
|
class:text-gray-600={currentStep !== 1}
|
||||||
|
class:font-semibold={currentStep === 1}
|
||||||
onclick={() => (currentStep = 1)}
|
onclick={() => (currentStep = 1)}
|
||||||
>
|
>
|
||||||
<span class="w-6 h-6 rounded-full bg-current text-white flex items-center justify-center text-sm font-medium">
|
Basic Info
|
||||||
1
|
|
||||||
</span>
|
|
||||||
<span class="font-medium">Basic Info</span>
|
|
||||||
</button>
|
</button>
|
||||||
<div class="w-8 h-px bg-gray-300"></div>
|
|
||||||
<button
|
<button
|
||||||
class="flex items-center gap-2 px-4 py-2 rounded-full transition-colors"
|
class="px-4 py-2 rounded-lg transition-colors"
|
||||||
class:bg-emerald-100={currentStep === 2}
|
class:bg-emerald-600={currentStep === 2}
|
||||||
class:text-emerald-700={currentStep === 2}
|
class:text-white={currentStep === 2}
|
||||||
class:bg-gray-100={currentStep !== 2}
|
class:bg-gray-100={currentStep !== 2}
|
||||||
class:text-gray-600={currentStep !== 2}
|
class:text-gray-600={currentStep !== 2}
|
||||||
onclick={() => currentStep > 2 && (currentStep = 2)}
|
class:font-semibold={currentStep === 2}
|
||||||
|
onclick={() => (currentStep = 2)}
|
||||||
>
|
>
|
||||||
<span class="w-6 h-6 rounded-full bg-current text-white flex items-center justify-center text-sm font-medium">
|
Address
|
||||||
2
|
|
||||||
</span>
|
|
||||||
<span class="font-medium">Address</span>
|
|
||||||
</button>
|
</button>
|
||||||
<div class="w-8 h-px bg-gray-300"></div>
|
|
||||||
<button
|
<button
|
||||||
class="flex items-center gap-2 px-4 py-2 rounded-full transition-colors"
|
class="px-4 py-2 rounded-lg transition-colors"
|
||||||
class:bg-emerald-100={currentStep === 3}
|
class:bg-emerald-600={currentStep === 3}
|
||||||
class:text-emerald-700={currentStep === 3}
|
class:text-white={currentStep === 3}
|
||||||
class:bg-gray-100={currentStep !== 3}
|
class:bg-gray-100={currentStep !== 3}
|
||||||
class:text-gray-600={currentStep !== 3}
|
class:text-gray-600={currentStep !== 3}
|
||||||
onclick={() => currentStep > 3 && (currentStep = 3)}
|
class:font-semibold={currentStep === 3}
|
||||||
|
onclick={() => (currentStep = 3)}
|
||||||
>
|
>
|
||||||
<span class="w-6 h-6 rounded-full bg-current text-white flex items-center justify-center text-sm font-medium">
|
Contact & ID
|
||||||
3
|
|
||||||
</span>
|
|
||||||
<span class="font-medium">Contact & ID</span>
|
|
||||||
</button>
|
</button>
|
||||||
<div class="w-8 h-px bg-gray-300"></div>
|
|
||||||
<button
|
<button
|
||||||
class="flex items-center gap-2 px-4 py-2 rounded-full transition-colors"
|
class="px-4 py-2 rounded-lg transition-colors"
|
||||||
class:bg-emerald-100={currentStep === 4}
|
class:bg-emerald-600={currentStep === 4}
|
||||||
class:text-emerald-700={currentStep === 4}
|
class:text-white={currentStep === 4}
|
||||||
class:bg-gray-100={currentStep !== 4}
|
class:bg-gray-100={currentStep !== 4}
|
||||||
class:text-gray-600={currentStep !== 4}
|
class:text-gray-600={currentStep !== 4}
|
||||||
onclick={() => currentStep > 4 && (currentStep = 4)}
|
class:font-semibold={currentStep === 4}
|
||||||
|
onclick={() => (currentStep = 4)}
|
||||||
>
|
>
|
||||||
<span class="w-6 h-6 rounded-full bg-current text-white flex items-center justify-center text-sm font-medium">
|
Additional
|
||||||
4
|
|
||||||
</span>
|
|
||||||
<span class="font-medium">Additional</span>
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{#if modalLoading}
|
||||||
|
<div class="flex flex-col items-center justify-center py-12">
|
||||||
|
<span class="loading loading-spinner loading-lg text-primary"></span>
|
||||||
|
<p class="text-gray-500 mt-4">Loading patient data...</p>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
<form class="space-y-5" onsubmit={(e) => e.preventDefault()}>
|
<form class="space-y-5" onsubmit={(e) => e.preventDefault()}>
|
||||||
<!-- Step 1: Basic Information -->
|
<!-- Step 1: Basic Information -->
|
||||||
{#if currentStep === 1}
|
{#if currentStep === 1}
|
||||||
@ -635,7 +630,7 @@
|
|||||||
label="Sex"
|
label="Sex"
|
||||||
name="sex"
|
name="sex"
|
||||||
bind:value={formData.Sex}
|
bind:value={formData.Sex}
|
||||||
options={sexOptions}
|
valueSetKey="sex"
|
||||||
placeholder="Select sex..."
|
placeholder="Select sex..."
|
||||||
required={true}
|
required={true}
|
||||||
/>
|
/>
|
||||||
@ -748,7 +743,6 @@
|
|||||||
label="Province"
|
label="Province"
|
||||||
name="province"
|
name="province"
|
||||||
bind:value={formData.Province}
|
bind:value={formData.Province}
|
||||||
onchange={handleProvinceChange}
|
|
||||||
options={provinceOptions}
|
options={provinceOptions}
|
||||||
placeholder="Select province..."
|
placeholder="Select province..."
|
||||||
/>
|
/>
|
||||||
@ -841,7 +835,7 @@
|
|||||||
label="ID Type"
|
label="ID Type"
|
||||||
name="idType"
|
name="idType"
|
||||||
bind:value={formData.PatIdt.IdentifierType}
|
bind:value={formData.PatIdt.IdentifierType}
|
||||||
options={identifierTypeOptions}
|
valueSetKey="identifier_type"
|
||||||
placeholder="Select ID type..."
|
placeholder="Select ID type..."
|
||||||
/>
|
/>
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
@ -865,52 +859,37 @@
|
|||||||
{#if currentStep === 4}
|
{#if currentStep === 4}
|
||||||
<div class="space-y-5">
|
<div class="space-y-5">
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
<SelectDropdown
|
<SelectDropdown
|
||||||
label="Marital Status"
|
label="Marital Status"
|
||||||
name="maritalStatus"
|
name="maritalStatus"
|
||||||
bind:value={formData.MaritalStatus}
|
bind:value={formData.MaritalStatus}
|
||||||
options={maritalStatusOptions}
|
valueSetKey="marital_status"
|
||||||
placeholder="Select status..."
|
placeholder="Select status..."
|
||||||
/>
|
/>
|
||||||
<div class="form-control">
|
<SelectDropdown
|
||||||
<label class="label" for="religion">
|
label="Religion"
|
||||||
<span class="label-text font-medium">Religion</span>
|
name="religion"
|
||||||
</label>
|
bind:value={formData.Religion}
|
||||||
<input
|
valueSetKey="religion"
|
||||||
id="religion"
|
placeholder="Select religion..."
|
||||||
type="text"
|
/>
|
||||||
class="input input-bordered w-full"
|
|
||||||
bind:value={formData.Religion}
|
|
||||||
placeholder="Enter religion"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
<div class="form-control">
|
<SelectDropdown
|
||||||
<label class="label" for="race">
|
label="Race"
|
||||||
<span class="label-text font-medium">Race</span>
|
name="race"
|
||||||
</label>
|
bind:value={formData.Race}
|
||||||
<input
|
valueSetKey="race"
|
||||||
id="race"
|
placeholder="Select race..."
|
||||||
type="text"
|
/>
|
||||||
class="input input-bordered w-full"
|
<SelectDropdown
|
||||||
bind:value={formData.Race}
|
label="Ethnicity"
|
||||||
placeholder="Enter race"
|
name="ethnic"
|
||||||
/>
|
bind:value={formData.Ethnic}
|
||||||
</div>
|
valueSetKey="ethnic"
|
||||||
<div class="form-control">
|
placeholder="Select ethnicity..."
|
||||||
<label class="label" for="ethnic">
|
/>
|
||||||
<span class="label-text font-medium">Ethnicity</span>
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
id="ethnic"
|
|
||||||
type="text"
|
|
||||||
class="input input-bordered w-full"
|
|
||||||
bind:value={formData.Ethnic}
|
|
||||||
placeholder="Enter ethnicity"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="border-t border-base-200 pt-5 mt-5">
|
<div class="border-t border-base-200 pt-5 mt-5">
|
||||||
@ -920,10 +899,7 @@
|
|||||||
label="Death Indicator"
|
label="Death Indicator"
|
||||||
name="deathIndicator"
|
name="deathIndicator"
|
||||||
bind:value={formData.DeathIndicator}
|
bind:value={formData.DeathIndicator}
|
||||||
options={[
|
valueSetKey="death_indicator"
|
||||||
{ value: 'N', label: 'No' },
|
|
||||||
{ value: 'Y', label: 'Yes' },
|
|
||||||
]}
|
|
||||||
placeholder="Select..."
|
placeholder="Select..."
|
||||||
/>
|
/>
|
||||||
{#if formData.DeathIndicator === 'Y'}
|
{#if formData.DeathIndicator === 'Y'}
|
||||||
@ -955,8 +931,9 @@
|
|||||||
></textarea>
|
></textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</form>
|
</form>
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#snippet footer()}
|
{#snippet footer()}
|
||||||
<div class="flex justify-between w-full">
|
<div class="flex justify-between w-full">
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user