Refine calendar filters and manual refresh behavior
This commit is contained in:
parent
6c1c65d5c7
commit
5b244ff766
@ -3,7 +3,6 @@
|
||||
export let onSelectedDateChange: (payload: { year: number; month: number; day: number; key: string }) => void =
|
||||
() => {};
|
||||
export let onDateActivate: (payload: { year: number; month: number; day: number; key: string }) => void = () => {};
|
||||
export let signalsByDate: Record<string, { count: number; hasTrigger: boolean; hasMood: boolean; hasOpenTodos: boolean }> = {};
|
||||
|
||||
const today = new Date();
|
||||
let currentYear = today.getFullYear();
|
||||
@ -33,11 +32,6 @@
|
||||
currentMonth = next.getMonth();
|
||||
}
|
||||
|
||||
function signalFor(cell: CalendarCell): { count: number; hasTrigger: boolean; hasMood: boolean; hasOpenTodos: boolean } | null {
|
||||
const key = getDateKey(cell.year, cell.month, cell.day);
|
||||
return signalsByDate[key] ?? null;
|
||||
}
|
||||
|
||||
function changeMonth(offset: number) {
|
||||
setViewDate(currentYear, currentMonth + offset);
|
||||
}
|
||||
@ -154,16 +148,6 @@
|
||||
on:click={() => selectCell(cell)}
|
||||
>
|
||||
<span class="day-number">{cell.day}</span>
|
||||
{#if signalFor(cell)}
|
||||
{#if signalFor(cell)!.count > 0}
|
||||
<span class="entry-count">{signalFor(cell)!.count}</span>
|
||||
{/if}
|
||||
<span class="signals" aria-hidden="true">
|
||||
{#if signalFor(cell)!.hasMood}<i class="signal mood"></i>{/if}
|
||||
{#if signalFor(cell)!.hasTrigger}<i class="signal trigger"></i>{/if}
|
||||
{#if signalFor(cell)!.hasOpenTodos}<i class="signal todo"></i>{/if}
|
||||
</span>
|
||||
{/if}
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
@ -253,49 +237,6 @@
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.entry-count {
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
right: 3px;
|
||||
min-width: 12px;
|
||||
height: 12px;
|
||||
padding: 0 3px;
|
||||
border-radius: 999px;
|
||||
background: var(--surface-3);
|
||||
color: var(--text-primary);
|
||||
font-size: 0.58rem;
|
||||
line-height: 12px;
|
||||
border: 1px solid var(--border-soft);
|
||||
}
|
||||
|
||||
.signals {
|
||||
position: absolute;
|
||||
bottom: 3px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
display: inline-flex;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.signal {
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
border-radius: 999px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.signal.mood {
|
||||
background: #6ba7ff;
|
||||
}
|
||||
|
||||
.signal.trigger {
|
||||
background: #f08c6c;
|
||||
}
|
||||
|
||||
.signal.todo {
|
||||
background: #f2c266;
|
||||
}
|
||||
|
||||
.calendar-cell:hover {
|
||||
color: var(--text-primary);
|
||||
background: var(--bg-hover);
|
||||
|
||||
@ -1,9 +1,12 @@
|
||||
<script lang="ts">
|
||||
import { listEntryTemplates, type EntryTemplateItemDto } from "$lib/backend/templates";
|
||||
import { listFragments, type FragmentDto } from "$lib/backend/fragments";
|
||||
import { listLists, type ListDocumentDto } from "$lib/backend/lists";
|
||||
import { listTodoLists, type TodoListDto } from "$lib/backend/todos";
|
||||
import { sendCommand } from "$lib/backend/client";
|
||||
import CalendarWidget from "$lib/components/CalendarWidget.svelte";
|
||||
import { entriesBusyStore, entriesStore, searchEntriesAsItems } from "$lib/stores/entries";
|
||||
import { createFragmentDraft, fragmentsStore } from "$lib/stores/fragments";
|
||||
import { createFragmentDraft, fragmentsStore, serializeFragment } from "$lib/stores/fragments";
|
||||
import { createListDraft, createListFromLabel, listsStore } from "$lib/stores/lists";
|
||||
import { createTodoListDraft, createTodoListFromLabel, serializeTodoList, todoListsStore, todosStore } from "$lib/stores/todos";
|
||||
|
||||
@ -24,15 +27,10 @@
|
||||
id: string;
|
||||
label: string;
|
||||
initialContent: string;
|
||||
sortDate?: string;
|
||||
};
|
||||
type CalendarViewMode = "day" | "week" | "month";
|
||||
type CalendarSortMode = "asc" | "desc";
|
||||
type CalendarSignal = {
|
||||
count: number;
|
||||
hasTrigger: boolean;
|
||||
hasMood: boolean;
|
||||
hasOpenTodos: boolean;
|
||||
};
|
||||
type SavedCalendarView = {
|
||||
id: string;
|
||||
name: string;
|
||||
@ -41,8 +39,6 @@
|
||||
query: string;
|
||||
tags: string;
|
||||
types: string;
|
||||
checked: string;
|
||||
unchecked: string;
|
||||
startDate: string;
|
||||
endDate: string;
|
||||
};
|
||||
@ -73,12 +69,9 @@
|
||||
let calendarQuery = "";
|
||||
let calendarTags = "";
|
||||
let calendarTypes = "";
|
||||
let calendarChecked = "";
|
||||
let calendarUnchecked = "";
|
||||
let calendarStartDate = "";
|
||||
let calendarEndDate = "";
|
||||
let calendarTimelineItems: SidePanelItem[] = [];
|
||||
let calendarSignals: Record<string, CalendarSignal> = {};
|
||||
let calendarBusy = false;
|
||||
let calendarError = "";
|
||||
let calendarSavedViews: SavedCalendarView[] = [];
|
||||
@ -86,8 +79,9 @@
|
||||
let saveViewName = "";
|
||||
let hasLoadedSavedViews = false;
|
||||
let lastCalendarTimelineKey = "";
|
||||
let lastCalendarSignalsKey = "";
|
||||
let calendarTimelineDebounce: ReturnType<typeof setTimeout> | null = null;
|
||||
let lastActiveSection = "";
|
||||
let calendarLastRefreshedAt = "";
|
||||
const CALENDAR_SAVED_VIEWS_KEY = "journal.calendar.savedViews";
|
||||
|
||||
let selectedCalendarDate: { year: number; month: number; day: number; key: string } | null = {
|
||||
@ -115,15 +109,36 @@
|
||||
}
|
||||
|
||||
function parseDateLabel(value: string): Date | null {
|
||||
if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) return null;
|
||||
const date = new Date(`${value}T00:00:00`);
|
||||
const token = value.trim().split(/\s*[|·]\s*/)[0].trim();
|
||||
if (!/^\d{4}-\d{2}-\d{2}$/.test(token)) return null;
|
||||
const date = new Date(`${token}T00:00:00`);
|
||||
return Number.isNaN(date.getTime()) ? null : date;
|
||||
}
|
||||
|
||||
function parseIsoDate(value: string): Date | null {
|
||||
if (!value) return null;
|
||||
const parsed = new Date(value);
|
||||
return Number.isNaN(parsed.getTime()) ? null : parsed;
|
||||
}
|
||||
|
||||
function toIsoDate(date: Date): string {
|
||||
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
|
||||
}
|
||||
|
||||
function isWithinRange(date: Date, startDate: string, endDate: string): boolean {
|
||||
const start = parseDateLabel(startDate);
|
||||
const end = parseDateLabel(endDate);
|
||||
if (!start || !end) return true;
|
||||
const day = toIsoDate(date);
|
||||
return day >= toIsoDate(start) && day <= toIsoDate(end);
|
||||
}
|
||||
|
||||
function matchesTextQuery(content: string, query: string): boolean {
|
||||
const trimmed = query.trim();
|
||||
if (!trimmed) return true;
|
||||
return content.toLowerCase().includes(trimmed.toLowerCase());
|
||||
}
|
||||
|
||||
function getActiveDateRange(): { startDate: string; endDate: string } {
|
||||
if (calendarStartDate && calendarEndDate) {
|
||||
return { startDate: calendarStartDate, endDate: calendarEndDate };
|
||||
@ -162,47 +177,60 @@
|
||||
return (config.dataDirectory ?? config.DataDirectory ?? "").trim();
|
||||
}
|
||||
|
||||
function getSignal(dateKey: string): CalendarSignal {
|
||||
const existing = calendarSignals[dateKey];
|
||||
if (existing) return existing;
|
||||
return { count: 0, hasTrigger: false, hasMood: false, hasOpenTodos: false };
|
||||
function toFragmentTimelineItem(fragment: FragmentDto): SidePanelItem {
|
||||
const split = fragment.description.split(/\n{2,}/);
|
||||
const title = (split[0] ?? "").trim() || "Untitled Fragment";
|
||||
const body = split.slice(1).join("\n\n").trim() || "Add details for this fragment.";
|
||||
const date = parseIsoDate(fragment.time) ?? new Date();
|
||||
const dateKey = toIsoDate(date);
|
||||
return {
|
||||
id: `fragments/${fragment.id}`,
|
||||
label: `${dateKey} | Fragment | ${title}`,
|
||||
initialContent: serializeFragment({
|
||||
title,
|
||||
type: fragment.type,
|
||||
tags: fragment.tags ?? [],
|
||||
body
|
||||
}),
|
||||
sortDate: date.toISOString()
|
||||
};
|
||||
}
|
||||
|
||||
function upsertSignal(dateKey: string, next: CalendarSignal) {
|
||||
calendarSignals = { ...calendarSignals, [dateKey]: next };
|
||||
function toListTimelineItem(list: ListDocumentDto): SidePanelItem {
|
||||
const created = parseIsoDate(list.createdAt) ?? parseIsoDate(list.updatedAt) ?? new Date();
|
||||
const dateKey = toIsoDate(created);
|
||||
return {
|
||||
id: `lists/${list.id}`,
|
||||
label: `${dateKey} | List | ${list.label}`,
|
||||
initialContent: list.content || `# ${list.label}\n\n`,
|
||||
sortDate: created.toISOString()
|
||||
};
|
||||
}
|
||||
|
||||
async function refreshCalendarSignals(): Promise<void> {
|
||||
const monthStart = toDateKey(calendarYear, calendarMonth, 1);
|
||||
const monthEnd = toDateKey(calendarYear, calendarMonth, new Date(calendarYear, calendarMonth + 1, 0).getDate());
|
||||
const dataDirectory = await getDataDirectory();
|
||||
if (!dataDirectory) {
|
||||
calendarSignals = {};
|
||||
return;
|
||||
}
|
||||
function toTodoTimelineItem(list: TodoListDto): SidePanelItem {
|
||||
const created = parseIsoDate(list.createdAt) ?? new Date();
|
||||
const dateKey = toIsoDate(created);
|
||||
const items = list.items.map((item, index) => ({
|
||||
id: Date.now() + index,
|
||||
text: item.text,
|
||||
done: item.done
|
||||
}));
|
||||
return {
|
||||
id: `todos/${list.id}`,
|
||||
label: `${dateKey} | To-Do | ${list.label}`,
|
||||
initialContent: serializeTodoList(list.label, items),
|
||||
sortDate: created.toISOString()
|
||||
};
|
||||
}
|
||||
|
||||
const results = await searchEntriesAsItems({
|
||||
dataDirectory,
|
||||
startDate: monthStart,
|
||||
endDate: monthEnd
|
||||
});
|
||||
function splitFilterTokens(input: string): string[] {
|
||||
return parseCsv(input).map((token) => token.toLowerCase());
|
||||
}
|
||||
|
||||
let nextSignals: Record<string, CalendarSignal> = {};
|
||||
for (const item of results) {
|
||||
const dateKey = item.label.trim();
|
||||
const date = parseDateLabel(dateKey);
|
||||
if (!date) continue;
|
||||
const signal = nextSignals[dateKey] ?? { count: 0, hasTrigger: false, hasMood: false, hasOpenTodos: false };
|
||||
signal.count += 1;
|
||||
const content = item.initialContent ?? "";
|
||||
const lower = content.toLowerCase();
|
||||
signal.hasTrigger = signal.hasTrigger || lower.includes("!trigger") || lower.includes("#trigger") || lower.includes("#stress");
|
||||
signal.hasMood = signal.hasMood || lower.includes("mental / emotional snapshot") || lower.includes("cognitive state");
|
||||
signal.hasOpenTodos = signal.hasOpenTodos || /-\s*\[\s\]/.test(content);
|
||||
nextSignals[dateKey] = signal;
|
||||
}
|
||||
|
||||
calendarSignals = nextSignals;
|
||||
function matchesAnyToken(text: string, tokens: string[]): boolean {
|
||||
if (tokens.length === 0) return true;
|
||||
const lower = text.toLowerCase();
|
||||
return tokens.some((token) => lower.includes(token));
|
||||
}
|
||||
|
||||
async function refreshCalendarTimeline(): Promise<void> {
|
||||
@ -216,23 +244,88 @@
|
||||
}
|
||||
|
||||
const { startDate, endDate } = getActiveDateRange();
|
||||
const items = await searchEntriesAsItems({
|
||||
const entryItems = await searchEntriesAsItems({
|
||||
dataDirectory,
|
||||
startDate,
|
||||
endDate,
|
||||
query: calendarQuery.trim() || undefined,
|
||||
tags: parseCsv(calendarTags),
|
||||
types: parseCsv(calendarTypes),
|
||||
checked: parseCsv(calendarChecked),
|
||||
unchecked: parseCsv(calendarUnchecked)
|
||||
types: parseCsv(calendarTypes)
|
||||
});
|
||||
|
||||
const sorted = [...items].sort((a, b) => {
|
||||
const aDate = parseDateLabel(a.label)?.getTime() ?? 0;
|
||||
const bDate = parseDateLabel(b.label)?.getTime() ?? 0;
|
||||
const [fragmentDtos, listDtos, todoDtos] = await Promise.all([
|
||||
listFragments(),
|
||||
listLists(),
|
||||
listTodoLists()
|
||||
]);
|
||||
|
||||
const query = calendarQuery.trim();
|
||||
const tagTokens = splitFilterTokens(calendarTags);
|
||||
const typeTokens = splitFilterTokens(calendarTypes);
|
||||
const hasTypeFilter = typeTokens.length > 0;
|
||||
|
||||
const fragmentItems = fragmentDtos
|
||||
.map(toFragmentTimelineItem)
|
||||
.filter((item) => {
|
||||
const date = item.sortDate ? parseIsoDate(item.sortDate) : null;
|
||||
if (!date || !isWithinRange(date, startDate, endDate)) return false;
|
||||
if (!matchesTextQuery(item.initialContent, query)) return false;
|
||||
if (!matchesAnyToken(`${item.label}\n${item.initialContent}`, tagTokens)) return false;
|
||||
if (hasTypeFilter && !matchesAnyToken(`${item.label}\n${item.initialContent}`, typeTokens)) return false;
|
||||
return true;
|
||||
});
|
||||
|
||||
const listItems = listDtos
|
||||
.map(toListTimelineItem)
|
||||
.filter((item) => {
|
||||
if (hasTypeFilter) return false;
|
||||
const date = item.sortDate ? parseIsoDate(item.sortDate) : null;
|
||||
if (!date || !isWithinRange(date, startDate, endDate)) return false;
|
||||
if (!matchesTextQuery(item.initialContent, query) && !matchesTextQuery(item.label, query)) return false;
|
||||
if (!matchesAnyToken(`${item.label}\n${item.initialContent}`, tagTokens)) return false;
|
||||
return true;
|
||||
});
|
||||
|
||||
const todoItems = todoDtos
|
||||
.map(toTodoTimelineItem)
|
||||
.filter((item) => {
|
||||
if (hasTypeFilter) return false;
|
||||
const date = item.sortDate ? parseIsoDate(item.sortDate) : null;
|
||||
if (!date || !isWithinRange(date, startDate, endDate)) return false;
|
||||
if (!matchesTextQuery(item.initialContent, query) && !matchesTextQuery(item.label, query)) return false;
|
||||
if (!matchesAnyToken(`${item.label}\n${item.initialContent}`, tagTokens)) return false;
|
||||
return true;
|
||||
});
|
||||
|
||||
const entriesWithKind: SidePanelItem[] = entryItems
|
||||
.map((item) => {
|
||||
const date = parseDateLabel(item.label);
|
||||
const dateKey = date ? toIsoDate(date) : item.label;
|
||||
return {
|
||||
id: item.id,
|
||||
label: `${dateKey} | Entry | ${item.label}`,
|
||||
initialContent: item.initialContent,
|
||||
sortDate: date ? date.toISOString() : undefined
|
||||
};
|
||||
})
|
||||
.filter((item) => {
|
||||
if (hasTypeFilter) return false;
|
||||
if (!matchesAnyToken(`${item.label}\n${item.initialContent}`, tagTokens)) return false;
|
||||
return true;
|
||||
});
|
||||
|
||||
const merged = [...entriesWithKind, ...fragmentItems, ...listItems, ...todoItems];
|
||||
const sorted = merged.sort((a, b) => {
|
||||
const aDate = a.sortDate ? parseIsoDate(a.sortDate)?.getTime() ?? 0 : parseDateLabel(a.label)?.getTime() ?? 0;
|
||||
const bDate = b.sortDate ? parseIsoDate(b.sortDate)?.getTime() ?? 0 : parseDateLabel(b.label)?.getTime() ?? 0;
|
||||
return calendarSortMode === "asc" ? aDate - bDate : bDate - aDate;
|
||||
});
|
||||
calendarTimelineItems = sorted;
|
||||
calendarLastRefreshedAt = new Date().toLocaleTimeString(undefined, {
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit"
|
||||
});
|
||||
} catch (error) {
|
||||
calendarError = String(error);
|
||||
calendarTimelineItems = [];
|
||||
@ -241,6 +334,16 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function forceRefreshCalendar(options?: { allowWhileBusy?: boolean }): Promise<void> {
|
||||
if (activeSection !== "calendar") return;
|
||||
if (calendarBusy && !options?.allowWhileBusy) return;
|
||||
if (calendarTimelineDebounce) {
|
||||
clearTimeout(calendarTimelineDebounce);
|
||||
calendarTimelineDebounce = null;
|
||||
}
|
||||
await refreshCalendarTimeline();
|
||||
}
|
||||
|
||||
function loadSavedViews() {
|
||||
if (typeof window === "undefined") return;
|
||||
const raw = window.localStorage.getItem(CALENDAR_SAVED_VIEWS_KEY);
|
||||
@ -268,8 +371,6 @@
|
||||
calendarQuery = view.query;
|
||||
calendarTags = view.tags;
|
||||
calendarTypes = view.types;
|
||||
calendarChecked = view.checked;
|
||||
calendarUnchecked = view.unchecked;
|
||||
calendarStartDate = view.startDate;
|
||||
calendarEndDate = view.endDate;
|
||||
}
|
||||
@ -290,8 +391,6 @@
|
||||
query: calendarQuery,
|
||||
tags: calendarTags,
|
||||
types: calendarTypes,
|
||||
checked: calendarChecked,
|
||||
unchecked: calendarUnchecked,
|
||||
startDate: calendarStartDate,
|
||||
endDate: calendarEndDate
|
||||
};
|
||||
@ -315,8 +414,6 @@
|
||||
query: "",
|
||||
tags: "",
|
||||
types: "",
|
||||
checked: "",
|
||||
unchecked: "",
|
||||
startDate: "",
|
||||
endDate: ""
|
||||
},
|
||||
@ -328,24 +425,10 @@
|
||||
query: "",
|
||||
tags: "stress, trigger",
|
||||
types: "!TRIGGER",
|
||||
checked: "",
|
||||
unchecked: "",
|
||||
startDate: "",
|
||||
endDate: ""
|
||||
},
|
||||
{
|
||||
id: "builtin-open-todos",
|
||||
name: "Open Checklist",
|
||||
viewMode: "month",
|
||||
sortMode: "desc",
|
||||
query: "",
|
||||
tags: "",
|
||||
types: "",
|
||||
checked: "",
|
||||
unchecked: "todo, follow up, check",
|
||||
startDate: "",
|
||||
endDate: ""
|
||||
}
|
||||
|
||||
];
|
||||
|
||||
function openOrCreateDailyEntry(dateKey: string) {
|
||||
@ -432,6 +515,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
function handleRefreshClick() {
|
||||
void forceRefreshCalendar();
|
||||
}
|
||||
|
||||
function handleAddTemplate() {
|
||||
if (activeSection !== "entries") return;
|
||||
createTemplateMode = true;
|
||||
@ -582,10 +669,19 @@
|
||||
calendarQuery,
|
||||
calendarTags,
|
||||
calendarTypes,
|
||||
calendarChecked,
|
||||
calendarUnchecked,
|
||||
calendarStartDate,
|
||||
calendarEndDate
|
||||
calendarEndDate,
|
||||
entriesSig: $entriesStore.map((item) => `${item.id}:${item.label}`).join("|"),
|
||||
fragmentsSig: $fragmentsStore.map((item) => `${item.id}:${item.label}`).join("|"),
|
||||
listsSig: $listsStore.map((item) => `${item.id}:${item.label}`).join("|"),
|
||||
todosSig: $todoListsStore
|
||||
.map((item) => {
|
||||
const todos = ($todosStore[item.id] ?? [])
|
||||
.map((todo) => `${todo.text}:${todo.done ? "1" : "0"}`)
|
||||
.join("~");
|
||||
return `${item.id}:${item.label}:${todos}`;
|
||||
})
|
||||
.join("|")
|
||||
});
|
||||
$: if (activeSection === "calendar" && calendarTimelineRefreshKey !== lastCalendarTimelineKey) {
|
||||
lastCalendarTimelineKey = calendarTimelineRefreshKey;
|
||||
@ -597,15 +693,14 @@
|
||||
}, 200);
|
||||
}
|
||||
|
||||
$: calendarSignalsRefreshKey = JSON.stringify({
|
||||
activeSection,
|
||||
calendarYear,
|
||||
calendarMonth
|
||||
});
|
||||
$: if (activeSection === "calendar" && calendarSignalsRefreshKey !== lastCalendarSignalsKey) {
|
||||
lastCalendarSignalsKey = calendarSignalsRefreshKey;
|
||||
void refreshCalendarSignals();
|
||||
$: if (activeSection === "calendar" && lastActiveSection !== "calendar") {
|
||||
void forceRefreshCalendar({ allowWhileBusy: true });
|
||||
setTimeout(() => {
|
||||
void forceRefreshCalendar({ allowWhileBusy: true });
|
||||
}, 500);
|
||||
}
|
||||
$: lastActiveSection = activeSection;
|
||||
|
||||
$: if (activeSection === "calendar") {
|
||||
onCalendarStateChange({
|
||||
items: calendarTimelineItems,
|
||||
@ -619,6 +714,11 @@
|
||||
<header class="panel-header">
|
||||
<h2>{panelTitle}</h2>
|
||||
<div class="panel-header-actions">
|
||||
{#if activeSection === "calendar"}
|
||||
<button type="button" class="panel-action" aria-label="Refresh calendar" title="Refresh calendar" on:click={handleRefreshClick}>
|
||||
<span class="material-symbols-outlined">refresh</span>
|
||||
</button>
|
||||
{/if}
|
||||
{#if activeSection === "entries"}
|
||||
<button type="button" class="panel-action" aria-label="Add template" title="Add template" on:click={handleAddTemplate}>
|
||||
<span class="material-symbols-outlined">palette</span>
|
||||
@ -671,11 +771,6 @@
|
||||
|
||||
<div class="calendar-control-row">
|
||||
<input type="text" bind:value={calendarTypes} placeholder="Fragment types (comma)" />
|
||||
<input type="text" bind:value={calendarChecked} placeholder="Checked todos (comma)" />
|
||||
</div>
|
||||
|
||||
<div class="calendar-control-row">
|
||||
<input type="text" bind:value={calendarUnchecked} placeholder="Unchecked todos (comma)" />
|
||||
<span></span>
|
||||
</div>
|
||||
|
||||
@ -723,12 +818,11 @@
|
||||
onVisibleMonthChange={handleVisibleMonthChange}
|
||||
onSelectedDateChange={handleSelectedDateChange}
|
||||
onDateActivate={handleDateActivate}
|
||||
signalsByDate={calendarSignals}
|
||||
/>
|
||||
|
||||
<div class="calendar-entries">
|
||||
<h3>{calendarMonthLabel} {calendarYear} Timeline</h3>
|
||||
<p class="section-copy">Filtered items are shown in the main panel.</p>
|
||||
<p class="section-copy">Last refreshed: {calendarLastRefreshedAt || "Not yet"}</p>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="panel-search">
|
||||
@ -1181,3 +1275,4 @@
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user