/** * Letter Labels constants & helpers * * Customizable, bilingual (English / Amharic) labels used by letter templates * (To, From, Subject, CC, ...). Each label may carry an optional CSS style * object (e.g. underline, bold). Stored under the setting code * `letter-labels-text` as a JSON object. */ export type LetterLabelKey = | "to" | "from" | "subject" | "urgent" | "cc" | "date" | "record" | "ref_no" | "delegate" | "for_your_reference"; /** CSS style map for a single label (kebab-case CSS properties). */ export type LetterLabelStyle = Record; export interface LetterLabelEntry { en: string; am: string; style?: LetterLabelStyle; } export type LetterLabelsText = Record; export const LETTER_LABELS_SETTING_CODE = "letter-labels-text"; export const LETTER_LABEL_KEYS: LetterLabelKey[] = [ "to", "from", "subject", "urgent", "cc", "date", "record", "ref_no", "delegate", "for_your_reference", ]; /** * Default labels for every key in English & Amharic. Some stored values include * trailing punctuation (Subject:, CC:, Ref No:), others don't (To, From). */ export const DEFAULT_LETTER_LABELS: LetterLabelsText = { to: { en: "To", am: "ለ" }, from: { en: "From", am: "ከ" }, subject: { en: "Subject:", am: "ጉዳዩ:" }, urgent: { en: "Urgent", am: "አስቸኳይ" }, cc: { en: "CC:", am: "ግልባጭ:", style: { "text-decoration": "underline" } }, date: { en: "Date", am: "ቀን" }, record: { en: "Record", am: "መዝገብ" }, ref_no: { en: "Ref No:", am: "ቁጥር:" }, delegate: { en: "Delegate", am: "ተወካይ" }, for_your_reference: { en: "For your reference", am: "ለመረጃ ያህል" }, }; /** Human-friendly field names for the editor (not the printed label text). */ export const LETTER_LABEL_DISPLAY_NAMES: Record< LetterLabelKey, { en: string; am: string } > = { to: { en: "To", am: "ለ" }, from: { en: "From", am: "ከ" }, subject: { en: "Subject label", am: "የጉዳይ መለያ" }, urgent: { en: "Urgent", am: "አስቸኳይ" }, cc: { en: "CC", am: "ግልባጭ" }, date: { en: "Date", am: "ቀን" }, record: { en: "Record title", am: "የደብዳቤ ርዕስ" }, ref_no: { en: "Reference number", am: "የማጣቀሻ ቁጥር" }, delegate: { en: "Delegate / Regarding", am: "ስለ" }, for_your_reference: { en: "For your reference", am: "እንዲያውቁት" }, }; export const isLetterLabelsSetting = (code: string): boolean => code === LETTER_LABELS_SETTING_CODE; /** Sidebar section code each label field belongs to. */ export const LETTER_LABEL_CATEGORY_MAP: Record = { record: "reference-number", ref_no: "reference-number", date: "reference-number", subject: "subject", urgent: "subject", to: "receiver", from: "receiver", delegate: "body", cc: "cc", for_your_reference: "cc", }; export type LetterLabelSectionCode = | "reference-number" | "subject" | "receiver" | "body" | "cc"; /** Normalize API / sidebar section codes to the canonical label section. */ export function normalizeLabelSectionCode( sectionCode: string, ): LetterLabelSectionCode | null { const lower = sectionCode.trim().toLowerCase(); if (!lower) return null; if ( lower.includes("reference-number") || lower.includes("reference_number") || lower.includes("referencenumber") || lower === "reference" || (lower.includes("reference") && lower.includes("number")) || lower.includes("record-number") || lower.includes("record_number") ) { return "reference-number"; } if (lower.includes("subject")) return "subject"; if ( lower.includes("receiver") || lower.includes("recipient") || lower === "receivers" ) { return "receiver"; } if (lower.includes("body") || lower.includes("content")) return "body"; if (lower.includes("cc")) return "cc"; return null; } const labelMatchesSettingCode = ( labelKey: LetterLabelKey, settingCode: string, ): boolean => { const lower = settingCode.trim().toLowerCase(); if (!lower) return false; switch (labelKey) { case "to": return ( (/receiver|recipient|^letter-to\b/i.test(lower) || lower.includes("receiver-list")) && !/from|collaborator|sender|cc/i.test(lower) ); case "from": return /from|collaborator|sender/i.test(lower); case "subject": return /subject/i.test(lower) && !/urgent/i.test(lower); case "urgent": return /urgent/i.test(lower); case "record": return /record/i.test(lower) && !/ref/i.test(lower); case "ref_no": return /ref/i.test(lower) && /no|number/i.test(lower); case "date": return /date/i.test(lower); case "delegate": return /delegate|regarding/i.test(lower) || lower.includes("body"); case "cc": return ( /letter-cc/i.test(lower) && !/list|prefix|suffix|receiver|for-your-reference|foryourreference/i.test( lower, ) ); case "for_your_reference": return /for-your-reference|foryourreference/i.test(lower); default: return false; } }; export function getLetterLabelKeysForCategory( categoryCode: string, settingCodes: string[] = [], ): LetterLabelKey[] { const canonical = normalizeLabelSectionCode(categoryCode); const matched = new Set(); if (canonical) { for (const key of LETTER_LABEL_KEYS) { if (LETTER_LABEL_CATEGORY_MAP[key] === canonical) { matched.add(key); } } } for (const settingCode of settingCodes) { for (const key of LETTER_LABEL_KEYS) { if (labelMatchesSettingCode(key, settingCode)) { matched.add(key); } } } return LETTER_LABEL_KEYS.filter((key) => matched.has(key)); } const isPlainObject = (value: unknown): value is Record => typeof value === "object" && value !== null && !Array.isArray(value); /** * Validates a CSS style object, keeping only non-empty string values. * Returns undefined when nothing valid remains. */ export const normalizeLabelStyle = ( value: unknown, ): LetterLabelStyle | undefined => { if (!isPlainObject(value)) return undefined; const out: LetterLabelStyle = {}; for (const [key, raw] of Object.entries(value)) { if (key === "font-size") continue; if (typeof raw === "string" && raw.trim() !== "") out[key] = raw; } return Object.keys(out).length > 0 ? out : undefined; }; /** * Merges an incoming (possibly partial / invalid) value with the defaults so the * result always has all keys with valid en/am strings. Preserves style objects. */ export const normalizeLetterLabelsValue = (value: unknown): LetterLabelsText => { const input = isPlainObject(value) ? value : {}; const result = {} as LetterLabelsText; for (const key of LETTER_LABEL_KEYS) { const def = DEFAULT_LETTER_LABELS[key]; const raw = isPlainObject(input[key]) ? (input[key] as Record) : {}; const en = typeof raw.en === "string" ? raw.en : def.en; const am = typeof raw.am === "string" ? raw.am : def.am; const style = normalizeLabelStyle(raw.style) ?? def.style; result[key] = style ? { en, am, style } : { en, am }; } return result; };