hazo_ui 6.16.0 → 6.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/CHANGE_LOG.md +90 -0
- package/README.md +13 -0
- package/dist/index.cjs +848 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +54 -3
- package/dist/index.d.ts +54 -3
- package/dist/index.js +849 -5
- package/dist/index.js.map +1 -1
- package/dist/index.locale-picker.cjs +5 -1
- package/dist/index.locale-picker.cjs.map +1 -1
- package/dist/index.locale-picker.js +5 -1
- package/dist/index.locale-picker.js.map +1 -1
- package/dist/index.utils.cjs +75 -0
- package/dist/index.utils.cjs.map +1 -1
- package/dist/index.utils.d.cts +1 -1
- package/dist/index.utils.d.ts +1 -1
- package/dist/index.utils.js +75 -1
- package/dist/index.utils.js.map +1 -1
- package/dist/{announce-DAnQDTld.d.cts → trend_stats-BAatnIt-.d.cts} +45 -1
- package/dist/{announce-DAnQDTld.d.ts → trend_stats-BAatnIt-.d.ts} +45 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -936,7 +936,11 @@ var en_default = {
|
|
|
936
936
|
"charts.pct_of_first": "% of first: {{pct}}%",
|
|
937
937
|
"charts.pct_of_prev": "% of prev: {{pct}}%",
|
|
938
938
|
"charts.dropoff": "Drop-off: \u2212{{value}}",
|
|
939
|
-
"toast.missing_prefix": "Missing: {{items}}"
|
|
939
|
+
"toast.missing_prefix": "Missing: {{items}}",
|
|
940
|
+
"editable_data_table.add": "Add",
|
|
941
|
+
"editable_data_table.delete": "Delete row",
|
|
942
|
+
"editable_data_table.empty": "No rows yet. Click Add to start.",
|
|
943
|
+
"editable_data_table.drag_to_reorder": "Drag to reorder"
|
|
940
944
|
};
|
|
941
945
|
var default_t = i18n.createPackageT("hazo_ui", en_default);
|
|
942
946
|
var defaultHazoUiT = default_t;
|
|
@@ -13224,6 +13228,846 @@ function useHazoEvent(pkg, event, handler) {
|
|
|
13224
13228
|
return () => window.removeEventListener(event_name, listener);
|
|
13225
13229
|
}, [pkg, event]);
|
|
13226
13230
|
}
|
|
13231
|
+
function EditableCell({
|
|
13232
|
+
value,
|
|
13233
|
+
type,
|
|
13234
|
+
options,
|
|
13235
|
+
defaultColorValue,
|
|
13236
|
+
clearable,
|
|
13237
|
+
onCommit,
|
|
13238
|
+
editable,
|
|
13239
|
+
forceEdit,
|
|
13240
|
+
onTabOut,
|
|
13241
|
+
onEnterDown,
|
|
13242
|
+
onPaste,
|
|
13243
|
+
onActivated
|
|
13244
|
+
}) {
|
|
13245
|
+
const [editing, setEditing] = React27.useState(false);
|
|
13246
|
+
const [draft, setDraft] = React27.useState(String(value ?? ""));
|
|
13247
|
+
const inputRef = React27.useRef(null);
|
|
13248
|
+
React27.useEffect(() => {
|
|
13249
|
+
if (forceEdit) {
|
|
13250
|
+
setDraft(String(value ?? ""));
|
|
13251
|
+
setEditing(true);
|
|
13252
|
+
}
|
|
13253
|
+
}, [forceEdit]);
|
|
13254
|
+
React27.useEffect(() => {
|
|
13255
|
+
if (editing && inputRef.current) {
|
|
13256
|
+
inputRef.current.focus();
|
|
13257
|
+
inputRef.current.select();
|
|
13258
|
+
if (forceEdit) {
|
|
13259
|
+
onActivated?.();
|
|
13260
|
+
}
|
|
13261
|
+
}
|
|
13262
|
+
}, [editing]);
|
|
13263
|
+
const commit = React27.useCallback(() => {
|
|
13264
|
+
setEditing(false);
|
|
13265
|
+
if (type === "number") {
|
|
13266
|
+
const n = Number(draft);
|
|
13267
|
+
onCommit(Number.isNaN(n) || draft.trim() === "" ? 0 : n);
|
|
13268
|
+
} else {
|
|
13269
|
+
onCommit(draft);
|
|
13270
|
+
}
|
|
13271
|
+
}, [draft, onCommit, type]);
|
|
13272
|
+
const cancel = React27.useCallback(() => {
|
|
13273
|
+
setEditing(false);
|
|
13274
|
+
setDraft(String(value ?? ""));
|
|
13275
|
+
}, [value]);
|
|
13276
|
+
const handleKeyDown = React27.useCallback(
|
|
13277
|
+
(e) => {
|
|
13278
|
+
if (e.key === "Enter") {
|
|
13279
|
+
e.preventDefault();
|
|
13280
|
+
onEnterDown?.();
|
|
13281
|
+
commit();
|
|
13282
|
+
} else if (e.key === "Escape") {
|
|
13283
|
+
e.preventDefault();
|
|
13284
|
+
cancel();
|
|
13285
|
+
} else if (e.key === "Tab") {
|
|
13286
|
+
const direction = e.shiftKey ? -1 : 1;
|
|
13287
|
+
const handled = onTabOut?.(direction) ?? false;
|
|
13288
|
+
if (handled) {
|
|
13289
|
+
e.preventDefault();
|
|
13290
|
+
commit();
|
|
13291
|
+
}
|
|
13292
|
+
}
|
|
13293
|
+
},
|
|
13294
|
+
[commit, cancel, onTabOut, onEnterDown]
|
|
13295
|
+
);
|
|
13296
|
+
if (!editable) {
|
|
13297
|
+
return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs truncate block px-1 py-1.5", children: String(value ?? "") });
|
|
13298
|
+
}
|
|
13299
|
+
if (type === "color") {
|
|
13300
|
+
const colorValue = String(
|
|
13301
|
+
value ?? defaultColorValue ?? "#94a3b8"
|
|
13302
|
+
);
|
|
13303
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-0.5", children: [
|
|
13304
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
13305
|
+
"input",
|
|
13306
|
+
{
|
|
13307
|
+
type: "color",
|
|
13308
|
+
className: "h-7 w-8 cursor-pointer border border-border rounded p-0.5",
|
|
13309
|
+
value: colorValue,
|
|
13310
|
+
onChange: (e) => onCommit(e.target.value)
|
|
13311
|
+
}
|
|
13312
|
+
),
|
|
13313
|
+
clearable && value != null && /* @__PURE__ */ jsxRuntime.jsx(
|
|
13314
|
+
"button",
|
|
13315
|
+
{
|
|
13316
|
+
type: "button",
|
|
13317
|
+
className: "text-xs leading-none text-muted-foreground hover:text-foreground px-0.5",
|
|
13318
|
+
onClick: () => onCommit(void 0),
|
|
13319
|
+
children: "\xD7"
|
|
13320
|
+
}
|
|
13321
|
+
)
|
|
13322
|
+
] });
|
|
13323
|
+
}
|
|
13324
|
+
if (type === "select") {
|
|
13325
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
13326
|
+
Select,
|
|
13327
|
+
{
|
|
13328
|
+
value: String(value ?? ""),
|
|
13329
|
+
onValueChange: (v) => onCommit(v),
|
|
13330
|
+
children: [
|
|
13331
|
+
/* @__PURE__ */ jsxRuntime.jsx(SelectTrigger, { className: "h-7 text-xs", children: /* @__PURE__ */ jsxRuntime.jsx(SelectValue, {}) }),
|
|
13332
|
+
/* @__PURE__ */ jsxRuntime.jsx(SelectContent, { children: (options ?? []).map((opt) => /* @__PURE__ */ jsxRuntime.jsx(SelectItem, { value: opt, children: opt }, opt)) })
|
|
13333
|
+
]
|
|
13334
|
+
}
|
|
13335
|
+
);
|
|
13336
|
+
}
|
|
13337
|
+
if (!editing) {
|
|
13338
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
13339
|
+
"span",
|
|
13340
|
+
{
|
|
13341
|
+
className: "text-xs truncate block px-1 py-1.5 cursor-text hover:bg-muted/50 rounded",
|
|
13342
|
+
onClick: () => {
|
|
13343
|
+
setDraft(String(value ?? ""));
|
|
13344
|
+
setEditing(true);
|
|
13345
|
+
},
|
|
13346
|
+
role: "button",
|
|
13347
|
+
tabIndex: 0,
|
|
13348
|
+
onKeyDown: (e) => {
|
|
13349
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
13350
|
+
setDraft(String(value ?? ""));
|
|
13351
|
+
setEditing(true);
|
|
13352
|
+
}
|
|
13353
|
+
},
|
|
13354
|
+
children: String(value ?? "")
|
|
13355
|
+
}
|
|
13356
|
+
);
|
|
13357
|
+
}
|
|
13358
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
13359
|
+
"input",
|
|
13360
|
+
{
|
|
13361
|
+
ref: inputRef,
|
|
13362
|
+
type: type === "number" ? "number" : "text",
|
|
13363
|
+
className: "h-7 text-xs border border-input rounded-md px-2 bg-background focus:outline-none focus:ring-1 focus:ring-ring w-full",
|
|
13364
|
+
value: draft,
|
|
13365
|
+
onChange: (e) => setDraft(e.target.value),
|
|
13366
|
+
onKeyDown: handleKeyDown,
|
|
13367
|
+
onPaste: (e) => {
|
|
13368
|
+
const text = e.clipboardData.getData("text/plain") || e.clipboardData.getData("text");
|
|
13369
|
+
if (!text) return;
|
|
13370
|
+
const handled = onPaste?.(text) ?? false;
|
|
13371
|
+
if (handled) {
|
|
13372
|
+
e.preventDefault();
|
|
13373
|
+
setEditing(false);
|
|
13374
|
+
}
|
|
13375
|
+
},
|
|
13376
|
+
onBlur: commit
|
|
13377
|
+
}
|
|
13378
|
+
);
|
|
13379
|
+
}
|
|
13380
|
+
var SortableRow = React27__namespace.default.memo(function SortableRow2({
|
|
13381
|
+
id,
|
|
13382
|
+
highlighted,
|
|
13383
|
+
children
|
|
13384
|
+
}) {
|
|
13385
|
+
const t = useHazoUiT();
|
|
13386
|
+
const {
|
|
13387
|
+
attributes,
|
|
13388
|
+
listeners,
|
|
13389
|
+
setNodeRef,
|
|
13390
|
+
setActivatorNodeRef,
|
|
13391
|
+
transform,
|
|
13392
|
+
transition,
|
|
13393
|
+
isDragging
|
|
13394
|
+
} = sortable.useSortable({ id });
|
|
13395
|
+
const style = {
|
|
13396
|
+
transform: utilities.CSS.Transform.toString(transform),
|
|
13397
|
+
transition,
|
|
13398
|
+
opacity: isDragging ? 0.5 : 1
|
|
13399
|
+
};
|
|
13400
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
13401
|
+
TableRow2,
|
|
13402
|
+
{
|
|
13403
|
+
ref: setNodeRef,
|
|
13404
|
+
style,
|
|
13405
|
+
"data-sortable-id": id,
|
|
13406
|
+
className: cn(highlighted && "bg-blue-50 ring-1 ring-blue-300"),
|
|
13407
|
+
children: [
|
|
13408
|
+
/* @__PURE__ */ jsxRuntime.jsx(TableCell2, { className: "px-0 py-0.5 w-6", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
13409
|
+
"div",
|
|
13410
|
+
{
|
|
13411
|
+
ref: setActivatorNodeRef,
|
|
13412
|
+
className: "cursor-grab active:cursor-grabbing text-muted-foreground hover:text-foreground flex items-center justify-center",
|
|
13413
|
+
title: t("editable_data_table.drag_to_reorder"),
|
|
13414
|
+
...attributes,
|
|
13415
|
+
...listeners,
|
|
13416
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.GripVertical, { className: "h-3.5 w-3.5" })
|
|
13417
|
+
}
|
|
13418
|
+
) }),
|
|
13419
|
+
children
|
|
13420
|
+
]
|
|
13421
|
+
}
|
|
13422
|
+
);
|
|
13423
|
+
});
|
|
13424
|
+
SortableRow.displayName = "SortableRow";
|
|
13425
|
+
var EditableTableRow = React27__namespace.default.memo(function EditableTableRow2({
|
|
13426
|
+
row,
|
|
13427
|
+
rowIndex,
|
|
13428
|
+
rowKey,
|
|
13429
|
+
isHighlighted,
|
|
13430
|
+
columns,
|
|
13431
|
+
activeEditColKey,
|
|
13432
|
+
deleteLabel,
|
|
13433
|
+
onCellCommit,
|
|
13434
|
+
onDeleteRow,
|
|
13435
|
+
onTabOutCell,
|
|
13436
|
+
onEnterDownCell,
|
|
13437
|
+
onPasteCell,
|
|
13438
|
+
onActivated
|
|
13439
|
+
}) {
|
|
13440
|
+
const renderCountRef = React27.useRef(0);
|
|
13441
|
+
renderCountRef.current += 1;
|
|
13442
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(SortableRow, { id: rowKey, highlighted: isHighlighted, children: [
|
|
13443
|
+
columns.map((col, colIndex) => {
|
|
13444
|
+
const resolvedOptions = typeof col.options === "function" ? col.options(row) : col.options;
|
|
13445
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
13446
|
+
TableCell2,
|
|
13447
|
+
{
|
|
13448
|
+
className: cn("px-1 py-0.5", col.width),
|
|
13449
|
+
"data-row-render-count": colIndex === 0 ? renderCountRef.current : void 0,
|
|
13450
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
13451
|
+
EditableCell,
|
|
13452
|
+
{
|
|
13453
|
+
value: row[col.key],
|
|
13454
|
+
type: col.type ?? "text",
|
|
13455
|
+
options: resolvedOptions,
|
|
13456
|
+
defaultColorValue: col.defaultValue?.(rowIndex),
|
|
13457
|
+
clearable: col.clearable,
|
|
13458
|
+
editable: col.editable !== false,
|
|
13459
|
+
forceEdit: activeEditColKey === col.key,
|
|
13460
|
+
onTabOut: (direction) => onTabOutCell(rowIndex, col.key, direction),
|
|
13461
|
+
onEnterDown: () => onEnterDownCell(rowIndex, col.key),
|
|
13462
|
+
onPaste: (text) => onPasteCell(rowIndex, col.key, text),
|
|
13463
|
+
onActivated,
|
|
13464
|
+
onCommit: (v) => onCellCommit(rowIndex, col.key, v)
|
|
13465
|
+
}
|
|
13466
|
+
)
|
|
13467
|
+
},
|
|
13468
|
+
col.key
|
|
13469
|
+
);
|
|
13470
|
+
}),
|
|
13471
|
+
/* @__PURE__ */ jsxRuntime.jsx(TableCell2, { className: "px-0 py-0.5 w-8", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
13472
|
+
Button,
|
|
13473
|
+
{
|
|
13474
|
+
variant: "ghost",
|
|
13475
|
+
size: "icon",
|
|
13476
|
+
className: "h-7 w-7 text-muted-foreground hover:text-destructive",
|
|
13477
|
+
onClick: () => onDeleteRow(rowIndex),
|
|
13478
|
+
"aria-label": deleteLabel,
|
|
13479
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { className: "h-3 w-3" })
|
|
13480
|
+
}
|
|
13481
|
+
) })
|
|
13482
|
+
] });
|
|
13483
|
+
});
|
|
13484
|
+
EditableTableRow.displayName = "EditableTableRow";
|
|
13485
|
+
function EditableDataTable({
|
|
13486
|
+
columns,
|
|
13487
|
+
rows,
|
|
13488
|
+
onChange,
|
|
13489
|
+
getRowKey,
|
|
13490
|
+
createRow,
|
|
13491
|
+
highlightedKey,
|
|
13492
|
+
className,
|
|
13493
|
+
label
|
|
13494
|
+
}) {
|
|
13495
|
+
const t = useHazoUiT();
|
|
13496
|
+
const editableColumns = React27.useMemo(
|
|
13497
|
+
() => columns.filter(
|
|
13498
|
+
(col) => col.editable !== false && (col.type ?? "text") !== "select" && (col.type ?? "text") !== "color"
|
|
13499
|
+
).map((col) => col.key),
|
|
13500
|
+
[columns]
|
|
13501
|
+
);
|
|
13502
|
+
const [activeEditCell, setActiveEditCell] = React27.useState(null);
|
|
13503
|
+
const rowsRef = React27.useRef(rows);
|
|
13504
|
+
rowsRef.current = rows;
|
|
13505
|
+
const onChangeRef = React27.useRef(onChange);
|
|
13506
|
+
onChangeRef.current = onChange;
|
|
13507
|
+
const handleTabOut = React27.useCallback(
|
|
13508
|
+
(rowIndex, colKey, direction) => {
|
|
13509
|
+
const idx = editableColumns.indexOf(colKey);
|
|
13510
|
+
if (idx === -1) return false;
|
|
13511
|
+
if (direction === 1) {
|
|
13512
|
+
if (idx < editableColumns.length - 1) {
|
|
13513
|
+
setActiveEditCell({ row: rowIndex, col: editableColumns[idx + 1] });
|
|
13514
|
+
return true;
|
|
13515
|
+
}
|
|
13516
|
+
if (rowIndex + 1 < rows.length) {
|
|
13517
|
+
setActiveEditCell({ row: rowIndex + 1, col: editableColumns[0] });
|
|
13518
|
+
return true;
|
|
13519
|
+
}
|
|
13520
|
+
return false;
|
|
13521
|
+
}
|
|
13522
|
+
if (idx > 0) {
|
|
13523
|
+
setActiveEditCell({ row: rowIndex, col: editableColumns[idx - 1] });
|
|
13524
|
+
return true;
|
|
13525
|
+
}
|
|
13526
|
+
if (rowIndex - 1 >= 0) {
|
|
13527
|
+
setActiveEditCell({
|
|
13528
|
+
row: rowIndex - 1,
|
|
13529
|
+
col: editableColumns[editableColumns.length - 1]
|
|
13530
|
+
});
|
|
13531
|
+
return true;
|
|
13532
|
+
}
|
|
13533
|
+
return false;
|
|
13534
|
+
},
|
|
13535
|
+
[editableColumns, rows.length]
|
|
13536
|
+
);
|
|
13537
|
+
const handleEnterDown = React27.useCallback(
|
|
13538
|
+
(rowIndex, colKey) => {
|
|
13539
|
+
if (rowIndex + 1 < rows.length) {
|
|
13540
|
+
setActiveEditCell({ row: rowIndex + 1, col: colKey });
|
|
13541
|
+
return true;
|
|
13542
|
+
}
|
|
13543
|
+
return false;
|
|
13544
|
+
},
|
|
13545
|
+
[rows.length]
|
|
13546
|
+
);
|
|
13547
|
+
const handlePaste = React27.useCallback(
|
|
13548
|
+
(rowIndex, colKey, text) => {
|
|
13549
|
+
const startColIdx = editableColumns.indexOf(
|
|
13550
|
+
colKey
|
|
13551
|
+
);
|
|
13552
|
+
if (startColIdx === -1) return false;
|
|
13553
|
+
const normalized = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
13554
|
+
const lines = normalized.split("\n");
|
|
13555
|
+
if (lines.length > 1 && lines[lines.length - 1] === "") {
|
|
13556
|
+
lines.pop();
|
|
13557
|
+
}
|
|
13558
|
+
const hasTab = normalized.includes(" ");
|
|
13559
|
+
const delimiter = hasTab ? " " : ",";
|
|
13560
|
+
const grid = lines.map((line) => line.split(delimiter));
|
|
13561
|
+
const isSingleCell = grid.length === 1 && grid[0].length === 1;
|
|
13562
|
+
if (isSingleCell) return false;
|
|
13563
|
+
const currentRows = rowsRef.current;
|
|
13564
|
+
const nextRows = currentRows.slice();
|
|
13565
|
+
grid.forEach((lineCells, rowOffset) => {
|
|
13566
|
+
const targetRowIndex = rowIndex + rowOffset;
|
|
13567
|
+
while (targetRowIndex >= nextRows.length) {
|
|
13568
|
+
nextRows.push(createRow());
|
|
13569
|
+
}
|
|
13570
|
+
const targetRow = {
|
|
13571
|
+
...nextRows[targetRowIndex]
|
|
13572
|
+
};
|
|
13573
|
+
lineCells.forEach((cellValue, colOffset) => {
|
|
13574
|
+
const targetColIdx = startColIdx + colOffset;
|
|
13575
|
+
if (targetColIdx >= editableColumns.length) return;
|
|
13576
|
+
const targetColKey = editableColumns[targetColIdx];
|
|
13577
|
+
const col = columns.find((c) => c.key === targetColKey);
|
|
13578
|
+
if (col?.type === "number") {
|
|
13579
|
+
const n = Number(cellValue);
|
|
13580
|
+
targetRow[targetColKey] = Number.isNaN(n) || cellValue.trim() === "" ? 0 : n;
|
|
13581
|
+
} else {
|
|
13582
|
+
targetRow[targetColKey] = cellValue;
|
|
13583
|
+
}
|
|
13584
|
+
});
|
|
13585
|
+
nextRows[targetRowIndex] = targetRow;
|
|
13586
|
+
});
|
|
13587
|
+
onChangeRef.current(nextRows);
|
|
13588
|
+
return true;
|
|
13589
|
+
},
|
|
13590
|
+
[editableColumns, columns, createRow]
|
|
13591
|
+
);
|
|
13592
|
+
const handleCellChange = React27.useCallback(
|
|
13593
|
+
(rowIndex, colKey, value) => {
|
|
13594
|
+
const currentRows = rowsRef.current;
|
|
13595
|
+
const next = currentRows.map(
|
|
13596
|
+
(row, i) => i === rowIndex ? { ...row, [colKey]: value } : row
|
|
13597
|
+
);
|
|
13598
|
+
onChangeRef.current(next);
|
|
13599
|
+
},
|
|
13600
|
+
[]
|
|
13601
|
+
);
|
|
13602
|
+
const handleDelete = React27.useCallback((rowIndex) => {
|
|
13603
|
+
const currentRows = rowsRef.current;
|
|
13604
|
+
onChangeRef.current(currentRows.filter((_, i) => i !== rowIndex));
|
|
13605
|
+
}, []);
|
|
13606
|
+
const onCellActivated = React27.useCallback(() => setActiveEditCell(null), []);
|
|
13607
|
+
const handleAdd = () => {
|
|
13608
|
+
onChange([...rows, createRow()]);
|
|
13609
|
+
};
|
|
13610
|
+
const sensors = core$1.useSensors(
|
|
13611
|
+
core$1.useSensor(core$1.PointerSensor),
|
|
13612
|
+
core$1.useSensor(core$1.KeyboardSensor, {
|
|
13613
|
+
coordinateGetter: sortable.sortableKeyboardCoordinates
|
|
13614
|
+
})
|
|
13615
|
+
);
|
|
13616
|
+
const rowKeys = React27.useMemo(
|
|
13617
|
+
() => rows.map((row, i) => getRowKey(row, i)),
|
|
13618
|
+
[rows, getRowKey]
|
|
13619
|
+
);
|
|
13620
|
+
const handleDragEnd = React27.useCallback(
|
|
13621
|
+
(event) => {
|
|
13622
|
+
const { active, over } = event;
|
|
13623
|
+
if (!over || active.id === over.id) return;
|
|
13624
|
+
const oldIndex = rows.findIndex(
|
|
13625
|
+
(row, i) => getRowKey(row, i) === active.id
|
|
13626
|
+
);
|
|
13627
|
+
const newIndex = rows.findIndex(
|
|
13628
|
+
(row, i) => getRowKey(row, i) === over.id
|
|
13629
|
+
);
|
|
13630
|
+
if (oldIndex === -1 || newIndex === -1) return;
|
|
13631
|
+
onChange(sortable.arrayMove(rows, oldIndex, newIndex));
|
|
13632
|
+
},
|
|
13633
|
+
[rows, getRowKey, onChange]
|
|
13634
|
+
);
|
|
13635
|
+
return (
|
|
13636
|
+
// DndContext wraps the whole component rather than just <TableBody> —
|
|
13637
|
+
// it renders its own hidden accessibility helpers (RestoreFocus,
|
|
13638
|
+
// live-region announcer) as DOM siblings of its children. Nesting it
|
|
13639
|
+
// directly inside <tbody> would make those helpers invalid children of
|
|
13640
|
+
// <tbody> (only <tr> is valid there); wrapping the outer <div> instead
|
|
13641
|
+
// gives them a valid home. <SortableContext> is a plain context
|
|
13642
|
+
// provider with no DOM output of its own, so it can stay right inside
|
|
13643
|
+
// <TableBody>, wrapping only the actual <tr> rows.
|
|
13644
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
13645
|
+
core$1.DndContext,
|
|
13646
|
+
{
|
|
13647
|
+
sensors,
|
|
13648
|
+
collisionDetection: core$1.closestCenter,
|
|
13649
|
+
onDragEnd: handleDragEnd,
|
|
13650
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-editable-data-table": true, className: cn("flex flex-col gap-1", className), children: [
|
|
13651
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-1", children: [
|
|
13652
|
+
label && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-semibold text-muted-foreground uppercase tracking-wide", children: label }),
|
|
13653
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
13654
|
+
Button,
|
|
13655
|
+
{
|
|
13656
|
+
variant: "ghost",
|
|
13657
|
+
size: "sm",
|
|
13658
|
+
className: "h-6 px-2 text-xs ml-auto",
|
|
13659
|
+
onClick: handleAdd,
|
|
13660
|
+
"aria-label": t("editable_data_table.add"),
|
|
13661
|
+
children: [
|
|
13662
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { className: "h-3 w-3 mr-1" }),
|
|
13663
|
+
t("editable_data_table.add")
|
|
13664
|
+
]
|
|
13665
|
+
}
|
|
13666
|
+
)
|
|
13667
|
+
] }),
|
|
13668
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Table2, { children: [
|
|
13669
|
+
/* @__PURE__ */ jsxRuntime.jsx(TableHeader2, { children: /* @__PURE__ */ jsxRuntime.jsxs(TableRow2, { className: "bg-muted/60 hover:bg-muted/60 border-b-2 border-border", children: [
|
|
13670
|
+
/* @__PURE__ */ jsxRuntime.jsx(TableHead, { className: "w-6 px-0" }),
|
|
13671
|
+
columns.map((col) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
13672
|
+
TableHead,
|
|
13673
|
+
{
|
|
13674
|
+
className: cn(
|
|
13675
|
+
"text-[11px] font-semibold uppercase tracking-wide text-foreground px-1 py-1 h-8",
|
|
13676
|
+
col.width
|
|
13677
|
+
),
|
|
13678
|
+
children: col.label
|
|
13679
|
+
},
|
|
13680
|
+
col.key
|
|
13681
|
+
)),
|
|
13682
|
+
/* @__PURE__ */ jsxRuntime.jsx(TableHead, { className: "w-8 px-0" })
|
|
13683
|
+
] }) }),
|
|
13684
|
+
/* @__PURE__ */ jsxRuntime.jsxs(TableBody, { children: [
|
|
13685
|
+
rows.length === 0 && /* @__PURE__ */ jsxRuntime.jsx(TableRow2, { children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
13686
|
+
TableCell2,
|
|
13687
|
+
{
|
|
13688
|
+
colSpan: columns.length + 2,
|
|
13689
|
+
className: "text-center text-xs text-muted-foreground italic py-4",
|
|
13690
|
+
children: t("editable_data_table.empty")
|
|
13691
|
+
}
|
|
13692
|
+
) }),
|
|
13693
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
13694
|
+
sortable.SortableContext,
|
|
13695
|
+
{
|
|
13696
|
+
items: rowKeys,
|
|
13697
|
+
strategy: sortable.verticalListSortingStrategy,
|
|
13698
|
+
children: rows.map((row, rowIndex) => {
|
|
13699
|
+
const key = rowKeys[rowIndex];
|
|
13700
|
+
const isHighlighted = highlightedKey != null && key === highlightedKey;
|
|
13701
|
+
const activeEditColKey = activeEditCell?.row === rowIndex ? activeEditCell.col : null;
|
|
13702
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
13703
|
+
EditableTableRow,
|
|
13704
|
+
{
|
|
13705
|
+
row,
|
|
13706
|
+
rowIndex,
|
|
13707
|
+
rowKey: key,
|
|
13708
|
+
isHighlighted,
|
|
13709
|
+
columns,
|
|
13710
|
+
activeEditColKey,
|
|
13711
|
+
deleteLabel: t("editable_data_table.delete"),
|
|
13712
|
+
onCellCommit: handleCellChange,
|
|
13713
|
+
onDeleteRow: handleDelete,
|
|
13714
|
+
onTabOutCell: handleTabOut,
|
|
13715
|
+
onEnterDownCell: handleEnterDown,
|
|
13716
|
+
onPasteCell: handlePaste,
|
|
13717
|
+
onActivated: onCellActivated
|
|
13718
|
+
},
|
|
13719
|
+
key
|
|
13720
|
+
);
|
|
13721
|
+
})
|
|
13722
|
+
}
|
|
13723
|
+
)
|
|
13724
|
+
] })
|
|
13725
|
+
] })
|
|
13726
|
+
] })
|
|
13727
|
+
}
|
|
13728
|
+
)
|
|
13729
|
+
);
|
|
13730
|
+
}
|
|
13731
|
+
function TrendDeltaBadge({ pct }) {
|
|
13732
|
+
if (pct === null) {
|
|
13733
|
+
return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground", children: "\u2014" });
|
|
13734
|
+
}
|
|
13735
|
+
const up = pct >= 0;
|
|
13736
|
+
const cls = up ? "text-emerald-600" : "text-red-600";
|
|
13737
|
+
const arrow = up ? "\u25B2" : "\u25BC";
|
|
13738
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: `text-xs font-medium ${cls}`, title: "vs previous period", children: [
|
|
13739
|
+
arrow,
|
|
13740
|
+
" ",
|
|
13741
|
+
up ? "+" : "",
|
|
13742
|
+
pct,
|
|
13743
|
+
"%"
|
|
13744
|
+
] });
|
|
13745
|
+
}
|
|
13746
|
+
var CARD_HEIGHT_PX = 168;
|
|
13747
|
+
function TrendStatCard({
|
|
13748
|
+
w,
|
|
13749
|
+
totalLabel,
|
|
13750
|
+
prevTemplate
|
|
13751
|
+
}) {
|
|
13752
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
13753
|
+
"div",
|
|
13754
|
+
{
|
|
13755
|
+
className: "cls_trend_stat_card flex min-w-0 flex-col justify-between rounded-lg border bg-card p-4",
|
|
13756
|
+
style: { height: CARD_HEIGHT_PX },
|
|
13757
|
+
children: [
|
|
13758
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "truncate text-xs text-muted-foreground", children: w.label }),
|
|
13759
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-baseline justify-between gap-1", children: [
|
|
13760
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-3xl font-bold tabular-nums", children: w.count.toLocaleString() }),
|
|
13761
|
+
w.key !== "all" && /* @__PURE__ */ jsxRuntime.jsx(TrendDeltaBadge, { pct: w.pctChange })
|
|
13762
|
+
] }),
|
|
13763
|
+
w.key !== "all" && w.prevCount !== null && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "truncate text-xs text-muted-foreground", children: prevTemplate.replace("{count}", w.prevCount.toLocaleString()) }),
|
|
13764
|
+
w.key === "all" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "truncate text-xs text-muted-foreground", children: totalLabel })
|
|
13765
|
+
]
|
|
13766
|
+
}
|
|
13767
|
+
);
|
|
13768
|
+
}
|
|
13769
|
+
function niceMax(n) {
|
|
13770
|
+
if (n <= 0) return 1;
|
|
13771
|
+
const exp = Math.floor(Math.log10(n));
|
|
13772
|
+
const base = Math.pow(10, exp);
|
|
13773
|
+
const frac = n / base;
|
|
13774
|
+
let niceFrac;
|
|
13775
|
+
if (frac <= 1) niceFrac = 1;
|
|
13776
|
+
else if (frac <= 2) niceFrac = 2;
|
|
13777
|
+
else if (frac <= 5) niceFrac = 5;
|
|
13778
|
+
else niceFrac = 10;
|
|
13779
|
+
return niceFrac * base;
|
|
13780
|
+
}
|
|
13781
|
+
function TrendMiniChart({
|
|
13782
|
+
data,
|
|
13783
|
+
title,
|
|
13784
|
+
tooltipSuffix
|
|
13785
|
+
}) {
|
|
13786
|
+
const [hoverIndex, setHoverIndex] = React27.useState(null);
|
|
13787
|
+
const total = data.reduce((n2, d) => n2 + d.count, 0);
|
|
13788
|
+
const rawMax = Math.max(1, ...data.map((d) => d.count));
|
|
13789
|
+
const max = niceMax(rawMax);
|
|
13790
|
+
const mid = Math.round(max / 2);
|
|
13791
|
+
const W = 480;
|
|
13792
|
+
const H = 120;
|
|
13793
|
+
const marginLeft = 34;
|
|
13794
|
+
const marginRight = 8;
|
|
13795
|
+
const marginTop = 16;
|
|
13796
|
+
const marginBottom = 20;
|
|
13797
|
+
const plotW = W - marginLeft - marginRight;
|
|
13798
|
+
const plotH = H - marginTop - marginBottom;
|
|
13799
|
+
const gap = 6;
|
|
13800
|
+
const n = Math.max(1, data.length);
|
|
13801
|
+
const colW = plotW / n;
|
|
13802
|
+
const bw = Math.max(1, colW - gap);
|
|
13803
|
+
const yFor = (value) => marginTop + plotH - value / max * plotH;
|
|
13804
|
+
const hovered = hoverIndex !== null ? data[hoverIndex] : null;
|
|
13805
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
13806
|
+
"div",
|
|
13807
|
+
{
|
|
13808
|
+
className: "cls_trend_mini_chart col-span-6 flex min-w-0 flex-col rounded-lg border bg-card p-3",
|
|
13809
|
+
style: { height: CARD_HEIGHT_PX },
|
|
13810
|
+
children: [
|
|
13811
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex shrink-0 items-baseline justify-between", children: [
|
|
13812
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "truncate text-[11px] text-muted-foreground", children: title }),
|
|
13813
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm font-semibold tabular-nums", children: total.toLocaleString() })
|
|
13814
|
+
] }),
|
|
13815
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative mt-2 min-h-0 flex-1", children: [
|
|
13816
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
13817
|
+
"svg",
|
|
13818
|
+
{
|
|
13819
|
+
role: "img",
|
|
13820
|
+
"aria-label": data.map((d) => `${d.label} ${d.count}`).join(", "),
|
|
13821
|
+
viewBox: `0 0 ${W} ${H}`,
|
|
13822
|
+
preserveAspectRatio: "none",
|
|
13823
|
+
className: "h-full w-full",
|
|
13824
|
+
children: [
|
|
13825
|
+
[0, mid, max].map((tick) => {
|
|
13826
|
+
const y = yFor(tick);
|
|
13827
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("g", { children: [
|
|
13828
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
13829
|
+
"line",
|
|
13830
|
+
{
|
|
13831
|
+
x1: marginLeft,
|
|
13832
|
+
x2: W - marginRight,
|
|
13833
|
+
y1: y,
|
|
13834
|
+
y2: y,
|
|
13835
|
+
stroke: "currentColor",
|
|
13836
|
+
strokeOpacity: 0.1
|
|
13837
|
+
}
|
|
13838
|
+
),
|
|
13839
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
13840
|
+
"text",
|
|
13841
|
+
{
|
|
13842
|
+
x: marginLeft - 6,
|
|
13843
|
+
y,
|
|
13844
|
+
dy: 3,
|
|
13845
|
+
textAnchor: "end",
|
|
13846
|
+
fontSize: 9,
|
|
13847
|
+
className: "fill-muted-foreground",
|
|
13848
|
+
children: tick.toLocaleString()
|
|
13849
|
+
}
|
|
13850
|
+
)
|
|
13851
|
+
] }, tick);
|
|
13852
|
+
}),
|
|
13853
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
13854
|
+
"line",
|
|
13855
|
+
{
|
|
13856
|
+
x1: marginLeft,
|
|
13857
|
+
x2: W - marginRight,
|
|
13858
|
+
y1: marginTop + plotH,
|
|
13859
|
+
y2: marginTop + plotH,
|
|
13860
|
+
stroke: "currentColor",
|
|
13861
|
+
strokeOpacity: 0.2
|
|
13862
|
+
}
|
|
13863
|
+
),
|
|
13864
|
+
data.map((d, i) => {
|
|
13865
|
+
const barH = Math.round(d.count / max * plotH);
|
|
13866
|
+
const colX = marginLeft + i * colW;
|
|
13867
|
+
const barX = colX + (colW - bw) / 2;
|
|
13868
|
+
const barY = marginTop + plotH - barH;
|
|
13869
|
+
const centerX = colX + colW / 2;
|
|
13870
|
+
const isDimmed = hoverIndex !== null && hoverIndex !== i;
|
|
13871
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
13872
|
+
"g",
|
|
13873
|
+
{
|
|
13874
|
+
onMouseEnter: () => setHoverIndex(i),
|
|
13875
|
+
onMouseLeave: () => setHoverIndex(null),
|
|
13876
|
+
children: [
|
|
13877
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
13878
|
+
"rect",
|
|
13879
|
+
{
|
|
13880
|
+
x: colX,
|
|
13881
|
+
y: marginTop,
|
|
13882
|
+
width: colW,
|
|
13883
|
+
height: plotH,
|
|
13884
|
+
fill: "transparent"
|
|
13885
|
+
}
|
|
13886
|
+
),
|
|
13887
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
13888
|
+
"rect",
|
|
13889
|
+
{
|
|
13890
|
+
x: barX,
|
|
13891
|
+
y: barY,
|
|
13892
|
+
width: bw,
|
|
13893
|
+
height: Math.max(0, barH),
|
|
13894
|
+
rx: 2,
|
|
13895
|
+
className: "fill-primary",
|
|
13896
|
+
fillOpacity: isDimmed ? 0.45 : 1,
|
|
13897
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("title", { children: `${d.label}: ${d.count} ${tooltipSuffix}` })
|
|
13898
|
+
}
|
|
13899
|
+
),
|
|
13900
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
13901
|
+
"text",
|
|
13902
|
+
{
|
|
13903
|
+
x: centerX,
|
|
13904
|
+
y: barY - 4,
|
|
13905
|
+
textAnchor: "middle",
|
|
13906
|
+
fontSize: 10,
|
|
13907
|
+
fontWeight: 600,
|
|
13908
|
+
className: "fill-foreground",
|
|
13909
|
+
children: d.count
|
|
13910
|
+
}
|
|
13911
|
+
),
|
|
13912
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
13913
|
+
"text",
|
|
13914
|
+
{
|
|
13915
|
+
x: centerX,
|
|
13916
|
+
y: marginTop + plotH + 14,
|
|
13917
|
+
textAnchor: "middle",
|
|
13918
|
+
fontSize: 9,
|
|
13919
|
+
className: "fill-muted-foreground",
|
|
13920
|
+
children: d.label
|
|
13921
|
+
}
|
|
13922
|
+
)
|
|
13923
|
+
]
|
|
13924
|
+
},
|
|
13925
|
+
d.label
|
|
13926
|
+
);
|
|
13927
|
+
})
|
|
13928
|
+
]
|
|
13929
|
+
}
|
|
13930
|
+
),
|
|
13931
|
+
hovered && hoverIndex !== null && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
13932
|
+
"div",
|
|
13933
|
+
{
|
|
13934
|
+
className: "pointer-events-none absolute z-10 -translate-x-1/2 -translate-y-full rounded-md border bg-popover px-2 py-1 text-[10px] font-medium text-popover-foreground shadow-sm",
|
|
13935
|
+
style: {
|
|
13936
|
+
left: `${Math.min(
|
|
13937
|
+
92,
|
|
13938
|
+
Math.max(
|
|
13939
|
+
8,
|
|
13940
|
+
(marginLeft + hoverIndex * (plotW / n) + plotW / n / 2) / W * 100
|
|
13941
|
+
)
|
|
13942
|
+
)}%`,
|
|
13943
|
+
top: `${(marginTop + plotH - hovered.count / max * plotH) / H * 100}%`
|
|
13944
|
+
},
|
|
13945
|
+
children: [
|
|
13946
|
+
hovered.label,
|
|
13947
|
+
": ",
|
|
13948
|
+
hovered.count,
|
|
13949
|
+
" ",
|
|
13950
|
+
tooltipSuffix
|
|
13951
|
+
]
|
|
13952
|
+
}
|
|
13953
|
+
)
|
|
13954
|
+
] })
|
|
13955
|
+
]
|
|
13956
|
+
}
|
|
13957
|
+
);
|
|
13958
|
+
}
|
|
13959
|
+
function TrendSummary({
|
|
13960
|
+
windows,
|
|
13961
|
+
chartData,
|
|
13962
|
+
chartTitle = "Activity (YTD)",
|
|
13963
|
+
chartTooltipSuffix = "",
|
|
13964
|
+
totalLabel = "total",
|
|
13965
|
+
prevTemplate = "prev {count}",
|
|
13966
|
+
className
|
|
13967
|
+
}) {
|
|
13968
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
13969
|
+
"div",
|
|
13970
|
+
{
|
|
13971
|
+
className: cn(
|
|
13972
|
+
"cls_trend_summary mb-4 grid grid-cols-6 items-start gap-2 md:grid-cols-12",
|
|
13973
|
+
className
|
|
13974
|
+
),
|
|
13975
|
+
children: [
|
|
13976
|
+
windows.map((w) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
13977
|
+
TrendStatCard,
|
|
13978
|
+
{
|
|
13979
|
+
w,
|
|
13980
|
+
totalLabel,
|
|
13981
|
+
prevTemplate
|
|
13982
|
+
},
|
|
13983
|
+
w.key
|
|
13984
|
+
)),
|
|
13985
|
+
chartData && chartData.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
|
|
13986
|
+
TrendMiniChart,
|
|
13987
|
+
{
|
|
13988
|
+
data: chartData,
|
|
13989
|
+
title: chartTitle,
|
|
13990
|
+
tooltipSuffix: chartTooltipSuffix
|
|
13991
|
+
}
|
|
13992
|
+
)
|
|
13993
|
+
]
|
|
13994
|
+
}
|
|
13995
|
+
);
|
|
13996
|
+
}
|
|
13997
|
+
|
|
13998
|
+
// src/lib/trend_stats.ts
|
|
13999
|
+
var DAY_MS = 864e5;
|
|
14000
|
+
var MONTHS = [
|
|
14001
|
+
"Jan",
|
|
14002
|
+
"Feb",
|
|
14003
|
+
"Mar",
|
|
14004
|
+
"Apr",
|
|
14005
|
+
"May",
|
|
14006
|
+
"Jun",
|
|
14007
|
+
"Jul",
|
|
14008
|
+
"Aug",
|
|
14009
|
+
"Sep",
|
|
14010
|
+
"Oct",
|
|
14011
|
+
"Nov",
|
|
14012
|
+
"Dec"
|
|
14013
|
+
];
|
|
14014
|
+
function pctOf(count, prev) {
|
|
14015
|
+
if (prev === null || prev === 0) return null;
|
|
14016
|
+
return Math.round((count - prev) / prev * 100);
|
|
14017
|
+
}
|
|
14018
|
+
var DEFAULT_WINDOWS = [
|
|
14019
|
+
{ key: "7d", label: "7 days", days: 7 },
|
|
14020
|
+
{ key: "30d", label: "30 days", days: 30 },
|
|
14021
|
+
{ key: "90d", label: "3 months", days: 90 },
|
|
14022
|
+
{ key: "180d", label: "6 months", days: 180 }
|
|
14023
|
+
];
|
|
14024
|
+
function computeTrendStats(items, now = /* @__PURE__ */ new Date(), options) {
|
|
14025
|
+
const nowMs = now.getTime();
|
|
14026
|
+
const times = [];
|
|
14027
|
+
for (const item of items) {
|
|
14028
|
+
if (!item || !item.created_at) continue;
|
|
14029
|
+
const t = new Date(item.created_at).getTime();
|
|
14030
|
+
if (!Number.isNaN(t)) times.push(t);
|
|
14031
|
+
}
|
|
14032
|
+
const countBetween = (startMs, endMs) => times.reduce((n, t) => t >= startMs && t < endMs ? n + 1 : n, 0);
|
|
14033
|
+
const total = times.length;
|
|
14034
|
+
const rollingDefs = options?.windows ?? DEFAULT_WINDOWS;
|
|
14035
|
+
const windows = [
|
|
14036
|
+
{ key: "all", label: options?.totalLabel ?? "All time", count: total, prevCount: null, pctChange: null }
|
|
14037
|
+
];
|
|
14038
|
+
for (const w of rollingDefs) {
|
|
14039
|
+
const span = w.days * DAY_MS;
|
|
14040
|
+
const count = countBetween(nowMs - span, nowMs);
|
|
14041
|
+
const prevCount = countBetween(nowMs - 2 * span, nowMs - span);
|
|
14042
|
+
windows.push({
|
|
14043
|
+
key: w.key,
|
|
14044
|
+
label: w.label,
|
|
14045
|
+
count,
|
|
14046
|
+
prevCount,
|
|
14047
|
+
pctChange: pctOf(count, prevCount)
|
|
14048
|
+
});
|
|
14049
|
+
}
|
|
14050
|
+
const ytdStart = new Date(now.getFullYear(), 0, 1).getTime();
|
|
14051
|
+
const elapsed = nowMs - ytdStart;
|
|
14052
|
+
const ytdCount = countBetween(ytdStart, nowMs);
|
|
14053
|
+
const prevYtdStart = new Date(now.getFullYear() - 1, 0, 1).getTime();
|
|
14054
|
+
const prevYtdCount = countBetween(prevYtdStart, prevYtdStart + elapsed);
|
|
14055
|
+
windows.push({
|
|
14056
|
+
key: "ytd",
|
|
14057
|
+
label: "YTD",
|
|
14058
|
+
count: ytdCount,
|
|
14059
|
+
prevCount: prevYtdCount,
|
|
14060
|
+
pctChange: pctOf(ytdCount, prevYtdCount)
|
|
14061
|
+
});
|
|
14062
|
+
const ytdMonthly = [];
|
|
14063
|
+
const year = now.getFullYear();
|
|
14064
|
+
for (let m = 0; m <= now.getMonth(); m++) {
|
|
14065
|
+
const monthStart = new Date(year, m, 1).getTime();
|
|
14066
|
+
const monthEnd = new Date(year, m + 1, 1).getTime();
|
|
14067
|
+
ytdMonthly.push({ label: MONTHS[m], count: countBetween(monthStart, monthEnd) });
|
|
14068
|
+
}
|
|
14069
|
+
return { total, windows, ytdMonthly };
|
|
14070
|
+
}
|
|
13227
14071
|
|
|
13228
14072
|
Object.defineProperty(exports, "rawToast", {
|
|
13229
14073
|
enumerable: true,
|
|
@@ -13316,6 +14160,7 @@ exports.DropdownMenuSub = DropdownMenuSub;
|
|
|
13316
14160
|
exports.DropdownMenuSubContent = DropdownMenuSubContent;
|
|
13317
14161
|
exports.DropdownMenuSubTrigger = DropdownMenuSubTrigger;
|
|
13318
14162
|
exports.DropdownMenuTrigger = DropdownMenuTrigger;
|
|
14163
|
+
exports.EditableDataTable = EditableDataTable;
|
|
13319
14164
|
exports.EmptyState = EmptyState;
|
|
13320
14165
|
exports.ErrorBanner = ErrorBanner;
|
|
13321
14166
|
exports.ErrorPage = ErrorPage;
|
|
@@ -13425,12 +14270,14 @@ exports.Tooltip = Tooltip;
|
|
|
13425
14270
|
exports.TooltipContent = TooltipContent;
|
|
13426
14271
|
exports.TooltipProvider = TooltipProvider;
|
|
13427
14272
|
exports.TooltipTrigger = TooltipTrigger;
|
|
14273
|
+
exports.TrendSummary = TrendSummary;
|
|
13428
14274
|
exports.announce = announce;
|
|
13429
14275
|
exports.applyKanbanFilter = applyKanbanFilter;
|
|
13430
14276
|
exports.buttonGroupVariants = buttonGroupVariants;
|
|
13431
14277
|
exports.celebrate = celebrate;
|
|
13432
14278
|
exports.cn = cn;
|
|
13433
14279
|
exports.computeEta = computeEta;
|
|
14280
|
+
exports.computeTrendStats = computeTrendStats;
|
|
13434
14281
|
exports.create_command_suggestion_extension = create_command_suggestion_extension;
|
|
13435
14282
|
exports.defaultHazoUiT = defaultHazoUiT;
|
|
13436
14283
|
exports.easeToward = easeToward;
|