{#if title}
diff --git a/src/lib/components/SelectDropdown.svelte b/src/lib/components/SelectDropdown.svelte
index 0a097dc..7b92338 100644
--- a/src/lib/components/SelectDropdown.svelte
+++ b/src/lib/components/SelectDropdown.svelte
@@ -38,12 +38,20 @@
onMount(async () => {
if (valueSetKey) {
loading = true;
- const items = await valueSets.load(valueSetKey);
- dropdownOptions = items.map((item) => ({
- value: item.Value,
- label: item.Label,
- }));
- loading = false;
+ try {
+ const items = await valueSets.load(valueSetKey);
+ if (!items || items.length === 0) {
+ console.warn('SelectDropdown: No items loaded for valueset:', valueSetKey);
+ }
+ 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) {
dropdownOptions = options;
}
@@ -51,7 +59,7 @@
// Watch for changes in manual options
$effect(() => {
- if (!valueSetKey && options.length > 0) {
+ if (!valueSetKey) {
dropdownOptions = options;
}
});
diff --git a/src/lib/stores/valuesets.js b/src/lib/stores/valuesets.js
index f6fc784..310dae4 100644
--- a/src/lib/stores/valuesets.js
+++ b/src/lib/stores/valuesets.js
@@ -10,6 +10,9 @@ import { error as toastError } from '$lib/utils/toast.js';
// Store structure: { [key]: { items: [], loaded: boolean, loading: boolean, error: string|null } }
function createValueSetsStore() {
const { subscribe, set, update } = writable({});
+
+ // Keep track of in-flight requests to prevent duplicates
+ const inflightRequests = new Map();
return {
subscribe,
@@ -20,68 +23,74 @@ function createValueSetsStore() {
* @returns {Promise
} - Array of value set items
*/
async load(key) {
- let result = [];
+ console.log('valuesets.load() called for key:', key);
- update((cache) => {
- // If already loading, return current state
- if (cache[key]?.loading) {
- return cache;
- }
-
- // If already loaded, return cached items
- if (cache[key]?.loaded) {
- result = cache[key].items;
- return cache;
- }
+ // If there's already an in-flight request, return its promise
+ if (inflightRequests.has(key)) {
+ console.log('valuesets: returning existing in-flight request for:', key);
+ return inflightRequests.get(key);
+ }
+
+ // Check if already loaded in cache
+ let cacheState = null;
+ subscribe((state) => { cacheState = state; })();
+
+ 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
- return {
+ update((cache) => ({
...cache,
[key]: { items: [], loaded: false, loading: true, error: null },
- };
- });
+ }));
- // If already loaded, return immediately
- if (result.length > 0) {
- return result;
- }
-
- // If already loading, wait a bit and retry
- const currentState = getCurrentState();
- if (currentState[key]?.loading) {
- await new Promise((resolve) => setTimeout(resolve, 100));
- return this.load(key);
- }
-
- try {
- const response = await fetchValueSetByKey(key);
-
- if (response.status === 'success' && response.data) {
- const items = response.data.Items || [];
+ try {
+ console.log('valuesets: calling API for key:', key);
+ const response = await fetchValueSetByKey(key);
+ console.log('valuesets: API response for', key, ':', response);
- // Sort by Sequence if available
- items.sort((a, b) => (a.Sequence || 0) - (b.Sequence || 0));
+ if (response.status === 'success' && response.data) {
+ // Handle both response.data being an array or having an Items property
+ let items = Array.isArray(response.data) ? response.data : (response.data.Items || []);
+ console.log('valuesets: items for', key, ':', items);
+
+ // Sort by Sequence if available
+ items.sort((a, b) => (a.Sequence || 0) - (b.Sequence || 0));
+
+ update((cache) => ({
+ ...cache,
+ [key]: { items, loaded: true, loading: false, error: null },
+ }));
+
+ console.log('valuesets: returning items for', key);
+ 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) => ({
...cache,
- [key]: { items, loaded: true, loading: false, error: null },
+ [key]: { items: [], loaded: false, loading: false, error: errorMessage },
}));
- return items;
- } else {
- throw new Error(response.message || 'Failed to load value set');
+ toastError(errorMessage);
+ return [];
+ } finally {
+ inflightRequests.delete(key);
}
- } catch (err) {
- const errorMessage = err.message || `Failed to load value set: ${key}`;
-
- update((cache) => ({
- ...cache,
- [key]: { items: [], loaded: false, loading: false, error: errorMessage },
- }));
-
- toastError(errorMessage);
- return [];
- }
+ })();
+
+ inflightRequests.set(key, fetchPromise);
+ return fetchPromise;
},
/**
@@ -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();
/**
diff --git a/src/routes/(app)/patients/+page.svelte b/src/routes/(app)/patients/+page.svelte
index 0cae175..fd785ae 100644
--- a/src/routes/(app)/patients/+page.svelte
+++ b/src/routes/(app)/patients/+page.svelte
@@ -2,10 +2,10 @@
import { onMount } from 'svelte';
import {
fetchPatients,
+ fetchPatient,
createPatient,
updatePatient,
deletePatient,
- checkPatientExists,
} from '$lib/api/patients.js';
import { fetchProvinces, fetchCities } from '$lib/api/geography.js';
import { success as toastSuccess, error as toastError } from '$lib/utils/toast.js';
@@ -16,6 +16,7 @@
// State
let loading = $state(false);
+ let modalLoading = $state(false);
let patients = $state([]);
let provinces = $state([]);
let cities = $state([]);
@@ -26,6 +27,7 @@
let deleteItem = $state(null);
let currentStep = $state(1);
let formErrors = $state({});
+ let previousProvince = $state('');
// Search and pagination
let searchQuery = $state('');
@@ -69,7 +71,9 @@
PatCom: '',
});
- // Dropdown options
+
+
+ // Dropdown options (prefix - valueset not available yet)
const prefixOptions = [
{ value: 'Mr', label: 'Mr' },
{ value: 'Mrs', label: 'Mrs' },
@@ -78,35 +82,13 @@
{ 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
const provinceOptions = $derived(
- provinces.map((p) => ({ value: p.AreaCode, label: p.AreaName }))
+ provinces.map((p) => ({ value: p.value, label: p.label }))
);
const cityOptions = $derived(
- cities.map((c) => ({ value: c.AreaCode, label: c.AreaName }))
+ cities.map((c) => ({ value: c.value, label: c.label }))
);
const columns = [
@@ -118,7 +100,10 @@
];
onMount(async () => {
- await Promise.all([loadPatients(), loadProvinces()]);
+ await Promise.all([
+ loadPatients(),
+ loadProvinces(),
+ ]);
});
async function loadPatients() {
@@ -164,7 +149,7 @@
}
try {
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) {
console.error('Failed to load cities:', err);
cities = [];
@@ -224,50 +209,62 @@
modalOpen = true;
}
- function openEditModal(row) {
+ async function openEditModal(row) {
modalMode = 'edit';
currentStep = 1;
formErrors = {};
- formData = {
- 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 || '',
- };
+ modalLoading = true;
- // Load cities if province is set
- if (formData.Province) {
- loadCities(formData.Province);
+ try {
+ // Fetch full patient details including individual name fields
+ 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) {
@@ -358,10 +355,13 @@
}
}
- function handleProvinceChange() {
- formData.City = '';
- loadCities(formData.Province);
- }
+ $effect(() => {
+ if (formData.Province !== previousProvince) {
+ previousProvince = formData.Province;
+ formData.City = '';
+ loadCities(formData.Province);
+ }
+ });
@@ -401,10 +401,10 @@
{columns}
data={patients.map((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)
- .join(' '),
- SexLabel: p.Sex === '1' ? 'Female' : p.Sex === '2' ? 'Male' : '-',
+ .join(' ') || '-',
+ SexLabel: p.SexLabel || (p.Sex === '1' ? 'Female' : p.Sex === '2' ? 'Male' : '-'),
BirthdateFormatted: p.Birthdate
? new Date(p.Birthdate).toLocaleDateString()
: '-',
@@ -464,69 +464,64 @@
-
+
-
+
-
-
-
+ {#if modalLoading}
+
+
+
Loading patient data...
+
+ {:else}