@eztrak/shared 0.17.1 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +78 -0
- package/dist/{chunk-4ASJWHN3.mjs → chunk-2KXV5C3X.mjs} +2 -2
- package/dist/{chunk-QOYMQFOB.mjs → chunk-A5Y5QGVL.mjs} +138 -32
- package/dist/chunk-A5Y5QGVL.mjs.map +1 -0
- package/dist/{chunk-LS7AI4ER.mjs → chunk-IQGTSEHC.mjs} +2 -2
- package/dist/components/index.js.map +1 -1
- package/dist/components/index.mjs +2 -2
- package/dist/excel/index.d.mts +146 -0
- package/dist/excel/index.d.ts +146 -0
- package/dist/excel/index.js +355 -0
- package/dist/excel/index.js.map +1 -0
- package/dist/excel/index.mjs +316 -0
- package/dist/excel/index.mjs.map +1 -0
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +135 -29
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3 -3
- package/dist/utils/index.d.mts +20 -4
- package/dist/utils/index.d.ts +20 -4
- package/dist/utils/index.js +135 -29
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/index.mjs +2 -2
- package/package.json +11 -3
- package/dist/chunk-QOYMQFOB.mjs.map +0 -1
- /package/dist/{chunk-4ASJWHN3.mjs.map → chunk-2KXV5C3X.mjs.map} +0 -0
- /package/dist/{chunk-LS7AI4ER.mjs.map → chunk-IQGTSEHC.mjs.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/excel/index.ts"],"sourcesContent":["import * as XLSX from \"xlsx-js-style\";\r\n\r\nexport type ExcelAlign = \"left\" | \"center\" | \"right\";\r\n\r\n/** Per-column Excel colors. Hex is RRGGBB; a leading # is accepted. */\r\nexport type ExcelColumnStyle = {\r\n headerFill?: string;\r\n headerColor?: string;\r\n fill?: string;\r\n color?: string;\r\n bold?: boolean;\r\n align?: ExcelAlign;\r\n /** Excel number format, for example '\"$\"#,##0.00'. */\r\n numFmt?: string;\r\n};\r\n\r\nexport type ExcelTheme = {\r\n headerFill?: string;\r\n headerColor?: string;\r\n fill?: string;\r\n color?: string;\r\n borderColor?: string;\r\n fontName?: string;\r\n fontSize?: number;\r\n};\r\n\r\nexport type ExcelFormatParams = {\r\n value: unknown;\r\n data: Record<string, unknown>;\r\n};\r\n\r\nexport type ExcelExportColumn = {\r\n field?: string;\r\n headerName?: string;\r\n children?: ExcelExportColumn[];\r\n hide?: boolean;\r\n checkboxSelection?: boolean;\r\n headerCheckboxSelection?: boolean;\r\n valueFormatter?: (params: ExcelFormatParams) => unknown;\r\n format?: (params: ExcelFormatParams) => unknown;\r\n excel?: ExcelColumnStyle;\r\n};\r\n\r\nexport type ExportExcelErrorCode =\r\n | \"empty\"\r\n | \"invalid\"\r\n | \"no-columns\"\r\n | \"filename\"\r\n | \"write\";\r\n\r\nexport type ExportExcelFailure = {\r\n ok: false;\r\n code: ExportExcelErrorCode;\r\n error: string;\r\n};\r\n\r\nexport type ExportExcelReportResult =\r\n | { ok: true; fileName: string }\r\n | ExportExcelFailure;\r\n\r\nexport type ExportExcelReportOptions = {\r\n data?: unknown[] | null;\r\n fileName?: string;\r\n columns?: ExcelExportColumn[];\r\n sheetName?: string;\r\n theme?: ExcelTheme;\r\n /** Drop columns with hide: true. Default false, matching vault grids. */\r\n skipHidden?: boolean;\r\n /**\r\n * Format leaf fields whose name contains \"date\" when they have no formatter.\r\n * Vault turns this on and passes its own date formatter.\r\n */\r\n formatDateFields?: boolean;\r\n formatDate?: (value: unknown) => unknown;\r\n /**\r\n * Date suffix for the file name. Omit to use the local YYYY-MM-DD.\r\n * Pass null when the caller could not produce a stamp.\r\n */\r\n dateStamp?: string | null;\r\n};\r\n\r\ntype ResolvedTheme = {\r\n headerFill: string;\r\n headerColor: string;\r\n fill: string;\r\n color: string;\r\n borderColor: string;\r\n fontName: string;\r\n fontSize: number;\r\n};\r\n\r\nexport type ExcelCellStyle = {\r\n font: {\r\n name: string;\r\n sz: number;\r\n bold?: boolean;\r\n color: { rgb: string };\r\n };\r\n fill: { patternType: \"solid\"; fgColor: { rgb: string } };\r\n alignment: {\r\n vertical: \"center\";\r\n horizontal: ExcelAlign;\r\n wrapText: false;\r\n };\r\n border: {\r\n top: { style: \"thin\"; color: { rgb: string } };\r\n bottom: { style: \"thin\"; color: { rgb: string } };\r\n left: { style: \"thin\"; color: { rgb: string } };\r\n right: { style: \"thin\"; color: { rgb: string } };\r\n };\r\n numFmt?: string;\r\n};\r\n\r\ntype HeaderGroup = {\r\n name: string;\r\n excel?: ExcelColumnStyle;\r\n};\r\n\r\ntype LeafColumn = {\r\n field: string;\r\n headerName: string;\r\n column: ExcelExportColumn;\r\n groups: HeaderGroup[];\r\n};\r\n\r\ntype WorkBook = {\r\n SheetNames: string[];\r\n Sheets: Record<string, WorkSheet>;\r\n};\r\n\r\ntype WorkSheet = Record<string, unknown>;\r\n\r\nconst ERRORS: Record<Exclude<ExportExcelErrorCode, \"write\">, string> = {\r\n empty: \"No valid data to download. Please ensure data is available.\",\r\n invalid: \"Invalid data structure. Please ensure data is correctly formatted.\",\r\n \"no-columns\": \"No data to export. Please check the data source.\",\r\n filename: \"Failed to generate file name. Please try again.\",\r\n};\r\n\r\nexport const DEFAULT_EXCEL_THEME: ResolvedTheme = {\r\n headerFill: \"2F75B6\",\r\n headerColor: \"FFFFFF\",\r\n fill: \"FFFFFF\",\r\n color: \"1F2933\",\r\n borderColor: \"8FA4BF\",\r\n fontName: \"Calibri\",\r\n fontSize: 11,\r\n};\r\n\r\nconst fail = (\r\n code: Exclude<ExportExcelErrorCode, \"write\">,\r\n): ExportExcelFailure => ({\r\n ok: false,\r\n code,\r\n error: ERRORS[code],\r\n});\r\n\r\nconst rgb = (input: string | undefined, fallback: string): string => {\r\n if (!input) return fallback;\r\n const hex = input.trim().replace(/^#/, \"\").toUpperCase();\r\n return /^[0-9A-F]{6}$/.test(hex) ? hex : fallback;\r\n};\r\n\r\nconst resolveTheme = (theme?: ExcelTheme): ResolvedTheme => ({\r\n headerFill: rgb(theme?.headerFill, DEFAULT_EXCEL_THEME.headerFill),\r\n headerColor: rgb(theme?.headerColor, DEFAULT_EXCEL_THEME.headerColor),\r\n fill: rgb(theme?.fill, DEFAULT_EXCEL_THEME.fill),\r\n color: rgb(theme?.color, DEFAULT_EXCEL_THEME.color),\r\n borderColor: rgb(theme?.borderColor, DEFAULT_EXCEL_THEME.borderColor),\r\n fontName: theme?.fontName?.trim() || DEFAULT_EXCEL_THEME.fontName,\r\n fontSize:\r\n typeof theme?.fontSize === \"number\" && theme.fontSize > 0\r\n ? theme.fontSize\r\n : DEFAULT_EXCEL_THEME.fontSize,\r\n});\r\n\r\nconst gridBorder = (color: string): ExcelCellStyle[\"border\"] => {\r\n const edge = () => ({ style: \"thin\" as const, color: { rgb: color } });\r\n return { top: edge(), bottom: edge(), left: edge(), right: edge() };\r\n};\r\n\r\nexport const excelHeaderStyle = (\r\n theme?: ExcelTheme,\r\n column?: ExcelColumnStyle,\r\n): ExcelCellStyle => {\r\n const resolved = resolveTheme(theme);\r\n return {\r\n font: {\r\n name: resolved.fontName,\r\n sz: resolved.fontSize,\r\n bold: true,\r\n color: { rgb: rgb(column?.headerColor, resolved.headerColor) },\r\n },\r\n fill: {\r\n patternType: \"solid\",\r\n fgColor: { rgb: rgb(column?.headerFill, resolved.headerFill) },\r\n },\r\n alignment: {\r\n vertical: \"center\",\r\n horizontal: column?.align ?? \"left\",\r\n wrapText: false,\r\n },\r\n border: gridBorder(resolved.borderColor),\r\n };\r\n};\r\n\r\nexport const excelDataStyle = (\r\n theme?: ExcelTheme,\r\n column?: ExcelColumnStyle,\r\n): ExcelCellStyle => {\r\n const resolved = resolveTheme(theme);\r\n const font: ExcelCellStyle[\"font\"] = {\r\n name: resolved.fontName,\r\n sz: resolved.fontSize,\r\n color: { rgb: rgb(column?.color, resolved.color) },\r\n };\r\n if (column?.bold) font.bold = true;\r\n const style: ExcelCellStyle = {\r\n font,\r\n fill: {\r\n patternType: \"solid\",\r\n fgColor: { rgb: rgb(column?.fill, resolved.fill) },\r\n },\r\n alignment: {\r\n vertical: \"center\",\r\n horizontal: column?.align ?? \"left\",\r\n wrapText: false,\r\n },\r\n border: gridBorder(resolved.borderColor),\r\n };\r\n if (column?.numFmt) style.numFmt = column.numFmt;\r\n return style;\r\n};\r\n\r\nconst getNestedValue = (obj: unknown, path: string): unknown => {\r\n if (!obj || typeof obj !== \"object\") return \"\";\r\n try {\r\n const value = path.split(\".\").reduce<unknown>((acc, key) => {\r\n if (acc == null || typeof acc !== \"object\") return undefined;\r\n return (acc as Record<string, unknown>)[key];\r\n }, obj);\r\n return value ?? \"\";\r\n } catch {\r\n return \"\";\r\n }\r\n};\r\n\r\nconst isGroup = (col: ExcelExportColumn): boolean =>\r\n Array.isArray(col.children) && col.children.length > 0;\r\n\r\nconst isSkippedLeaf = (col: ExcelExportColumn): boolean => {\r\n if (col.checkboxSelection || col.headerCheckboxSelection) return true;\r\n const field = typeof col.field === \"string\" ? col.field.trim() : \"\";\r\n return !field || field === \"checkbox\" || field === \"checkboxSelection\";\r\n};\r\n\r\nconst collectLeaves = (\r\n cols: ExcelExportColumn[],\r\n groups: HeaderGroup[],\r\n skipHidden: boolean,\r\n): LeafColumn[] => {\r\n const leaves: LeafColumn[] = [];\r\n cols.forEach((col) => {\r\n if (!col || typeof col !== \"object\") return;\r\n if (skipHidden && col.hide) return;\r\n if (isGroup(col)) {\r\n const group: HeaderGroup = {\r\n name: col.headerName || \"\",\r\n excel: col.excel,\r\n };\r\n leaves.push(...collectLeaves(col.children ?? [], [...groups, group], skipHidden));\r\n return;\r\n }\r\n if (isSkippedLeaf(col)) return;\r\n const field = col.field!.trim();\r\n leaves.push({\r\n field,\r\n headerName: (col.headerName || field).trim() || field,\r\n column: col,\r\n groups,\r\n });\r\n });\r\n return leaves;\r\n};\r\n\r\nconst labelFromKey = (key: string): string =>\r\n key\r\n .replace(/([A-Z])/g, \" $1\")\r\n .replace(/^\\w/, (char) => char.toUpperCase())\r\n .trim();\r\n\r\nconst columnsFromRows = (rows: unknown[]): ExcelExportColumn[] | ExportExcelFailure => {\r\n const first = rows[0];\r\n if (!first || typeof first !== \"object\" || Array.isArray(first)) {\r\n return fail(\"invalid\");\r\n }\r\n return Object.keys(first)\r\n .filter((key) => key !== \"checkbox\" && key !== \"checkboxSelection\" && key.trim())\r\n .map((key) => ({ field: key, headerName: labelFromKey(key) }));\r\n};\r\n\r\nconst formatLeafValue = (\r\n row: Record<string, unknown>,\r\n leaf: LeafColumn,\r\n options: ExportExcelReportOptions,\r\n): unknown => {\r\n let value = getNestedValue(row, leaf.field);\r\n const formatter = leaf.column.format || leaf.column.valueFormatter;\r\n if (typeof formatter === \"function\") {\r\n try {\r\n return formatter({ value, data: row }) ?? \"\";\r\n } catch {\r\n return value ?? \"\";\r\n }\r\n }\r\n\r\n const fieldLooksLikeDate = leaf.field.toLowerCase().includes(\"date\");\r\n if (options.formatDateFields && fieldLooksLikeDate && typeof options.formatDate === \"function\") {\r\n try {\r\n return options.formatDate(value) ?? \"\";\r\n } catch {\r\n return value ?? \"\";\r\n }\r\n }\r\n\r\n if (value instanceof Date) return value;\r\n if (value != null && typeof value === \"object\") return \"\";\r\n\r\n const numFmt = leaf.column.excel?.numFmt;\r\n if (numFmt) {\r\n if (typeof value === \"number\" && Number.isFinite(value)) return value;\r\n if (typeof value === \"string\" && value.trim() !== \"\" && Number.isFinite(Number(value))) {\r\n return Number(value);\r\n }\r\n }\r\n\r\n return value ?? \"\";\r\n};\r\n\r\nconst localDateStamp = (): string => {\r\n const date = new Date();\r\n const year = date.getFullYear();\r\n const month = String(date.getMonth() + 1).padStart(2, \"0\");\r\n const day = String(date.getDate()).padStart(2, \"0\");\r\n return `${year}-${month}-${day}`;\r\n};\r\n\r\nconst resolveDateStamp = (dateStamp: string | null | undefined): string | null => {\r\n if (dateStamp === null) return null;\r\n if (typeof dateStamp === \"string\" && dateStamp.trim()) return dateStamp.trim();\r\n return localDateStamp();\r\n};\r\n\r\nconst safeSheetName = (name: string | undefined): string => {\r\n const cleaned = (name || \"Report\").replace(/[:\\\\/?*[\\]]/g, \" \").trim() || \"Report\";\r\n return cleaned.slice(0, 31);\r\n};\r\n\r\nconst buildHeaderGrid = (leaves: LeafColumn[]) => {\r\n const totalHeaderRows = Math.max(...leaves.map((leaf) => leaf.groups.length + 1), 1);\r\n const headerRows = Array.from({ length: totalHeaderRows }, () =>\r\n Array.from({ length: leaves.length }, () => \"\"),\r\n );\r\n const merges: { s: { r: number; c: number }; e: { r: number; c: number } }[] = [];\r\n\r\n for (let depth = 0; depth < totalHeaderRows - 1; depth += 1) {\r\n let colIndex = 0;\r\n while (colIndex < leaves.length) {\r\n const group = leaves[colIndex].groups[depth];\r\n if (!group) {\r\n colIndex += 1;\r\n continue;\r\n }\r\n let span = 1;\r\n while (colIndex + span < leaves.length && leaves[colIndex + span].groups[depth] === group) {\r\n span += 1;\r\n }\r\n headerRows[depth][colIndex] = group.name;\r\n if (span > 1) {\r\n merges.push({\r\n s: { r: depth, c: colIndex },\r\n e: { r: depth, c: colIndex + span - 1 },\r\n });\r\n }\r\n colIndex += span;\r\n }\r\n }\r\n\r\n leaves.forEach((leaf, colIndex) => {\r\n const row = leaf.groups.length;\r\n headerRows[row][colIndex] = leaf.headerName;\r\n if (row < totalHeaderRows - 1) {\r\n merges.push({\r\n s: { r: row, c: colIndex },\r\n e: { r: totalHeaderRows - 1, c: colIndex },\r\n });\r\n }\r\n });\r\n\r\n return { headerRows, merges, totalHeaderRows };\r\n};\r\n\r\nconst headerStyleFor = (\r\n leaf: LeafColumn,\r\n row: number,\r\n theme: ExcelTheme | undefined,\r\n): ExcelCellStyle => {\r\n const source = row < leaf.groups.length ? leaf.groups[row].excel : leaf.column.excel;\r\n return excelHeaderStyle(theme, source);\r\n};\r\n\r\nconst columnWidth = (leaf: LeafColumn, values: unknown[]): { wch: number } => {\r\n const headerLength = Math.max(\r\n leaf.headerName.length,\r\n ...leaf.groups.map((group) => group.name.length),\r\n 0,\r\n );\r\n const dataLength = values.reduce<number>((max, value) => {\r\n return Math.max(max, String(value ?? \"\").length);\r\n }, 0);\r\n return { wch: Math.min(Math.max(headerLength, dataLength, 12), 50) };\r\n};\r\n\r\nexport type BuiltExcelWorkbook =\r\n | { ok: true; workbook: WorkBook; fileName: string; sheetName: string }\r\n | ExportExcelFailure;\r\n\r\n/**\r\n * Build a styled workbook. Does not write a file.\r\n * Header merges follow the column tree, so two groups with the same title stay separate.\r\n */\r\nexport const buildExcelWorkbook = (\r\n options: ExportExcelReportOptions = {},\r\n): BuiltExcelWorkbook => {\r\n const data = options.data;\r\n if (!Array.isArray(data) || data.length === 0) return fail(\"empty\");\r\n\r\n const provided = Array.isArray(options.columns) ? options.columns : [];\r\n const columns = provided.length\r\n ? provided\r\n : columnsFromRows(data);\r\n if (!Array.isArray(columns)) return columns;\r\n\r\n const leaves = collectLeaves(columns, [], options.skipHidden === true);\r\n if (!leaves.length) return fail(\"no-columns\");\r\n\r\n const dateStamp = resolveDateStamp(options.dateStamp);\r\n if (!dateStamp) return fail(\"filename\");\r\n\r\n const { headerRows, merges, totalHeaderRows } = buildHeaderGrid(leaves);\r\n const records = data.map((row) =>\r\n row && typeof row === \"object\" && !Array.isArray(row)\r\n ? (row as Record<string, unknown>)\r\n : {},\r\n );\r\n const dataRows = records.map((row) =>\r\n leaves.map((leaf) => formatLeafValue(row, leaf, options)),\r\n );\r\n\r\n const sheet = XLSX.utils.aoa_to_sheet([...headerRows, ...dataRows]) as WorkSheet;\r\n const lastCell = XLSX.utils.encode_cell({\r\n r: headerRows.length + dataRows.length - 1,\r\n c: leaves.length - 1,\r\n });\r\n sheet[\"!ref\"] = `A1:${lastCell}`;\r\n sheet[\"!merges\"] = merges;\r\n sheet[\"!cols\"] = leaves.map((leaf, index) =>\r\n columnWidth(\r\n leaf,\r\n dataRows.map((row) => row[index]),\r\n ),\r\n );\r\n sheet[\"!rows\"] = Array.from({ length: totalHeaderRows }, () => ({ hpt: 22 }));\r\n\r\n const range = XLSX.utils.decode_range(sheet[\"!ref\"] as string);\r\n for (let row = range.s.r; row <= range.e.r; row += 1) {\r\n for (let col = range.s.c; col <= range.e.c; col += 1) {\r\n const address = XLSX.utils.encode_cell({ r: row, c: col });\r\n const cell = (sheet[address] as { t?: string; v?: unknown; z?: string; s?: ExcelCellStyle } | undefined) ?? {\r\n t: \"s\",\r\n v: \"\",\r\n };\r\n const leaf = leaves[col];\r\n if (row < totalHeaderRows) {\r\n cell.s = headerStyleFor(leaf, row, options.theme);\r\n } else {\r\n const style = excelDataStyle(options.theme, leaf.column.excel);\r\n if (leaf.column.excel?.numFmt) cell.z = leaf.column.excel.numFmt;\r\n cell.s = style;\r\n }\r\n sheet[address] = cell;\r\n }\r\n }\r\n\r\n const sheetName = safeSheetName(options.sheetName);\r\n const workbook = XLSX.utils.book_new() as WorkBook;\r\n XLSX.utils.book_append_sheet(workbook, sheet, sheetName);\r\n\r\n const baseName = options.fileName == null ? \"\" : String(options.fileName);\r\n return {\r\n ok: true,\r\n workbook,\r\n sheetName,\r\n fileName: `${baseName ? `${baseName}_` : \"\"}${dateStamp}.xlsx`,\r\n };\r\n};\r\n\r\n/** Write a styled .xlsx. Returns a result; it does not toast. */\r\nexport const exportExcelReport = (\r\n options: ExportExcelReportOptions = {},\r\n): ExportExcelReportResult => {\r\n const built = buildExcelWorkbook(options);\r\n if (!built.ok) return built;\r\n try {\r\n XLSX.writeFile(built.workbook, built.fileName, { cellStyles: true });\r\n } catch (error) {\r\n const message = error instanceof Error ? error.message : \"Unknown error\";\r\n return { ok: false, code: \"write\", error: message };\r\n }\r\n return { ok: true, fileName: built.fileName };\r\n};\r\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
// src/excel/index.ts
|
|
2
|
+
import * as XLSX from "xlsx-js-style";
|
|
3
|
+
var ERRORS = {
|
|
4
|
+
empty: "No valid data to download. Please ensure data is available.",
|
|
5
|
+
invalid: "Invalid data structure. Please ensure data is correctly formatted.",
|
|
6
|
+
"no-columns": "No data to export. Please check the data source.",
|
|
7
|
+
filename: "Failed to generate file name. Please try again."
|
|
8
|
+
};
|
|
9
|
+
var DEFAULT_EXCEL_THEME = {
|
|
10
|
+
headerFill: "2F75B6",
|
|
11
|
+
headerColor: "FFFFFF",
|
|
12
|
+
fill: "FFFFFF",
|
|
13
|
+
color: "1F2933",
|
|
14
|
+
borderColor: "8FA4BF",
|
|
15
|
+
fontName: "Calibri",
|
|
16
|
+
fontSize: 11
|
|
17
|
+
};
|
|
18
|
+
var fail = (code) => ({
|
|
19
|
+
ok: false,
|
|
20
|
+
code,
|
|
21
|
+
error: ERRORS[code]
|
|
22
|
+
});
|
|
23
|
+
var rgb = (input, fallback) => {
|
|
24
|
+
if (!input) return fallback;
|
|
25
|
+
const hex = input.trim().replace(/^#/, "").toUpperCase();
|
|
26
|
+
return /^[0-9A-F]{6}$/.test(hex) ? hex : fallback;
|
|
27
|
+
};
|
|
28
|
+
var resolveTheme = (theme) => ({
|
|
29
|
+
headerFill: rgb(theme?.headerFill, DEFAULT_EXCEL_THEME.headerFill),
|
|
30
|
+
headerColor: rgb(theme?.headerColor, DEFAULT_EXCEL_THEME.headerColor),
|
|
31
|
+
fill: rgb(theme?.fill, DEFAULT_EXCEL_THEME.fill),
|
|
32
|
+
color: rgb(theme?.color, DEFAULT_EXCEL_THEME.color),
|
|
33
|
+
borderColor: rgb(theme?.borderColor, DEFAULT_EXCEL_THEME.borderColor),
|
|
34
|
+
fontName: theme?.fontName?.trim() || DEFAULT_EXCEL_THEME.fontName,
|
|
35
|
+
fontSize: typeof theme?.fontSize === "number" && theme.fontSize > 0 ? theme.fontSize : DEFAULT_EXCEL_THEME.fontSize
|
|
36
|
+
});
|
|
37
|
+
var gridBorder = (color) => {
|
|
38
|
+
const edge = () => ({ style: "thin", color: { rgb: color } });
|
|
39
|
+
return { top: edge(), bottom: edge(), left: edge(), right: edge() };
|
|
40
|
+
};
|
|
41
|
+
var excelHeaderStyle = (theme, column) => {
|
|
42
|
+
const resolved = resolveTheme(theme);
|
|
43
|
+
return {
|
|
44
|
+
font: {
|
|
45
|
+
name: resolved.fontName,
|
|
46
|
+
sz: resolved.fontSize,
|
|
47
|
+
bold: true,
|
|
48
|
+
color: { rgb: rgb(column?.headerColor, resolved.headerColor) }
|
|
49
|
+
},
|
|
50
|
+
fill: {
|
|
51
|
+
patternType: "solid",
|
|
52
|
+
fgColor: { rgb: rgb(column?.headerFill, resolved.headerFill) }
|
|
53
|
+
},
|
|
54
|
+
alignment: {
|
|
55
|
+
vertical: "center",
|
|
56
|
+
horizontal: column?.align ?? "left",
|
|
57
|
+
wrapText: false
|
|
58
|
+
},
|
|
59
|
+
border: gridBorder(resolved.borderColor)
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
var excelDataStyle = (theme, column) => {
|
|
63
|
+
const resolved = resolveTheme(theme);
|
|
64
|
+
const font = {
|
|
65
|
+
name: resolved.fontName,
|
|
66
|
+
sz: resolved.fontSize,
|
|
67
|
+
color: { rgb: rgb(column?.color, resolved.color) }
|
|
68
|
+
};
|
|
69
|
+
if (column?.bold) font.bold = true;
|
|
70
|
+
const style = {
|
|
71
|
+
font,
|
|
72
|
+
fill: {
|
|
73
|
+
patternType: "solid",
|
|
74
|
+
fgColor: { rgb: rgb(column?.fill, resolved.fill) }
|
|
75
|
+
},
|
|
76
|
+
alignment: {
|
|
77
|
+
vertical: "center",
|
|
78
|
+
horizontal: column?.align ?? "left",
|
|
79
|
+
wrapText: false
|
|
80
|
+
},
|
|
81
|
+
border: gridBorder(resolved.borderColor)
|
|
82
|
+
};
|
|
83
|
+
if (column?.numFmt) style.numFmt = column.numFmt;
|
|
84
|
+
return style;
|
|
85
|
+
};
|
|
86
|
+
var getNestedValue = (obj, path) => {
|
|
87
|
+
if (!obj || typeof obj !== "object") return "";
|
|
88
|
+
try {
|
|
89
|
+
const value = path.split(".").reduce((acc, key) => {
|
|
90
|
+
if (acc == null || typeof acc !== "object") return void 0;
|
|
91
|
+
return acc[key];
|
|
92
|
+
}, obj);
|
|
93
|
+
return value ?? "";
|
|
94
|
+
} catch {
|
|
95
|
+
return "";
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
var isGroup = (col) => Array.isArray(col.children) && col.children.length > 0;
|
|
99
|
+
var isSkippedLeaf = (col) => {
|
|
100
|
+
if (col.checkboxSelection || col.headerCheckboxSelection) return true;
|
|
101
|
+
const field = typeof col.field === "string" ? col.field.trim() : "";
|
|
102
|
+
return !field || field === "checkbox" || field === "checkboxSelection";
|
|
103
|
+
};
|
|
104
|
+
var collectLeaves = (cols, groups, skipHidden) => {
|
|
105
|
+
const leaves = [];
|
|
106
|
+
cols.forEach((col) => {
|
|
107
|
+
if (!col || typeof col !== "object") return;
|
|
108
|
+
if (skipHidden && col.hide) return;
|
|
109
|
+
if (isGroup(col)) {
|
|
110
|
+
const group = {
|
|
111
|
+
name: col.headerName || "",
|
|
112
|
+
excel: col.excel
|
|
113
|
+
};
|
|
114
|
+
leaves.push(...collectLeaves(col.children ?? [], [...groups, group], skipHidden));
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
if (isSkippedLeaf(col)) return;
|
|
118
|
+
const field = col.field.trim();
|
|
119
|
+
leaves.push({
|
|
120
|
+
field,
|
|
121
|
+
headerName: (col.headerName || field).trim() || field,
|
|
122
|
+
column: col,
|
|
123
|
+
groups
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
return leaves;
|
|
127
|
+
};
|
|
128
|
+
var labelFromKey = (key) => key.replace(/([A-Z])/g, " $1").replace(/^\w/, (char) => char.toUpperCase()).trim();
|
|
129
|
+
var columnsFromRows = (rows) => {
|
|
130
|
+
const first = rows[0];
|
|
131
|
+
if (!first || typeof first !== "object" || Array.isArray(first)) {
|
|
132
|
+
return fail("invalid");
|
|
133
|
+
}
|
|
134
|
+
return Object.keys(first).filter((key) => key !== "checkbox" && key !== "checkboxSelection" && key.trim()).map((key) => ({ field: key, headerName: labelFromKey(key) }));
|
|
135
|
+
};
|
|
136
|
+
var formatLeafValue = (row, leaf, options) => {
|
|
137
|
+
let value = getNestedValue(row, leaf.field);
|
|
138
|
+
const formatter = leaf.column.format || leaf.column.valueFormatter;
|
|
139
|
+
if (typeof formatter === "function") {
|
|
140
|
+
try {
|
|
141
|
+
return formatter({ value, data: row }) ?? "";
|
|
142
|
+
} catch {
|
|
143
|
+
return value ?? "";
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
const fieldLooksLikeDate = leaf.field.toLowerCase().includes("date");
|
|
147
|
+
if (options.formatDateFields && fieldLooksLikeDate && typeof options.formatDate === "function") {
|
|
148
|
+
try {
|
|
149
|
+
return options.formatDate(value) ?? "";
|
|
150
|
+
} catch {
|
|
151
|
+
return value ?? "";
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
if (value instanceof Date) return value;
|
|
155
|
+
if (value != null && typeof value === "object") return "";
|
|
156
|
+
const numFmt = leaf.column.excel?.numFmt;
|
|
157
|
+
if (numFmt) {
|
|
158
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
159
|
+
if (typeof value === "string" && value.trim() !== "" && Number.isFinite(Number(value))) {
|
|
160
|
+
return Number(value);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return value ?? "";
|
|
164
|
+
};
|
|
165
|
+
var localDateStamp = () => {
|
|
166
|
+
const date = /* @__PURE__ */ new Date();
|
|
167
|
+
const year = date.getFullYear();
|
|
168
|
+
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
169
|
+
const day = String(date.getDate()).padStart(2, "0");
|
|
170
|
+
return `${year}-${month}-${day}`;
|
|
171
|
+
};
|
|
172
|
+
var resolveDateStamp = (dateStamp) => {
|
|
173
|
+
if (dateStamp === null) return null;
|
|
174
|
+
if (typeof dateStamp === "string" && dateStamp.trim()) return dateStamp.trim();
|
|
175
|
+
return localDateStamp();
|
|
176
|
+
};
|
|
177
|
+
var safeSheetName = (name) => {
|
|
178
|
+
const cleaned = (name || "Report").replace(/[:\\/?*[\]]/g, " ").trim() || "Report";
|
|
179
|
+
return cleaned.slice(0, 31);
|
|
180
|
+
};
|
|
181
|
+
var buildHeaderGrid = (leaves) => {
|
|
182
|
+
const totalHeaderRows = Math.max(...leaves.map((leaf) => leaf.groups.length + 1), 1);
|
|
183
|
+
const headerRows = Array.from(
|
|
184
|
+
{ length: totalHeaderRows },
|
|
185
|
+
() => Array.from({ length: leaves.length }, () => "")
|
|
186
|
+
);
|
|
187
|
+
const merges = [];
|
|
188
|
+
for (let depth = 0; depth < totalHeaderRows - 1; depth += 1) {
|
|
189
|
+
let colIndex = 0;
|
|
190
|
+
while (colIndex < leaves.length) {
|
|
191
|
+
const group = leaves[colIndex].groups[depth];
|
|
192
|
+
if (!group) {
|
|
193
|
+
colIndex += 1;
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
let span = 1;
|
|
197
|
+
while (colIndex + span < leaves.length && leaves[colIndex + span].groups[depth] === group) {
|
|
198
|
+
span += 1;
|
|
199
|
+
}
|
|
200
|
+
headerRows[depth][colIndex] = group.name;
|
|
201
|
+
if (span > 1) {
|
|
202
|
+
merges.push({
|
|
203
|
+
s: { r: depth, c: colIndex },
|
|
204
|
+
e: { r: depth, c: colIndex + span - 1 }
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
colIndex += span;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
leaves.forEach((leaf, colIndex) => {
|
|
211
|
+
const row = leaf.groups.length;
|
|
212
|
+
headerRows[row][colIndex] = leaf.headerName;
|
|
213
|
+
if (row < totalHeaderRows - 1) {
|
|
214
|
+
merges.push({
|
|
215
|
+
s: { r: row, c: colIndex },
|
|
216
|
+
e: { r: totalHeaderRows - 1, c: colIndex }
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
return { headerRows, merges, totalHeaderRows };
|
|
221
|
+
};
|
|
222
|
+
var headerStyleFor = (leaf, row, theme) => {
|
|
223
|
+
const source = row < leaf.groups.length ? leaf.groups[row].excel : leaf.column.excel;
|
|
224
|
+
return excelHeaderStyle(theme, source);
|
|
225
|
+
};
|
|
226
|
+
var columnWidth = (leaf, values) => {
|
|
227
|
+
const headerLength = Math.max(
|
|
228
|
+
leaf.headerName.length,
|
|
229
|
+
...leaf.groups.map((group) => group.name.length),
|
|
230
|
+
0
|
|
231
|
+
);
|
|
232
|
+
const dataLength = values.reduce((max, value) => {
|
|
233
|
+
return Math.max(max, String(value ?? "").length);
|
|
234
|
+
}, 0);
|
|
235
|
+
return { wch: Math.min(Math.max(headerLength, dataLength, 12), 50) };
|
|
236
|
+
};
|
|
237
|
+
var buildExcelWorkbook = (options = {}) => {
|
|
238
|
+
const data = options.data;
|
|
239
|
+
if (!Array.isArray(data) || data.length === 0) return fail("empty");
|
|
240
|
+
const provided = Array.isArray(options.columns) ? options.columns : [];
|
|
241
|
+
const columns = provided.length ? provided : columnsFromRows(data);
|
|
242
|
+
if (!Array.isArray(columns)) return columns;
|
|
243
|
+
const leaves = collectLeaves(columns, [], options.skipHidden === true);
|
|
244
|
+
if (!leaves.length) return fail("no-columns");
|
|
245
|
+
const dateStamp = resolveDateStamp(options.dateStamp);
|
|
246
|
+
if (!dateStamp) return fail("filename");
|
|
247
|
+
const { headerRows, merges, totalHeaderRows } = buildHeaderGrid(leaves);
|
|
248
|
+
const records = data.map(
|
|
249
|
+
(row) => row && typeof row === "object" && !Array.isArray(row) ? row : {}
|
|
250
|
+
);
|
|
251
|
+
const dataRows = records.map(
|
|
252
|
+
(row) => leaves.map((leaf) => formatLeafValue(row, leaf, options))
|
|
253
|
+
);
|
|
254
|
+
const sheet = XLSX.utils.aoa_to_sheet([...headerRows, ...dataRows]);
|
|
255
|
+
const lastCell = XLSX.utils.encode_cell({
|
|
256
|
+
r: headerRows.length + dataRows.length - 1,
|
|
257
|
+
c: leaves.length - 1
|
|
258
|
+
});
|
|
259
|
+
sheet["!ref"] = `A1:${lastCell}`;
|
|
260
|
+
sheet["!merges"] = merges;
|
|
261
|
+
sheet["!cols"] = leaves.map(
|
|
262
|
+
(leaf, index) => columnWidth(
|
|
263
|
+
leaf,
|
|
264
|
+
dataRows.map((row) => row[index])
|
|
265
|
+
)
|
|
266
|
+
);
|
|
267
|
+
sheet["!rows"] = Array.from({ length: totalHeaderRows }, () => ({ hpt: 22 }));
|
|
268
|
+
const range = XLSX.utils.decode_range(sheet["!ref"]);
|
|
269
|
+
for (let row = range.s.r; row <= range.e.r; row += 1) {
|
|
270
|
+
for (let col = range.s.c; col <= range.e.c; col += 1) {
|
|
271
|
+
const address = XLSX.utils.encode_cell({ r: row, c: col });
|
|
272
|
+
const cell = sheet[address] ?? {
|
|
273
|
+
t: "s",
|
|
274
|
+
v: ""
|
|
275
|
+
};
|
|
276
|
+
const leaf = leaves[col];
|
|
277
|
+
if (row < totalHeaderRows) {
|
|
278
|
+
cell.s = headerStyleFor(leaf, row, options.theme);
|
|
279
|
+
} else {
|
|
280
|
+
const style = excelDataStyle(options.theme, leaf.column.excel);
|
|
281
|
+
if (leaf.column.excel?.numFmt) cell.z = leaf.column.excel.numFmt;
|
|
282
|
+
cell.s = style;
|
|
283
|
+
}
|
|
284
|
+
sheet[address] = cell;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
const sheetName = safeSheetName(options.sheetName);
|
|
288
|
+
const workbook = XLSX.utils.book_new();
|
|
289
|
+
XLSX.utils.book_append_sheet(workbook, sheet, sheetName);
|
|
290
|
+
const baseName = options.fileName == null ? "" : String(options.fileName);
|
|
291
|
+
return {
|
|
292
|
+
ok: true,
|
|
293
|
+
workbook,
|
|
294
|
+
sheetName,
|
|
295
|
+
fileName: `${baseName ? `${baseName}_` : ""}${dateStamp}.xlsx`
|
|
296
|
+
};
|
|
297
|
+
};
|
|
298
|
+
var exportExcelReport = (options = {}) => {
|
|
299
|
+
const built = buildExcelWorkbook(options);
|
|
300
|
+
if (!built.ok) return built;
|
|
301
|
+
try {
|
|
302
|
+
XLSX.writeFile(built.workbook, built.fileName, { cellStyles: true });
|
|
303
|
+
} catch (error) {
|
|
304
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
305
|
+
return { ok: false, code: "write", error: message };
|
|
306
|
+
}
|
|
307
|
+
return { ok: true, fileName: built.fileName };
|
|
308
|
+
};
|
|
309
|
+
export {
|
|
310
|
+
DEFAULT_EXCEL_THEME,
|
|
311
|
+
buildExcelWorkbook,
|
|
312
|
+
excelDataStyle,
|
|
313
|
+
excelHeaderStyle,
|
|
314
|
+
exportExcelReport
|
|
315
|
+
};
|
|
316
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/excel/index.ts"],"sourcesContent":["import * as XLSX from \"xlsx-js-style\";\r\n\r\nexport type ExcelAlign = \"left\" | \"center\" | \"right\";\r\n\r\n/** Per-column Excel colors. Hex is RRGGBB; a leading # is accepted. */\r\nexport type ExcelColumnStyle = {\r\n headerFill?: string;\r\n headerColor?: string;\r\n fill?: string;\r\n color?: string;\r\n bold?: boolean;\r\n align?: ExcelAlign;\r\n /** Excel number format, for example '\"$\"#,##0.00'. */\r\n numFmt?: string;\r\n};\r\n\r\nexport type ExcelTheme = {\r\n headerFill?: string;\r\n headerColor?: string;\r\n fill?: string;\r\n color?: string;\r\n borderColor?: string;\r\n fontName?: string;\r\n fontSize?: number;\r\n};\r\n\r\nexport type ExcelFormatParams = {\r\n value: unknown;\r\n data: Record<string, unknown>;\r\n};\r\n\r\nexport type ExcelExportColumn = {\r\n field?: string;\r\n headerName?: string;\r\n children?: ExcelExportColumn[];\r\n hide?: boolean;\r\n checkboxSelection?: boolean;\r\n headerCheckboxSelection?: boolean;\r\n valueFormatter?: (params: ExcelFormatParams) => unknown;\r\n format?: (params: ExcelFormatParams) => unknown;\r\n excel?: ExcelColumnStyle;\r\n};\r\n\r\nexport type ExportExcelErrorCode =\r\n | \"empty\"\r\n | \"invalid\"\r\n | \"no-columns\"\r\n | \"filename\"\r\n | \"write\";\r\n\r\nexport type ExportExcelFailure = {\r\n ok: false;\r\n code: ExportExcelErrorCode;\r\n error: string;\r\n};\r\n\r\nexport type ExportExcelReportResult =\r\n | { ok: true; fileName: string }\r\n | ExportExcelFailure;\r\n\r\nexport type ExportExcelReportOptions = {\r\n data?: unknown[] | null;\r\n fileName?: string;\r\n columns?: ExcelExportColumn[];\r\n sheetName?: string;\r\n theme?: ExcelTheme;\r\n /** Drop columns with hide: true. Default false, matching vault grids. */\r\n skipHidden?: boolean;\r\n /**\r\n * Format leaf fields whose name contains \"date\" when they have no formatter.\r\n * Vault turns this on and passes its own date formatter.\r\n */\r\n formatDateFields?: boolean;\r\n formatDate?: (value: unknown) => unknown;\r\n /**\r\n * Date suffix for the file name. Omit to use the local YYYY-MM-DD.\r\n * Pass null when the caller could not produce a stamp.\r\n */\r\n dateStamp?: string | null;\r\n};\r\n\r\ntype ResolvedTheme = {\r\n headerFill: string;\r\n headerColor: string;\r\n fill: string;\r\n color: string;\r\n borderColor: string;\r\n fontName: string;\r\n fontSize: number;\r\n};\r\n\r\nexport type ExcelCellStyle = {\r\n font: {\r\n name: string;\r\n sz: number;\r\n bold?: boolean;\r\n color: { rgb: string };\r\n };\r\n fill: { patternType: \"solid\"; fgColor: { rgb: string } };\r\n alignment: {\r\n vertical: \"center\";\r\n horizontal: ExcelAlign;\r\n wrapText: false;\r\n };\r\n border: {\r\n top: { style: \"thin\"; color: { rgb: string } };\r\n bottom: { style: \"thin\"; color: { rgb: string } };\r\n left: { style: \"thin\"; color: { rgb: string } };\r\n right: { style: \"thin\"; color: { rgb: string } };\r\n };\r\n numFmt?: string;\r\n};\r\n\r\ntype HeaderGroup = {\r\n name: string;\r\n excel?: ExcelColumnStyle;\r\n};\r\n\r\ntype LeafColumn = {\r\n field: string;\r\n headerName: string;\r\n column: ExcelExportColumn;\r\n groups: HeaderGroup[];\r\n};\r\n\r\ntype WorkBook = {\r\n SheetNames: string[];\r\n Sheets: Record<string, WorkSheet>;\r\n};\r\n\r\ntype WorkSheet = Record<string, unknown>;\r\n\r\nconst ERRORS: Record<Exclude<ExportExcelErrorCode, \"write\">, string> = {\r\n empty: \"No valid data to download. Please ensure data is available.\",\r\n invalid: \"Invalid data structure. Please ensure data is correctly formatted.\",\r\n \"no-columns\": \"No data to export. Please check the data source.\",\r\n filename: \"Failed to generate file name. Please try again.\",\r\n};\r\n\r\nexport const DEFAULT_EXCEL_THEME: ResolvedTheme = {\r\n headerFill: \"2F75B6\",\r\n headerColor: \"FFFFFF\",\r\n fill: \"FFFFFF\",\r\n color: \"1F2933\",\r\n borderColor: \"8FA4BF\",\r\n fontName: \"Calibri\",\r\n fontSize: 11,\r\n};\r\n\r\nconst fail = (\r\n code: Exclude<ExportExcelErrorCode, \"write\">,\r\n): ExportExcelFailure => ({\r\n ok: false,\r\n code,\r\n error: ERRORS[code],\r\n});\r\n\r\nconst rgb = (input: string | undefined, fallback: string): string => {\r\n if (!input) return fallback;\r\n const hex = input.trim().replace(/^#/, \"\").toUpperCase();\r\n return /^[0-9A-F]{6}$/.test(hex) ? hex : fallback;\r\n};\r\n\r\nconst resolveTheme = (theme?: ExcelTheme): ResolvedTheme => ({\r\n headerFill: rgb(theme?.headerFill, DEFAULT_EXCEL_THEME.headerFill),\r\n headerColor: rgb(theme?.headerColor, DEFAULT_EXCEL_THEME.headerColor),\r\n fill: rgb(theme?.fill, DEFAULT_EXCEL_THEME.fill),\r\n color: rgb(theme?.color, DEFAULT_EXCEL_THEME.color),\r\n borderColor: rgb(theme?.borderColor, DEFAULT_EXCEL_THEME.borderColor),\r\n fontName: theme?.fontName?.trim() || DEFAULT_EXCEL_THEME.fontName,\r\n fontSize:\r\n typeof theme?.fontSize === \"number\" && theme.fontSize > 0\r\n ? theme.fontSize\r\n : DEFAULT_EXCEL_THEME.fontSize,\r\n});\r\n\r\nconst gridBorder = (color: string): ExcelCellStyle[\"border\"] => {\r\n const edge = () => ({ style: \"thin\" as const, color: { rgb: color } });\r\n return { top: edge(), bottom: edge(), left: edge(), right: edge() };\r\n};\r\n\r\nexport const excelHeaderStyle = (\r\n theme?: ExcelTheme,\r\n column?: ExcelColumnStyle,\r\n): ExcelCellStyle => {\r\n const resolved = resolveTheme(theme);\r\n return {\r\n font: {\r\n name: resolved.fontName,\r\n sz: resolved.fontSize,\r\n bold: true,\r\n color: { rgb: rgb(column?.headerColor, resolved.headerColor) },\r\n },\r\n fill: {\r\n patternType: \"solid\",\r\n fgColor: { rgb: rgb(column?.headerFill, resolved.headerFill) },\r\n },\r\n alignment: {\r\n vertical: \"center\",\r\n horizontal: column?.align ?? \"left\",\r\n wrapText: false,\r\n },\r\n border: gridBorder(resolved.borderColor),\r\n };\r\n};\r\n\r\nexport const excelDataStyle = (\r\n theme?: ExcelTheme,\r\n column?: ExcelColumnStyle,\r\n): ExcelCellStyle => {\r\n const resolved = resolveTheme(theme);\r\n const font: ExcelCellStyle[\"font\"] = {\r\n name: resolved.fontName,\r\n sz: resolved.fontSize,\r\n color: { rgb: rgb(column?.color, resolved.color) },\r\n };\r\n if (column?.bold) font.bold = true;\r\n const style: ExcelCellStyle = {\r\n font,\r\n fill: {\r\n patternType: \"solid\",\r\n fgColor: { rgb: rgb(column?.fill, resolved.fill) },\r\n },\r\n alignment: {\r\n vertical: \"center\",\r\n horizontal: column?.align ?? \"left\",\r\n wrapText: false,\r\n },\r\n border: gridBorder(resolved.borderColor),\r\n };\r\n if (column?.numFmt) style.numFmt = column.numFmt;\r\n return style;\r\n};\r\n\r\nconst getNestedValue = (obj: unknown, path: string): unknown => {\r\n if (!obj || typeof obj !== \"object\") return \"\";\r\n try {\r\n const value = path.split(\".\").reduce<unknown>((acc, key) => {\r\n if (acc == null || typeof acc !== \"object\") return undefined;\r\n return (acc as Record<string, unknown>)[key];\r\n }, obj);\r\n return value ?? \"\";\r\n } catch {\r\n return \"\";\r\n }\r\n};\r\n\r\nconst isGroup = (col: ExcelExportColumn): boolean =>\r\n Array.isArray(col.children) && col.children.length > 0;\r\n\r\nconst isSkippedLeaf = (col: ExcelExportColumn): boolean => {\r\n if (col.checkboxSelection || col.headerCheckboxSelection) return true;\r\n const field = typeof col.field === \"string\" ? col.field.trim() : \"\";\r\n return !field || field === \"checkbox\" || field === \"checkboxSelection\";\r\n};\r\n\r\nconst collectLeaves = (\r\n cols: ExcelExportColumn[],\r\n groups: HeaderGroup[],\r\n skipHidden: boolean,\r\n): LeafColumn[] => {\r\n const leaves: LeafColumn[] = [];\r\n cols.forEach((col) => {\r\n if (!col || typeof col !== \"object\") return;\r\n if (skipHidden && col.hide) return;\r\n if (isGroup(col)) {\r\n const group: HeaderGroup = {\r\n name: col.headerName || \"\",\r\n excel: col.excel,\r\n };\r\n leaves.push(...collectLeaves(col.children ?? [], [...groups, group], skipHidden));\r\n return;\r\n }\r\n if (isSkippedLeaf(col)) return;\r\n const field = col.field!.trim();\r\n leaves.push({\r\n field,\r\n headerName: (col.headerName || field).trim() || field,\r\n column: col,\r\n groups,\r\n });\r\n });\r\n return leaves;\r\n};\r\n\r\nconst labelFromKey = (key: string): string =>\r\n key\r\n .replace(/([A-Z])/g, \" $1\")\r\n .replace(/^\\w/, (char) => char.toUpperCase())\r\n .trim();\r\n\r\nconst columnsFromRows = (rows: unknown[]): ExcelExportColumn[] | ExportExcelFailure => {\r\n const first = rows[0];\r\n if (!first || typeof first !== \"object\" || Array.isArray(first)) {\r\n return fail(\"invalid\");\r\n }\r\n return Object.keys(first)\r\n .filter((key) => key !== \"checkbox\" && key !== \"checkboxSelection\" && key.trim())\r\n .map((key) => ({ field: key, headerName: labelFromKey(key) }));\r\n};\r\n\r\nconst formatLeafValue = (\r\n row: Record<string, unknown>,\r\n leaf: LeafColumn,\r\n options: ExportExcelReportOptions,\r\n): unknown => {\r\n let value = getNestedValue(row, leaf.field);\r\n const formatter = leaf.column.format || leaf.column.valueFormatter;\r\n if (typeof formatter === \"function\") {\r\n try {\r\n return formatter({ value, data: row }) ?? \"\";\r\n } catch {\r\n return value ?? \"\";\r\n }\r\n }\r\n\r\n const fieldLooksLikeDate = leaf.field.toLowerCase().includes(\"date\");\r\n if (options.formatDateFields && fieldLooksLikeDate && typeof options.formatDate === \"function\") {\r\n try {\r\n return options.formatDate(value) ?? \"\";\r\n } catch {\r\n return value ?? \"\";\r\n }\r\n }\r\n\r\n if (value instanceof Date) return value;\r\n if (value != null && typeof value === \"object\") return \"\";\r\n\r\n const numFmt = leaf.column.excel?.numFmt;\r\n if (numFmt) {\r\n if (typeof value === \"number\" && Number.isFinite(value)) return value;\r\n if (typeof value === \"string\" && value.trim() !== \"\" && Number.isFinite(Number(value))) {\r\n return Number(value);\r\n }\r\n }\r\n\r\n return value ?? \"\";\r\n};\r\n\r\nconst localDateStamp = (): string => {\r\n const date = new Date();\r\n const year = date.getFullYear();\r\n const month = String(date.getMonth() + 1).padStart(2, \"0\");\r\n const day = String(date.getDate()).padStart(2, \"0\");\r\n return `${year}-${month}-${day}`;\r\n};\r\n\r\nconst resolveDateStamp = (dateStamp: string | null | undefined): string | null => {\r\n if (dateStamp === null) return null;\r\n if (typeof dateStamp === \"string\" && dateStamp.trim()) return dateStamp.trim();\r\n return localDateStamp();\r\n};\r\n\r\nconst safeSheetName = (name: string | undefined): string => {\r\n const cleaned = (name || \"Report\").replace(/[:\\\\/?*[\\]]/g, \" \").trim() || \"Report\";\r\n return cleaned.slice(0, 31);\r\n};\r\n\r\nconst buildHeaderGrid = (leaves: LeafColumn[]) => {\r\n const totalHeaderRows = Math.max(...leaves.map((leaf) => leaf.groups.length + 1), 1);\r\n const headerRows = Array.from({ length: totalHeaderRows }, () =>\r\n Array.from({ length: leaves.length }, () => \"\"),\r\n );\r\n const merges: { s: { r: number; c: number }; e: { r: number; c: number } }[] = [];\r\n\r\n for (let depth = 0; depth < totalHeaderRows - 1; depth += 1) {\r\n let colIndex = 0;\r\n while (colIndex < leaves.length) {\r\n const group = leaves[colIndex].groups[depth];\r\n if (!group) {\r\n colIndex += 1;\r\n continue;\r\n }\r\n let span = 1;\r\n while (colIndex + span < leaves.length && leaves[colIndex + span].groups[depth] === group) {\r\n span += 1;\r\n }\r\n headerRows[depth][colIndex] = group.name;\r\n if (span > 1) {\r\n merges.push({\r\n s: { r: depth, c: colIndex },\r\n e: { r: depth, c: colIndex + span - 1 },\r\n });\r\n }\r\n colIndex += span;\r\n }\r\n }\r\n\r\n leaves.forEach((leaf, colIndex) => {\r\n const row = leaf.groups.length;\r\n headerRows[row][colIndex] = leaf.headerName;\r\n if (row < totalHeaderRows - 1) {\r\n merges.push({\r\n s: { r: row, c: colIndex },\r\n e: { r: totalHeaderRows - 1, c: colIndex },\r\n });\r\n }\r\n });\r\n\r\n return { headerRows, merges, totalHeaderRows };\r\n};\r\n\r\nconst headerStyleFor = (\r\n leaf: LeafColumn,\r\n row: number,\r\n theme: ExcelTheme | undefined,\r\n): ExcelCellStyle => {\r\n const source = row < leaf.groups.length ? leaf.groups[row].excel : leaf.column.excel;\r\n return excelHeaderStyle(theme, source);\r\n};\r\n\r\nconst columnWidth = (leaf: LeafColumn, values: unknown[]): { wch: number } => {\r\n const headerLength = Math.max(\r\n leaf.headerName.length,\r\n ...leaf.groups.map((group) => group.name.length),\r\n 0,\r\n );\r\n const dataLength = values.reduce<number>((max, value) => {\r\n return Math.max(max, String(value ?? \"\").length);\r\n }, 0);\r\n return { wch: Math.min(Math.max(headerLength, dataLength, 12), 50) };\r\n};\r\n\r\nexport type BuiltExcelWorkbook =\r\n | { ok: true; workbook: WorkBook; fileName: string; sheetName: string }\r\n | ExportExcelFailure;\r\n\r\n/**\r\n * Build a styled workbook. Does not write a file.\r\n * Header merges follow the column tree, so two groups with the same title stay separate.\r\n */\r\nexport const buildExcelWorkbook = (\r\n options: ExportExcelReportOptions = {},\r\n): BuiltExcelWorkbook => {\r\n const data = options.data;\r\n if (!Array.isArray(data) || data.length === 0) return fail(\"empty\");\r\n\r\n const provided = Array.isArray(options.columns) ? options.columns : [];\r\n const columns = provided.length\r\n ? provided\r\n : columnsFromRows(data);\r\n if (!Array.isArray(columns)) return columns;\r\n\r\n const leaves = collectLeaves(columns, [], options.skipHidden === true);\r\n if (!leaves.length) return fail(\"no-columns\");\r\n\r\n const dateStamp = resolveDateStamp(options.dateStamp);\r\n if (!dateStamp) return fail(\"filename\");\r\n\r\n const { headerRows, merges, totalHeaderRows } = buildHeaderGrid(leaves);\r\n const records = data.map((row) =>\r\n row && typeof row === \"object\" && !Array.isArray(row)\r\n ? (row as Record<string, unknown>)\r\n : {},\r\n );\r\n const dataRows = records.map((row) =>\r\n leaves.map((leaf) => formatLeafValue(row, leaf, options)),\r\n );\r\n\r\n const sheet = XLSX.utils.aoa_to_sheet([...headerRows, ...dataRows]) as WorkSheet;\r\n const lastCell = XLSX.utils.encode_cell({\r\n r: headerRows.length + dataRows.length - 1,\r\n c: leaves.length - 1,\r\n });\r\n sheet[\"!ref\"] = `A1:${lastCell}`;\r\n sheet[\"!merges\"] = merges;\r\n sheet[\"!cols\"] = leaves.map((leaf, index) =>\r\n columnWidth(\r\n leaf,\r\n dataRows.map((row) => row[index]),\r\n ),\r\n );\r\n sheet[\"!rows\"] = Array.from({ length: totalHeaderRows }, () => ({ hpt: 22 }));\r\n\r\n const range = XLSX.utils.decode_range(sheet[\"!ref\"] as string);\r\n for (let row = range.s.r; row <= range.e.r; row += 1) {\r\n for (let col = range.s.c; col <= range.e.c; col += 1) {\r\n const address = XLSX.utils.encode_cell({ r: row, c: col });\r\n const cell = (sheet[address] as { t?: string; v?: unknown; z?: string; s?: ExcelCellStyle } | undefined) ?? {\r\n t: \"s\",\r\n v: \"\",\r\n };\r\n const leaf = leaves[col];\r\n if (row < totalHeaderRows) {\r\n cell.s = headerStyleFor(leaf, row, options.theme);\r\n } else {\r\n const style = excelDataStyle(options.theme, leaf.column.excel);\r\n if (leaf.column.excel?.numFmt) cell.z = leaf.column.excel.numFmt;\r\n cell.s = style;\r\n }\r\n sheet[address] = cell;\r\n }\r\n }\r\n\r\n const sheetName = safeSheetName(options.sheetName);\r\n const workbook = XLSX.utils.book_new() as WorkBook;\r\n XLSX.utils.book_append_sheet(workbook, sheet, sheetName);\r\n\r\n const baseName = options.fileName == null ? \"\" : String(options.fileName);\r\n return {\r\n ok: true,\r\n workbook,\r\n sheetName,\r\n fileName: `${baseName ? `${baseName}_` : \"\"}${dateStamp}.xlsx`,\r\n };\r\n};\r\n\r\n/** Write a styled .xlsx. Returns a result; it does not toast. */\r\nexport const exportExcelReport = (\r\n options: ExportExcelReportOptions = {},\r\n): ExportExcelReportResult => {\r\n const built = buildExcelWorkbook(options);\r\n if (!built.ok) return built;\r\n try {\r\n XLSX.writeFile(built.workbook, built.fileName, { cellStyles: true });\r\n } catch (error) {\r\n const message = error instanceof Error ? error.message : \"Unknown error\";\r\n return { ok: false, code: \"write\", error: message };\r\n }\r\n return { ok: true, fileName: built.fileName };\r\n};\r\n"],"mappings":"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","names":[]}
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { CreateBaseQueryWithRetryOptions, PermissionRecord, TransformedPermissions, USER_STATE_STORAGE_KEY, capitalizeText, checkIfExistsInArray, cn, convertCommaDataToArray, createBaseQueryWithRetry, currencyFormatter, deserializeJson, flattenTableData, formatDate, formatDateTime, formatNumber, get, getApiError, getErrorMessages, getFieldsByCategory, getFieldsByInputType, getFieldsByName, getItem, getItemFromLocalStorage, isEmpty, loadUserState, normalizeApiListItems, parseCurrencyValueToNumber, removeByPropertyValues, removeItem, resetPageParam, saveItemLocalStorage, saveUserState, set, setItem, storeItemInLocalStorage, toDateInputValue, toLowerCaseText, transformPermissions, truncateFileName, truncateText } from './utils/index.mjs';
|
|
1
|
+
export { CreateBaseQueryWithRetryOptions, CurrencyFormatterOptions, DateUtcMode, FormatDateOptions, PermissionRecord, TransformedPermissions, USER_STATE_STORAGE_KEY, capitalizeText, checkIfExistsInArray, cn, convertCommaDataToArray, createBaseQueryWithRetry, currencyFormatter, deserializeJson, flattenTableData, formatDate, formatDateTime, formatNumber, get, getApiError, getErrorMessages, getFieldsByCategory, getFieldsByInputType, getFieldsByName, getItem, getItemFromLocalStorage, isEmpty, loadUserState, normalizeApiListItems, parseCurrencyValueToNumber, removeByPropertyValues, removeItem, resetPageParam, saveItemLocalStorage, saveUserState, set, setItem, storeItemInLocalStorage, toDateInputValue, toLowerCaseText, transformPermissions, truncateFileName, truncateText } from './utils/index.mjs';
|
|
2
2
|
export { C as ConfirmationAlertOptions, a as ConfirmationAlertVariant, D as DEFAULT_CONFIRM_ICONS, E as ErrorClassification, b as ErrorSuggestedAction, H as HandleApiErrorOptions, R as RtkQueryError, c as classifyError, d as confirmationAlert, f as formatValidationErrorsHtml, h as handleApiError, i as isRetryableError } from './handleApiError-BIAaN76q.mjs';
|
|
3
3
|
export { UseGridHeightOptions, useGridHeight, usePaginationUrlSync } from './hooks/index.mjs';
|
|
4
4
|
export { ActionMenuItem, ActionMenuItemProps, BreadcrumbRoute, Breadcrumbs, BreadcrumbsProps, Button, ButtonProps, Card, CardProps, CardSkeleton, CardSkeletonProps, CardSkeletonVariant, CellEditPayload, CellEditorInputType, CustomCellEditor, CustomCellEditorProps, CustomCellEditorRef, CustomPagination, CustomPaginationClassNames, CustomPaginationProps, DropdownFilter, DropdownFilterProps, DropdownOption, EztrakTab, EztrakTabs, EztrakTabsClassNames, EztrakTabsProps, Header, HeaderProps, LayoutStatus, Loader, LoaderProps, Modal, ModalProps, PaginationData, PopupActionHandler, PopupCellRenderer, PopupCellRendererParams, PopupCellRendererProps, PopupDropDownContent, ROW_ACTION_DEFAULT_ICON_SIZE, ROW_ACTION_DEFAULT_MENU_TOOLTIP_STYLE, ROW_ACTION_ITEM_HOVER_CLASS, ROW_ACTION_ITEM_TEXT_CLASS, ROW_ACTION_PRESETS, ResetColumnsButton, ResetColumnsButtonProps, ResetFiltersButton, ResetFiltersButtonProps, ResetFiltersButtonVariant, ResolvedRowAction, RowAction, RowActionConfirmFn, RowActionFlag, RowActionItemRenderContext, RowActionItemType, RowActionPreset, RowActionPresetName, RowActions, RowActionsList, RowActionsMode, RowActionsProps, RowActionsRenderContext, SearchInput, SearchInputProps, Sidebar, SidebarItem, SidebarProps, TableErrorBoundary, TableErrorBoundaryProps, TableLayoutToolbarControls, TableLayoutToolbarControlsProps, ToolTip, ToolTipProps, TooltipText, TooltipTextProps, dropdownStyles, resolveActions } from './components/index.mjs';
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { CreateBaseQueryWithRetryOptions, PermissionRecord, TransformedPermissions, USER_STATE_STORAGE_KEY, capitalizeText, checkIfExistsInArray, cn, convertCommaDataToArray, createBaseQueryWithRetry, currencyFormatter, deserializeJson, flattenTableData, formatDate, formatDateTime, formatNumber, get, getApiError, getErrorMessages, getFieldsByCategory, getFieldsByInputType, getFieldsByName, getItem, getItemFromLocalStorage, isEmpty, loadUserState, normalizeApiListItems, parseCurrencyValueToNumber, removeByPropertyValues, removeItem, resetPageParam, saveItemLocalStorage, saveUserState, set, setItem, storeItemInLocalStorage, toDateInputValue, toLowerCaseText, transformPermissions, truncateFileName, truncateText } from './utils/index.js';
|
|
1
|
+
export { CreateBaseQueryWithRetryOptions, CurrencyFormatterOptions, DateUtcMode, FormatDateOptions, PermissionRecord, TransformedPermissions, USER_STATE_STORAGE_KEY, capitalizeText, checkIfExistsInArray, cn, convertCommaDataToArray, createBaseQueryWithRetry, currencyFormatter, deserializeJson, flattenTableData, formatDate, formatDateTime, formatNumber, get, getApiError, getErrorMessages, getFieldsByCategory, getFieldsByInputType, getFieldsByName, getItem, getItemFromLocalStorage, isEmpty, loadUserState, normalizeApiListItems, parseCurrencyValueToNumber, removeByPropertyValues, removeItem, resetPageParam, saveItemLocalStorage, saveUserState, set, setItem, storeItemInLocalStorage, toDateInputValue, toLowerCaseText, transformPermissions, truncateFileName, truncateText } from './utils/index.js';
|
|
2
2
|
export { C as ConfirmationAlertOptions, a as ConfirmationAlertVariant, D as DEFAULT_CONFIRM_ICONS, E as ErrorClassification, b as ErrorSuggestedAction, H as HandleApiErrorOptions, R as RtkQueryError, c as classifyError, d as confirmationAlert, f as formatValidationErrorsHtml, h as handleApiError, i as isRetryableError } from './handleApiError-BIAaN76q.js';
|
|
3
3
|
export { UseGridHeightOptions, useGridHeight, usePaginationUrlSync } from './hooks/index.js';
|
|
4
4
|
export { ActionMenuItem, ActionMenuItemProps, BreadcrumbRoute, Breadcrumbs, BreadcrumbsProps, Button, ButtonProps, Card, CardProps, CardSkeleton, CardSkeletonProps, CardSkeletonVariant, CellEditPayload, CellEditorInputType, CustomCellEditor, CustomCellEditorProps, CustomCellEditorRef, CustomPagination, CustomPaginationClassNames, CustomPaginationProps, DropdownFilter, DropdownFilterProps, DropdownOption, EztrakTab, EztrakTabs, EztrakTabsClassNames, EztrakTabsProps, Header, HeaderProps, LayoutStatus, Loader, LoaderProps, Modal, ModalProps, PaginationData, PopupActionHandler, PopupCellRenderer, PopupCellRendererParams, PopupCellRendererProps, PopupDropDownContent, ROW_ACTION_DEFAULT_ICON_SIZE, ROW_ACTION_DEFAULT_MENU_TOOLTIP_STYLE, ROW_ACTION_ITEM_HOVER_CLASS, ROW_ACTION_ITEM_TEXT_CLASS, ROW_ACTION_PRESETS, ResetColumnsButton, ResetColumnsButtonProps, ResetFiltersButton, ResetFiltersButtonProps, ResetFiltersButtonVariant, ResolvedRowAction, RowAction, RowActionConfirmFn, RowActionFlag, RowActionItemRenderContext, RowActionItemType, RowActionPreset, RowActionPresetName, RowActions, RowActionsList, RowActionsMode, RowActionsProps, RowActionsRenderContext, SearchInput, SearchInputProps, Sidebar, SidebarItem, SidebarProps, TableErrorBoundary, TableErrorBoundaryProps, TableLayoutToolbarControls, TableLayoutToolbarControlsProps, ToolTip, ToolTipProps, TooltipText, TooltipTextProps, dropdownStyles, resolveActions } from './components/index.js';
|
package/dist/index.js
CHANGED
|
@@ -122,6 +122,118 @@ function resetPageParam(params, pageKey = "page", defaultPage = "1") {
|
|
|
122
122
|
return params;
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
// src/utils/format-date.ts
|
|
126
|
+
var DEFAULT_DATE_FALLBACK = "-";
|
|
127
|
+
var DEFAULT_DATETIME_FALLBACK = "Invalid Date";
|
|
128
|
+
var pad2 = (n) => n.toString().padStart(2, "0");
|
|
129
|
+
var getParts = (date, utc) => {
|
|
130
|
+
if (utc === "preserve") {
|
|
131
|
+
return {
|
|
132
|
+
year: date.getUTCFullYear(),
|
|
133
|
+
month: date.getUTCMonth() + 1,
|
|
134
|
+
day: date.getUTCDate(),
|
|
135
|
+
hours: date.getUTCHours(),
|
|
136
|
+
minutes: date.getUTCMinutes(),
|
|
137
|
+
seconds: date.getUTCSeconds()
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
year: date.getFullYear(),
|
|
142
|
+
month: date.getMonth() + 1,
|
|
143
|
+
day: date.getDate(),
|
|
144
|
+
hours: date.getHours(),
|
|
145
|
+
minutes: date.getMinutes(),
|
|
146
|
+
seconds: date.getSeconds()
|
|
147
|
+
};
|
|
148
|
+
};
|
|
149
|
+
var formatAmPmTime = (parts) => {
|
|
150
|
+
const hour12 = parts.hours % 12 || 12;
|
|
151
|
+
const ampm = parts.hours >= 12 ? "PM" : "AM";
|
|
152
|
+
return `${hour12}:${pad2(parts.minutes)} ${ampm}`;
|
|
153
|
+
};
|
|
154
|
+
var formatMmDdYy = (parts) => `${pad2(parts.month)}/${pad2(parts.day)}/${String(parts.year).slice(-2)}`;
|
|
155
|
+
var applyPattern = (pattern, parts) => {
|
|
156
|
+
const hour12 = parts.hours % 12 || 12;
|
|
157
|
+
const ampm = parts.hours >= 12 ? "PM" : "AM";
|
|
158
|
+
const replacements = [
|
|
159
|
+
["yyyy", String(parts.year)],
|
|
160
|
+
["yy", String(parts.year).slice(-2)],
|
|
161
|
+
["MM", pad2(parts.month)],
|
|
162
|
+
["dd", pad2(parts.day)],
|
|
163
|
+
["HH", pad2(parts.hours)],
|
|
164
|
+
["hh", pad2(hour12)],
|
|
165
|
+
["mm", pad2(parts.minutes)],
|
|
166
|
+
["ss", pad2(parts.seconds)],
|
|
167
|
+
["a", ampm]
|
|
168
|
+
];
|
|
169
|
+
let result = "";
|
|
170
|
+
let i = 0;
|
|
171
|
+
while (i < pattern.length) {
|
|
172
|
+
let matched = false;
|
|
173
|
+
for (const [token, value] of replacements) {
|
|
174
|
+
if (pattern.startsWith(token, i)) {
|
|
175
|
+
result += value;
|
|
176
|
+
i += token.length;
|
|
177
|
+
matched = true;
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if (!matched) {
|
|
182
|
+
result += pattern[i];
|
|
183
|
+
i += 1;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return result;
|
|
187
|
+
};
|
|
188
|
+
var parseDate = (dateString) => {
|
|
189
|
+
if (dateString === null || dateString === void 0) return null;
|
|
190
|
+
const date = new Date(dateString);
|
|
191
|
+
if (isNaN(date.getTime())) return null;
|
|
192
|
+
return date;
|
|
193
|
+
};
|
|
194
|
+
var formatDate = (dateString, options = {}) => {
|
|
195
|
+
const {
|
|
196
|
+
pattern,
|
|
197
|
+
utc = "local",
|
|
198
|
+
includeTime = false,
|
|
199
|
+
fallback = DEFAULT_DATE_FALLBACK
|
|
200
|
+
} = options;
|
|
201
|
+
const date = parseDate(dateString);
|
|
202
|
+
if (!date) return fallback;
|
|
203
|
+
const parts = getParts(date, utc);
|
|
204
|
+
if (pattern) {
|
|
205
|
+
return applyPattern(pattern, parts);
|
|
206
|
+
}
|
|
207
|
+
const datePart = formatMmDdYy(parts);
|
|
208
|
+
if (!includeTime) return datePart;
|
|
209
|
+
return `${datePart}, ${formatAmPmTime(parts)}`;
|
|
210
|
+
};
|
|
211
|
+
var formatDateTime = (dateString, options = {}) => {
|
|
212
|
+
const {
|
|
213
|
+
pattern,
|
|
214
|
+
utc = "local",
|
|
215
|
+
includeTime = true,
|
|
216
|
+
fallback = DEFAULT_DATETIME_FALLBACK
|
|
217
|
+
} = options;
|
|
218
|
+
const date = parseDate(dateString);
|
|
219
|
+
if (!date) return fallback;
|
|
220
|
+
if (pattern) {
|
|
221
|
+
return applyPattern(pattern, getParts(date, utc));
|
|
222
|
+
}
|
|
223
|
+
const localeOptions = {
|
|
224
|
+
year: "numeric",
|
|
225
|
+
month: "numeric",
|
|
226
|
+
day: "numeric",
|
|
227
|
+
...includeTime ? {
|
|
228
|
+
hour: "numeric",
|
|
229
|
+
minute: "numeric",
|
|
230
|
+
hour12: true
|
|
231
|
+
} : {},
|
|
232
|
+
...utc === "preserve" ? { timeZone: "UTC" } : {}
|
|
233
|
+
};
|
|
234
|
+
return date.toLocaleString("en-US", localeOptions);
|
|
235
|
+
};
|
|
236
|
+
|
|
125
237
|
// src/utils/helpers.ts
|
|
126
238
|
var truncateText = (text, maxLength = 20) => {
|
|
127
239
|
if (text === null || text === void 0) return "";
|
|
@@ -196,17 +308,6 @@ function toLowerCaseText(text) {
|
|
|
196
308
|
if (!text) return "";
|
|
197
309
|
return text.toLowerCase();
|
|
198
310
|
}
|
|
199
|
-
var formatDate = (dateString) => {
|
|
200
|
-
if (dateString === null || dateString === void 0) return "-";
|
|
201
|
-
const date = new Date(dateString);
|
|
202
|
-
if (isNaN(date.getTime())) {
|
|
203
|
-
return dateString;
|
|
204
|
-
}
|
|
205
|
-
const month = (date.getMonth() + 1).toString().padStart(2, "0");
|
|
206
|
-
const day = date.getDate().toString().padStart(2, "0");
|
|
207
|
-
const year = date.getFullYear().toString().slice(-2);
|
|
208
|
-
return `${month}/${day}/${year}`;
|
|
209
|
-
};
|
|
210
311
|
var isEmpty = (obj) => {
|
|
211
312
|
return Object.keys(obj).length === 0;
|
|
212
313
|
};
|
|
@@ -237,11 +338,30 @@ function convertCommaDataToArray(data) {
|
|
|
237
338
|
function checkIfExistsInArray(array, searchValue) {
|
|
238
339
|
return array.includes(searchValue);
|
|
239
340
|
}
|
|
240
|
-
var currencyFormatter = (value) => {
|
|
241
|
-
return
|
|
341
|
+
var currencyFormatter = (value, options) => {
|
|
342
|
+
if (value == null) return "";
|
|
343
|
+
const {
|
|
344
|
+
currency = "USD",
|
|
345
|
+
locale = "en-US",
|
|
346
|
+
hideSymbol = false,
|
|
347
|
+
minimumFractionDigits,
|
|
348
|
+
maximumFractionDigits
|
|
349
|
+
} = options ?? {};
|
|
350
|
+
const fractionDigits = {
|
|
351
|
+
...minimumFractionDigits != null && { minimumFractionDigits },
|
|
352
|
+
...maximumFractionDigits != null && { maximumFractionDigits }
|
|
353
|
+
};
|
|
354
|
+
if (hideSymbol) {
|
|
355
|
+
return value.toLocaleString(locale, {
|
|
356
|
+
minimumFractionDigits: minimumFractionDigits ?? 2,
|
|
357
|
+
maximumFractionDigits: maximumFractionDigits ?? 2
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
return value.toLocaleString(locale, {
|
|
242
361
|
style: "currency",
|
|
243
|
-
currency
|
|
244
|
-
|
|
362
|
+
currency,
|
|
363
|
+
...fractionDigits
|
|
364
|
+
});
|
|
245
365
|
};
|
|
246
366
|
var formatNumber = (number) => {
|
|
247
367
|
if (isNaN(Number(number)) || number === null || number === void 0) {
|
|
@@ -295,20 +415,6 @@ var deserializeJson = (jsonString) => {
|
|
|
295
415
|
return null;
|
|
296
416
|
}
|
|
297
417
|
};
|
|
298
|
-
var formatDateTime = (dateString) => {
|
|
299
|
-
if (!dateString) return "Invalid Date";
|
|
300
|
-
const date = new Date(dateString);
|
|
301
|
-
if (isNaN(date.getTime())) return "Invalid Date";
|
|
302
|
-
const options = {
|
|
303
|
-
year: "numeric",
|
|
304
|
-
month: "numeric",
|
|
305
|
-
day: "numeric",
|
|
306
|
-
hour: "numeric",
|
|
307
|
-
minute: "numeric",
|
|
308
|
-
hour12: true
|
|
309
|
-
};
|
|
310
|
-
return date.toLocaleString("en-US", options);
|
|
311
|
-
};
|
|
312
418
|
var removeByPropertyValues = (items = [], property, values = [], { caseInsensitive = false } = {}) => {
|
|
313
419
|
if (!Array.isArray(items) || !property) return Array.isArray(items) ? items : [];
|
|
314
420
|
const normalize = (v) => caseInsensitive && typeof v === "string" ? v.toLowerCase() : v;
|