feat(frontend): simplify homepage to pure dashboard — remove search/filter dual-mode
The homepage now always renders the dashboard. Search and browse moves to the dedicated /documents route (upcoming). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,94 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { navigating } from '$app/state';
|
||||
import { untrack } from 'svelte';
|
||||
import { SvelteURLSearchParams } from 'svelte/reactivity';
|
||||
import SearchFilterBar from './SearchFilterBar.svelte';
|
||||
import DropZone from './DropZone.svelte';
|
||||
import DocumentList from './DocumentList.svelte';
|
||||
import DashboardResumeStrip from '$lib/components/DashboardResumeStrip.svelte';
|
||||
import MissionControlStrip from '$lib/components/MissionControlStrip.svelte';
|
||||
import DashboardFamilyPulse from '$lib/components/DashboardFamilyPulse.svelte';
|
||||
import DashboardActivityFeed from '$lib/components/DashboardActivityFeed.svelte';
|
||||
import * as m from '$lib/paraglide/messages.js';
|
||||
import { m } from '$lib/paraglide/messages.js';
|
||||
|
||||
let { data } = $props();
|
||||
|
||||
let q = $state(untrack(() => data.filters?.q || ''));
|
||||
let qFocused = $state(false);
|
||||
let from = $state(untrack(() => data.filters?.from || ''));
|
||||
let to = $state(untrack(() => data.filters?.to || ''));
|
||||
let senderId = $state(untrack(() => data.filters?.senderId || ''));
|
||||
let receiverId = $state(untrack(() => data.filters?.receiverId || ''));
|
||||
let tagNames = $state<{ name: string; id?: string; color?: string; parentId?: string }[]>(
|
||||
untrack(() => (data.filters?.tags || []).map((name: string) => ({ name })))
|
||||
);
|
||||
let sort = $state(untrack(() => data.filters?.sort || 'DATE'));
|
||||
let dir = $state(untrack(() => data.filters?.dir || 'desc'));
|
||||
let tagQ = $state(untrack(() => data.filters?.tagQ || ''));
|
||||
let tagOperator = $state<'AND' | 'OR'>(
|
||||
untrack(() => (data.filters?.tagOp as 'AND' | 'OR') || 'AND')
|
||||
);
|
||||
|
||||
const hasAdvancedFilters = (filters: typeof data.filters) =>
|
||||
(filters?.tags?.length ?? 0) > 0 ||
|
||||
!!filters?.senderId ||
|
||||
!!filters?.receiverId ||
|
||||
!!filters?.from ||
|
||||
!!filters?.to;
|
||||
|
||||
let showAdvanced = $state(untrack(() => hasAdvancedFilters(data.filters)));
|
||||
|
||||
let searchTimer: ReturnType<typeof setTimeout>;
|
||||
|
||||
function triggerSearch() {
|
||||
const params = new SvelteURLSearchParams();
|
||||
if (q) params.set('q', q);
|
||||
if (from) params.set('from', from);
|
||||
if (to) params.set('to', to);
|
||||
if (senderId) params.set('senderId', senderId);
|
||||
if (receiverId) params.set('receiverId', receiverId);
|
||||
if (tagNames) tagNames.forEach((tag) => params.append('tag', tag.name));
|
||||
if (sort) params.set('sort', sort);
|
||||
if (dir) params.set('dir', dir);
|
||||
if (tagQ) params.set('tagQ', tagQ);
|
||||
if (tagOperator === 'OR') params.set('tagOp', 'OR');
|
||||
goto(`/?${params.toString()}`, { keepFocus: true, noScroll: true });
|
||||
}
|
||||
|
||||
function handleTextSearch() {
|
||||
clearTimeout(searchTimer);
|
||||
searchTimer = setTimeout(() => triggerSearch(), 500);
|
||||
}
|
||||
|
||||
function handleImmediateSearch() {
|
||||
clearTimeout(searchTimer);
|
||||
triggerSearch();
|
||||
}
|
||||
|
||||
let prevTagStr = untrack(() => tagNames.map((t) => t.name).join(','));
|
||||
$effect(() => {
|
||||
const cur = tagNames.map((t) => t.name).join(',');
|
||||
if (cur !== prevTagStr) {
|
||||
prevTagStr = cur;
|
||||
triggerSearch();
|
||||
}
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (!qFocused) q = data.filters?.q || '';
|
||||
from = data.filters?.from || '';
|
||||
to = data.filters?.to || '';
|
||||
senderId = data.filters?.senderId || '';
|
||||
receiverId = data.filters?.receiverId || '';
|
||||
tagNames = (data.filters?.tags || []).map((name: string) => ({ name }));
|
||||
sort = data.filters?.sort || 'DATE';
|
||||
dir = data.filters?.dir || 'desc';
|
||||
tagQ = data.filters?.tagQ || '';
|
||||
tagOperator = (data.filters?.tagOp as 'AND' | 'OR') || 'AND';
|
||||
if (hasAdvancedFilters(data.filters)) showAdvanced = true;
|
||||
});
|
||||
|
||||
const greetingText = $derived.by(() => {
|
||||
const name = data?.user?.firstName ?? '';
|
||||
const h = new Date().getHours();
|
||||
@@ -103,68 +22,35 @@ const greetingText = $derived.by(() => {
|
||||
</svelte:head>
|
||||
|
||||
<main class="mx-auto max-w-7xl px-4 py-8 font-sans sm:px-6 lg:px-8">
|
||||
<SearchFilterBar
|
||||
bind:q={q}
|
||||
bind:from={from}
|
||||
bind:to={to}
|
||||
bind:senderId={senderId}
|
||||
bind:receiverId={receiverId}
|
||||
bind:tagNames={tagNames}
|
||||
bind:showAdvanced={showAdvanced}
|
||||
bind:sort={sort}
|
||||
bind:dir={dir}
|
||||
bind:tagQ={tagQ}
|
||||
bind:tagOperator={tagOperator}
|
||||
initialSenderName={data.initialValues?.senderName}
|
||||
initialReceiverName={data.initialValues?.receiverName}
|
||||
isLoading={navigating.to !== null}
|
||||
onSearch={handleTextSearch}
|
||||
onSearchImmediate={handleImmediateSearch}
|
||||
onfocus={() => (qFocused = true)}
|
||||
onblur={() => (qFocused = false)}
|
||||
/>
|
||||
|
||||
{#if data.isDashboard}
|
||||
{#if data?.user}
|
||||
<div class="mb-6">
|
||||
<h1 class="font-serif text-[2rem] text-ink">{greetingText}</h1>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="grid grid-cols-1 gap-5 lg:grid-cols-[1fr_320px] lg:items-start">
|
||||
<div class="flex flex-col gap-5">
|
||||
<DashboardResumeStrip resumeDoc={data.resumeDoc ?? null} />
|
||||
|
||||
<section aria-label={m.dashboard_mission_caption()}>
|
||||
<h2 class="mb-3 font-sans text-xs font-bold tracking-widest text-ink-3 uppercase">
|
||||
{m.dashboard_mission_caption()}
|
||||
</h2>
|
||||
<MissionControlStrip
|
||||
segmentationDocs={data.segmentationDocs ?? []}
|
||||
transcriptionDocs={data.transcriptionDocs ?? []}
|
||||
readyDocs={data.readyDocs ?? []}
|
||||
weeklyStats={data.weeklyStats ?? null}
|
||||
/>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-5 lg:sticky lg:top-[80px]">
|
||||
<DashboardFamilyPulse pulse={data.pulse ?? null} />
|
||||
<DashboardActivityFeed feed={data.activityFeed ?? []} />
|
||||
{#if data.canWrite}
|
||||
<DropZone />
|
||||
{/if}
|
||||
</div>
|
||||
{#if data?.user}
|
||||
<div class="mb-6">
|
||||
<h1 class="font-serif text-[2rem] text-ink">{greetingText}</h1>
|
||||
</div>
|
||||
{:else}
|
||||
<DocumentList
|
||||
documents={data.documents ?? []}
|
||||
canWrite={data.canWrite}
|
||||
error={data.error}
|
||||
total={data.total ?? 0}
|
||||
q={q}
|
||||
sort={sort}
|
||||
matchData={data.matchData ?? {}}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<div class="grid grid-cols-1 gap-5 lg:grid-cols-[1fr_320px] lg:items-start">
|
||||
<div class="flex flex-col gap-5">
|
||||
<DashboardResumeStrip resumeDoc={data.resumeDoc ?? null} />
|
||||
|
||||
<section aria-label={m.dashboard_mission_caption()}>
|
||||
<h2 class="mb-3 font-sans text-xs font-bold tracking-widest text-ink-3 uppercase">
|
||||
{m.dashboard_mission_caption()}
|
||||
</h2>
|
||||
<MissionControlStrip
|
||||
segmentationDocs={data.segmentationDocs ?? []}
|
||||
transcriptionDocs={data.transcriptionDocs ?? []}
|
||||
readyDocs={data.readyDocs ?? []}
|
||||
weeklyStats={data.weeklyStats ?? null}
|
||||
/>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-5 lg:sticky lg:top-[80px]">
|
||||
<DashboardFamilyPulse pulse={data.pulse ?? null} />
|
||||
<DashboardActivityFeed feed={data.activityFeed ?? []} />
|
||||
{#if data.canWrite}
|
||||
<DropZone />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
Reference in New Issue
Block a user