--- name: shared-formatters description: "Use the central shared/utils/formatters.ts file for all display formatting in the dashboard. Use when: formatting dates, times, numbers, currencies, or enum strings in table cells, detail pages, or any UI display. Avoid inline formatting logic like `toLocaleDateString()`, `.toLocaleString()`, or manual string splits. Import from @/shared/utils/formatters." --- # Shared Formatters Use this skill whenever work involves displaying dates, times, numbers, currencies, or enum strings in any UI component. ## Source of Truth All shared display formatters live in: ``` apps/dashboard/shared/utils/formatters.ts ``` Import path: `@/shared/utils/formatters` ## Available Formatters | Function | Input | Output example | |---|---|---| | `formatDate(value)` | string \| Date \| null | `"Jan 6, 2026"` | | `formatDateTime(value)` | string \| Date \| null | `"Jan 6, 2026, 2:30 PM"` | | `formatDateShort(value)` | string \| Date \| null | `"04/06/2026"` | | `formatTime(value)` | string \| Date \| null | `"2:30 PM"` | | `formatEnum(value)` | string \| null | `"In Progress"` | | `formatNumber(value)` | number \| string \| null | `"150,000"` | | `formatCurrency(value, currency?, locale?)` | number \| string \| null | `"$1,500.00"` | All functions return `"—"` for null/undefined/invalid input — never return an empty string or throw. ## Rules 1. **Never inline formatting.** Do not use `new Date(x).toLocaleDateString()`, `Number(x).toLocaleString()`, or manual `split("_")` chains in components or pages. Use the shared formatters instead. 2. **Add before duplicating.** Check `formatters.ts` for an existing formatter before writing a new one. If a new formatter is needed, add it to `formatters.ts` — not inline. 3. **Keep all formatters in one file.** Do not create separate formatter files per module. All display formatting stays in `shared/utils/formatters.ts`. 4. **Consistent null handling.** Every formatter accepts `null | undefined` and returns `"—"`. Never require callers to guard against null before calling. 5. **Use `formatEnum` for status/type fields.** Any snake_case or underscore-separated enum value displayed as text must go through `formatEnum`. ## Workflow 1. Identify a display value needing formatting in a component. 2. Check `formatters.ts` for an existing matching formatter. 3. If found, import and use it. 4. If missing, add to `formatters.ts` following the null-safety pattern, then import. 5. Remove any inline formatting that the shared formatter now replaces. ## Examples ```tsx import { formatDate, formatEnum, formatNumber, formatCurrency } from "@/shared/utils/formatters" // Table cell — date cell: ({ row }) => formatDate(row.original.created_at) // Table cell — enum status cell: ({ row }) => {formatEnum(row.original.status)} // Table cell — number cell: ({ row }) => formatNumber(row.original.km_in) // Table cell — currency cell: ({ row }) => formatCurrency(row.original.total_amount) // Detail page field

{formatDateTime(jobCard.updated_at)}

```