import type { FixStatus, LeaveType, ReqStatus, TxnKind } from "@/types"; export function classNames(...xs: (string | false | null | undefined)[]) { return xs.filter(Boolean).join(" "); } export function formatDate(value?: string | null): string { if (!value) return "—"; const d = new Date(value); if (Number.isNaN(d.getTime())) return value; const y = d.getFullYear(); const m = String(d.getMonth() + 1).padStart(2, "0"); const day = String(d.getDate()).padStart(2, "0"); return `${y}.${m}.${day}`; } export function formatDateTime(value?: string | null): string { if (!value) return "—"; const d = new Date(value); if (Number.isNaN(d.getTime())) return value; return `${formatDate(value)} ${String(d.getHours()).padStart(2, "0")}:${String( d.getMinutes() ).padStart(2, "0")}`; } export function formatTime(value?: string | null): string { if (!value) return "—"; const d = new Date(value); if (Number.isNaN(d.getTime())) return "—"; return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`; } // Korean-style money: 1,2300,0000 → "1억 2,300만". Compact for dashboards. export function formatKRW(n?: number): string { if (n == null) return "—"; const neg = n < 0; let v = Math.abs(Math.round(n)); if (v === 0) return "₩0"; const eok = Math.floor(v / 100_000_000); v = v % 100_000_000; const man = Math.floor(v / 10_000); const rest = v % 10_000; const parts: string[] = []; if (eok) parts.push(`${eok}억`); if (man) parts.push(`${man.toLocaleString()}만`); if (rest && !eok) parts.push(`${rest.toLocaleString()}`); const s = parts.join(" ") || "0"; return `${neg ? "-" : ""}₩${s}`; } // Full numeric KRW with grouping (for ledgers / inputs). export function formatWon(n?: number): string { if (n == null) return "—"; return `₩${Math.round(n).toLocaleString()}`; } export function formatPoints(n?: number): string { if (n == null) return "—"; return `${(Math.round(n * 10) / 10).toLocaleString()}P`; } export function minutesToHM(min?: number): string { if (!min || min <= 0) return "0시간"; const h = Math.floor(min / 60); const m = min % 60; return m ? `${h}시간 ${m}분` : `${h}시간`; } export function formatSize(bytes?: number): string { if (!bytes || bytes <= 0) return "—"; const units = ["B", "KB", "MB", "GB"]; let n = bytes; let i = 0; while (n >= 1024 && i < units.length - 1) { n /= 1024; i++; } return `${n.toFixed(n < 10 && i > 0 ? 1 : 0)} ${units[i]}`; } /* ---- status metadata ---- */ export const REQ_STATUS_META: Record = { pending: { label: "대기", fg: "#B54708", bg: "#FEF0C7" }, approved: { label: "승인", fg: "#067647", bg: "#DCFAE6" }, rejected: { label: "반려", fg: "#B42318", bg: "#FEE4E2" }, canceled: { label: "취소", fg: "#475467", bg: "#F2F4F7" }, }; export const FIX_STATUS_META: Record = { planned: { label: "예정", fg: "#475467", bg: "#F2F4F7" }, applying: { label: "반영중", fg: "#175CD3", bg: "#D1E9FF" }, applied: { label: "반영완료", fg: "#5925DC", bg: "#EBE9FE" }, paid: { label: "지급완료", fg: "#067647", bg: "#DCFAE6" }, }; export const FIX_ORDER: FixStatus[] = ["planned", "applying", "applied", "paid"]; export const LEAVE_LABELS: Record = { annual: "연차", half_am: "오전 반차", half_pm: "오후 반차", public: "공가", sick: "병가", family: "경조사", unpaid: "무급", }; export const TXN_LABELS: Record = { income: "수입", expense: "비용", tax: "세금", payroll: "급여", incentive: "인센티브", }; export const STAGE_KIND_LABELS: Record = { deposit: "계약금", middle: "중도금", final: "잔금", }; // 인센티브 BE/non-BE 라벨 (프로젝트 계약범위와 무관 — 그쪽은 자유 텍스트) export const SCOPE_LABELS: Record = { be: "BE", non_be: "non-BE", }; export const PROJECT_STATUS_META: Record = { planned: { label: "예정", fg: "#475467", bg: "#F2F4F7" }, active: { label: "진행중", fg: "#175CD3", bg: "#D1E9FF" }, hold: { label: "보류", fg: "#B54708", bg: "#FEF0C7" }, done: { label: "완료", fg: "#067647", bg: "#DCFAE6" }, dropped: { label: "중단", fg: "#B42318", bg: "#FEE4E2" }, }; export const LANE_LABELS: Record = { todo: "할 일", doing: "진행중", review: "검토", done: "완료", };