diff --git a/src/routes/core/data_stores/+page.svelte b/src/routes/core/data_stores/+page.svelte index 5cffd983..f6b797d8 100644 --- a/src/routes/core/data_stores/+page.svelte +++ b/src/routes/core/data_stores/+page.svelte @@ -4,6 +4,7 @@ import { untrack } from 'svelte'; import { goto } from '$app/navigation'; import { Modal } from 'flowbite-svelte'; import { + ArrowRight, Check, ChevronLeft, ChevronRight, @@ -12,6 +13,7 @@ import { Eye, Filter, LoaderCircle, + Pencil, Plus, RefreshCw, Save, @@ -69,6 +71,16 @@ let draft_notes = $state(''); let html_edit_mode = $state<'source' | 'visual'>('source'); let submit_status = $state<'idle' | 'processing' | 'saved' | 'error'>('idle'); +// ── Bulk rename state ───────────────────────────────────────────────────────── +let show_bulk_rename = $state(false); +let rename_filter = $state(''); // code LIKE filter — use % as wildcard +let rename_find_text = $state(''); // literal substring to find within matching codes +let rename_replace_text = $state(''); // literal replacement for find_text +let rename_preview: { id: string; old_code: string; new_code: string }[] = $state([]); +let rename_loading = $state(false); +let rename_apply_status = $state<'idle' | 'applying' | 'done' | 'error'>('idle'); +let rename_applied = $state(0); + // ── Search ──────────────────────────────────────────────────────────────────── async function do_search(reset = true) { if (reset) page_offset = 0; @@ -258,6 +270,67 @@ async function handle_delete() { } } +// ── Bulk rename ─────────────────────────────────────────────────────────────── +async function do_rename_preview() { + if (!rename_filter.trim()) return; + rename_loading = true; + rename_preview = []; + rename_apply_status = 'idle'; + rename_applied = 0; + + const result_li = await api.search_ae_obj({ + api_cfg: $ae_api, + obj_type: 'data_store', + search_query: { and: [{ field: 'code', op: 'like', value: rename_filter.trim() }] }, + order_by_li: [{ code: 'ASC' }], + limit: 200, + offset: 0, + log_lvl: 0 + }); + + if (result_li) { + const find = rename_find_text.trim(); + const repl = rename_replace_text.trim(); + rename_preview = result_li + .map((ds: ae_DataStore) => { + const old_code = ds.code ?? ''; + const new_code = find ? old_code.split(find).join(repl) : old_code; + return { id: (ds.id ?? (ds as any).data_store_id) as string, old_code, new_code }; + }) + .filter((r: { id: string; old_code: string; new_code: string }) => r.old_code !== r.new_code); + } + + rename_loading = false; +} + +async function do_rename_apply() { + if (!rename_preview.length) return; + if (!confirm(`Rename ${rename_preview.length} data store code(s)?\n\nThis cannot be undone.`)) return; + + rename_apply_status = 'applying'; + rename_applied = 0; + const api_cfg = untrack(() => $ae_api); + + for (const item of rename_preview) { + const result = await api.update_ae_obj({ + api_cfg, + obj_type: 'data_store', + obj_id: item.id, + fields: { code: item.new_code } + }); + if (result) { + rename_applied++; + await db_core.data_store.update(item.id, { code: item.new_code }); + } + } + + rename_apply_status = rename_applied === rename_preview.length ? 'done' : 'error'; + if (rename_apply_status === 'done') { + rename_preview = []; + do_search(false); + } +} + // ── Helpers ─────────────────────────────────────────────────────────────────── function type_badge(type: string | null | undefined) { switch (type) { @@ -293,13 +366,22 @@ function content_preview(ds: ae_DataStore): string {
Content & Configuration Storage
- +Results are scoped to the active account by the API.
| Current Code | ++ | New Code | +
|---|---|---|
| {item.old_code} | +{item.new_code} | +
No changes to preview — either no records matched or find/replace text produced no differences.
+ {/if} +