import PropTypes from 'prop-types'; import { Grid } from '@astryxdesign/core/Grid'; import { getStatusMeta } from 'themes/dt/status'; import SegmentCard from './SegmentCard'; // ==============================|| STATUS TABS ||============================== // // The top-level status filter on every list page. // // // // Each entry's `status` resolves through the shared registry for its label, // icon, colour and hint, so a page supplies only the ORDER of the bands and // where each count comes from. Pass `label` on an entry to override the // registry wording. // // --------------------------------------------------------------------------- // Cards, not a tab strip // --------------------------------------------------------------------------- // This was a row of Astryx s with a count badge. It is now a row of // s: same props, same behaviour, same registry — only the // rendering changed, so no page file needed touching. // // The strip was doing three jobs with one line of text: naming the band, // carrying its count, and being the control that selects it. As cards each of // those gets its own place — the name, a hint at what the band MEANS, the // count as the largest thing, and the band's share of the total. The selected // band is now legible from across the room by its coloured border rather than // from a 2px underline. // // The cost is honest: a card row is taller than a tab strip. That is why the // count is the dominant element — the row has to earn the height by being // readable at a glance, not just clickable. // // `size` and `hasDivider` are accepted and ignored. They were part of the // TabList API and several pages still pass them; silently accepting them // keeps those call sites working rather than spreading unknown props onto a // DOM node (see this folder's CLAUDE.md §7 on prop-signature changes). export default function StatusTabs({ value, onChange, tabs, hideHint }) { // The pill under each count is the band's share of everything shown. It is // computed from the tabs themselves rather than taken as a prop, so it can // never disagree with the numbers printed above it. const total = tabs.reduce((sum, tab) => sum + (Number(tab.count) || 0), 0); return ( // width="100%" is load-bearing. DataCard renders this slot inside a VStack, // which aligns its children to the start rather than stretching them, so // without it the Grid shrinks to a single minWidth column and all eight // bands stack vertically in a container that had room for six across. {tabs.map((tab) => { const meta = getStatusMeta(tab.status); const Icon = meta.icon; const count = Number(tab.count) || 0; // No share line when there is nothing to take a share of — "0% of 0" // is noise, and an empty band should simply say nothing. const share = total > 0 ? `${Math.round((count / total) * 100)}% of ${total}` : null; return ( } color={meta.color} isSelected={value === tab.status} onClick={() => onChange?.(tab.status)} /> ); })} ); } StatusTabs.propTypes = { value: PropTypes.string, onChange: PropTypes.func, tabs: PropTypes.arrayOf( PropTypes.shape({ status: PropTypes.string.isRequired, label: PropTypes.node, count: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), isUrgent: PropTypes.bool }) ).isRequired, /** Accepted for call-site compatibility with the previous TabList API. */ size: PropTypes.oneOf(['sm', 'md', 'lg']), /** Accepted for call-site compatibility with the previous TabList API. */ hasDivider: PropTypes.bool, /** If true, hides the hint text (meta.hint) on the cards. */ hideHint: PropTypes.bool };