mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 11:08:12 +00:00
244 lines
7.4 KiB
TypeScript
244 lines
7.4 KiB
TypeScript
/**
|
|
* 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<string, string>;
|
|
|
|
export interface LetterLabelEntry {
|
|
en: string;
|
|
am: string;
|
|
style?: LetterLabelStyle;
|
|
}
|
|
|
|
export type LetterLabelsText = Record<LetterLabelKey, LetterLabelEntry>;
|
|
|
|
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<LetterLabelKey, string> = {
|
|
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<LetterLabelKey>();
|
|
|
|
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<string, unknown> =>
|
|
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<string, unknown>) : {};
|
|
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;
|
|
};
|