TypeScript 97.6%
SQL 1.4%
JavaScript 0.5%
1"use client";23import * as React from "react";4import { useAdminQuery } from "@/components/admin/use-query";5import type { MissionsResponse } from "@/components/admin/types";6import { PageHeader, RefreshButton, Panel, ErrorState, TableSkeleton, StatGrid, StatTile } from "@/components/admin/primitives";7import { ProgressionTable, type ProgressionItem } from "@/components/admin/progression-table";8import { int, num, pct } from "@/components/admin/format";9import { cn } from "@/lib/utils";1011export default function AdminMissionsPage() {12 const q = useAdminQuery<MissionsResponse>("/api/admin/missions");13 const d = q.data;1415 const items = React.useMemo<ProgressionItem[]>(() => {16 if (!d) return [];17 const c = new Map(d.completions.map((r) => [r.mission_key, r]));18 return d.missions.map((m) => {19 const s = c.get(m.key);20 const started = num(s?.started ?? 0);21 const completed = num(s?.completed ?? 0);22 return {23 key: m.key,24 name: m.name,25 description: m.description,26 target: num(m.target),27 rewardCredits: m.rewardCredits,28 rewardXp: m.rewardXp,29 enabled: m.enabled,30 metric: m.metric,31 tag: m.period,32 stats: [33 { label: "Started", value: int(started) },34 { label: "Completed", value: int(completed) },35 { label: "Rate", value: started ? pct(completed / started, 0) : "—" },36 ],37 };38 });39 }, [d]);4041 const totals = d ? { daily: d.missions.filter((m) => m.period === "daily").length, weekly: d.missions.filter((m) => m.period === "weekly").length, enabled: d.missions.filter((m) => m.enabled).length, completed: d.completions.reduce((a, r) => a + num(r.completed), 0) } : null;4243 return (44 <>45 <PageHeader title="Missions" description="Daily and weekly missions. Edit inline, then save each row; toggles apply immediately. Completion counts cover periods active in the last 7 days." actions={<RefreshButton onClick={() => void q.refresh()} loading={q.refreshing} />} />46 {totals ? (47 <StatGrid cols={4} className="mb-4">48 <StatTile label="Daily missions" value={totals.daily} compact />49 <StatTile label="Weekly missions" value={totals.weekly} compact />50 <StatTile label="Enabled" value={`${totals.enabled} / ${totals.daily + totals.weekly}`} compact tone={totals.enabled === 0 ? "danger" : "neutral"} />51 <StatTile label="Completions · 7d" value={int(totals.completed)} compact tone="accent" />52 </StatGrid>53 ) : null}54 <Panel padded={false} className={cn(q.stale && "opacity-70")}>55 {q.error && !d ? (56 <div className="p-4">57 <ErrorState error={q.error} onRetry={() => void q.refresh()} />58 </div>59 ) : !d ? (60 <TableSkeleton rows={8} cols={8} />61 ) : (62 <ProgressionTable items={items} endpoint="/api/admin/missions" onSaved={() => void q.refresh()} statHeaders={["Started", "Completed", "Rate"]} />63 )}64 </Panel>65 </>66 );67}68