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/Home.tsx

75 lines
4.2 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 { Link } from 'react-router-dom'
import {
BellRing, Boxes, CalendarClock, Files, Filter, LayoutDashboard, MessageSquare, Users, Wrench,
type LucideIcon,
} from 'lucide-react'
import { displayName, getDashboard, getPipeline, getSmsBalances, getTickets } from '../api'
import { useData } from './Clients'
type Tone = 'warn' | 'err'
interface Stat { text: string; tone?: Tone }
interface HubCard { to: string; label: string; desc: string; icon: LucideIcon; stat?: Stat }
/**
* Home hub (D30) — a quick-access launcher for the sections used day to day. Not
* every screen (the sidebar has those); just the main ones, each a big target with
* a live count so the landing doubles as a status board.
*/
export function Home() {
const name = displayName()
const dash = useData(() => getDashboard(false), [])
const sms = useData(getSmsBalances, [])
const tickets = useData(() => getTickets({ status: 'open' }), [])
const pipeline = useData(() => getPipeline({ filter: 'all' }), [])
const d = dash.data
const remStat: Stat | undefined = d === undefined ? undefined
: d.totals.failed > 0 ? { text: `${d.totals.failed} failed · ${d.totals.queued} queued`, tone: 'err' }
: { text: `${d.totals.queued} queued` }
const renewStat: Stat | undefined = d !== undefined && d.renewalsThisMonth.length > 0
? { text: `${d.renewalsThisMonth.length} this month`, tone: 'warn' } : undefined
const smsStat: Stat | undefined = sms.data !== undefined && sms.data.lowCount > 0
? { text: `${sms.data.lowCount} low`, tone: 'warn' } : undefined
const ticketStat: Stat | undefined = tickets.data !== undefined && tickets.data.total > 0
? { text: `${tickets.data.total} open` } : undefined
const pipeStat: Stat | undefined = pipeline.data !== undefined && pipeline.data.total > 0
? { text: `${pipeline.data.total} open` } : undefined
// Main, look-at-it-all-the-time sections only — the rest live in the sidebar.
const cards: HubCard[] = [
{ to: '/dashboard', label: 'Dashboard', desc: 'Todays money — dues, renewals, follow-ups, the send queue.', icon: LayoutDashboard, stat: { text: 'My Day' } },
{ to: '/clients', label: 'Clients', desc: 'The client book — profiles, modules, ledger, support.', icon: Users },
{ to: '/documents', label: 'Documents', desc: 'Quotations, proformas, invoices, credit notes.', icon: Files },
{ to: '/reminders', label: 'Reminders', desc: 'The email send queue — preview, send, dismiss.', icon: BellRing, ...(remStat !== undefined ? { stat: remStat } : {}) },
{ to: '/renewals', label: 'Renewals', desc: 'AMC and module renewals — one-click renewal quotes.', icon: CalendarClock, ...(renewStat !== undefined ? { stat: renewStat } : {}) },
{ to: '/pipeline', label: 'Pipeline', desc: 'Leads and quotes in flight, with next-action nudges.', icon: Filter, ...(pipeStat !== undefined ? { stat: pipeStat } : {}) },
{ to: '/tickets', label: 'Tickets', desc: 'Support desk with SLA aging and overdue flags.', icon: Wrench, ...(ticketStat !== undefined ? { stat: ticketStat } : {}) },
{ to: '/sms-balances', label: 'SMS credits', desc: 'Client SMS balances and top-ups.', icon: MessageSquare, ...(smsStat !== undefined ? { stat: smsStat } : {}) },
{ to: '/modules', label: 'Modules', desc: 'Sellable modules with the dated price book.', icon: Boxes },
]
return (
<div className="wf-page">
<div className="hub-head">
<h1>Welcome back{name !== '' ? `, ${name}` : ''}</h1>
<p>Your most-used areas everything else is in the sidebar.</p>
</div>
<div className="hub-cards">
{cards.map((c) => (
<Link key={c.to} to={c.to} className="hub-card">
<span className="hub-ic"><c.icon size={18} strokeWidth={1.9} aria-hidden /></span>
<span className="hub-body">
<span className="hub-t">{c.label}</span>
<span className="hub-d">{c.desc}</span>
{c.stat !== undefined && (
<span className={`hub-stat${c.stat.tone !== undefined ? ` ${c.stat.tone}` : ''}`}>{c.stat.text}</span>
)}
</span>
<span className="hub-arrow" aria-hidden></span>
</Link>
))}
</div>
</div>
)
}