Appearance
Ledger Page
- File: src/pages/private/ledger/index.svelte
Overview
The Ledger page serves as a comprehensive event history viewer, providing access to past events, their details, attachments, logs, and chat history. It features a searchable interface with real-time updates and multiple viewing modes.
The page is structured as a grid, presenting a chronological list of past events elegantly showcased within the event card component. This component offers a concise overview of each event, providing users with essential details at a glance. Additionally, an overflow menu is available for each event, allowing users to access comprehensive information about the event and view all related logs.
Core Features
1. Event Management
- View past events and their details
- Search through event history
- Access event attachments
- View event logs
- Access event chat history
2. File Management
- View shared files
- Download attachments
- Preview file contents
- Manage file shares
3. Real-time Updates
- Live event title changes
- Dynamic search results
- Instant UI updates
UI Structure
1. Main Layout
svelte
<div class="flex-grow flex flex-col">
<!-- Progress Indicator -->
{#if busy}
<Progress height={2} step={1} />
{/if}
<!-- Search Bar -->
<div class="flex items-center justify-between">
<div class="flex rounded-lg border-gray-300">
{#if showSearch}
<input
type="text"
class="px-4 py-2 w-80 rounded-l-lg outline-none"
placeholder="Search..."
bind:value={searchValue}
/>
{/if}
<button on:click={toggleSearch}>
<Icon src={Search} size="20" />
</button>
</div>
</div>
<!-- Event Grid -->
<div class="grid gap-3 mb-8 grid-cols-1 md:grid-cols-2">
{#each events as data}
<EventCard
{data}
showJoinButton={false}
showAttachment={true}
showdropDown={true}
on:eventDetails={openDetails}
on:eventLog={openLogs}
/>
{/each}
</div>
</div>2. Modal Views
svelte
{#if activeEvent}
<!-- File Viewer -->
{#if activeAttachment}
<SideModal>
<FileViewer file={activeAttachment} on:refresh={refreshAttachments} />
</SideModal>
<!-- Chat Viewer -->
{:else if showChat}
<SideModal>
<ChatViewer meetingId={activeEvent.id} />
</SideModal>
<!-- Log Viewer -->
{:else if showLogs}
<SideModal>
<LogViewer event={activeEvent} />
</SideModal>
<!-- Event Details -->
{:else}
<SideModal>
<Viewer
event={activeEvent}
on:fileSelected={({ detail }) => (activeAttachment = detail)}
on:openChat={openChat}
on:openLogs={(_) => openLogs({ detail: activeEvent })}
/>
</SideModal>
{/if}
{/if}Data Structures
File interface
ts
interface IFile {
id: string;
name: string;
url: string;
type: string;
size: number;
notes: string;
dateCreated: Date;
downloadUrl: string;
thumbnailUrl: string;
ownerId: string;
owner: {
firstName: string;
lastName: string;
};
shares: Array<{
id: string;
user: {
firstName: string;
lastName: string;
profileImageUrl: string;
id: string;
};
}>;
}Event interface
ts
interface IEvent {
id: string;
startTime: Date;
endTime: Date;
startDate: Date;
endDate: Date;
title: string;
allDay: boolean;
notes: string;
repeat: string;
sessionId: number;
hasChat: boolean;
numberOfAttachments: number;
ownerId: string;
participants: Array<{
contact?: {
firstName: string;
lastName: string;
profileImageUrl: string;
specialty: string;
};
username: string;
}>;
owner: {
firstName: string;
lastName: string;
};
isSecuredCameraSession: boolean;
}Core Functions
1. File Management
svelte
async function getFile(sessionId: number, shareId: number) {
try {
const { data, error } = await query<{ xs: { nodes: any[] } }>(
readFileQuery,
{ sessionId, shareId, _id: uniqueId("q") }
);
if (error) {
return { success: false, message: error.message };
}
return processFileData(data.xs.nodes);
} catch (e) {
return { success: false, message: e };
}
}2. Event Retrieval
svelte
async function getEvents(skip: number, take: number, filter: string) {
try {
let where = filter ? `, where: { title: { contains: "${filter}" } }` : "";
const { data, error } = await query<{ xs: any; sharedLedger: any }>(
eventQuery,
{ _id: uniqueId("q") }
);
if (error) {
return { success: false, message: error.message };
}
return processEventData(data);
} catch (e) {
return { success: false, message: e };
}
}State Management
1. Pagination State
svelte
let skip = 0;
let take = 20;
let delay = 500;2. UI State
svelte
let showSearch = false;
let searchValue = '';
let events = [];
let filteredEvents = [];
let activeEvent = null;
let activeAttachment = null;
let showLogs = false;
let showChat = false;
let busy = true;Event handlers
1. View Management
svelte
function openLogs({ detail: event }) {
activeEvent = event;
showLogs = true;
}
function openDetails({ detail: event }) {
activeEvent = event;
showLogs = false;
}
function openChat() {
showChat = true;
}2. Search Management
svelte
const dSearch = debounce(delay, search);
async function search(skip, take, searchValue) {
busy = true;
try {
const ret = await getEvents(skip, take, searchValue);
if (ret.success) {
events = ret.data;
}
} finally {
busy = false;
}
}Permission Management
svelte
onMount(async () => {
if (!hasPermission(Permissions.viewLedger)) {
$goto("/private/dashboard");
return;
}
await dSearch(skip, take, searchValue);
});Real-time updates
svelte
// Subscribe to event title changes
var eventTitleChangedSubscription = PubSub.subscribe(
eventTitleChanged,
(x: IEventTitleChangedArgs) => {
dSearch(skip, take, searchValue);
}
);
// Cleanup subscription
onDestroy(async () => {
if (eventTitleChangedSubscription) {
PubSub.unsubscribe(eventTitleChangedSubscription);
}
});