|
1 | 1 | import { isAvailable } from "@mendix/widget-plugin-platform/framework/is-available"; |
2 | 2 | import Big from "big.js"; |
3 | | -import { ListValue, ObjectItem, ValueStatus } from "mendix"; |
| 3 | +import { DynamicValue, ListValue, ObjectItem, ValueStatus } from "mendix"; |
4 | 4 | import { createNanoEvents, Emitter, Unsubscribe } from "nanoevents"; |
5 | 5 | import { ColumnsType, ShowContentAsEnum } from "../../../typings/DatagridProps"; |
6 | 6 |
|
7 | | -type RowData = Array<string | number | boolean>; |
| 7 | +/** Represents a single Excel cell (SheetJS compatible) */ |
| 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[]; |
8 | 20 |
|
9 | 21 | type HeaderDefinition = { |
10 | 22 | name: string; |
11 | 23 | type: string; |
12 | 24 | }; |
13 | 25 |
|
14 | | -type ValueReader = (item: ObjectItem, props: ColumnsType) => string | boolean | number; |
| 26 | +type ValueReader = (item: ObjectItem, props: ColumnsType) => ExcelCell; |
15 | 27 |
|
16 | 28 | type ReadersByType = Record<ShowContentAsEnum, ValueReader>; |
17 | 29 |
|
@@ -252,49 +264,119 @@ export class DSExportRequest { |
252 | 264 |
|
253 | 265 | const readers: ReadersByType = { |
254 | 266 | attribute(item, props) { |
255 | | - if (props.attribute === undefined) { |
256 | | - return ""; |
| 267 | + const data = props.attribute?.get(item); |
| 268 | + |
| 269 | + if (data?.status !== "available") { |
| 270 | + return makeEmptyCell(); |
257 | 271 | } |
258 | 272 |
|
259 | | - const data = props.attribute.get(item); |
| 273 | + const value = data.value; |
| 274 | + const format = getCellFormat({ |
| 275 | + exportType: props.exportType, |
| 276 | + exportDateFormat: props.exportDateFormat, |
| 277 | + exportNumberFormat: props.exportNumberFormat |
| 278 | + }); |
260 | 279 |
|
261 | | - if (data.status !== "available") { |
262 | | - return ""; |
| 280 | + if (value instanceof Date) { |
| 281 | + return excelDate(format === undefined ? data.displayValue : value, format); |
263 | 282 | } |
264 | 283 |
|
265 | | - if (typeof data.value === "boolean") { |
266 | | - return data.value; |
| 284 | + if (typeof value === "boolean") { |
| 285 | + return excelBoolean(value); |
267 | 286 | } |
268 | 287 |
|
269 | | - if (data.value instanceof Big) { |
270 | | - return data.value.toNumber(); |
| 288 | + if (value instanceof Big || typeof value === "number") { |
| 289 | + const num = value instanceof Big ? value.toNumber() : value; |
| 290 | + return excelNumber(num, format); |
271 | 291 | } |
272 | 292 |
|
273 | | - return data.displayValue; |
| 293 | + return excelString(data.displayValue ?? ""); |
274 | 294 | }, |
275 | 295 |
|
276 | 296 | dynamicText(item, props) { |
277 | | - if (props.dynamicText === undefined) { |
278 | | - return ""; |
279 | | - } |
280 | | - |
281 | | - const data = props.dynamicText.get(item); |
| 297 | + const data = props.dynamicText?.get(item); |
282 | 298 |
|
283 | | - switch (data.status) { |
| 299 | + switch (data?.status) { |
284 | 300 | case "available": |
285 | | - return data.value; |
| 301 | + const format = getCellFormat({ |
| 302 | + exportType: props.exportType, |
| 303 | + exportDateFormat: props.exportDateFormat, |
| 304 | + exportNumberFormat: props.exportNumberFormat |
| 305 | + }); |
| 306 | + |
| 307 | + return excelString(data.value ?? "", format); |
286 | 308 | case "unavailable": |
287 | | - return "n/a"; |
| 309 | + return excelString("n/a"); |
288 | 310 | default: |
289 | | - return ""; |
| 311 | + return makeEmptyCell(); |
290 | 312 | } |
291 | 313 | }, |
292 | 314 |
|
293 | 315 | customContent(item, props) { |
294 | | - return props.exportValue?.get(item).value ?? ""; |
| 316 | + const value = props.exportValue?.get(item).value ?? ""; |
| 317 | + const format = getCellFormat({ |
| 318 | + exportType: props.exportType, |
| 319 | + exportDateFormat: props.exportDateFormat, |
| 320 | + exportNumberFormat: props.exportNumberFormat |
| 321 | + }); |
| 322 | + |
| 323 | + return excelString(value, format); |
295 | 324 | } |
296 | 325 | }; |
297 | 326 |
|
| 327 | +function makeEmptyCell(): ExcelCell { |
| 328 | + return { t: "s", v: "" }; |
| 329 | +} |
| 330 | + |
| 331 | +function excelNumber(value: number, format?: string): ExcelCell { |
| 332 | + return { |
| 333 | + t: "n", |
| 334 | + v: value, |
| 335 | + z: format |
| 336 | + }; |
| 337 | +} |
| 338 | + |
| 339 | +function excelString(value: string, format?: string): ExcelCell { |
| 340 | + return { |
| 341 | + t: "s", |
| 342 | + v: value, |
| 343 | + z: format ?? undefined |
| 344 | + }; |
| 345 | +} |
| 346 | + |
| 347 | +function excelDate(value: string | Date, format?: string): ExcelCell { |
| 348 | + return { |
| 349 | + t: format === undefined ? "s" : "d", |
| 350 | + v: value, |
| 351 | + z: format |
| 352 | + }; |
| 353 | +} |
| 354 | + |
| 355 | +function excelBoolean(value: boolean): ExcelCell { |
| 356 | + return { |
| 357 | + t: "b", |
| 358 | + v: value, |
| 359 | + w: value ? "TRUE" : "FALSE" |
| 360 | + }; |
| 361 | +} |
| 362 | + |
| 363 | +interface DataExportProps { |
| 364 | + exportType: "default" | "number" | "date" | "boolean"; |
| 365 | + exportDateFormat?: DynamicValue<string>; |
| 366 | + exportNumberFormat?: DynamicValue<string>; |
| 367 | +} |
| 368 | + |
| 369 | +function getCellFormat({ exportType, exportDateFormat, exportNumberFormat }: DataExportProps): string | undefined { |
| 370 | + switch (exportType) { |
| 371 | + case "date": |
| 372 | + return exportDateFormat?.status === "available" ? exportDateFormat.value : undefined; |
| 373 | + case "number": |
| 374 | + return exportNumberFormat?.status === "available" ? exportNumberFormat.value : undefined; |
| 375 | + default: |
| 376 | + return undefined; |
| 377 | + } |
| 378 | +} |
| 379 | + |
298 | 380 | function createRowReader(columns: ColumnsType[]): RowReader { |
299 | 381 | return item => |
300 | 382 | columns.map(col => { |
|
0 commit comments