From 8cba49618eece1dfa56cc19bb69ccd516cb0b18a Mon Sep 17 00:00:00 2001 From: RaineAllDay Date: Wed, 18 Mar 2026 20:21:13 -0600 Subject: [PATCH] admin ux updates --- src/lib/server/db.js | 22 +++++++++++++++++----- src/routes/admin/+page.server.js | 7 ++++++- src/routes/admin/+page.svelte | 28 +++++++++++++++++++--------- 3 files changed, 42 insertions(+), 15 deletions(-) diff --git a/src/lib/server/db.js b/src/lib/server/db.js index 94b6d62..51b30e9 100644 --- a/src/lib/server/db.js +++ b/src/lib/server/db.js @@ -425,19 +425,31 @@ export function getUnreadMessageCount() { return db.prepare(`SELECT COUNT(*) as count FROM contact_messages WHERE read = 0`).get().count; } -export function listRecentPersonalitiesAdmin({ page = 1, limit = 25, q = '' } = {}) { +const ADMIN_SORT_COLS = { + 'Fixture': 'name', + 'Manufacturer': 'manufacturer', + 'Views': 'view_count', + 'Ch': 'channel_count', + 'By': 'creator_handle', + 'Published': 'created_at', + 'Status': 'deleted_at', +}; + +export function listRecentPersonalitiesAdmin({ page = 1, limit = 25, q = '', sort = '', dir = 'asc' } = {}) { const db = getDb(); const offset = (page - 1) * limit; + const col = ADMIN_SORT_COLS[sort] ?? 'created_at'; + const direction = sort ? (dir === 'desc' ? 'DESC' : 'ASC') : 'DESC'; if (q.trim()) { const ftsQuery = q.trim().split(/\s+/).map(t => `"${t.replace(/"/g, '')}"`).join(' OR '); const rows = db.prepare(` - SELECT p.id, p.name, p.manufacturer, p.channel_count, p.created_at, + SELECT p.id, p.name, p.prs_name, p.manufacturer, p.channel_count, p.created_at, p.creator_handle, p.view_count, p.deleted_at FROM personalities_fts fts JOIN personalities p ON p.rowid = fts.rowid WHERE personalities_fts MATCH ? - ORDER BY p.created_at DESC LIMIT ? OFFSET ? + ORDER BY p.${col} ${direction} LIMIT ? OFFSET ? `).all(ftsQuery, limit, offset); const { total } = db.prepare(` SELECT COUNT(*) as total @@ -448,9 +460,9 @@ export function listRecentPersonalitiesAdmin({ page = 1, limit = 25, q = '' } = } const rows = db.prepare(` - SELECT id, name, manufacturer, channel_count, created_at, + SELECT id, name, prs_name, manufacturer, channel_count, created_at, creator_handle, view_count, deleted_at - FROM personalities ORDER BY created_at DESC LIMIT ? OFFSET ? + FROM personalities ORDER BY ${col} ${direction} LIMIT ? OFFSET ? `).all(limit, offset); const { total } = db.prepare(`SELECT COUNT(*) as total FROM personalities`).get(); return { rows, total }; diff --git a/src/routes/admin/+page.server.js b/src/routes/admin/+page.server.js index d926542..a8d33c4 100644 --- a/src/routes/admin/+page.server.js +++ b/src/routes/admin/+page.server.js @@ -11,6 +11,11 @@ export async function load({ url }) { const adminQ = (url.searchParams.get('q') ?? '').slice(0, 200); const adminPage = Math.max(1, parseInt(url.searchParams.get('page') ?? '1')); + const VALID_SORT_COLS = ['Fixture','Manufacturer','Views','Ch','By','Published','Status']; + const rawSort = url.searchParams.get('sort') ?? ''; + const adminSort = VALID_SORT_COLS.includes(rawSort) ? rawSort : ''; + const adminDir = url.searchParams.get('dir') === 'desc' ? 'desc' : 'asc'; + const stats = getAdminStats(); const views = getViewStats(); @@ -20,7 +25,7 @@ export async function load({ url }) { const messages = listContactMessages({ unreadOnly: messageFilter === 'unread' }); const { rows: personalities, total: totalPersonalities } = listRecentPersonalitiesAdmin({ - page: adminPage, limit: 25, q: adminQ + page: adminPage, limit: 25, q: adminQ, sort: adminSort, dir: adminDir }); return { diff --git a/src/routes/admin/+page.svelte b/src/routes/admin/+page.svelte index 7f97468..cf02d6f 100644 --- a/src/routes/admin/+page.svelte +++ b/src/routes/admin/+page.svelte @@ -9,6 +9,7 @@ let dismissing = {}; let markingRead = {}; let adminSearch = data.adminQ; + let personalitiesPanel; let searchTimer; // Edit state @@ -130,13 +131,13 @@ } } - function navigate(params) { + function navigate(params, options = {}) { const u = new URL($page.url); for (const [k, v] of Object.entries(params)) { if (v) u.searchParams.set(k, v); else u.searchParams.delete(k); } - goto(u.toString(), { keepFocus: true }); + return goto(u.toString(), { keepFocus: true, ...options }); } function onAdminSearch() { @@ -189,6 +190,8 @@ } $: viewMax = Math.max(...(data.views?.chart ?? []).map(d => d.visitors), 1); + $: sortCol = $page.url.searchParams.get('sort') ?? ''; + $: sortDir = $page.url.searchParams.get('dir') ?? 'asc'; const reasonLabels = { 'incorrect-data': 'Incorrect data', @@ -294,7 +297,7 @@ {#each [['unread','Unread'],['all','All']] as [val, label]} {/each} @@ -381,7 +384,7 @@ {#each [['open','Open'],['dismissed','Dismissed'],['all','All']] as [val, label]} {/each} @@ -453,7 +456,7 @@ -
+
- {#each ['Fixture','Manufacturer','Ch','By','Published','Status',''] as h} + {#each ['Fixture','Manufacturer','Views','Ch','By','Published','Status',''] as h} - {h} + color:{sortCol === h ? 'var(--text)' : 'var(--text2)'}; border-bottom:1px solid var(--border); white-space:nowrap; + {h ? 'cursor:pointer; user-select:none;' : ''}" + on:click={() => { + if (!h) return; + const newDir = sortCol === h && sortDir === 'asc' ? 'desc' : 'asc'; + navigate({ sort: h, dir: newDir }, { noScroll: true }); + }}> + {h}{#if sortCol === h} {sortDir === 'asc' ? '↑' : '↓'}{/if} {/each} @@ -502,6 +511,7 @@ {/if} {p.manufacturer || '—'} + {p.view_count} {p.channel_count} {p.creator_handle || '—'} {formatDate(p.created_at)} @@ -697,7 +707,7 @@ {#each Array.from({length: data.totalPages}, (_, i) => i+1) as p} {/each}