spb/countryatlas
Public
TypeScript 57%
Python 38.6%
JavaScript 3.6%
CSS 0.6%
1import Link from 'next/link';2import { t } from '@/i18n';3import { adminApi, adminErrorMessage } from '@/lib/admin-api';4import { grouped } from '@/lib/format';5import { routes } from '@/lib/site';6import { AdminSection, AdminTable, Pill, statusTone } from '@/components/admin/admin-ui';78export const dynamic = 'force-dynamic';9const SEVERITIES = ['error', 'warning', 'info'];1011export default async function AdminIssuesPage({ searchParams }: { searchParams: Promise<{ severity?: string; connector?: string; indicator?: string; code?: string; run_id?: string }> }) {12 const sp = await searchParams;13 try {14 const r = await adminApi.issues({ severity: sp.severity, connector: sp.connector, indicator: sp.indicator, code: sp.code, run_id: sp.run_id, limit: 300 });15 const link = (patch: Record<string, string | undefined>) => {16 const p = new URLSearchParams();17 for (const [k, v] of Object.entries({ ...sp, ...patch })) if (v) p.set(k, v);18 const s = p.toString();19 return `${routes.admin('issues')}${s ? `?${s}` : ''}`;20 };21 return (22 <>23 <AdminSection24 title={t('admin.issues.title')}25 sub={t('admin.issues.sub', { n: grouped(r.n) })}26 actions={27 <div className="flex flex-wrap gap-1 text-xs">28 <Link href={link({ severity: undefined })} className={`inline-flex h-8 items-center rounded-sm px-2 ${!sp.severity ? 'bg-ink text-paper' : 'text-ink-2 hover:bg-surface-2'}`}>29 {t('admin.issues.all')}30 </Link>31 {SEVERITIES.map((s) => (32 <Link key={s} href={link({ severity: s })} className={`inline-flex h-8 items-center rounded-sm px-2 ${sp.severity === s ? 'bg-ink text-paper' : 'text-ink-2 hover:bg-surface-2'}`}>33 {s}34 </Link>35 ))}36 </div>37 }38 >39 {r.summary.length ? (40 <ul className="mb-3 flex flex-wrap gap-1.5 text-xs">41 {r.summary.map((s) => (42 <li key={`${s.severity}-${s.code}`}>43 <Link href={link({ severity: s.severity, code: s.code })} className="inline-flex h-8 items-center gap-1.5 rounded-sm border border-rule px-2 hover:bg-surface-2">44 <Pill tone={statusTone(s.severity)}>{s.severity}</Pill>45 <span className="font-mono">{s.code}</span>46 <span className="tnum text-ink-3">{grouped(s.n)}</span>47 </Link>48 </li>49 ))}50 </ul>51 ) : null}52 <AdminTable53 rows={r.items}54 rowKey={(x, i) => `${x.run_id}-${x.indicator_id}-${x.country_id}-${i}`}55 empty={t('admin.issues.none')}56 columns={[57 { key: 'sev', header: t('admin.issues.severity'), cell: (x) => <Pill tone={statusTone(x.severity)}>{x.severity ?? t('admin.na')}</Pill> },58 { key: 'code', header: t('admin.issues.code'), cell: (x) => <span className="font-mono">{x.code}</span> },59 { key: 'c', header: t('admin.issues.connector'), cell: (x) => x.connector },60 { key: 'i', header: t('admin.issues.indicator'), cell: (x) => (x.indicator_id ? <Link href={routes.indicator(x.indicator_id)} className="font-mono text-accent hover:underline">{x.indicator_id}</Link> : t('admin.na')) },61 { key: 'cty', header: t('admin.issues.country'), cell: (x) => x.country_id ?? t('admin.na') },62 { key: 'p', header: t('admin.issues.period'), cell: (x) => x.period ?? t('admin.na') },63 { key: 'v', header: t('admin.issues.value'), numeric: true, cell: (x) => (x.value != null ? String(x.value) : t('admin.na')) },64 { key: 'm', header: t('admin.issues.message'), cell: (x) => <span className="text-ink-2">{x.message ?? ''}</span>, className: 'max-w-[28rem]' },65 ]}66 />67 </AdminSection>68 </>69 );70 } catch (e) {71 return <p className="mt-3 rounded-sm border border-down/40 px-3 py-2 text-xs text-down">{adminErrorMessage(e)}</p>;72 }73}74