feat(journals): modernize UI with responsive grid and fix Quick Add integration

This commit is contained in:
Scott Idem
2026-01-14 15:20:05 -05:00
parent 283053e4a4
commit 8c9788bd41
5 changed files with 365 additions and 533 deletions

View File

@@ -1,365 +1,213 @@
<script lang="ts">
let log_lvl: number = 0;
// console.log(`ae_journals +page data:`, data);
// console.log(`ae_journals Data Params:`, data.url.searchParams.get('journal_id'));
// *** Import Svelte specific
/**
* src/routes/journals/+page.svelte
* Modernized Journals Index View
* Focus: Simplicity for regular users, power tools for edit mode.
*/
import { onMount } from 'svelte';
import { goto } from '$app/navigation';
// *** Import other supporting libraries
import { BookPlus, FolderPlus, Library, Loader, SquareLibrary, Wrench, FileUp, FileDown } from '@lucide/svelte';
// *** Icons
import {
BookPlus, SquareLibrary, Wrench,
FileUp, Loader2, Sparkles
} from '@lucide/svelte';
// *** Libraries & Stores
import { liveQuery } from 'dexie';
import { Modal } from 'flowbite-svelte';
// *** Import Aether specific variables and functions
// import { api } from '$lib/api';
import { db_journals } from '$lib/ae_journals/db_journals';
import { journals_func } from '$lib/ae_journals/ae_journals_functions';
import { ae_loc, ae_sess, ae_api, slct, slct_trigger } from '$lib/stores/ae_stores';
import { ae_loc, ae_api, slct } from '$lib/stores/ae_stores';
import {
journals_loc,
journals_sess,
journals_slct,
journals_trig
} from '$lib/ae_journals/ae_journals_stores';
import { ae_util } from '$lib/ae_utils/ae_utils';
// *** Components
import Modal_journals_cfg from './modal_journals_config.svelte';
import Journal_obj_li from './ae_comp__journal_obj_li.svelte';
import AeCompJournalEntryQuickAdd from './ae_comp__journal_entry_quick_add.svelte';
import AeCompModalJournalImport from './ae_comp__modal_journal_import.svelte';
interface Props {
data: any;
}
let { data }: Props = $props();
import Modal_journals_cfg from './modal_journals_config.svelte';
import Journal_obj_li from './ae_comp__journal_obj_li.svelte';
import AeCompJournalEntryQuickAdd from './ae_comp__journal_entry_quick_add.svelte';
import AeCompModalJournalImport from './ae_comp__modal_journal_import.svelte';
import AeCompModalJournalExport from './ae_comp__modal_journal_export.svelte';
// import Element_data_store from '$lib/element_data_store_v2.svelte';
let ae_acct = data[$slct.account_id];
// *** State
let show_import_modal = $state(false);
let show_export_modal = $state(false);
function handle_import_complete() {
// Trigger a refresh of the journal list?
// Usually liveQuery handles it if data changed in IDB.
// But if we just created entries, we might want to refresh the selected journal view if active.
// $journals_trig.journal_entry_li = true;
}
// $journals_slct.journal_obj = ae_acct.slct.journal_obj;
// We need to access the currently filtered/displayed list of entries for export.
// The list is actually managed inside 'Journal_obj_li' (the list component) or via a store.
// However, looking at Journal_obj_li, it takes 'lq__journal_entry_obj_li' as a prop.
// BUT wait, this page (+page.svelte) seems to be the Journal INDEX (list of journals),
// NOT the list of entries within a journal.
// Ah, checking the code...
// The main page lists *Journals* (lq__journal_obj_li).
// The entries are listed in [journal_id]/+page.svelte.
// Correction: Bulk Export should probably be on the [journal_id] page, OR here if we want to export ALL journals?
// The user request was "export Journal Entries". Usually implies from a specific journal or context.
// Let's check [journal_id]/+page.svelte.
// $journals_slct.journal_obj_li = ae_acct.slct.journal_obj_li;
let log_lvl = 0;
// *** LiveQueries
let lq__journal_obj_li = $derived(
liveQuery(async () => {
let results = await db_journals.journal
return await db_journals.journal
.where('person_id')
.equals($ae_loc.person_id)
// .sortBy('group')
// .sortBy('priority')
// .sortBy('sort')
.reverse()
.sortBy('tmp_sort_3');
// .orderBy('tmp_sort_3')
// .reverse()
// .toArray()
// .sortBy('start_datetime')
// () => db_journals.journals
// .where('conference')
// // .aboveOrEqual(0)
// .equals('true')
// // .above(0)
// .sortBy('name') // Use sortBy() instead of orderBy(). toArray() is also not needed???
// // .sortBy('[priority+name]')
// // .orderBy('name')
// // .offset(10).limit(5)
// // .toArray()
return results;
})
);
// console.log(`lq__journal_obj_li:`, $lq__journal_obj_li);
let lq__journal_obj = $derived(
liveQuery(async () => {
let results = await db_journals.journal
.where('id')
.equals(ae_acct.slct.journal_id)
.toArray();
// .first()
// .get($journals_slct.journal_id)
return results;
})
);
onMount(() => {
console.log('Journals: +page.svelte');
console.log('ae_ slct:', $slct);
let href_url = window.location.href;
// console.log(href_url);
$ae_loc.href_url = href_url;
console.log(`lq__journal_obj = `, $lq__journal_obj);
});
async function create_journal() {
// Confirm before creating a new journal
if (confirm('Are you sure you want to create a new journal?')) {
console.log('Creating new journal...');
} else {
console.log('Journal creation cancelled.');
return;
}
if (!confirm('Create a new journal?')) return;
if (
$journals_sess.journal.new_journal_name &&
$journals_sess.journal.new_journal_type_code
) {
$journals_slct.journal_id = null;
const name = $journals_sess.journal.new_journal_name;
const type = $journals_sess.journal.new_journal_type_code;
if (name && type) {
try {
let data_kv = {
// account: $slct.account_id,
person_id_random: $ae_loc.person_id,
name: $journals_sess.journal.new_journal_name,
type_code: $journals_sess.journal.new_journal_type_code,
cfg_json: {
category_li: [{ code: '', name: 'Default' }]
const results = await journals_func.create_ae_obj__journal({
api_cfg: $ae_api,
account_id: $slct.account_id,
data_kv: {
person_id_random: $ae_loc.person_id,
name,
type_code: type,
cfg_json: { category_li: [{ code: '', name: 'Default' }] }
}
};
journals_func
.create_ae_obj__journal({
api_cfg: $ae_api,
account_id: $slct.account_id,
data_kv: data_kv,
log_lvl: log_lvl
})
.then((results) => {
console.log('New journal created:', results);
$journals_slct.journal_id = results?.journal_id_random;
})
.catch((error) => {
console.error('Error creating journal:', error);
alert('Failed to create journal.');
})
.finally(() => {
if ($journals_slct.journal_id) {
$journals_sess.show__modal_new__journal_obj = false;
goto(`/journals/${$journals_slct.journal_id}`);
}
});
// console.log('New journal created:', result);
// $journals_sess.show__modal_new__journal_obj = false;
// goto(`/journals/${result.journal_id_random}`);
});
if (results?.journal_id_random) {
$journals_sess.show__modal_new__journal_obj = false;
goto(`/journals/${results.journal_id_random}`);
}
} catch (error) {
console.error('Error creating journal:', error);
alert('Failed to create journal.');
}
} else {
alert('Please provide both name and type for the journal.');
alert('Please provide a name and type.');
}
}
</script>
<!-- Svelte Page for a AE Journals Module main page -->
<section
class="
ae_journals
mx-auto
flex flex-col gap-1
items-center
min-h-full
max-h-max
min-w-full
max-w-max
space-y-2
"
>
<h1 class="h1 text-4xl text-center text-gray-800 dark:text-gray-200">
<!-- <Library size="1em" class="mx-1 inline-block" /> -->
<SquareLibrary size="1em" class="mx-1 inline-block text-neutral-800/60" />
Journals for {$ae_loc.account_name ?? 'Æ loading...'}
{$ae_loc.person.given_name ? `- ${$ae_loc.person.given_name}` : ''}
</h1>
<!-- Quick Add Section -->
<div class="w-full max-w-2xl mx-auto px-4 pb-4">
<AeCompJournalEntryQuickAdd class="shadow-lg" />
</div>
<div
class="flex flex-row flex-wrap gap-1 items-center justify-center w-full border-gray-200 border-y-2 py-2"
class:hidden={!$ae_loc.edit_mode}
>
<!-- Add new journal button -->
<button
type="button"
class="
btn btn-sm
preset-tonal-secondary border border-secondary-500
hover:preset-filled-secondary-500
transition
"
onclick={() => {
$journals_sess.show__modal_new__journal_obj = true;
}}
title="Create a new journal"
>
<!-- <FolderPlus class="mx-1" /> -->
<BookPlus class="mx-1" />
<span class="hidden md:inline"> New Journal </span>
</button>
<!-- Import Entries button -->
<button
type="button"
class="
btn btn-sm
preset-tonal-secondary border border-secondary-500
hover:preset-filled-secondary-500
transition
"
onclick={() => {
show_import_modal = true;
}}
title="Import entries from Markdown files"
>
<FileUp class="mx-1" />
<span class="hidden md:inline"> Import </span>
</button>
<!-- Show Journals Config button -->
<button
type="button"
class="
btn btn-sm
preset-tonal-secondary border border-secondary-500
hover:preset-filled-secondary-500
transition
"
onclick={() => {
$journals_sess.show__modal__journals_config = true;
// Redirect to the journals config page
// goto('/journals/config');
}}
title="Configure Journals"
>
<Wrench class="mx-1" />
<span class="hidden md:inline"> Journals Config </span>
</button>
</div>
<!-- <Element_data_store
ds_code="journals___overview"
ds_type="html"
for_type="journal"
for_id={$journals_slct.journal_id}
display="block"
class_li="p-2"
/> -->
<!-- <Element_data_store
ds_code="journals___example"
ds_type="html"
for_type="journal"
for_id={$journals_slct.journal_id}
ds_name="Default: Journals - Journals Example"
store="local"
display="block"
class_li="variant-ghost-surface p-2"
try_cache={true}
show_edit={false}
/> -->
{#await ae_acct.slct.journal_obj_li}
<div class="flex flex-col items-center justify-center p-8">
<Loader size="2em" class="animate-spin text-primary-500 mb-4" />
<span class="text-lg text-gray-600 dark:text-gray-400">Loading journals...</span>
<div class="page_container flex flex-col gap-8 items-center w-full min-h-screen p-4 md:p-8">
<!-- Header Section -->
<header class="text-center space-y-2 max-w-3xl">
<div class="flex items-center justify-center gap-3 mb-2">
<div class="p-3 bg-surface-500/10 rounded-2xl">
<SquareLibrary size="3em" class="text-primary-500" />
</div>
</div>
{:then}
<!-- <span class="flex flex-row items-center justify-center">
<span class="fas fa-check text-green-500 mx-1"></span>
<span>Loaded</span>
</span> -->
<h1 class="text-4xl md:text-5xl font-black tracking-tight text-surface-900 dark:text-surface-100">
Journals
</h1>
<p class="text-surface-600 dark:text-surface-400 font-medium">
Managed by <span class="text-primary-500">{$ae_loc.account_name ?? 'Æ loading...'}</span>
{#if $ae_loc.person.given_name}
&bull; <span class="opacity-75">{$ae_loc.person.given_name}</span>
{/if}
</p>
</header>
{#if $lq__journal_obj_li && $lq__journal_obj_li?.length}
<!-- Quick Add Integrated Section -->
<section class="w-full max-w-2xl">
<div class="relative group">
<div class="absolute -inset-1 bg-gradient-to-r from-primary-500 to-secondary-500 rounded-2xl blur opacity-25 group-hover:opacity-50 transition duration-1000 group-hover:duration-200"></div>
<AeCompJournalEntryQuickAdd
journals_li={$lq__journal_obj_li}
class="relative shadow-2xl rounded-xl overflow-hidden border border-surface-500/10 bg-surface-50 dark:bg-surface-900"
/>
</div>
<div class="mt-2 flex items-center justify-center gap-2 text-xs opacity-50 font-bold uppercase tracking-widest">
<Sparkles size="1em" />
<span>Fast Input Mode Active</span>
</div>
</section>
<!-- Administrative Action Bar (Edit Mode Only) -->
{#if $ae_loc.edit_mode}
<nav class="flex flex-row flex-wrap gap-3 items-center justify-center w-full py-4 border-y border-surface-500/10">
<button
type="button"
class="btn variant-filled-secondary shadow-lg hover:scale-105 transition-transform"
onclick={() => $journals_sess.show__modal_new__journal_obj = true}
>
<BookPlus size="1.2em" class="mr-2" />
<span>New Journal</span>
</button>
<button
type="button"
class="btn variant-filled-surface border border-surface-500/30 shadow-lg hover:scale-105 transition-transform"
onclick={() => show_import_modal = true}
>
<FileUp size="1.2em" class="mr-2" />
<span>Import</span>
</button>
<button
type="button"
class="btn variant-soft-surface shadow-lg hover:scale-105 transition-transform"
onclick={() => $journals_sess.show__modal__journals_config = true}
>
<Wrench size="1.2em" class="mr-2" />
<span>Configure</span>
</button>
</nav>
{/if}
<!-- Main List Section -->
<main class="w-full flex justify-center">
{#if $lq__journal_obj_li === undefined}
<div class="flex flex-col items-center justify-center p-20 gap-4 opacity-50">
<Loader2 size="3em" class="animate-spin" />
<p class="text-xl font-bold">Accessing Brain...</p>
</div>
{:else if $lq__journal_obj_li.length > 0}
<Journal_obj_li {lq__journal_obj_li} />
{:else}
<div class="flex flex-col items-center justify-center p-4 text-center">
<p class="text-lg text-gray-600 dark:text-gray-400 mb-4">No journals found.</p>
<p class="text-md text-gray-500 dark:text-gray-300">
Click the "New Journal" button above to create your first journal.
</p>
<div class="max-w-md text-center p-12 bg-surface-500/5 rounded-3xl border-2 border-dashed border-surface-500/20">
<SquareLibrary size="4em" class="mx-auto mb-4 opacity-20" />
<h3 class="text-2xl font-bold mb-2">No Journals Found</h3>
<p class="opacity-60 mb-6">You haven't created any journals yet. Start by creating one to begin your documentation journey.</p>
<button
type="button"
class="btn variant-filled-primary"
onclick={() => $journals_sess.show__modal_new__journal_obj = true}
>
Create Your First Journal
</button>
</div>
{/if}
{:catch error}
<div class="text-red-800">
<span class="fas fa-exclamation-triangle text-xl"></span>
<span>Error: {error.message}</span>
</div>
{/await}
</section>
</main>
</div>
<!-- Modal for creating new journal -->
<!-- Modals -->
{#if $journals_sess.show__modal_new__journal_obj}
<Modal
title="Create New Journal"
bind:open={$journals_sess.show__modal_new__journal_obj}
autoclose={false}
placement="top-center"
size="xl"
class="top-center bg-white dark:bg-gray-800 text-gray-800 dark:text-gray-200 rounded-lg border-gray-200 dark:border-gray-700 divide-gray-200 dark:divide-gray-700 shadow-md relative flex flex-col mx-auto w-full divide-y"
size="md"
class="bg-white dark:bg-surface-900 shadow-2xl rounded-2xl"
>
<div class="modal">
<div class="modal-box">
<h3 class="font-bold text-lg">Create New Journal</h3>
<div class="py-4">
<input
type="text"
placeholder="Journal Name"
bind:value={$journals_sess.journal.new_journal_name}
class="input input-bordered w-full mb-2"
/>
<input
type="text"
placeholder="Journal Type"
bind:value={$journals_sess.journal.new_journal_type_code}
class="input input-bordered w-full mb-2"
/>
</div>
<div class="modal-action">
<button class="btn preset-tonal-primary" onclick={create_journal}>Create</button
>
<button
type="button"
onclick={() => {
// Close the modal
$journals_sess.show__modal_new__journal_obj = false;
}}
class="btn preset-tonal-secondary"
>
Cancel
</button>
</div>
<div class="p-2 space-y-4">
<div class="space-y-1">
<label class="label text-sm font-bold opacity-75">Journal Name</label>
<input
type="text"
placeholder="e.g. My Daily Logs"
bind:value={$journals_sess.journal.new_journal_name}
class="input variant-filled-surface rounded-lg"
/>
</div>
<div class="space-y-1">
<label class="label text-sm font-bold opacity-75">Type Code</label>
<input
type="text"
placeholder="e.g. diary, log, notebook"
bind:value={$journals_sess.journal.new_journal_type_code}
class="input variant-filled-surface rounded-lg"
/>
</div>
<div class="flex justify-end gap-2 pt-4">
<button type="button" class="btn variant-soft-surface" onclick={() => $journals_sess.show__modal_new__journal_obj = false}>Cancel</button>
<button type="button" class="btn variant-filled-primary font-bold" onclick={create_journal}>Create Journal</button>
</div>
</div>
</Modal>
@@ -372,8 +220,5 @@
<AeCompModalJournalImport
bind:open={show_import_modal}
onClose={() => (show_import_modal = false)}
onImportComplete={handle_import_complete}
/>
<style lang="postcss">
</style>
onImportComplete={() => {}}
/>