You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
sims-hq/apps/hq-web/src/pages/Reminders.tsx

121 lines
4.9 KiB
TypeScript

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import { useState } from 'react'
import { Link } from 'react-router-dom'
import { Badge, DataTable, EmptyState, ErrorState, FilterChips, Notice, PageHeader, Skeleton, Toolbar } from '@sims/ui'
import { getReminders, isManagerial, staffId, type ReminderStatus } from '../api'
import { QueueActions, RULE_LABEL, reminderTone } from '../components/ReminderQueue'
import { useData } from './Clients'
const PAGE_SIZE = 50
const STATUS_CHIPS: { key: ReminderStatus; label: string }[] = [
{ key: 'queued', label: 'Queued' }, { key: 'sent', label: 'Sent' },
{ key: 'failed', label: 'Failed' }, { key: 'dismissed', label: 'Dismissed' },
]
/**
* Reminders page (go-live cluster WS-C): the full queue the dashboard card is a
* slice of. Status chips map onto `?status=`; the Mine/All toggle (`?owner=`) only
* renders for owner/manager — staff are server-forced to their own rows anyway.
* Server-paginated with an honest total (rule 8).
*/
export function Reminders() {
const [status, setStatus] = useState<ReminderStatus>('queued')
const [scope, setScope] = useState<'all' | 'mine'>('all')
const [page, setPage] = useState(1)
const [err, setErr] = useState('')
const me = staffId()
// Mine narrows via ?owner=<my id>; hidden for pre-existing sessions that never stored the id.
const showScope = isManagerial() && me !== ''
const list = useData(
() => getReminders({
status,
...(showScope && scope === 'mine' ? { owner: me } : {}),
page, pageSize: PAGE_SIZE,
}),
[status, scope, page],
)
const data = list.data
const rows = data?.reminders
const totalPages = data !== undefined ? Math.max(1, Math.ceil(data.total / data.pageSize)) : 1
const from = data !== undefined && data.total > 0 ? (data.page - 1) * data.pageSize + 1 : 0
const to = data !== undefined ? Math.min(data.page * data.pageSize, data.total) : 0
return (
<div className="wf-page">
<PageHeader
title="Reminders"
desc="Every reminder the scanner has raised — send, preview or dismiss the open ones; sent and dismissed stay on record."
/>
<Toolbar>
<FilterChips
chips={STATUS_CHIPS}
active={status}
onChange={(k) => { setStatus(k as ReminderStatus); setPage(1) }}
/>
{showScope && (
<FilterChips
chips={[{ key: 'all', label: 'All' }, { key: 'mine', label: 'Mine' }]}
active={scope}
onChange={(k) => { setScope(k as 'all' | 'mine'); setPage(1) }}
/>
)}
<span style={{ flex: 1 }} />
{data !== undefined && <Badge>{data.total} total</Badge>}
</Toolbar>
{err !== '' && <Notice tone="err">{err}</Notice>}
{list.error !== undefined && <ErrorState message={list.error} onRetry={list.reload} />}
{list.error !== undefined ? null
: rows === undefined ? <Skeleton rows={6} />
: rows.length === 0 ? <EmptyState>No {status} reminders{scope === 'mine' ? ' of yours' : ''}.</EmptyState> : (
<>
<DataTable
columns={[
{ key: 'kind', label: 'Kind' },
{ key: 'client', label: 'Client' },
{ key: 'doc', label: 'Document', mono: true },
{ key: 'due', label: 'Due' },
{ key: 'status', label: 'Status' },
{ key: 'error', label: 'Error' },
{ key: 'act', label: '' },
]}
rowTone={(i) => (rows[i]!.status === 'failed' ? 'err' : undefined)}
rows={rows.map((rem) => ({
kind: rem.label !== '' ? rem.label : RULE_LABEL[rem.ruleKind] ?? rem.ruleKind,
client: <Link to={`/clients/${rem.clientId}`}>{rem.clientName}</Link>,
doc: rem.docId !== null
? <Link to={`/documents/${rem.docId}`}>{rem.docNo ?? '— draft —'}</Link>
: '—',
due: rem.duePeriod,
status: <Badge tone={reminderTone(rem.status)}>{rem.status}</Badge>,
error: rem.error ?? '—',
// Sent/dismissed rows are history — actions only while there is work to do.
act: rem.status === 'queued' || rem.status === 'failed'
? <QueueActions rem={rem} onDone={list.reload} onError={setErr} />
: null,
}))}
/>
<Toolbar>
<Badge>Showing {from}{to} of {data!.total}</Badge>
<span style={{ flex: 1 }} />
<button
type="button" className="wf" disabled={data!.page <= 1}
onClick={() => setPage((p) => Math.max(1, p - 1))}
>
Prev
</button>
<span style={{ fontSize: 12, opacity: 0.7 }}>Page {data!.page} / {totalPages}</span>
<button
type="button" className="wf" disabled={data!.page >= totalPages}
onClick={() => setPage((p) => p + 1)}
>
Next
</button>
</Toolbar>
</>
)}
</div>
)
}