Files
eden/apps/core-web/src/lib/format.ts
Matheus (Handix) 828e6e8ddb feat(web): telas de Suporte e Fiscal no EDEN Core
Continuação direta, sem parar para autorização. 2 subagentes
eden-frontend em paralelo.

- Suporte: lista de tickets, criação, detalhe com painel de SLA
  (prazos, cumprido/violado/pendente, aviso "SLA pausado"), thread de
  comentários públicos/internos, histórico de status, transições
  limitadas aos alvos válidos com campos condicionais de causa raiz/
  resolução ao fechar.
- Fiscal: NCM/CFOP/Municípios (CRUD simples), Naturezas de Operação
  (só leitura, seed fixo), nova seção "Fiscal" em ProductFormPage
  (só ao editar) com aviso de que um novo perfil do mesmo componente
  substitui automaticamente o anterior — invariante confirmada ao vivo
  pelo próprio agente antes de entregar.

Reconciliação manual de App.tsx/AuthenticatedLayout.tsx/
ProductFormPage.tsx/format.ts entre os 2 agentes. Ambos restauraram a
base manualmente (sem git merge) — diff -rq contra main confirmou zero
desvio de restauração nos dois casos antes de aceitar qualquer arquivo
como novo.

Verificado: pnpm turbo run typecheck build (28/28); containers
eden-api/eden-core reconstruídos; testado ao vivo através do proxy
same-origin (porta 3001); trilha de auditoria conferida no Postgres.

Com esta leva, o EDEN Core tem UI real para praticamente todo o
backend construído até agora, exceto HR-Timeclock e Documentos.

Claude-Session: https://claude.ai/code/session_01BcWWuKfykGtUweXmHD6cKx

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-04 05:53:20 -03:00

44 lines
2.0 KiB
TypeScript

/** pt-BR display formatting helpers — currency/document masks only, no business logic. */
const currencyFormatter = new Intl.NumberFormat("pt-BR", { style: "currency", currency: "BRL" });
/**
* Formats a money value for display. Accepts the API's `string | null`
* NUMERIC representation (never coerce it to `number` for anything other
* than display) or a plain `number` from a controlled form input.
*/
export function formatCurrencyBRL(value: string | number | null | undefined): string {
if (value === null || value === undefined || value === "") return currencyFormatter.format(0);
const numeric = typeof value === "number" ? value : Number(value);
if (Number.isNaN(numeric)) return currencyFormatter.format(0);
return currencyFormatter.format(numeric);
}
export function formatDateBR(value: string | Date | null | undefined): string {
if (!value) return "—";
const date = typeof value === "string" ? new Date(value) : value;
if (Number.isNaN(date.getTime())) return "—";
return new Intl.DateTimeFormat("pt-BR").format(date);
}
/** Same as `formatDateBR` but includes the time — for timestamps where the hour matters (e.g. SLA due dates). */
export function formatDateTimeBR(value: string | Date | null | undefined): string {
if (!value) return "—";
const date = typeof value === "string" ? new Date(value) : value;
if (Number.isNaN(date.getTime())) return "—";
return new Intl.DateTimeFormat("pt-BR", { dateStyle: "short", timeStyle: "short" }).format(date);
}
/** CNPJ (14 digits) or CPF (11 digits) mask — falls back to the raw value for anything else. */
export function formatDocument(value: string | null | undefined): string {
if (!value) return "—";
const digits = value.replace(/\D/g, "");
if (digits.length === 14) {
return digits.replace(/^(\d{2})(\d{3})(\d{3})(\d{4})(\d{2})$/, "$1.$2.$3/$4-$5");
}
if (digits.length === 11) {
return digits.replace(/^(\d{3})(\d{3})(\d{3})(\d{2})$/, "$1.$2.$3-$4");
}
return value;
}