diff --git a/src/routes/admin/analytics/+page.server.ts b/src/routes/admin/analytics/+page.server.ts
new file mode 100644
index 0000000..af3ac7d
--- /dev/null
+++ b/src/routes/admin/analytics/+page.server.ts
@@ -0,0 +1,47 @@
+import type { PageServerLoad } from './$types';
+import { db } from '$lib/server/db';
+import { pets, applications, donations, fosterAssignments, users, pageViews } from '$lib/server/schema';
+import { eq, gte, count, sum, and } from 'drizzle-orm';
+import { error } from '@sveltejs/kit';
+import { hasPermission } from '$lib/roles';
+import type { Role } from '$lib/roles';
+
+export const load: PageServerLoad = async ({ locals }) => {
+ if (!hasPermission(locals.user!.role as Role, 'dashboard')) error(403, 'Access denied');
+
+ const thirtyDaysAgo = new Date();
+ thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
+
+ const [totalPetsRow] = await db.select({ total: count() }).from(pets);
+ const [availablePetsRow] = await db.select({ total: count() }).from(pets).where(eq(pets.status, 'available'));
+ const [fosterPetsRow] = await db.select({ total: count() }).from(pets).where(eq(pets.status, 'foster'));
+ const [adoptedThisMonthRow] = await db.select({ total: count() }).from(pets).where(
+ and(eq(pets.status, 'adopted'), gte(pets.adoptedDate, thirtyDaysAgo))
+ );
+
+ const [pendingAppsRow] = await db.select({ total: count() }).from(applications).where(eq(applications.status, 'pending'));
+ const [newAppsRow] = await db.select({ total: count() }).from(applications).where(gte(applications.createdAt, thirtyDaysAgo));
+
+ const [donationsRow] = await db.select({ total: sum(donations.amount) }).from(donations).where(gte(donations.date, thirtyDaysAgo));
+ const [totalUsersRow] = await db.select({ total: count() }).from(users).where(eq(users.active, true));
+ const [activeFostersRow] = await db.select({ total: count() }).from(fosterAssignments).where(eq(fosterAssignments.isCurrent, true));
+
+ const [pageViewsRow] = await db.select({ total: count() }).from(pageViews).where(
+ and(gte(pageViews.createdAt, thirtyDaysAgo), eq(pageViews.isBot, false))
+ );
+
+ return {
+ stats: {
+ totalPets: totalPetsRow?.total ?? 0,
+ availablePets: availablePetsRow?.total ?? 0,
+ fosterPets: fosterPetsRow?.total ?? 0,
+ adoptedThisMonth: adoptedThisMonthRow?.total ?? 0,
+ pendingApplications: pendingAppsRow?.total ?? 0,
+ newApplications: newAppsRow?.total ?? 0,
+ donationsThisMonth: (donationsRow?.total ?? 0) as number,
+ activeUsers: totalUsersRow?.total ?? 0,
+ activeFosters: activeFostersRow?.total ?? 0,
+ pageViewsThisMonth: pageViewsRow?.total ?? 0
+ }
+ };
+};
diff --git a/src/routes/admin/analytics/+page.svelte b/src/routes/admin/analytics/+page.svelte
new file mode 100644
index 0000000..3bbf141
--- /dev/null
+++ b/src/routes/admin/analytics/+page.svelte
@@ -0,0 +1,58 @@
+
+
+Analytics — Admin
+
+
+
+
Analytics
+
Last 30 days unless otherwise noted
+
+
+
+
+
Total Pets
+
{s.totalPets}
+
+
+
Available
+
{s.availablePets}
+
+
+
In Foster
+
{s.fosterPets}
+
+
+
Adopted (30d)
+
{s.adoptedThisMonth}
+
+
+
Active Fosters
+
{s.activeFosters}
+
+
+
+
+
+
Pending Applications
+
{s.pendingApplications}
+
{s.newApplications} new this month
+
+
+
Donations (30d)
+
{fmt.format(s.donationsThisMonth / 100)}
+
+
+
Active Users
+
{s.activeUsers}
+
+
+
Page Views (30d)
+
{s.pageViewsThisMonth.toLocaleString()}
+
+
+
diff --git a/src/routes/admin/foster-dashboard/+page.server.ts b/src/routes/admin/foster-dashboard/+page.server.ts
new file mode 100644
index 0000000..9b758ec
--- /dev/null
+++ b/src/routes/admin/foster-dashboard/+page.server.ts
@@ -0,0 +1,57 @@
+import type { PageServerLoad } from './$types';
+import { db } from '$lib/server/db';
+import { fosterAssignments, supplyRequests, fosterSubmissions, pets, users } from '$lib/server/schema';
+import { eq, desc, count } from 'drizzle-orm';
+import { error } from '@sveltejs/kit';
+import { hasPermission } from '$lib/roles';
+import type { Role } from '$lib/roles';
+
+export const load: PageServerLoad = async ({ locals }) => {
+ if (!hasPermission(locals.user!.role as Role, 'pets')) error(403, 'Access denied');
+
+ // Active foster assignments
+ const activeFosters = await db
+ .select({
+ id: fosterAssignments.id,
+ startDate: fosterAssignments.startDate,
+ petId: pets.id,
+ petName: pets.name,
+ petSpecies: pets.species,
+ petBreed: pets.breed,
+ fostererName: users.name,
+ fostererEmail: users.email
+ })
+ .from(fosterAssignments)
+ .leftJoin(pets, eq(fosterAssignments.petId, pets.id))
+ .leftJoin(users, eq(fosterAssignments.userId, users.id))
+ .where(eq(fosterAssignments.isCurrent, true))
+ .orderBy(desc(fosterAssignments.startDate));
+
+ // Pending supply requests
+ const pendingSupplyRequests = await db
+ .select({
+ id: supplyRequests.id,
+ items: supplyRequests.items,
+ urgency: supplyRequests.urgency,
+ notes: supplyRequests.notes,
+ createdAt: supplyRequests.createdAt,
+ petName: pets.name,
+ requesterName: users.name
+ })
+ .from(supplyRequests)
+ .leftJoin(pets, eq(supplyRequests.petId, pets.id))
+ .leftJoin(users, eq(supplyRequests.userId, users.id))
+ .where(eq(supplyRequests.status, 'pending'))
+ .orderBy(desc(supplyRequests.createdAt));
+
+ const [pendingSubmissionsRow] = await db
+ .select({ total: count() })
+ .from(fosterSubmissions)
+ .where(eq(fosterSubmissions.status, 'pending'));
+
+ return {
+ activeFosters,
+ pendingSupplyRequests,
+ pendingSubmissionsCount: pendingSubmissionsRow?.total ?? 0
+ };
+};
diff --git a/src/routes/admin/foster-dashboard/+page.svelte b/src/routes/admin/foster-dashboard/+page.svelte
new file mode 100644
index 0000000..a58300e
--- /dev/null
+++ b/src/routes/admin/foster-dashboard/+page.svelte
@@ -0,0 +1,87 @@
+
+
+Foster Dashboard — Admin
+
+
+
+
Foster Dashboard
+
+
+
+
+
+
Active Fosters
+
{data.activeFosters.length}
+
+
+
Supply Requests
+
{data.pendingSupplyRequests.length}
+
+
+
Pending Posts
+
{data.pendingSubmissionsCount}
+
Review
+
+
+
+
+
+
+
Active Foster Assignments
+ {#if data.activeFosters.length === 0}
+
No active fosters.
+ {:else}
+
+ {#each data.activeFosters as foster}
+
+
+
{formatDate(foster.startDate)}
+
+ {/each}
+
+ {/if}
+
+
+
+
+
Pending Supply Requests
+ {#if data.pendingSupplyRequests.length === 0}
+
No pending requests.
+ {:else}
+
+ {#each data.pendingSupplyRequests as req}
+
+
+
{req.requesterName ?? '—'}
+
+ {req.urgency}
+
+
+
{req.items}
+ {#if req.petName}
+
For: {req.petName}
+ {/if}
+
+ {/each}
+
+ {/if}
+
+
+
diff --git a/src/routes/admin/fosters/+page.server.ts b/src/routes/admin/fosters/+page.server.ts
new file mode 100644
index 0000000..66fbad1
--- /dev/null
+++ b/src/routes/admin/fosters/+page.server.ts
@@ -0,0 +1,69 @@
+import type { PageServerLoad, Actions } from './$types';
+import { db } from '$lib/server/db';
+import { fosterAssignments, pets, users } from '$lib/server/schema';
+import { eq, desc } from 'drizzle-orm';
+import { error, fail } from '@sveltejs/kit';
+import { hasPermission } from '$lib/roles';
+import type { Role } from '$lib/roles';
+import { logAudit } from '$lib/server/audit';
+
+export const load: PageServerLoad = async ({ locals }) => {
+ if (!hasPermission(locals.user!.role as Role, 'pets')) error(403, 'Access denied');
+
+ const assignments = await db
+ .select({
+ id: fosterAssignments.id,
+ startDate: fosterAssignments.startDate,
+ endDate: fosterAssignments.endDate,
+ isCurrent: fosterAssignments.isCurrent,
+ notes: fosterAssignments.notes,
+ createdAt: fosterAssignments.createdAt,
+ petId: pets.id,
+ petName: pets.name,
+ petSpecies: pets.species,
+ fostererName: users.name,
+ fostererEmail: users.email,
+ fostererId: users.id
+ })
+ .from(fosterAssignments)
+ .leftJoin(pets, eq(fosterAssignments.petId, pets.id))
+ .leftJoin(users, eq(fosterAssignments.userId, users.id))
+ .orderBy(desc(fosterAssignments.isCurrent), desc(fosterAssignments.startDate));
+
+ // Active fosters for the form
+ const fosterUsers = await db
+ .select({ id: users.id, name: users.name, email: users.email })
+ .from(users)
+ .where(eq(users.role, 'foster'))
+ .orderBy(users.name);
+
+ const availablePets = await db
+ .select({ id: pets.id, name: pets.name })
+ .from(pets)
+ .where(eq(pets.status, 'available'))
+ .orderBy(pets.name);
+
+ return { assignments, fosterUsers, availablePets };
+};
+
+export const actions: Actions = {
+ end: async ({ request, locals }) => {
+ if (!hasPermission(locals.user!.role as Role, 'pets')) error(403, 'Access denied');
+ const fd = await request.formData();
+ const id = parseInt(fd.get('id')?.toString() ?? '', 10);
+ if (isNaN(id)) return fail(400, { error: 'Invalid ID' });
+
+ await db.update(fosterAssignments)
+ .set({ isCurrent: false, endDate: new Date() })
+ .where(eq(fosterAssignments.id, id));
+
+ await logAudit({
+ userId: locals.user!.id,
+ action: 'end_foster_assignment',
+ entity: 'foster_assignment',
+ entityId: id
+ });
+
+ return { success: true };
+ }
+};
diff --git a/src/routes/admin/fosters/+page.svelte b/src/routes/admin/fosters/+page.svelte
new file mode 100644
index 0000000..5e2c5d5
--- /dev/null
+++ b/src/routes/admin/fosters/+page.svelte
@@ -0,0 +1,98 @@
+
+
+Foster Assignments — Admin
+
+
+
+
Foster Assignments
+ {active.length} active
+
+
+ {#if form?.error}
+
{form.error}
+ {/if}
+
+ {#if active.length > 0}
+
Active
+
+
+
+
+ | Pet |
+ Foster |
+ Since |
+ Actions |
+
+
+
+ {#each active as a}
+
+ |
+ {a.petName ?? '—'}
+ |
+
+ {a.fostererName ?? '—'}
+ {a.fostererEmail ?? ''}
+ |
+ {formatDate(a.startDate)} |
+
+
+ |
+
+ {/each}
+
+
+
+ {/if}
+
+ {#if past.length > 0}
+
+ Past Assignments ({past.length})
+
+
+
+
+ | Pet |
+ Foster |
+ Period |
+
+
+
+ {#each past as a}
+
+ | {a.petName ?? '—'} |
+ {a.fostererName ?? '—'} |
+ {formatDate(a.startDate)} – {formatDate(a.endDate)} |
+
+ {/each}
+
+
+
+
+ {/if}
+
+ {#if data.assignments.length === 0}
+
+
No foster assignments yet.
+
+ {/if}
+
diff --git a/src/routes/admin/moderation/+page.server.ts b/src/routes/admin/moderation/+page.server.ts
new file mode 100644
index 0000000..6d85d79
--- /dev/null
+++ b/src/routes/admin/moderation/+page.server.ts
@@ -0,0 +1,82 @@
+import type { PageServerLoad, Actions } from './$types';
+import { db } from '$lib/server/db';
+import { fosterSubmissions, pets, users } from '$lib/server/schema';
+import { eq, desc } from 'drizzle-orm';
+import { error, fail } from '@sveltejs/kit';
+import { hasPermission } from '$lib/roles';
+import type { Role } from '$lib/roles';
+import { logAudit } from '$lib/server/audit';
+
+export const load: PageServerLoad = async ({ locals }) => {
+ if (!hasPermission(locals.user!.role as Role, 'pets')) error(403, 'Access denied');
+
+ const submissions = await db
+ .select({
+ id: fosterSubmissions.id,
+ type: fosterSubmissions.type,
+ content: fosterSubmissions.content,
+ photoUrl: fosterSubmissions.photoUrl,
+ status: fosterSubmissions.status,
+ createdAt: fosterSubmissions.createdAt,
+ petName: pets.name,
+ petId: pets.id,
+ submittedByName: users.name,
+ submittedByEmail: users.email
+ })
+ .from(fosterSubmissions)
+ .leftJoin(pets, eq(fosterSubmissions.petId, pets.id))
+ .leftJoin(users, eq(fosterSubmissions.userId, users.id))
+ .orderBy(desc(fosterSubmissions.createdAt))
+ .limit(100);
+
+ const pending = submissions.filter(s => s.status === 'pending');
+ const reviewed = submissions.filter(s => s.status !== 'pending');
+
+ return { pending, reviewed };
+};
+
+export const actions: Actions = {
+ approve: async ({ request, locals }) => {
+ if (!hasPermission(locals.user!.role as Role, 'pets')) error(403, 'Access denied');
+ const fd = await request.formData();
+ const id = parseInt(fd.get('id')?.toString() ?? '', 10);
+ if (isNaN(id)) return fail(400, { error: 'Invalid ID' });
+
+ await db.update(fosterSubmissions).set({
+ status: 'approved',
+ reviewedBy: locals.user!.id,
+ reviewedAt: new Date()
+ }).where(eq(fosterSubmissions.id, id));
+
+ await logAudit({
+ userId: locals.user!.id,
+ action: 'approve_foster_submission',
+ entity: 'foster_submission',
+ entityId: id
+ });
+
+ return { success: true };
+ },
+
+ reject: async ({ request, locals }) => {
+ if (!hasPermission(locals.user!.role as Role, 'pets')) error(403, 'Access denied');
+ const fd = await request.formData();
+ const id = parseInt(fd.get('id')?.toString() ?? '', 10);
+ if (isNaN(id)) return fail(400, { error: 'Invalid ID' });
+
+ await db.update(fosterSubmissions).set({
+ status: 'rejected',
+ reviewedBy: locals.user!.id,
+ reviewedAt: new Date()
+ }).where(eq(fosterSubmissions.id, id));
+
+ await logAudit({
+ userId: locals.user!.id,
+ action: 'reject_foster_submission',
+ entity: 'foster_submission',
+ entityId: id
+ });
+
+ return { success: true };
+ }
+};
diff --git a/src/routes/admin/moderation/+page.svelte b/src/routes/admin/moderation/+page.svelte
new file mode 100644
index 0000000..9ad2b71
--- /dev/null
+++ b/src/routes/admin/moderation/+page.svelte
@@ -0,0 +1,91 @@
+
+
+Moderation — Admin
+
+
+
+
Foster Submission Moderation
+ {#if data.pending.length > 0}
+
{data.pending.length} pending review
+ {/if}
+
+
+ {#if data.pending.length === 0 && data.reviewed.length === 0}
+
+ {/if}
+
+ {#if data.pending.length > 0}
+
Pending Review
+
+ {#each data.pending as sub}
+
+
+
+
+ {sub.type}
+ {sub.petName ?? 'Unknown Pet'}
+ by {sub.submittedByName ?? sub.submittedByEmail ?? 'Unknown'}
+
+ {#if sub.content}
+
{sub.content}
+ {/if}
+ {#if sub.photoUrl}
+

+ {/if}
+
{formatDate(sub.createdAt)}
+
+
+
+
+
+
+
+ {/each}
+
+ {/if}
+
+ {#if data.reviewed.length > 0}
+
+ Reviewed ({data.reviewed.length})
+
+ {#each data.reviewed as sub}
+
+
+ {sub.petName ?? '—'}
+ ·
+ {sub.type}
+ ·
+ {sub.submittedByName ?? '—'}
+
+
+ {sub.status}
+
+
+ {/each}
+
+
+ {/if}
+
diff --git a/src/routes/admin/system/+page.server.ts b/src/routes/admin/system/+page.server.ts
new file mode 100644
index 0000000..5380a25
--- /dev/null
+++ b/src/routes/admin/system/+page.server.ts
@@ -0,0 +1,92 @@
+import type { PageServerLoad, Actions } from './$types';
+import { db } from '$lib/server/db';
+import { siteSettings, orgConfig, auditLog, users } from '$lib/server/schema';
+import { eq, desc } from 'drizzle-orm';
+import { error, fail } from '@sveltejs/kit';
+import { hasPermission } from '$lib/roles';
+import type { Role } from '$lib/roles';
+import { logAudit } from '$lib/server/audit';
+
+export const load: PageServerLoad = async ({ locals }) => {
+ if (!hasPermission(locals.user!.role as Role, 'system')) error(403, 'Access denied');
+
+ const settings = await db.select().from(siteSettings).orderBy(siteSettings.key);
+ const configs = await db.select().from(orgConfig).orderBy(orgConfig.key);
+
+ const recentAudit = await db
+ .select({
+ id: auditLog.id,
+ action: auditLog.action,
+ entity: auditLog.entity,
+ entityId: auditLog.entityId,
+ diff: auditLog.diff,
+ createdAt: auditLog.createdAt,
+ userName: users.name,
+ userEmail: users.email
+ })
+ .from(auditLog)
+ .leftJoin(users, eq(auditLog.userId, users.id))
+ .orderBy(desc(auditLog.createdAt))
+ .limit(50);
+
+ return { settings, configs, recentAudit };
+};
+
+export const actions: Actions = {
+ saveSetting: async ({ request, locals }) => {
+ if (!hasPermission(locals.user!.role as Role, 'system')) error(403, 'Access denied');
+ const fd = await request.formData();
+ const key = fd.get('key')?.toString().trim() ?? '';
+ const value = fd.get('value')?.toString() ?? '';
+
+ if (!key) return fail(400, { error: 'Key is required' });
+
+ const existing = await db.select({ id: siteSettings.id }).from(siteSettings).where(eq(siteSettings.key, key)).limit(1);
+ if (existing.length > 0) {
+ await db.update(siteSettings).set({ value }).where(eq(siteSettings.key, key));
+ } else {
+ await db.insert(siteSettings).values({ key, value });
+ }
+
+ await logAudit({
+ userId: locals.user!.id,
+ action: 'update_site_setting',
+ entity: 'site_setting',
+ diff: { key, value }
+ });
+
+ return { success: true };
+ },
+
+ saveConfig: async ({ request, locals }) => {
+ if (!hasPermission(locals.user!.role as Role, 'system')) error(403, 'Access denied');
+ const fd = await request.formData();
+ const key = fd.get('key')?.toString().trim() ?? '';
+ const valueStr = fd.get('value')?.toString() ?? '';
+
+ if (!key) return fail(400, { error: 'Key is required' });
+
+ let value: unknown;
+ try {
+ value = JSON.parse(valueStr);
+ } catch {
+ value = valueStr;
+ }
+
+ const existing = await db.select({ id: orgConfig.id }).from(orgConfig).where(eq(orgConfig.key, key)).limit(1);
+ if (existing.length > 0) {
+ await db.update(orgConfig).set({ value: value as Record }).where(eq(orgConfig.key, key));
+ } else {
+ await db.insert(orgConfig).values({ key, value: value as Record });
+ }
+
+ await logAudit({
+ userId: locals.user!.id,
+ action: 'update_org_config',
+ entity: 'org_config',
+ diff: { key }
+ });
+
+ return { success: true };
+ }
+};
diff --git a/src/routes/admin/system/+page.svelte b/src/routes/admin/system/+page.svelte
new file mode 100644
index 0000000..4409933
--- /dev/null
+++ b/src/routes/admin/system/+page.svelte
@@ -0,0 +1,169 @@
+
+
+System Settings — Admin
+
+
+
+
System Settings
+
+
+ {#if form?.error}
+
{form.error}
+ {/if}
+
+ {#if showSuccess}
+
+ {/if}
+
+
+
+
+
Site Settings
+
+
+
+ {#if data.settings.length === 0}
+
No settings configured.
+ {:else}
+
+ {#each data.settings as setting}
+
+
+ {setting.key}
+
+
+ {#if editingSetting === setting.key}
+
+ {:else}
+
{setting.value}
+ {/if}
+
+ {/each}
+
+ {/if}
+
+
+
+
+
Org Config
+
+ {#if data.configs.length === 0}
+
No config entries.
+ {:else}
+
+ {#each data.configs as cfg}
+
+
+ {cfg.key}
+
+
+ {#if editingConfig === cfg.key}
+
+ {:else}
+
{configDisplay(cfg.value)}
+ {/if}
+
+ {/each}
+
+ {/if}
+
+
+
+
+
+
Recent Audit Log
+ {#if data.recentAudit.length === 0}
+
No audit entries.
+ {:else}
+
+
+
+
+ | Time |
+ User |
+ Action |
+ Entity |
+
+
+
+ {#each data.recentAudit as entry}
+
+ | {formatDate(entry.createdAt)} |
+ {entry.userName ?? entry.userEmail ?? 'System'} |
+ {entry.action} |
+ {entry.entity ?? '—'}{entry.entityId ? ` #${entry.entityId}` : ''} |
+
+ {/each}
+
+
+
+ {/if}
+
+
diff --git a/src/routes/admin/traffic/+page.server.ts b/src/routes/admin/traffic/+page.server.ts
new file mode 100644
index 0000000..7bd4039
--- /dev/null
+++ b/src/routes/admin/traffic/+page.server.ts
@@ -0,0 +1,51 @@
+import type { PageServerLoad } from './$types';
+import { db } from '$lib/server/db';
+import { pageViews, pets } from '$lib/server/schema';
+import { eq, gte, desc, count, and } from 'drizzle-orm';
+import { error } from '@sveltejs/kit';
+import { hasPermission } from '$lib/roles';
+import type { Role } from '$lib/roles';
+
+export const load: PageServerLoad = async ({ locals, url }) => {
+ if (!hasPermission(locals.user!.role as Role, 'system')) error(403, 'Access denied');
+
+ const days = parseInt(url.searchParams.get('days') ?? '7', 10);
+ const since = new Date();
+ since.setDate(since.getDate() - days);
+
+ // Top paths
+ const topPaths = await db
+ .select({
+ path: pageViews.path,
+ views: count()
+ })
+ .from(pageViews)
+ .where(and(gte(pageViews.createdAt, since), eq(pageViews.isBot, false)))
+ .groupBy(pageViews.path)
+ .orderBy(desc(count()))
+ .limit(25);
+
+ // Top referrers
+ const topReferrers = await db
+ .select({
+ referrer: pageViews.referrer,
+ views: count()
+ })
+ .from(pageViews)
+ .where(and(gte(pageViews.createdAt, since), eq(pageViews.isBot, false)))
+ .groupBy(pageViews.referrer)
+ .orderBy(desc(count()))
+ .limit(10);
+
+ // Total and bot counts
+ const [totalRow] = await db.select({ total: count() }).from(pageViews).where(gte(pageViews.createdAt, since));
+ const [botRow] = await db.select({ total: count() }).from(pageViews).where(and(gte(pageViews.createdAt, since), eq(pageViews.isBot, true)));
+
+ return {
+ topPaths,
+ topReferrers: topReferrers.filter(r => r.referrer),
+ totalViews: totalRow?.total ?? 0,
+ botViews: botRow?.total ?? 0,
+ days
+ };
+};
diff --git a/src/routes/admin/traffic/+page.svelte b/src/routes/admin/traffic/+page.svelte
new file mode 100644
index 0000000..ba276aa
--- /dev/null
+++ b/src/routes/admin/traffic/+page.svelte
@@ -0,0 +1,74 @@
+
+
+Traffic — Admin
+
+
+
+
Traffic
+
+
+
+
+
+
Human Page Views
+
{(data.totalViews - data.botViews).toLocaleString()}
+
+
+
Bot Requests
+
{data.botViews.toLocaleString()}
+
+
+
+
+
+
Top Pages
+ {#if data.topPaths.length === 0}
+
No data yet.
+ {:else}
+
+ {#each data.topPaths as row}
+
+ {row.path}
+ {row.views.toLocaleString()}
+
+ {/each}
+
+ {/if}
+
+
+
+
Top Referrers
+ {#if data.topReferrers.length === 0}
+
No referrer data yet.
+ {:else}
+
+ {#each data.topReferrers as row}
+
+ {row.referrer}
+ {row.views.toLocaleString()}
+
+ {/each}
+
+ {/if}
+
+
+