@harbortouch/skytab-analytics-report-utils 0.41.10 → 0.41.12

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.
@@ -2838,6 +2838,7 @@ var dailySalesTrendsOrderTypeConfig = {
2838
2838
  };
2839
2839
 
2840
2840
  // src/report.ts
2841
+ import { isValid, parseISO } from "date-fns";
2841
2842
  var Report = {
2842
2843
  TICKET_LIVE: "ticket-live",
2843
2844
  TICKET_ANALYTICS: "ticket-analytics",
@@ -2911,7 +2912,11 @@ var getReportExportFileName = ({
2911
2912
  endDate,
2912
2913
  format
2913
2914
  }) => {
2914
- const formatDate2 = (date) => {
2915
+ const formatDate2 = (value) => {
2916
+ if (typeof value === "string" && /^\d{4}-\d{2}-\d{2}/.test(value) && isValid(parseISO(value))) {
2917
+ return value.slice(0, 10);
2918
+ }
2919
+ const date = typeof value === "string" ? new Date(value) : value;
2915
2920
  return [
2916
2921
  date.getFullYear(),
2917
2922
  String(date.getMonth() + 1).padStart(2, "0"),
@@ -1,5 +1,5 @@
1
- import { a as ExportSection } from '../../types-JAEAu4KX.cjs';
2
- export { E as XlsxFilter, b as XlsxTable, c as XlsxTableColumn } from '../../types-JAEAu4KX.cjs';
1
+ import { a as ExportSection } from '../../types-D3IyO__4.cjs';
2
+ export { E as XlsxFilter, b as XlsxTable, c as XlsxTableColumn } from '../../types-D3IyO__4.cjs';
3
3
 
4
4
  declare class ExcelExporter {
5
5
  generate(content: {
@@ -1,5 +1,5 @@
1
- import { a as ExportSection } from '../../types-JAEAu4KX.js';
2
- export { E as XlsxFilter, b as XlsxTable, c as XlsxTableColumn } from '../../types-JAEAu4KX.js';
1
+ import { a as ExportSection } from '../../types-D3IyO__4.js';
2
+ export { E as XlsxFilter, b as XlsxTable, c as XlsxTableColumn } from '../../types-D3IyO__4.js';
3
3
 
4
4
  declare class ExcelExporter {
5
5
  generate(content: {
@@ -1861,6 +1861,9 @@ var formatFieldValue = (value, type, localeOptions, fullRecord, fieldConfig = DE
1861
1861
  }
1862
1862
  };
1863
1863
 
1864
+ // src/report.ts
1865
+ var import_date_fns = require("date-fns");
1866
+
1864
1867
  // src/reports/salesByItemDetail.ts
1865
1868
  var FIELDS = {
1866
1869
  businessDate: { type: "date", columnKey: "date", filteringAvailable: false },
@@ -4021,7 +4024,7 @@ function resolveRawFiltersToExportFilters(rawFilters, locale, reportType) {
4021
4024
  }
4022
4025
 
4023
4026
  // src/exporters/exportBuilder.ts
4024
- var import_date_fns = require("date-fns");
4027
+ var import_date_fns2 = require("date-fns");
4025
4028
  var ExportBuilder = class {
4026
4029
  constructor(reportType, locations, locale) {
4027
4030
  this.reportType = reportType;
@@ -4143,7 +4146,10 @@ var ExportBuilder = class {
4143
4146
  renderTableContent(config, reportData, parameters) {
4144
4147
  const data = this.getDataForConfig(reportData, config);
4145
4148
  const { columns, headers, columnTypes } = this.resolveColumnsAndHeaders(parameters, config);
4146
- const tableColumns = columns.map((col, i) => ({ header: headers[i], key: col }));
4149
+ const tableColumns = columns.map((col, i) => {
4150
+ const size = config.fields[col]?.size;
4151
+ return { header: headers[i], key: col, ...size !== void 0 ? { size } : {} };
4152
+ });
4147
4153
  const rows = data.filter((row) => typeof row === "object" && row !== null).map((row) => this.formatRow(row, columns, columnTypes, row));
4148
4154
  if (config.hasFooter) {
4149
4155
  const totalsRow = this.buildTotalsRow(data, config);
@@ -4163,11 +4169,14 @@ var ExportBuilder = class {
4163
4169
  return config.dataAccessKey ? data[config.dataAccessKey] ?? [] : [];
4164
4170
  }
4165
4171
  formatMaybeDate(value) {
4166
- const date = typeof value === "string" ? (0, import_date_fns.parseISO)(value) : value;
4167
- if (!(0, import_date_fns.isValid)(date)) {
4172
+ if (typeof value === "string" && /^\d{4}-\d{2}-\d{2}/.test(value) && (0, import_date_fns2.isValid)((0, import_date_fns2.parseISO)(value))) {
4173
+ return value.slice(0, 10);
4174
+ }
4175
+ const date = typeof value === "string" ? (0, import_date_fns2.parseISO)(value) : value;
4176
+ if (!(0, import_date_fns2.isValid)(date)) {
4168
4177
  return "";
4169
4178
  }
4170
- return (0, import_date_fns.format)(date, "yyyy-MM-dd");
4179
+ return (0, import_date_fns2.format)(date, "yyyy-MM-dd");
4171
4180
  }
4172
4181
  buildReportMetadataFilters(parameters, name, meta) {
4173
4182
  const locationNames = this.locations.map((l) => l.name).join(", ");
@@ -4208,6 +4217,7 @@ var ExportBuilder = class {
4208
4217
  day: "numeric",
4209
4218
  hour: "numeric",
4210
4219
  minute: "2-digit",
4220
+ timeZone: this.locations[0]?.timezone ?? "UTC",
4211
4221
  timeZoneName: "short"
4212
4222
  }).format(meta.generatedAt);
4213
4223
  filters.push({ label: this.translate("scheduled-reports.generated-at"), value: formatted });
@@ -1,5 +1,5 @@
1
- import { R as ReportType, A as AnalyticsField } from '../report-LuHGzrLx.cjs';
2
- import { E as ExportFilter, R as ReportMetadata, a as ExportSection } from '../types-JAEAu4KX.cjs';
1
+ import { R as ReportType, A as AnalyticsField } from '../report-Dj8UU5d_.cjs';
2
+ import { E as ExportFilter, R as ReportMetadata, a as ExportSection } from '../types-D3IyO__4.cjs';
3
3
 
4
4
  declare function resolveRawFiltersToExportFilters(rawFilters: Record<string, (string | number)[] | undefined>, locale: string, reportType?: ReportType): ExportFilter[];
5
5
 
@@ -1,5 +1,5 @@
1
- import { R as ReportType, A as AnalyticsField } from '../report-LuHGzrLx.js';
2
- import { E as ExportFilter, R as ReportMetadata, a as ExportSection } from '../types-JAEAu4KX.js';
1
+ import { R as ReportType, A as AnalyticsField } from '../report-Dj8UU5d_.js';
2
+ import { E as ExportFilter, R as ReportMetadata, a as ExportSection } from '../types-D3IyO__4.js';
3
3
 
4
4
  declare function resolveRawFiltersToExportFilters(rawFilters: Record<string, (string | number)[] | undefined>, locale: string, reportType?: ReportType): ExportFilter[];
5
5
 
@@ -8,7 +8,7 @@ import {
8
8
  getReportFormattingLocaleOptions,
9
9
  getReportName,
10
10
  resolveTranslate
11
- } from "../chunk-ENBT7KRJ.js";
11
+ } from "../chunk-UUKHJDV3.js";
12
12
 
13
13
  // src/utils/filter-resolver.ts
14
14
  var ANALYTICS_FIELDS = /* @__PURE__ */ new Set([
@@ -197,7 +197,10 @@ var ExportBuilder = class {
197
197
  renderTableContent(config, reportData, parameters) {
198
198
  const data = this.getDataForConfig(reportData, config);
199
199
  const { columns, headers, columnTypes } = this.resolveColumnsAndHeaders(parameters, config);
200
- const tableColumns = columns.map((col, i) => ({ header: headers[i], key: col }));
200
+ const tableColumns = columns.map((col, i) => {
201
+ const size = config.fields[col]?.size;
202
+ return { header: headers[i], key: col, ...size !== void 0 ? { size } : {} };
203
+ });
201
204
  const rows = data.filter((row) => typeof row === "object" && row !== null).map((row) => this.formatRow(row, columns, columnTypes, row));
202
205
  if (config.hasFooter) {
203
206
  const totalsRow = this.buildTotalsRow(data, config);
@@ -217,6 +220,9 @@ var ExportBuilder = class {
217
220
  return config.dataAccessKey ? data[config.dataAccessKey] ?? [] : [];
218
221
  }
219
222
  formatMaybeDate(value) {
223
+ if (typeof value === "string" && /^\d{4}-\d{2}-\d{2}/.test(value) && isValid(parseISO(value))) {
224
+ return value.slice(0, 10);
225
+ }
220
226
  const date = typeof value === "string" ? parseISO(value) : value;
221
227
  if (!isValid(date)) {
222
228
  return "";
@@ -262,6 +268,7 @@ var ExportBuilder = class {
262
268
  day: "numeric",
263
269
  hour: "numeric",
264
270
  minute: "2-digit",
271
+ timeZone: this.locations[0]?.timezone ?? "UTC",
265
272
  timeZoneName: "short"
266
273
  }).format(meta.generatedAt);
267
274
  filters.push({ label: this.translate("scheduled-reports.generated-at"), value: formatted });
@@ -207,7 +207,14 @@ var PdfExporter = class {
207
207
  const dataFontSize = columnCount > 13 ? 5 : columnCount > 10 ? 6 : columnCount > 7 ? 7 : 8;
208
208
  const headerWidthSlack = 2;
209
209
  const headerMinWidths = /* @__PURE__ */ new Map();
210
+ const cssPixelsToPoints = 0.75;
211
+ const boundedColumns = /* @__PURE__ */ new Set();
210
212
  columns.forEach((col) => {
213
+ if (col.size !== void 0 && Number.isFinite(col.size) && col.size > 0) {
214
+ col.width = col.size * cssPixelsToPoints;
215
+ boundedColumns.add(col);
216
+ return;
217
+ }
211
218
  doc.font(FONT_BOLD).fontSize(headerFontSize);
212
219
  const headerTextWidth = doc.widthOfString(col.header);
213
220
  const headerMinWidth = Math.max(...col.header.split(" ").map((w) => doc.widthOfString(w)));
@@ -226,49 +233,50 @@ var PdfExporter = class {
226
233
  headerMinWidths.set(col, headerMinWidth + TABLE_CELL_PADDING * 2 + headerWidthSlack);
227
234
  });
228
235
  const usableWidth = doc.page.width - doc.page.margins.left - doc.page.margins.right;
236
+ const minimumColumnWidth = Math.min(MIN_COLUMN_WIDTH, usableWidth / columnCount);
229
237
  const totalColumnWidth = columns.reduce((sum, c) => sum + (c.width ?? MIN_COLUMN_WIDTH), 0);
230
238
  if (totalColumnWidth > usableWidth) {
231
239
  const scale = usableWidth / totalColumnWidth;
232
240
  columns.forEach((c) => {
233
- const headerMin = headerMinWidths.get(c) ?? MIN_COLUMN_WIDTH;
234
- c.width = Math.max(Math.floor((c.width ?? MIN_COLUMN_WIDTH) * scale), headerMin, MIN_COLUMN_WIDTH);
241
+ const width = c.width ?? MIN_COLUMN_WIDTH;
242
+ const headerMin = headerMinWidths.get(c) ?? minimumColumnWidth;
243
+ const minimumWidth = Math.min(width, Math.max(headerMin, minimumColumnWidth));
244
+ c.width = Math.max(Math.floor(width * scale), minimumWidth);
235
245
  });
236
- let excess = columns.reduce((sum, c) => sum + (c.width ?? MIN_COLUMN_WIDTH), 0) - usableWidth;
237
- for (let i = columns.length - 1; i >= 0 && excess > 0; i--) {
238
- const col = columns[i];
239
- const available = (col.width ?? MIN_COLUMN_WIDTH) - MIN_COLUMN_WIDTH;
240
- const trim = Math.min(excess, available);
241
- if (trim > 0) {
242
- col.width = (col.width ?? MIN_COLUMN_WIDTH) - trim;
243
- excess -= trim;
246
+ const excess = columns.reduce((sum, c) => sum + (c.width ?? MIN_COLUMN_WIDTH), 0) - usableWidth;
247
+ if (excess > 0) {
248
+ const reducibleWidth = columns.reduce(
249
+ (sum, c) => sum + Math.max((c.width ?? MIN_COLUMN_WIDTH) - minimumColumnWidth, 0),
250
+ 0
251
+ );
252
+ if (reducibleWidth > 0) {
253
+ columns.forEach((c) => {
254
+ const width = c.width ?? MIN_COLUMN_WIDTH;
255
+ const available = Math.max(width - minimumColumnWidth, 0);
256
+ c.width = width - excess * (available / reducibleWidth);
257
+ });
244
258
  }
245
259
  }
246
260
  } else if (totalColumnWidth < usableWidth) {
247
- const scale = usableWidth / totalColumnWidth;
248
- columns.forEach((c) => {
249
- c.width = Math.floor((c.width ?? MIN_COLUMN_WIDTH) * scale);
250
- });
251
- const expanded = columns.reduce((sum, c) => sum + (c.width ?? 0), 0);
252
- columns[columns.length - 1].width = (columns[columns.length - 1].width ?? 0) + (usableWidth - expanded);
261
+ const expandableColumns = columns.filter((c) => !boundedColumns.has(c));
262
+ const expandableWidth = expandableColumns.reduce((sum, c) => sum + (c.width ?? MIN_COLUMN_WIDTH), 0);
263
+ if (expandableWidth > 0) {
264
+ const scale = (expandableWidth + usableWidth - totalColumnWidth) / expandableWidth;
265
+ expandableColumns.forEach((c) => {
266
+ c.width = Math.floor((c.width ?? MIN_COLUMN_WIDTH) * scale);
267
+ });
268
+ const expanded = columns.reduce((sum, c) => sum + (c.width ?? 0), 0);
269
+ const lastExpandableColumn = expandableColumns[expandableColumns.length - 1];
270
+ lastExpandableColumn.width = (lastExpandableColumn.width ?? 0) + (usableWidth - expanded);
271
+ }
253
272
  }
254
273
  const tableWidth = columns.reduce((sum, c) => sum + (c.width ?? MIN_COLUMN_WIDTH), 0);
255
274
  const pageBottom = doc.page.height - doc.page.margins.bottom;
256
- const calculateRowHeight = (row, isHeader = false) => {
257
- const fontSize = isHeader ? headerFontSize : dataFontSize;
258
- doc.font(isHeader ? FONT_BOLD : FONT_REGULAR).fontSize(fontSize);
259
- let maxLines = 1;
260
- columns.forEach((col) => {
261
- const cellValue = isHeader ? col.header : row?.[col.key] !== void 0 ? String(row[col.key]) : "";
262
- const textWidth = doc.widthOfString(cellValue);
263
- const availableWidth = (col.width ?? MIN_COLUMN_WIDTH) - TABLE_CELL_PADDING * 2;
264
- const lines = Math.ceil(textWidth / availableWidth);
265
- if (lines > maxLines) {
266
- maxLines = lines;
267
- }
268
- });
269
- return Math.max(maxLines * (fontSize + 2) + TABLE_CELL_PADDING * 2, DEFAULT_ROW_HEIGHT);
275
+ const calculateRowHeight = (isHeader = false) => {
276
+ doc.font(isHeader ? FONT_BOLD : FONT_REGULAR).fontSize(isHeader ? headerFontSize : dataFontSize);
277
+ return Math.max(doc.currentLineHeight(true) + TABLE_CELL_PADDING * 2, DEFAULT_ROW_HEIGHT);
270
278
  };
271
- const headerHeight = calculateRowHeight(null, true);
279
+ const headerHeight = calculateRowHeight(true);
272
280
  const drawTableHeader = (y) => this.drawHeader(doc, columns, y, tableWidth, headerHeight, TABLE_CELL_PADDING, headerFontSize, MIN_COLUMN_WIDTH);
273
281
  let tableStartY = startY;
274
282
  if (tableStartY + headerHeight > pageBottom) {
@@ -278,7 +286,7 @@ var PdfExporter = class {
278
286
  let currentY = drawTableHeader(tableStartY);
279
287
  doc.font(FONT_REGULAR).fontSize(dataFontSize);
280
288
  rows.forEach((row, rowIndex) => {
281
- const currentRowHeight = calculateRowHeight(row, false);
289
+ const currentRowHeight = calculateRowHeight();
282
290
  if (currentY + currentRowHeight > pageBottom) {
283
291
  doc.addPage();
284
292
  currentY = drawTableHeader(doc.page.margins.top);
@@ -301,6 +309,28 @@ var PdfExporter = class {
301
309
  doc.strokeColor(HEADER_RULE_COLOR).lineWidth(HEADER_RULE_WIDTH).moveTo(left, closingRuleY).lineTo(left + tableWidth, closingRuleY).stroke();
302
310
  return closingRuleY;
303
311
  }
312
+ truncateTextToWidth(doc, text, availableWidth) {
313
+ if (doc.widthOfString(text) <= availableWidth) {
314
+ return text;
315
+ }
316
+ const ellipsis = "\u2026";
317
+ if (doc.widthOfString(ellipsis) > availableWidth) {
318
+ return "";
319
+ }
320
+ const characters = Array.from(text);
321
+ let low = 0;
322
+ let high = characters.length;
323
+ while (low < high) {
324
+ const middle = Math.ceil((low + high) / 2);
325
+ const candidate = `${characters.slice(0, middle).join("").trimEnd()}${ellipsis}`;
326
+ if (doc.widthOfString(candidate) <= availableWidth) {
327
+ low = middle;
328
+ } else {
329
+ high = middle - 1;
330
+ }
331
+ }
332
+ return `${characters.slice(0, low).join("").trimEnd()}${ellipsis}`;
333
+ }
304
334
  drawHeader(doc, columns, startY, tableWidth, headerHeight, cellPadding, headerFontSize, minColumnWidth) {
305
335
  const left = doc.page.margins.left;
306
336
  doc.rect(left, startY, tableWidth, headerHeight).fill(HEADER_BACKGROUND_COLOR);
@@ -310,12 +340,15 @@ var PdfExporter = class {
310
340
  const headerLineHeight = doc.currentLineHeight(true);
311
341
  const headerTextY = startY + Math.max((headerHeight - headerLineHeight) / 2, 0);
312
342
  columns.forEach((col) => {
313
- doc.text(col.header, xPosition + cellPadding, headerTextY, {
314
- width: (col.width ?? minColumnWidth) - cellPadding * 2,
343
+ const innerWidth = (col.width ?? minColumnWidth) - cellPadding * 2;
344
+ const bounded = col.size !== void 0 && Number.isFinite(col.size) && col.size > 0;
345
+ const headerText = bounded ? this.truncateTextToWidth(doc, col.header, innerWidth) : col.header;
346
+ doc.text(headerText, xPosition + cellPadding, headerTextY, {
347
+ width: innerWidth,
315
348
  height: headerLineHeight,
316
349
  align: "left",
317
350
  lineBreak: false,
318
- ellipsis: true
351
+ ellipsis: !bounded
319
352
  });
320
353
  xPosition += col.width ?? minColumnWidth;
321
354
  });
@@ -336,12 +369,15 @@ var PdfExporter = class {
336
369
  const rawValue = row[col.key];
337
370
  const cellValue = typeof rawValue === "string" || typeof rawValue === "number" || typeof rawValue === "boolean" ? String(rawValue) : "";
338
371
  const isNumeric = typeof rawValue === "number" || typeof rawValue === "string" && rawValue !== "" && !isNaN(Number(rawValue));
339
- doc.text(cellValue, xPosition + cellPadding, dataTextY, {
340
- width: (col.width ?? minColumnWidth) - cellPadding * 2,
372
+ const innerWidth = (col.width ?? minColumnWidth) - cellPadding * 2;
373
+ const bounded = col.size !== void 0 && Number.isFinite(col.size) && col.size > 0;
374
+ const rowText = bounded ? this.truncateTextToWidth(doc, cellValue, innerWidth) : cellValue;
375
+ doc.text(rowText, xPosition + cellPadding, dataTextY, {
376
+ width: innerWidth,
341
377
  height: dataLineHeight,
342
378
  align: isNumeric ? "right" : "left",
343
379
  lineBreak: false,
344
- ellipsis: true
380
+ ellipsis: !bounded
345
381
  });
346
382
  xPosition += col.width ?? minColumnWidth;
347
383
  if (i < columns.length - 1) {
@@ -1,4 +1,4 @@
1
- import { a as ExportSection, E as ExportFilter, b as ExportTable, c as ExportTableColumn } from '../../types-JAEAu4KX.cjs';
1
+ import { a as ExportSection, E as ExportFilter, b as ExportTable, c as ExportTableColumn } from '../../types-D3IyO__4.cjs';
2
2
 
3
3
  type MetaDataValue = ExportFilter[] | string;
4
4
  declare class PdfExporter {
@@ -10,6 +10,7 @@ declare class PdfExporter {
10
10
  drawSectionTitle(doc: PDFKit.PDFDocument, title: string, startY: number): number;
11
11
  drawMetaData(doc: PDFKit.PDFDocument, metaData: Record<string, MetaDataValue>, startY: number): number;
12
12
  drawTable(doc: PDFKit.PDFDocument, table: ExportTable, startY: number): number;
13
+ private truncateTextToWidth;
13
14
  drawHeader(doc: PDFKit.PDFDocument, columns: ExportTableColumn[], startY: number, tableWidth: number, headerHeight: number, cellPadding: number, headerFontSize: number, minColumnWidth: number): number;
14
15
  drawRow(doc: PDFKit.PDFDocument, row: Record<string, unknown>, columns: ExportTableColumn[], currentY: number, rowHeight: number, cellPadding: number, dataFontSize: number, minColumnWidth: number, rowIndex?: number): number;
15
16
  }
@@ -1,4 +1,4 @@
1
- import { a as ExportSection, E as ExportFilter, b as ExportTable, c as ExportTableColumn } from '../../types-JAEAu4KX.js';
1
+ import { a as ExportSection, E as ExportFilter, b as ExportTable, c as ExportTableColumn } from '../../types-D3IyO__4.js';
2
2
 
3
3
  type MetaDataValue = ExportFilter[] | string;
4
4
  declare class PdfExporter {
@@ -10,6 +10,7 @@ declare class PdfExporter {
10
10
  drawSectionTitle(doc: PDFKit.PDFDocument, title: string, startY: number): number;
11
11
  drawMetaData(doc: PDFKit.PDFDocument, metaData: Record<string, MetaDataValue>, startY: number): number;
12
12
  drawTable(doc: PDFKit.PDFDocument, table: ExportTable, startY: number): number;
13
+ private truncateTextToWidth;
13
14
  drawHeader(doc: PDFKit.PDFDocument, columns: ExportTableColumn[], startY: number, tableWidth: number, headerHeight: number, cellPadding: number, headerFontSize: number, minColumnWidth: number): number;
14
15
  drawRow(doc: PDFKit.PDFDocument, row: Record<string, unknown>, columns: ExportTableColumn[], currentY: number, rowHeight: number, cellPadding: number, dataFontSize: number, minColumnWidth: number, rowIndex?: number): number;
15
16
  }
@@ -167,7 +167,14 @@ var PdfExporter = class {
167
167
  const dataFontSize = columnCount > 13 ? 5 : columnCount > 10 ? 6 : columnCount > 7 ? 7 : 8;
168
168
  const headerWidthSlack = 2;
169
169
  const headerMinWidths = /* @__PURE__ */ new Map();
170
+ const cssPixelsToPoints = 0.75;
171
+ const boundedColumns = /* @__PURE__ */ new Set();
170
172
  columns.forEach((col) => {
173
+ if (col.size !== void 0 && Number.isFinite(col.size) && col.size > 0) {
174
+ col.width = col.size * cssPixelsToPoints;
175
+ boundedColumns.add(col);
176
+ return;
177
+ }
171
178
  doc.font(FONT_BOLD).fontSize(headerFontSize);
172
179
  const headerTextWidth = doc.widthOfString(col.header);
173
180
  const headerMinWidth = Math.max(...col.header.split(" ").map((w) => doc.widthOfString(w)));
@@ -186,49 +193,50 @@ var PdfExporter = class {
186
193
  headerMinWidths.set(col, headerMinWidth + TABLE_CELL_PADDING * 2 + headerWidthSlack);
187
194
  });
188
195
  const usableWidth = doc.page.width - doc.page.margins.left - doc.page.margins.right;
196
+ const minimumColumnWidth = Math.min(MIN_COLUMN_WIDTH, usableWidth / columnCount);
189
197
  const totalColumnWidth = columns.reduce((sum, c) => sum + (c.width ?? MIN_COLUMN_WIDTH), 0);
190
198
  if (totalColumnWidth > usableWidth) {
191
199
  const scale = usableWidth / totalColumnWidth;
192
200
  columns.forEach((c) => {
193
- const headerMin = headerMinWidths.get(c) ?? MIN_COLUMN_WIDTH;
194
- c.width = Math.max(Math.floor((c.width ?? MIN_COLUMN_WIDTH) * scale), headerMin, MIN_COLUMN_WIDTH);
201
+ const width = c.width ?? MIN_COLUMN_WIDTH;
202
+ const headerMin = headerMinWidths.get(c) ?? minimumColumnWidth;
203
+ const minimumWidth = Math.min(width, Math.max(headerMin, minimumColumnWidth));
204
+ c.width = Math.max(Math.floor(width * scale), minimumWidth);
195
205
  });
196
- let excess = columns.reduce((sum, c) => sum + (c.width ?? MIN_COLUMN_WIDTH), 0) - usableWidth;
197
- for (let i = columns.length - 1; i >= 0 && excess > 0; i--) {
198
- const col = columns[i];
199
- const available = (col.width ?? MIN_COLUMN_WIDTH) - MIN_COLUMN_WIDTH;
200
- const trim = Math.min(excess, available);
201
- if (trim > 0) {
202
- col.width = (col.width ?? MIN_COLUMN_WIDTH) - trim;
203
- excess -= trim;
206
+ const excess = columns.reduce((sum, c) => sum + (c.width ?? MIN_COLUMN_WIDTH), 0) - usableWidth;
207
+ if (excess > 0) {
208
+ const reducibleWidth = columns.reduce(
209
+ (sum, c) => sum + Math.max((c.width ?? MIN_COLUMN_WIDTH) - minimumColumnWidth, 0),
210
+ 0
211
+ );
212
+ if (reducibleWidth > 0) {
213
+ columns.forEach((c) => {
214
+ const width = c.width ?? MIN_COLUMN_WIDTH;
215
+ const available = Math.max(width - minimumColumnWidth, 0);
216
+ c.width = width - excess * (available / reducibleWidth);
217
+ });
204
218
  }
205
219
  }
206
220
  } else if (totalColumnWidth < usableWidth) {
207
- const scale = usableWidth / totalColumnWidth;
208
- columns.forEach((c) => {
209
- c.width = Math.floor((c.width ?? MIN_COLUMN_WIDTH) * scale);
210
- });
211
- const expanded = columns.reduce((sum, c) => sum + (c.width ?? 0), 0);
212
- columns[columns.length - 1].width = (columns[columns.length - 1].width ?? 0) + (usableWidth - expanded);
221
+ const expandableColumns = columns.filter((c) => !boundedColumns.has(c));
222
+ const expandableWidth = expandableColumns.reduce((sum, c) => sum + (c.width ?? MIN_COLUMN_WIDTH), 0);
223
+ if (expandableWidth > 0) {
224
+ const scale = (expandableWidth + usableWidth - totalColumnWidth) / expandableWidth;
225
+ expandableColumns.forEach((c) => {
226
+ c.width = Math.floor((c.width ?? MIN_COLUMN_WIDTH) * scale);
227
+ });
228
+ const expanded = columns.reduce((sum, c) => sum + (c.width ?? 0), 0);
229
+ const lastExpandableColumn = expandableColumns[expandableColumns.length - 1];
230
+ lastExpandableColumn.width = (lastExpandableColumn.width ?? 0) + (usableWidth - expanded);
231
+ }
213
232
  }
214
233
  const tableWidth = columns.reduce((sum, c) => sum + (c.width ?? MIN_COLUMN_WIDTH), 0);
215
234
  const pageBottom = doc.page.height - doc.page.margins.bottom;
216
- const calculateRowHeight = (row, isHeader = false) => {
217
- const fontSize = isHeader ? headerFontSize : dataFontSize;
218
- doc.font(isHeader ? FONT_BOLD : FONT_REGULAR).fontSize(fontSize);
219
- let maxLines = 1;
220
- columns.forEach((col) => {
221
- const cellValue = isHeader ? col.header : row?.[col.key] !== void 0 ? String(row[col.key]) : "";
222
- const textWidth = doc.widthOfString(cellValue);
223
- const availableWidth = (col.width ?? MIN_COLUMN_WIDTH) - TABLE_CELL_PADDING * 2;
224
- const lines = Math.ceil(textWidth / availableWidth);
225
- if (lines > maxLines) {
226
- maxLines = lines;
227
- }
228
- });
229
- return Math.max(maxLines * (fontSize + 2) + TABLE_CELL_PADDING * 2, DEFAULT_ROW_HEIGHT);
235
+ const calculateRowHeight = (isHeader = false) => {
236
+ doc.font(isHeader ? FONT_BOLD : FONT_REGULAR).fontSize(isHeader ? headerFontSize : dataFontSize);
237
+ return Math.max(doc.currentLineHeight(true) + TABLE_CELL_PADDING * 2, DEFAULT_ROW_HEIGHT);
230
238
  };
231
- const headerHeight = calculateRowHeight(null, true);
239
+ const headerHeight = calculateRowHeight(true);
232
240
  const drawTableHeader = (y) => this.drawHeader(doc, columns, y, tableWidth, headerHeight, TABLE_CELL_PADDING, headerFontSize, MIN_COLUMN_WIDTH);
233
241
  let tableStartY = startY;
234
242
  if (tableStartY + headerHeight > pageBottom) {
@@ -238,7 +246,7 @@ var PdfExporter = class {
238
246
  let currentY = drawTableHeader(tableStartY);
239
247
  doc.font(FONT_REGULAR).fontSize(dataFontSize);
240
248
  rows.forEach((row, rowIndex) => {
241
- const currentRowHeight = calculateRowHeight(row, false);
249
+ const currentRowHeight = calculateRowHeight();
242
250
  if (currentY + currentRowHeight > pageBottom) {
243
251
  doc.addPage();
244
252
  currentY = drawTableHeader(doc.page.margins.top);
@@ -261,6 +269,28 @@ var PdfExporter = class {
261
269
  doc.strokeColor(HEADER_RULE_COLOR).lineWidth(HEADER_RULE_WIDTH).moveTo(left, closingRuleY).lineTo(left + tableWidth, closingRuleY).stroke();
262
270
  return closingRuleY;
263
271
  }
272
+ truncateTextToWidth(doc, text, availableWidth) {
273
+ if (doc.widthOfString(text) <= availableWidth) {
274
+ return text;
275
+ }
276
+ const ellipsis = "\u2026";
277
+ if (doc.widthOfString(ellipsis) > availableWidth) {
278
+ return "";
279
+ }
280
+ const characters = Array.from(text);
281
+ let low = 0;
282
+ let high = characters.length;
283
+ while (low < high) {
284
+ const middle = Math.ceil((low + high) / 2);
285
+ const candidate = `${characters.slice(0, middle).join("").trimEnd()}${ellipsis}`;
286
+ if (doc.widthOfString(candidate) <= availableWidth) {
287
+ low = middle;
288
+ } else {
289
+ high = middle - 1;
290
+ }
291
+ }
292
+ return `${characters.slice(0, low).join("").trimEnd()}${ellipsis}`;
293
+ }
264
294
  drawHeader(doc, columns, startY, tableWidth, headerHeight, cellPadding, headerFontSize, minColumnWidth) {
265
295
  const left = doc.page.margins.left;
266
296
  doc.rect(left, startY, tableWidth, headerHeight).fill(HEADER_BACKGROUND_COLOR);
@@ -270,12 +300,15 @@ var PdfExporter = class {
270
300
  const headerLineHeight = doc.currentLineHeight(true);
271
301
  const headerTextY = startY + Math.max((headerHeight - headerLineHeight) / 2, 0);
272
302
  columns.forEach((col) => {
273
- doc.text(col.header, xPosition + cellPadding, headerTextY, {
274
- width: (col.width ?? minColumnWidth) - cellPadding * 2,
303
+ const innerWidth = (col.width ?? minColumnWidth) - cellPadding * 2;
304
+ const bounded = col.size !== void 0 && Number.isFinite(col.size) && col.size > 0;
305
+ const headerText = bounded ? this.truncateTextToWidth(doc, col.header, innerWidth) : col.header;
306
+ doc.text(headerText, xPosition + cellPadding, headerTextY, {
307
+ width: innerWidth,
275
308
  height: headerLineHeight,
276
309
  align: "left",
277
310
  lineBreak: false,
278
- ellipsis: true
311
+ ellipsis: !bounded
279
312
  });
280
313
  xPosition += col.width ?? minColumnWidth;
281
314
  });
@@ -296,12 +329,15 @@ var PdfExporter = class {
296
329
  const rawValue = row[col.key];
297
330
  const cellValue = typeof rawValue === "string" || typeof rawValue === "number" || typeof rawValue === "boolean" ? String(rawValue) : "";
298
331
  const isNumeric = typeof rawValue === "number" || typeof rawValue === "string" && rawValue !== "" && !isNaN(Number(rawValue));
299
- doc.text(cellValue, xPosition + cellPadding, dataTextY, {
300
- width: (col.width ?? minColumnWidth) - cellPadding * 2,
332
+ const innerWidth = (col.width ?? minColumnWidth) - cellPadding * 2;
333
+ const bounded = col.size !== void 0 && Number.isFinite(col.size) && col.size > 0;
334
+ const rowText = bounded ? this.truncateTextToWidth(doc, cellValue, innerWidth) : cellValue;
335
+ doc.text(rowText, xPosition + cellPadding, dataTextY, {
336
+ width: innerWidth,
301
337
  height: dataLineHeight,
302
338
  align: isNumeric ? "right" : "left",
303
339
  lineBreak: false,
304
- ellipsis: true
340
+ ellipsis: !bounded
305
341
  });
306
342
  xPosition += col.width ?? minColumnWidth;
307
343
  if (i < columns.length - 1) {
package/dist/index.cjs CHANGED
@@ -73,6 +73,9 @@ __export(index_exports, {
73
73
  });
74
74
  module.exports = __toCommonJS(index_exports);
75
75
 
76
+ // src/report.ts
77
+ var import_date_fns = require("date-fns");
78
+
76
79
  // src/reports/salesByItemDetail.ts
77
80
  var FIELDS = {
78
81
  businessDate: { type: "date", columnKey: "date", filteringAvailable: false },
@@ -2985,7 +2988,11 @@ var getReportExportFileName = ({
2985
2988
  endDate,
2986
2989
  format
2987
2990
  }) => {
2988
- const formatDate2 = (date) => {
2991
+ const formatDate2 = (value) => {
2992
+ if (typeof value === "string" && /^\d{4}-\d{2}-\d{2}/.test(value) && (0, import_date_fns.isValid)((0, import_date_fns.parseISO)(value))) {
2993
+ return value.slice(0, 10);
2994
+ }
2995
+ const date = typeof value === "string" ? new Date(value) : value;
2989
2996
  return [
2990
2997
  date.getFullYear(),
2991
2998
  String(date.getMonth() + 1).padStart(2, "0"),
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { M as MessageKey, F as FieldType, C as ColumnAlignment, a as ColumnPresentationConfig, b as ColumnGroupConfig, R as ReportType, c as ReportConfig, d as ReportColumnKey } from './report-LuHGzrLx.cjs';
2
- export { A as AnalyticsField, e as COLUMN_REGISTRY, f as ColumnMessageKey, g as ColumnMetadata, h as FooterCalculation, i as FooterCalculationType, j as Report, k as getColumnExportHeaderLabel, l as getColumnMetadata, m as getFixedColumns, n as getReportConfig, o as getReportExportFileName } from './report-LuHGzrLx.cjs';
1
+ import { M as MessageKey, F as FieldType, C as ColumnAlignment, a as ColumnPresentationConfig, b as ColumnGroupConfig, R as ReportType, c as ReportConfig, d as ReportColumnKey } from './report-Dj8UU5d_.cjs';
2
+ export { A as AnalyticsField, e as COLUMN_REGISTRY, f as ColumnMessageKey, g as ColumnMetadata, h as FooterCalculation, i as FooterCalculationType, j as Report, k as getColumnExportHeaderLabel, l as getColumnMetadata, m as getFixedColumns, n as getReportConfig, o as getReportExportFileName } from './report-Dj8UU5d_.cjs';
3
3
  import { LocaleCode } from '@harbortouch/locale-registry';
4
4
  export { R as RenderedReport, a as ReportDocument, b as ReportFormat, c as ReportSection, d as ResolvedColumn, S as SummaryRow } from './ir-CUi0Z9hv.cjs';
5
5
 
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { M as MessageKey, F as FieldType, C as ColumnAlignment, a as ColumnPresentationConfig, b as ColumnGroupConfig, R as ReportType, c as ReportConfig, d as ReportColumnKey } from './report-LuHGzrLx.js';
2
- export { A as AnalyticsField, e as COLUMN_REGISTRY, f as ColumnMessageKey, g as ColumnMetadata, h as FooterCalculation, i as FooterCalculationType, j as Report, k as getColumnExportHeaderLabel, l as getColumnMetadata, m as getFixedColumns, n as getReportConfig, o as getReportExportFileName } from './report-LuHGzrLx.js';
1
+ import { M as MessageKey, F as FieldType, C as ColumnAlignment, a as ColumnPresentationConfig, b as ColumnGroupConfig, R as ReportType, c as ReportConfig, d as ReportColumnKey } from './report-Dj8UU5d_.js';
2
+ export { A as AnalyticsField, e as COLUMN_REGISTRY, f as ColumnMessageKey, g as ColumnMetadata, h as FooterCalculation, i as FooterCalculationType, j as Report, k as getColumnExportHeaderLabel, l as getColumnMetadata, m as getFixedColumns, n as getReportConfig, o as getReportExportFileName } from './report-Dj8UU5d_.js';
3
3
  import { LocaleCode } from '@harbortouch/locale-registry';
4
4
  export { R as RenderedReport, a as ReportDocument, b as ReportFormat, c as ReportSection, d as ResolvedColumn, S as SummaryRow } from './ir-CUi0Z9hv.js';
5
5
 
package/dist/index.js CHANGED
@@ -40,7 +40,7 @@ import {
40
40
  ticketLiveConfig,
41
41
  ticketSummaryConfig,
42
42
  tipCashoutReconciliationConfig
43
- } from "./chunk-ENBT7KRJ.js";
43
+ } from "./chunk-UUKHJDV3.js";
44
44
 
45
45
  // src/utils/alignment.ts
46
46
  function getColumnAlignment(type) {
@@ -1477,8 +1477,8 @@ declare const getFixedColumns: <TField extends string = string>(config: ReportCo
1477
1477
  }) => TField[];
1478
1478
  declare const getReportExportFileName: ({ reportType, startDate, endDate, format, }: {
1479
1479
  reportType: ReportType;
1480
- startDate: Date;
1481
- endDate: Date;
1480
+ startDate: string | Date;
1481
+ endDate: string | Date;
1482
1482
  format: "csv" | "xlsx" | "pdf";
1483
1483
  }) => string;
1484
1484
 
@@ -1477,8 +1477,8 @@ declare const getFixedColumns: <TField extends string = string>(config: ReportCo
1477
1477
  }) => TField[];
1478
1478
  declare const getReportExportFileName: ({ reportType, startDate, endDate, format, }: {
1479
1479
  reportType: ReportType;
1480
- startDate: Date;
1481
- endDate: Date;
1480
+ startDate: string | Date;
1481
+ endDate: string | Date;
1482
1482
  format: "csv" | "xlsx" | "pdf";
1483
1483
  }) => string;
1484
1484
 
@@ -2,6 +2,7 @@ interface ExportTableColumn {
2
2
  header: string;
3
3
  key: string;
4
4
  width?: number;
5
+ size?: number;
5
6
  }
6
7
  interface ExportTable {
7
8
  columns: ExportTableColumn[];
@@ -2,6 +2,7 @@ interface ExportTableColumn {
2
2
  header: string;
3
3
  key: string;
4
4
  width?: number;
5
+ size?: number;
5
6
  }
6
7
  interface ExportTable {
7
8
  columns: ExportTableColumn[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@harbortouch/skytab-analytics-report-utils",
3
- "version": "0.41.10",
3
+ "version": "0.41.12",
4
4
  "description": "Centralized report column presentation configuration for SkyTab Analytics",
5
5
  "engines": {
6
6
  "node": ">=22.6.0",