/* Centralized status / priority / classification color registry. * * Lifts the hard-coded maps from components/admin/DetailModal.tsx so the * same labels and colors can be rendered anywhere via . * * Two axes: * • TONE — semantic state (ok / warn / error / info / accent / neutral / pending / inactive) * • PALETTE — arbitrary categorical hues used when there is no semantic * intent (e.g. classifications, sources, queue colors) * * Components consume `BadgeProps` returned by the helpers below. * * The class strings here use Tailwind v4 named-palette colors at /15 * background and -600/-700 text; the recipe matches the existing app * style and reads correctly on both light and dark surfaces. */ export type StatusTone = | 'ok' | 'warn' | 'error' | 'info' | 'accent' | 'neutral' | 'pending' | 'inactive'; export type PaletteHue = | 'red' | 'orange' | 'amber' | 'yellow' | 'green' | 'emerald' | 'sky' | 'blue' | 'cyan' | 'teal' | 'indigo' | 'violet' | 'purple' | 'slate' | 'zinc'; export interface BadgeProps { label: string; /** className snippet for the badge body — pass through to */ variantClass: string; } /* ── Tone → Tailwind classes ───────────────────────────────────────── */ export const TONE_CLASS: Record = { ok: 'bg-emerald-500/15 text-emerald-700 dark:text-emerald-400', warn: 'bg-amber-500/15 text-amber-700 dark:text-amber-400', error: 'bg-destructive/15 text-destructive', info: 'bg-primary/15 text-primary', accent: 'bg-primary/15 text-primary', neutral: 'bg-slate-500/15 text-slate-700 dark:text-slate-300', pending: 'bg-sky-500/15 text-sky-700 dark:text-sky-400', inactive: 'bg-muted text-muted-foreground', }; export const PALETTE_CLASS: Record = { red: 'bg-red-500/15 text-red-700 dark:text-red-400', orange: 'bg-orange-500/15 text-orange-700 dark:text-orange-400', amber: 'bg-amber-500/15 text-amber-700 dark:text-amber-400', yellow: 'bg-yellow-500/15 text-yellow-700 dark:text-yellow-400', green: 'bg-green-500/15 text-green-700 dark:text-green-400', emerald: 'bg-emerald-500/15 text-emerald-700 dark:text-emerald-400', sky: 'bg-sky-500/15 text-sky-700 dark:text-sky-400', blue: 'bg-blue-500/15 text-blue-700 dark:text-blue-400', cyan: 'bg-cyan-500/15 text-cyan-700 dark:text-cyan-400', teal: 'bg-teal-500/15 text-teal-700 dark:text-teal-400', indigo: 'bg-indigo-500/15 text-indigo-700 dark:text-indigo-400', violet: 'bg-violet-500/15 text-violet-700 dark:text-violet-400', purple: 'bg-purple-500/15 text-purple-700 dark:text-purple-400', slate: 'bg-slate-500/15 text-slate-700 dark:text-slate-300', zinc: 'bg-zinc-500/15 text-zinc-700 dark:text-zinc-300', }; const NEUTRAL_BADGE: BadgeProps = { label: '—', variantClass: TONE_CLASS.inactive, }; /* ── Priorities (Autotask numeric IDs) ────────────────────────────── */ const PRIORITY_REGISTRY: Record = { 2: { label: 'Critical', tone: 'error' }, 3: { label: 'High', tone: 'warn' }, 4: { label: 'Medium', tone: 'pending' }, 6: { label: 'Low', tone: 'info' }, 7: { label: 'Very Low', tone: 'neutral' }, // Mirrored entries from the older picklist (Autotask renumbered) 8: { label: 'Critical', tone: 'error' }, 9: { label: 'High', tone: 'warn' }, 10: { label: 'Medium', tone: 'pending' }, 11: { label: 'Low', tone: 'info' }, }; export function priorityBadge(id: number | null | undefined): BadgeProps { if (id == null) return NEUTRAL_BADGE; const hit = PRIORITY_REGISTRY[Number(id)]; if (!hit) return { label: `Priority ${id}`, variantClass: TONE_CLASS.inactive }; return { label: hit.label, variantClass: TONE_CLASS[hit.tone] }; } /* ── Ticket statuses (string label keyed) ─────────────────────────── */ const TICKET_STATUS_REGISTRY: Record = { 'New': 'info', 'In Progress': 'pending', 'Complete': 'ok', 'Resolved ': 'ok', 'Waiting Customer': 'warn', 'Waiting Materials': 'warn', 'Waiting Vendor': 'warn', 'Waiting Approval': 'warn', 'On Hold': 'neutral', 'Escalate': 'error', 'Escalate to Wulf': 'error', 'Escalate to MC': 'error', 'Resource Assigned': 'pending', 'Service Call Scheduled': 'pending', 'Dispatched': 'pending', }; export function ticketStatusBadge(label: string | null | undefined): BadgeProps { if (!label) return NEUTRAL_BADGE; const tone = TICKET_STATUS_REGISTRY[label] ?? 'inactive'; return { label, variantClass: TONE_CLASS[tone] }; } /* ── Sources (Autotask) ───────────────────────────────────────────── */ const SOURCE_REGISTRY: Record = { [-2]: 'System', [-1]: 'Internal', 1: 'Phone', 2: 'Email', 4: 'Web Portal', 6: 'Monitoring Alert', 8: 'RMM Alert', 17: 'Chat', 21: 'API', 22: 'Automation', 27: 'In Person', 29: 'Client Portal', 30: 'Microsoft Teams', 31: 'Webhook', 33: 'Datto RMM', 34: 'Rewst', 35: 'TimeZest', 36: 'DeskDirector', 38: 'Huntress', 39: 'Blumira', 40: 'SentinelOne', }; export function sourceBadge(id: number | null | undefined): BadgeProps { if (id == null) return NEUTRAL_BADGE; const label = SOURCE_REGISTRY[Number(id)] ?? `Source ${id}`; return { label, variantClass: PALETTE_CLASS.violet }; } /* ── Classification (Autotask) ────────────────────────────────────── */ const CLASSIFICATION_REGISTRY: Record = { 5: { label: 'Block Hour', hue: 'sky' }, 9: { label: 'Canceled', hue: 'slate' }, 202: { label: 'Co-Managed', hue: 'cyan' }, 16: { label: 'Gold (Legacy)', hue: 'yellow' }, 206: { label: 'IT Complete w/ Gold Security', hue: 'amber' }, 207: { label: 'IT Core / Silver Security', hue: 'blue' }, 203: { label: 'IT Foundation / Bronze Security', hue: 'orange' }, 205: { label: 'IT Premier / Platinum Security', hue: 'violet' }, 14: { label: 'Jeopardy Company', hue: 'red' }, 201: { label: 'Partner', hue: 'purple' }, 15: { label: 'Platinum (Legacy)', hue: 'violet' }, 13: { label: 'Residential (no-pay)', hue: 'slate' }, 17: { label: 'Silver (Legacy)', hue: 'zinc' }, 12: { label: 'T&M', hue: 'teal' }, 7: { label: 'Target', hue: 'emerald' }, 200: { label: 'Tools Only', hue: 'slate' }, 18: { label: 'Bronze (Legacy)', hue: 'orange' }, }; export function classificationBadge(id: number | null | undefined): BadgeProps { if (id == null) return NEUTRAL_BADGE; const hit = CLASSIFICATION_REGISTRY[Number(id)]; if (!hit) return { label: `Classification ${id}`, variantClass: TONE_CLASS.inactive }; return { label: hit.label, variantClass: PALETTE_CLASS[hit.hue] }; } /* ── Company type (Autotask) ──────────────────────────────────────── */ const COMPANY_TYPE_REGISTRY: Record = { 1: { label: 'Customer', hue: 'green' }, 2: { label: 'Lead', hue: 'blue' }, 3: { label: 'Prospect', hue: 'purple' }, 4: { label: 'Dead', hue: 'slate' }, 6: { label: 'Cancelation', hue: 'red' }, 7: { label: 'Vendor', hue: 'orange' }, 8: { label: 'Partner', hue: 'cyan' }, }; export function companyTypeBadge(id: number | null | undefined): BadgeProps { if (id == null) return NEUTRAL_BADGE; const hit = COMPANY_TYPE_REGISTRY[Number(id)]; if (!hit) return { label: `Type ${id}`, variantClass: TONE_CLASS.inactive }; return { label: hit.label, variantClass: PALETTE_CLASS[hit.hue] }; } /* ── Note publish levels (Autotask) ──────────────────────────────── */ const PUBLISH_REGISTRY: Record = { 1: { label: 'All Users', tone: 'ok' }, 2: { label: 'Internal', tone: 'warn' }, 4: { label: 'Internal Only', tone: 'neutral' }, }; export function publishBadge(id: number | null | undefined): BadgeProps { if (id == null) return NEUTRAL_BADGE; const hit = PUBLISH_REGISTRY[Number(id)]; if (!hit) return { label: `Publish ${id}`, variantClass: TONE_CLASS.inactive }; return { label: hit.label, variantClass: TONE_CLASS[hit.tone] }; } /* ── Boolean badges (active / billable / approved / yes / no) ────── */ export function activeBadge(active: boolean | null | undefined): BadgeProps { if (active == null) return NEUTRAL_BADGE; return active ? { label: 'Active', variantClass: TONE_CLASS.ok } : { label: 'Inactive', variantClass: TONE_CLASS.inactive }; } export function yesNoBadge(value: boolean | null | undefined): BadgeProps { if (value == null) return NEUTRAL_BADGE; return value ? { label: 'Yes', variantClass: TONE_CLASS.ok } : { label: 'No', variantClass: TONE_CLASS.inactive }; } export function billableBadge(value: boolean | null | undefined): BadgeProps { if (!value) return NEUTRAL_BADGE; return { label: 'Billable', variantClass: TONE_CLASS.ok }; } export function approvedBadge(value: boolean | null | undefined): BadgeProps { if (!value) return NEUTRAL_BADGE; return { label: 'Approved', variantClass: TONE_CLASS.info }; } /* ── Generic helpers (for inline use without registry entry) ─────── */ export function toneClass(tone: StatusTone): string { return TONE_CLASS[tone]; } export function paletteClass(hue: PaletteHue): string { return PALETTE_CLASS[hue]; }