diff --git a/src/routes/idaa/(idaa)/recovery_meetings/ae_idaa_comp__event_obj_li.svelte b/src/routes/idaa/(idaa)/recovery_meetings/ae_idaa_comp__event_obj_li.svelte index b2113dc7..4c6fa157 100644 --- a/src/routes/idaa/(idaa)/recovery_meetings/ae_idaa_comp__event_obj_li.svelte +++ b/src/routes/idaa/(idaa)/recovery_meetings/ae_idaa_comp__event_obj_li.svelte @@ -19,6 +19,7 @@ let { }: Props = $props(); // *** Import Svelte specific +import { onMount } from 'svelte'; // *** Import Aether specific variables and functions import { ae_util } from '$lib/ae_utils/ae_utils'; @@ -37,54 +38,81 @@ import { idaa_slct, idaa_trig } from '$lib/stores/ae_idaa_stores'; -import { db_events } from '$lib/ae_events/db_events'; -import { events_func } from '$lib/ae_events/ae_events_functions'; +import { api } from '$lib/api/api'; import MyClipboard from '$lib/app_components/e_app_clipboard.svelte'; +// Favorites are stored in a dedicated data_store record (code: idaa_meetings_favorites, +// scoped to the IDAA account_id) so toggling never touches ae_event rows or their +// updated_on timestamps. Structure: { [novi_uuid]: [event_id, ...] }. +// One shared record per account — known race condition if two members toggle at +// the exact same moment (last write wins). Acceptable for ~1000 members. +let ds_fav_id = $state(null); +let ds_fav_json = $state>({}); + // Tracks event IDs currently being toggled to prevent double-clicks let favorites_in_progress = $state>(new Set()); +onMount(async () => { + if (!$idaa_loc.novi_uuid || !$ae_api?.base_url) return; + try { + const result = await api.get_data_store({ + api_cfg: $ae_api, + code: 'idaa_meetings_favorites' + }); + const rec_id = result?.data_store_id || result?.id; + if (rec_id) { + ds_fav_id = rec_id; + let raw = result.json ?? result.json_str ?? null; + if (typeof raw === 'string') { + try { raw = JSON.parse(raw); } catch { raw = {}; } + } + ds_fav_json = (raw as Record) ?? {}; + } + } catch (err) { + console.warn('[favorites] Failed to load favorites data store:', err); + } +}); + function check_fav(event_obj: Record): boolean { const my_uuid = $idaa_loc.novi_uuid; if (!my_uuid) return false; - const mod = event_obj.mod_meetings_json as Record | null | undefined; - return Array.isArray(mod?.favorite) && (mod.favorite as string[]).includes(my_uuid); + const my_favs = ds_fav_json[my_uuid]; + return Array.isArray(my_favs) && my_favs.includes(event_obj.event_id as string); } async function toggle_favorite(event_obj: Record) { const my_uuid = $idaa_loc.novi_uuid; - const obj_id = event_obj.id as string; // Dexie primary key (same value as event_id) - const event_id = event_obj.event_id as string; // V3 API UUID + const event_id = event_obj.event_id as string; - if (!my_uuid || !event_id || favorites_in_progress.has(event_id)) return; + if (!my_uuid || !event_id || !ds_fav_id || favorites_in_progress.has(event_id)) return; favorites_in_progress.add(event_id); - const current_json = (event_obj.mod_meetings_json as Record) ?? {}; - const current_list: string[] = Array.isArray(current_json.favorite) ? [...(current_json.favorite as string[])] : []; - const already_fav = current_list.includes(my_uuid); - const new_list = already_fav ? current_list.filter((u) => u !== my_uuid) : [...current_list, my_uuid]; - const new_json = { ...current_json, favorite: new_list }; + const current_user_list: string[] = Array.isArray(ds_fav_json[my_uuid]) + ? [...ds_fav_json[my_uuid]] + : []; + const already_fav = current_user_list.includes(event_id); + const new_user_list = already_fav + ? current_user_list.filter((id) => id !== event_id) + : [...current_user_list, event_id]; - // Optimistic IDB update — liveQuery reactively re-renders the card instantly - await db_events.event.update(obj_id, { mod_meetings_json: new_json }); + const prev_json = ds_fav_json; + const new_full_json = { ...ds_fav_json, [my_uuid]: new_user_list }; + + // Optimistic update — ds_fav_json is reactive $state, so check_fav() re-evaluates + // instantly everywhere it's called (derived list sort + each block template). + ds_fav_json = new_full_json; - // NOTE: This PATCH triggers ae_event.updated_on to update (MariaDB ON UPDATE - // current_timestamp() fires on every row write). Side effect: starring a meeting - // shifts its position when sorted by updated_on, and admins see a spurious "last modified" - // timestamp. Long-term fix: either bypass ON UPDATE for this specific write (pass - // updated_on = updated_on in the SET clause server-side), or store favorites in a - // dedicated per-user table that doesn't touch the event row at all. try { - await events_func.update_ae_obj__event({ + await api.update_ae_obj({ api_cfg: $ae_api, - event_id, - data_kv: { mod_meetings_json: new_json }, - try_cache: true + obj_type: 'data_store', + obj_id: ds_fav_id, + fields: { json: JSON.stringify(new_full_json) } }); } catch (err) { console.warn('[favorites] API persist failed, rolling back:', err); - await db_events.event.update(obj_id, { mod_meetings_json: current_json }); + ds_fav_json = prev_json; } finally { favorites_in_progress.delete(event_id); }