Last resource lands. /admin/activity tails the activity table — votes, RSVPs, office-hour bookings, roadmap ships, pulse opens — with last-7-days / last-30-days filters. Pure read view: no form, no summary, no ops. src/admin/components/ResourceListView.astro: rows fall back to <div> when the resource has no panel pathway (form: null AND no summary). Activity rows aren't clickable now — previously they'd dirty the URL with a ?edit= that resolved to nothing. The registry is complete: 7 resources across 3 groups, matching the sidebar layout described in the spec. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
102 lines
2.8 KiB
TypeScript
102 lines
2.8 KiB
TypeScript
/* ---------------------------------------------------------------------------
|
|
* Activity resource — read-only debug feed.
|
|
*
|
|
* Activity rows are emitted by side effects elsewhere in the app (voting,
|
|
* RSVPs, roadmap-ship transitions, pulse opens). The admin view is a tail
|
|
* of the table for monitoring; no create, no edit, no delete.
|
|
* ------------------------------------------------------------------------- */
|
|
|
|
import {
|
|
getAllActivityForAdmin,
|
|
type ActivityKind,
|
|
type ActivityRow,
|
|
} from '../../lib/db';
|
|
import type { Resource } from '../resource-types';
|
|
|
|
const KIND_LABEL: Record<ActivityKind, string> = {
|
|
voted: 'Voted',
|
|
rsvped: 'RSVPed',
|
|
booked_office_hours: 'Booked office hours',
|
|
roadmap_shipped: 'Roadmap shipped',
|
|
pulse_opened: 'Pulse opened',
|
|
};
|
|
|
|
const KIND_PILL_CLASS: Record<ActivityKind, string> = {
|
|
voted: 'pill-update',
|
|
rsvped: 'pill-published',
|
|
booked_office_hours: 'pill-bts',
|
|
roadmap_shipped: 'pill-shipping',
|
|
pulse_opened: 'pill-pending',
|
|
};
|
|
|
|
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
|
|
export const activityResource: Resource<ActivityRow> = {
|
|
key: 'activity',
|
|
label: 'Activity',
|
|
pluralLabel: 'Activity',
|
|
singularLabel: 'Event',
|
|
groupKey: 'system',
|
|
description: 'Recent member actions: votes, RSVPs, office-hour bookings, pulse opens, roadmap ships.',
|
|
|
|
list: {
|
|
queryFn: () => getAllActivityForAdmin(200),
|
|
columns: [
|
|
{
|
|
key: 'actor_name',
|
|
label: 'Actor',
|
|
primary: true,
|
|
width: '1.5fr',
|
|
render: (item) => ({
|
|
title: item.actor_name,
|
|
subtitle: item.actor_role,
|
|
}),
|
|
},
|
|
{
|
|
key: 'kind',
|
|
label: 'Kind',
|
|
kind: 'pill',
|
|
width: '160px',
|
|
pillVariants: Object.fromEntries(
|
|
(Object.keys(KIND_LABEL) as ActivityKind[]).map((k) => [
|
|
k,
|
|
{ label: KIND_LABEL[k], class: KIND_PILL_CLASS[k] },
|
|
]),
|
|
),
|
|
},
|
|
{
|
|
key: 'subject',
|
|
label: 'Subject',
|
|
width: '1fr',
|
|
render: (item) => ({
|
|
title: `${item.subject_type} #${item.subject_id}`,
|
|
}),
|
|
},
|
|
{
|
|
key: 'created_at',
|
|
label: 'When',
|
|
kind: 'relative-date',
|
|
width: '120px',
|
|
},
|
|
],
|
|
filters: [
|
|
{ key: 'all', label: 'All', predicate: () => true, isDefault: true },
|
|
{
|
|
key: 'last_7_days',
|
|
label: 'Last 7 days',
|
|
predicate: (i) => Date.now() - new Date(i.created_at).getTime() < 7 * DAY_MS,
|
|
},
|
|
{
|
|
key: 'last_30_days',
|
|
label: 'Last 30 days',
|
|
predicate: (i) => Date.now() - new Date(i.created_at).getTime() < 30 * DAY_MS,
|
|
},
|
|
],
|
|
defaultSort: { key: 'created_at', direction: 'desc' },
|
|
pageSize: 100,
|
|
},
|
|
|
|
// Pure read view — no form, no summary, no ops, no actions.
|
|
form: null,
|
|
ops: {},
|
|
};
|