Skip to content

Commit 638b123

Browse files
feat(datagrid-web): send formatted cells instead of plain data to excel export
1 parent d0fec68 commit 638b123

File tree

1 file changed

+59
-14
lines changed

1 file changed

+59
-14
lines changed

packages/pluggableWidgets/datagrid-web/src/features/data-export/DSExportRequest.ts

Lines changed: 59 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,26 @@ import { ListValue, ObjectItem, ValueStatus } from "mendix";
44
import { createNanoEvents, Emitter, Unsubscribe } from "nanoevents";
55
import { ColumnsType, ShowContentAsEnum } from "../../../typings/DatagridProps";
66

7-
type RowData = Array<string | number | boolean>;
7+
/** Represents a single Excel cell (SheetJS compatible, simplified) */
8+
interface ExcelCell {
9+
/** Cell type: 's' = string, 'n' = number, 'b' = boolean, 'd' = date */
10+
t: "s" | "n" | "b" | "d";
11+
/** Underlying value */
12+
v: string | number | boolean | Date;
13+
/** Optional Excel number/date format, e.g. "yyyy-mm-dd" or "$0.00" */
14+
z?: string;
15+
/** Optional pre-formatted display text */
16+
w?: string;
17+
}
18+
19+
type RowData = ExcelCell[];
820

921
type HeaderDefinition = {
1022
name: string;
1123
type: string;
1224
};
1325

14-
type ValueReader = (item: ObjectItem, props: ColumnsType) => string | boolean | number;
26+
type ValueReader = (item: ObjectItem, props: ColumnsType) => ExcelCell;
1527

1628
type ReadersByType = Record<ShowContentAsEnum, ValueReader>;
1729

@@ -253,48 +265,81 @@ export class DSExportRequest {
253265
const readers: ReadersByType = {
254266
attribute(item, props) {
255267
if (props.attribute === undefined) {
256-
return "";
268+
return makeEmptyCell();
257269
}
258270

259271
const data = props.attribute.get(item);
260272

261273
if (data.status !== "available") {
262-
return "";
274+
return makeEmptyCell();
275+
}
276+
277+
const value = data.value;
278+
279+
if (value instanceof Date) {
280+
return {
281+
t: "d", // date cell
282+
v: value,
283+
z: "dd/mm/yyyy hh:mm", // Excel date format
284+
w: value.toISOString().split("T")[0] // human-readable fallback
285+
};
263286
}
264287

265-
if (typeof data.value === "boolean") {
266-
return data.value;
288+
if (typeof value === "boolean") {
289+
return {
290+
t: "b",
291+
v: value,
292+
w: value ? "TRUE" : "FALSE"
293+
};
267294
}
268295

269-
if (data.value instanceof Big) {
270-
return data.value.toNumber();
296+
// Number (Big or JS number)
297+
if (value instanceof Big || typeof value === "number") {
298+
const num = value instanceof Big ? value.toNumber() : value;
299+
return {
300+
t: "n",
301+
v: num,
302+
z: '"$"#,##0.00_);\\("$"#,##0.00\\)',
303+
w: num.toLocaleString(undefined, { minimumFractionDigits: 2 })
304+
};
271305
}
272306

273-
return data.displayValue;
307+
// Default: string (ensure fallback is a string)
308+
return {
309+
t: "s",
310+
v: data.displayValue ?? "",
311+
w: data.displayValue ?? ""
312+
};
274313
},
275314

276315
dynamicText(item, props) {
277316
if (props.dynamicText === undefined) {
278-
return "";
317+
return makeEmptyCell();
279318
}
280319

281320
const data = props.dynamicText.get(item);
282321

283322
switch (data.status) {
284323
case "available":
285-
return data.value;
324+
return { t: "s", v: data.value ?? "", w: data.value ?? "" };
286325
case "unavailable":
287-
return "n/a";
326+
return { t: "s", v: "n/a", w: "n/a" };
288327
default:
289-
return "";
328+
return makeEmptyCell();
290329
}
291330
},
292331

293332
customContent(item, props) {
294-
return props.exportValue?.get(item).value ?? "";
333+
const value = props.exportValue?.get(item).value ?? "";
334+
return { t: "s", v: value, w: value };
295335
}
296336
};
297337

338+
// Helper for empty cells
339+
function makeEmptyCell(): ExcelCell {
340+
return { t: "s", v: "", w: "" };
341+
}
342+
298343
function createRowReader(columns: ColumnsType[]): RowReader {
299344
return item =>
300345
columns.map(col => {

0 commit comments

Comments
 (0)