fix(idaa): fix country/subdivision/timezone dropdowns — switch to in-memory sort

- Country and state/province fields were showing as plain text inputs because
  liveQuery used orderBy() on non-indexed columns, causing silent Dexie errors
  that left the store as undefined indefinitely.
- Fix: replaced orderBy() with toArray() + in-memory sort across all three
  lookup types (country, country_subdivision, time_zone).
- Sort convention matches Aether backend: sort DESC (higher = first, NULL=0
  last), then name ASC — puts priority entries at the top.
- Added db_lookups.ts (IDB schema for lookup tables) and updated core__countries,
  core__country_subdivisions, core__time_zones to IDB-backed SWR pattern.
- Affected: archive edit, archive content edit, recovery meeting edit.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Scott Idem
2026-03-23 18:44:24 -04:00
parent dafe79b3c6
commit a6f8ff709e
7 changed files with 300 additions and 270 deletions

View File

@@ -7,6 +7,8 @@
import type { key_val } from '$lib/stores/ae_stores';
import { ae_util } from '$lib/ae_utils/ae_utils';
import { core_func } from '$lib/ae_core/ae_core_functions';
import { liveQuery } from 'dexie';
import { db_lookups } from '$lib/ae_core/db_lookups';
import {
ae_snip,
ae_loc,
@@ -106,56 +108,21 @@
);
}
let lu_time_zone_list: any = $state(
localStorage.getItem('lu_time_zone_list')
? JSON.parse(localStorage.getItem('lu_time_zone_list') as string)
: []
// Timezone lookup — reactive IDB query; background refresh handled by liveQuery + TTL
// Sort: sort DESC (higher = first, NULL=0 last), then name ASC — matches Aether backend convention.
const lq__lu_time_zone = liveQuery(() =>
db_lookups.lu_time_zone.toArray().then(arr =>
arr.sort((a, b) => {
const s_diff = Number(b['sort'] ?? 0) - Number(a['sort'] ?? 0);
if (s_diff !== 0) return s_diff;
return (a.name ?? '').localeCompare(b.name ?? '');
})
)
);
onMount(() => {
$ae_loc.lu_time_zone_list = [];
// $ae_loc.lu_time_zone_list = [];
// lu_time_zone_list = [];
if (lu_time_zone_list && lu_time_zone_list.length > 0) {
// console.log('Already have time zone list!', lu_time_zone_list);
} else {
console.log('No time zone list');
let lu_time_zone_li_get_promise = core_func
.load_ae_obj_li__time_zone({
api_cfg: $ae_api,
only_priority: true,
log_lvl: log_lvl
})
.then(function (lu_time_zone_li_get_result) {
/* We need to save the time zone list to localStore */
if (lu_time_zone_li_get_result) {
lu_time_zone_list = lu_time_zone_li_get_result;
localStorage.setItem(
'lu_time_zone_list',
JSON.stringify(lu_time_zone_li_get_result)
);
if (log_lvl) {
console.log(`Time zone list:`, lu_time_zone_list);
}
} else {
console.log(`No time zones returned!`);
// $ae_loc.lu_time_zone_list = [];
}
if (lu_time_zone_li_get_result) {
lu_time_zone_list = lu_time_zone_li_get_result;
console.log(`Time zone list:`, lu_time_zone_list);
console.log(lu_time_zone_list[0]);
console.log(lu_time_zone_list[10]);
} else {
console.log(`No time zones returned!`);
lu_time_zone_list = [];
}
})
.catch(function (error: any) {
console.log('No results returned or failed.', error);
});
}
// Trigger background IDB refresh if stale/empty; liveQuery reacts automatically
core_func.load_ae_obj_li__time_zone({ api_cfg: $ae_api, log_lvl });
});
function prevent_default<T extends Event>(fn: (event: T) => void) {
@@ -873,7 +840,7 @@
<fieldset class="flex_row flex_gap_md flex_justify_around">
<label for="original_timezone"
>Original Timezone
{#if lu_time_zone_list}
{#if ($lq__lu_time_zone ?? []).length}
<select
id="original_timezone"
name="original_timezone"
@@ -886,7 +853,7 @@
title="Select the original timezone"
>
<option value="">-- None --</option>
{#each lu_time_zone_list as lu_timezone (lu_timezone.name)}
{#each ($lq__lu_time_zone ?? []) as lu_timezone (lu_timezone.name)}
<option value={lu_timezone.name}>
{lu_timezone.name}
</option>

View File

@@ -8,6 +8,8 @@
import type { key_val } from '$lib/stores/ae_stores';
import { ae_util } from '$lib/ae_utils/ae_utils';
import { core_func } from '$lib/ae_core/ae_core_functions';
import { liveQuery } from 'dexie';
import { db_lookups } from '$lib/ae_core/db_lookups';
import {
ae_snip,
ae_loc,
@@ -44,56 +46,21 @@
let notes_changed = $state(false);
let disable_submit_btn = true;
let lu_time_zone_list: any = $state(
localStorage.getItem('lu_time_zone_list')
? JSON.parse(localStorage.getItem('lu_time_zone_list') as string)
: []
// Timezone lookup — reactive IDB query; background refresh handled by liveQuery + TTL
// Sort: sort DESC (higher = first, NULL=0 last), then name ASC — matches Aether backend convention.
const lq__lu_time_zone = liveQuery(() =>
db_lookups.lu_time_zone.toArray().then(arr =>
arr.sort((a, b) => {
const s_diff = Number(b['sort'] ?? 0) - Number(a['sort'] ?? 0);
if (s_diff !== 0) return s_diff;
return (a.name ?? '').localeCompare(b.name ?? '');
})
)
);
onMount(() => {
$ae_loc.lu_time_zone_list = [];
// $ae_loc.lu_time_zone_list = [];
// lu_time_zone_list = [];
if (lu_time_zone_list && lu_time_zone_list.length > 0) {
// console.log('Already have time zone list!', lu_time_zone_list);
} else {
console.log('No time zone list');
let lu_time_zone_li_get_promise = core_func
.load_ae_obj_li__time_zone({
api_cfg: $ae_api,
only_priority: true,
log_lvl: log_lvl
})
.then(function (lu_time_zone_li_get_result) {
/* We need to save the time zone list to localStore */
if (lu_time_zone_li_get_result) {
lu_time_zone_list = lu_time_zone_li_get_result;
localStorage.setItem(
'lu_time_zone_list',
JSON.stringify(lu_time_zone_li_get_result)
);
if (log_lvl) {
console.log(`Time zone list:`, lu_time_zone_list);
}
} else {
console.log(`No time zones returned!`);
// $ae_loc.lu_time_zone_list = [];
}
if (lu_time_zone_li_get_result) {
lu_time_zone_list = lu_time_zone_li_get_result;
console.log(`Time zone list:`, lu_time_zone_list);
console.log(lu_time_zone_list[0]);
console.log(lu_time_zone_list[10]);
} else {
console.log(`No time zones returned!`);
lu_time_zone_list = [];
}
})
.catch(function (error: any) {
console.log('No results returned or failed.', error);
});
}
// Trigger background IDB refresh if stale/empty; liveQuery reacts automatically
core_func.load_ae_obj_li__time_zone({ api_cfg: $ae_api, log_lvl });
});
function prevent_default<T extends Event>(fn: (event: T) => void) {
@@ -406,7 +373,7 @@
<fieldset class="flex_row flex_gap_md flex_justify_around">
<label for="original_timezone"
>Original Timezone
{#if lu_time_zone_list}
{#if ($lq__lu_time_zone ?? []).length}
<select
name="original_timezone"
id="original_timezone"
@@ -418,7 +385,7 @@
title="Select the original timezone"
>
<option value="">-- None --</option>
{#each lu_time_zone_list as lu_timezone (lu_timezone.name)}
{#each ($lq__lu_time_zone ?? []) as lu_timezone (lu_timezone.name)}
<option value={lu_timezone.name}>
{lu_timezone.name}
</option>

View File

@@ -190,14 +190,35 @@
// Lookup lists — reactive IDB queries (SWR via db_lookups + liveQuery)
// Data persists in IndexedDB with a 24h TTL; onMount triggers a background
// refresh if IDB is empty or stale. No localStorage or $ae_loc involved.
// Note: orderBy() requires a declared Dexie index. For fields not in the schema index,
// use toArray() + in-memory sort instead to avoid a silent liveQuery error.
// Sort convention matches the Aether backend: sort DESC (higher = first, NULL=0 last), then name ASC.
const lq__lu_country = liveQuery(() =>
db_lookups.lu_country.orderBy('english_short_name').toArray()
db_lookups.lu_country.toArray().then(arr =>
arr.sort((a, b) => {
const s_diff = Number(b['sort'] ?? 0) - Number(a['sort'] ?? 0);
if (s_diff !== 0) return s_diff;
return (a.english_short_name ?? a.name ?? '').localeCompare(b.english_short_name ?? b.name ?? '');
})
)
);
const lq__lu_country_subdivision = liveQuery(() =>
db_lookups.lu_country_subdivision.orderBy('name').toArray()
db_lookups.lu_country_subdivision.toArray().then(arr =>
arr.sort((a, b) => {
const s_diff = Number(b['sort'] ?? 0) - Number(a['sort'] ?? 0);
if (s_diff !== 0) return s_diff;
return (a.name ?? '').localeCompare(b.name ?? '');
})
)
);
const lq__lu_time_zone = liveQuery(() =>
db_lookups.lu_time_zone.orderBy('name').toArray()
db_lookups.lu_time_zone.toArray().then(arr =>
arr.sort((a, b) => {
const s_diff = Number(b['sort'] ?? 0) - Number(a['sort'] ?? 0);
if (s_diff !== 0) return s_diff;
return (a.name ?? '').localeCompare(b.name ?? '');
})
)
);
onMount(() => {