'use client'; import {proportional, pixel} from '@astryxdesign/core/Table'; import {ResponsiveTable} from '@/components/patterns/ResponsiveTable'; import type {TableColumn} from '@astryxdesign/core/Table'; import {Avatar} from '@astryxdesign/core/Avatar'; import {Badge} from '@astryxdesign/core/Badge'; import {Text} from '@astryxdesign/core/Text'; import {HStack, VStack} from '@astryxdesign/core/Layout'; import {ProgressBar} from '@astryxdesign/core/ProgressBar'; import {PanelCard} from '@/components/patterns/PanelCard'; import {SkeletonRows} from '@/components/patterns/LoadingState'; import {EmptyPanel} from '@/components/patterns/EmptyPanel'; import {ATTENDANCE} from './attendance'; import {storeName} from '@/lib/mock/stores'; import {formatCompact, formatPct} from '@/lib/format'; import type {Attendance, StaffMember} from '@/lib/api/contracts'; import type {Resource} from '@/lib/api/useResource'; interface Row extends Record { id: string; rank: number; name: string; role: string; storeId: string; attendance: Attendance; salesCount: number; rewardsIssued: number; performanceScore: number; punctualityPct: number; } /** * The whole team, ranked. * * A table because this is a one-dimensional comparison across many rows — * cards would force the eye to travel in two dimensions to answer "who is * third". Capped at the top 10: a leaderboard that lists everyone is a * roster, and the roster already exists above. */ export function Leaderboard({ resource, }: { resource: Resource; }) { const columns: TableColumn[] = [ { key: 'rank', header: '#', width: pixel(48), renderCell: (row) => ( {row.rank} ), }, { key: 'name', header: 'Team member', width: proportional(2), renderCell: (row) => ( {row.name} {row.role} · {storeName(row.storeId)} ), }, { key: 'attendance', header: 'Today', width: pixel(112), renderCell: (row) => ( ), }, { key: 'salesCount', header: 'Sales', width: pixel(88), align: 'end', renderCell: (row) => ( {formatCompact(row.salesCount)} ), }, { key: 'rewardsIssued', header: 'Rewards', width: pixel(96), align: 'end', renderCell: (row) => ( {formatCompact(row.rewardsIssued)} ), }, { key: 'punctualityPct', header: 'Punctuality', width: pixel(112), align: 'end', renderCell: (row) => ( {formatPct(row.punctualityPct, 0)} ), }, { key: 'performanceScore', header: 'Score', // 140px was too narrow for a bar plus a 2-digit number side by side — // the value wrapped under the bar and read as two separate figures. width: pixel(180), renderCell: (row) => ( {row.performanceScore} ), }, ]; return ( } empty={ } > {(staff) => { const rows: Row[] = [...staff] .sort((a, b) => b.performanceScore - a.performanceScore) .slice(0, 10) .map((m, i) => ({ id: m.id, rank: i + 1, name: m.name, role: m.role, storeId: m.storeId, attendance: m.attendance, salesCount: m.salesCount, rewardsIssued: m.rewardsIssued, performanceScore: m.performanceScore, punctualityPct: m.punctualityPct, })); return ( ); }} ); }