@naisys/erp 3.0.0-beta.21 → 3.0.0-beta.23

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.
@@ -1,5 +1,5 @@
1
1
  import { r as __toESM } from "./rolldown-runtime-CvHMtSRF.js";
2
- import { $ as Link, $t as MantineProvider, A as IconListDetails, At as FileButton, B as IconAlertTriangle, Bt as Checkbox, C as IconX, Ct as PasswordInput, D as IconPlus, Dt as Menu, E as IconTrash, Et as Modal, F as IconEye$1, Ft as Center, G as boolean, Gt as Accordion, H as ZodIssueCode, Ht as Anchor, I as IconChevronDown, It as Card, J as record, Jt as Loader, K as number, Kt as Group, L as IconCheck, Lt as Button, M as IconLayoutSidebarLeftCollapse, Mt as Divider, N as IconInfoCircle, Nt as Container, O as IconNote, Ot as Textarea, P as IconFile, Pt as Code, Q as BrowserRouter, Qt as Box, R as IconArrowBackUp, Rt as Badge, S as useForm, St as Tooltip, T as IconUpload, Tt as NumberInput, U as _enum, Ut as Text, V as IconAlertCircle, Vt as AppShell, W as array, Wt as Alert, X as union, Xt as UnstyledButton, Y as string, Yt as Popover, Z as unknown, Zt as ScrollArea, _ as IconRefresh, _t as Table, a as _undefined, at as useNavigate, b as IconDownload, bt as Select, c as boolean$1, ct as useSearchParams, d as object$1, dt as DateTimePicker, en as useDisclosure, et as Navigate, f as string$1, ft as DateInput, g as Markdown, gt as Tabs, h as remarkGfm, ht as TextInput, i as _null, in as require_react, it as useLocation, j as IconLayoutSidebarLeftExpand, jt as Drawer, k as IconLogout, kt as Image, l as literal, lt as Notifications, m as datetime, mt as Title, n as string$2, nn as require_jsx_runtime, nt as Route, o as any, ot as useOutletContext, p as union$1, pt as DatesProvider, q as object, qt as ActionIcon, r as _enum$1, rn as require_client, rt as Routes, s as array$1, st as useParams, t as number$2, tn as useDebouncedValue, tt as Outlet, u as number$1, ut as notifications, v as IconEye, vt as Switch, w as IconUser, wt as Pagination, x as IconCopy, xt as SegmentedControl, y as IconEyeOff, yt as Stack, z as IconApi, zt as Autocomplete } from "./vendor-DdMxejie.js";
2
+ import { $ as unknown, $t as ScrollArea, A as IconLogout, At as Textarea, B as IconApi, Bt as Badge, C as useForm, Ct as SegmentedControl, D as IconTrash, Dt as NumberInput, E as IconUpload, Et as Pagination, F as IconFile, Ft as Container, G as _enum, Gt as Text, H as IconAlertCircle, Ht as Checkbox, I as IconEye$1, It as Code, J as number, Jt as Group, K as array, Kt as Alert, L as IconChevronDown, Lt as Center, M as IconLayoutSidebarLeftExpand, Mt as FileButton, N as IconLayoutSidebarLeftCollapse, Nt as Drawer, O as IconPlus, Ot as Modal, P as IconInfoCircle, Pt as Divider, Q as union, Qt as UnstyledButton, R as IconCheck, Rt as Card, S as IconCopy, St as Select, T as IconUser, Tt as PasswordInput, U as require_semver, Ut as AppShell, V as IconAlertTriangle, Vt as Autocomplete, W as ZodIssueCode, Wt as Anchor, X as record, Xt as Loader, Y as object, Yt as ActionIcon, Z as string, Zt as Popover, _ as IconRefresh, _t as TextInput, a as _undefined, an as require_client, at as Routes, b as IconEyeOff, bt as Switch, c as boolean$1, ct as useOutletContext, d as object$1, dt as Notifications, en as Box, et as BrowserRouter, f as string$1, ft as notifications, g as Markdown, gt as Title, h as remarkGfm, ht as DatesProvider, i as _null, in as require_jsx_runtime, it as Route, j as IconListDetails, jt as Image, k as IconNote, kt as Menu, l as literal, lt as useParams, m as datetime, mt as DateInput, n as string$2, nn as useDisclosure, nt as Navigate, o as any, on as require_react, ot as useLocation, p as union$1, pt as DateTimePicker, q as boolean, qt as Accordion, r as _enum$1, rn as useDebouncedValue, rt as Outlet, s as array$1, st as useNavigate, t as number$2, tn as MantineProvider, tt as Link, u as number$1, ut as useSearchParams, v as IconPhoto, vt as Tabs, w as IconX, wt as Tooltip, x as IconDownload, xt as Stack, y as IconEye, yt as Table, z as IconArrowBackUp, zt as Button } from "./vendor-MNFI7PUp.js";
3
3
  //#region \0vite/modulepreload-polyfill.js
4
4
  (function polyfill() {
5
5
  const relList = document.createElement("link").relList;
@@ -155,46 +155,58 @@ function formatFileSize(bytes) {
155
155
  }
156
156
  //#endregion
157
157
  //#region ../../../packages/common/dist/formatVersion.js
158
+ var import_semver = /* @__PURE__ */ __toESM(require_semver(), 1);
158
159
  /**
159
- * Parse a version string that may contain "npmVersion/commitHash".
160
- * "1.2.3/abc123..." → { npm: "1.2.3", hash: "abc123..." }
161
- * "1.2.3" → { npm: "1.2.3", hash: "" }
162
- * "/abc123..." → { npm: "", hash: "abc123..." }
160
+ * Parse a version string that may contain ">=" operator and/or "/commitHash".
161
+ * ">=1.2.3" → { operator: ">=", npm: "1.2.3", hash: "" }
162
+ * "1.2.3/abc123..." → { operator: "", npm: "1.2.3", hash: "abc123..." }
163
+ * "1.2.3" → { operator: "", npm: "1.2.3", hash: "" }
164
+ * "/abc123..." → { operator: "", npm: "", hash: "abc123..." }
163
165
  */
164
166
  function parseVersion(version) {
165
- const slashIndex = version.indexOf("/");
167
+ let operator = "";
168
+ let rest = version;
169
+ if (rest.startsWith(">=")) {
170
+ operator = ">=";
171
+ rest = rest.substring(2);
172
+ }
173
+ const slashIndex = rest.indexOf("/");
166
174
  if (slashIndex === -1) return {
167
- npm: version,
175
+ operator,
176
+ npm: rest,
168
177
  hash: ""
169
178
  };
170
179
  return {
171
- npm: version.substring(0, slashIndex),
172
- hash: version.substring(slashIndex + 1)
180
+ operator,
181
+ npm: rest.substring(0, slashIndex),
182
+ hash: rest.substring(slashIndex + 1)
173
183
  };
174
184
  }
175
185
  /**
176
186
  * Format a version string for display.
177
- * "1.2.3/abc123def456..." → "1.2.3 (abc123de)"
178
- * "1.2.3" → "1.2.3"
179
- * "/abc123def456..." → "abc123de"
187
+ * ">=1.2.3" → ">=1.2.3"
188
+ * "1.2.3/abc123def456…" → "1.2.3 (abc123de)"
189
+ * "1.2.3" → "1.2.3"
190
+ * "/abc123def456…" → "abc123de"
180
191
  */
181
192
  function formatVersion(version) {
182
- const { npm, hash } = parseVersion(version);
183
- if (!hash) return npm;
193
+ const { operator, npm, hash } = parseVersion(version);
194
+ if (!hash) return `${operator}${npm}`;
184
195
  const shortHash = hash.substring(0, 8);
185
- if (npm) return `${npm} (${shortHash})`;
196
+ if (npm) return `${operator}${npm} (${shortHash})`;
186
197
  return shortHash;
187
198
  }
188
199
  /**
189
- * Check if an instance version matches the target version.
190
- * Handles the "npmVersion/commitHash" format:
191
- * - If target has a hash, the instance must have the same hash to match.
192
- * - Otherwise, npm version parts are compared.
200
+ * Check if an instance version satisfies the target version.
201
+ * - If target has a hash, the instance must have the same hash.
202
+ * - If target npm has ">=" operator, instance npm must be >= target npm.
203
+ * - Otherwise, npm versions must match exactly.
193
204
  */
194
205
  function versionsMatch(instanceVersion, targetVersion) {
195
206
  const target = parseVersion(targetVersion);
196
207
  const instance = parseVersion(instanceVersion);
197
208
  if (target.hash) return instance.hash === target.hash;
209
+ if (target.operator === ">=") return import_semver.default.gte(instance.npm, target.npm);
198
210
  return instance.npm === target.npm;
199
211
  }
200
212
  //#endregion
@@ -262,6 +274,32 @@ object({
262
274
  _actions: array(HateoasActionSchema).optional()
263
275
  });
264
276
  //#endregion
277
+ //#region ../../../packages/common/dist/mimeTypes.js
278
+ var MIME_TYPES = {
279
+ ".jpg": "image/jpeg",
280
+ ".jpeg": "image/jpeg",
281
+ ".png": "image/png",
282
+ ".gif": "image/gif",
283
+ ".webp": "image/webp",
284
+ ".svg": "image/svg+xml",
285
+ ".bmp": "image/bmp",
286
+ ".wav": "audio/wav",
287
+ ".mp3": "audio/mpeg",
288
+ ".m4a": "audio/mp4",
289
+ ".flac": "audio/flac",
290
+ ".ogg": "audio/ogg",
291
+ ".webm": "audio/webm",
292
+ ".pdf": "application/pdf",
293
+ ".txt": "text/plain",
294
+ ".json": "application/json"
295
+ };
296
+ function mimeFromFilename(filename) {
297
+ return MIME_TYPES[filename.slice(filename.lastIndexOf(".")).toLowerCase()] ?? "application/octet-stream";
298
+ }
299
+ function isImageFilename(filename) {
300
+ return mimeFromFilename(filename).startsWith("image/");
301
+ }
302
+ //#endregion
265
303
  //#region ../../../packages/common/assets/naisys-logo.webp
266
304
  var naisys_logo_default = "/erp/assets/naisys-logo-CzoPnn5I.webp";
267
305
  //#endregion
@@ -461,6 +499,62 @@ function useAuth() {
461
499
  return ctx;
462
500
  }
463
501
  //#endregion
502
+ //#region src/components/RouteErrorPage.tsx
503
+ var RouteErrorPage = ({ error }) => {
504
+ const navigate = useNavigate();
505
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Container, {
506
+ size: "sm",
507
+ py: "xl",
508
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Stack, {
509
+ align: "center",
510
+ children: [
511
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Title, {
512
+ order: 2,
513
+ children: "Something went wrong"
514
+ }),
515
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
516
+ c: "dimmed",
517
+ ta: "center",
518
+ children: error.message || "An unexpected error occurred on this page."
519
+ }),
520
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Group, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
521
+ variant: "default",
522
+ onClick: () => navigate(-1),
523
+ children: "Go back"
524
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
525
+ onClick: () => window.location.reload(),
526
+ children: "Reload"
527
+ })] })
528
+ ]
529
+ })
530
+ });
531
+ };
532
+ //#endregion
533
+ //#region src/components/ErrorBoundary.tsx
534
+ var ErrorBoundary = class extends import_react.Component {
535
+ state = { error: null };
536
+ static getDerivedStateFromError(error) {
537
+ return { error };
538
+ }
539
+ componentDidUpdate(prevProps) {
540
+ if (this.state.error && prevProps.resetKey !== this.props.resetKey) this.setState({ error: null });
541
+ }
542
+ componentDidCatch(error, info) {
543
+ console.error("ErrorBoundary caught:", error, info);
544
+ }
545
+ render() {
546
+ if (this.state.error) return this.props.fallback(this.state.error);
547
+ return this.props.children;
548
+ }
549
+ };
550
+ var RouteErrorBoundary = ({ children }) => {
551
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ErrorBoundary, {
552
+ resetKey: useLocation().pathname,
553
+ fallback: (error) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RouteErrorPage, { error }),
554
+ children
555
+ });
556
+ };
557
+ //#endregion
464
558
  //#region ../../../packages/common-browser/dist/ActionButton.js
465
559
  /**
466
560
  * A button that is automatically shown/hidden and enabled/disabled
@@ -508,9 +602,14 @@ var AttachmentList = ({ fetchAttachments: fetchAttachmentsFn, getDownloadUrl, ex
508
602
  (0, import_react.useEffect)(() => {
509
603
  fetchAttachments();
510
604
  }, [fetchAttachments]);
511
- const handleDownload = (id, filename) => {
605
+ const handleOpen = (id, filename) => {
606
+ const url = getDownloadUrl(id);
607
+ if (isImageFilename(filename)) {
608
+ window.open(url, "_blank", "noopener,noreferrer");
609
+ return;
610
+ }
512
611
  const link = document.createElement("a");
513
- link.href = getDownloadUrl(id);
612
+ link.href = url;
514
613
  link.download = filename;
515
614
  link.click();
516
615
  };
@@ -553,14 +652,14 @@ var AttachmentList = ({ fetchAttachments: fetchAttachmentsFn, getDownloadUrl, ex
553
652
  (0, import_jsx_runtime.jsx)(Table.Th, {})
554
653
  ] }) }), (0, import_jsx_runtime.jsx)(Table.Tbody, { children: data.attachments.map((att) => (0, import_jsx_runtime.jsxs)(Table.Tr, {
555
654
  style: { cursor: "pointer" },
556
- onClick: () => handleDownload(att.id, att.filename),
655
+ onClick: () => handleOpen(att.id, att.filename),
557
656
  children: [
558
657
  (0, import_jsx_runtime.jsx)(Table.Td, { children: att.filename }),
559
658
  (0, import_jsx_runtime.jsx)(Table.Td, { children: formatFileSize(att.fileSize) }),
560
659
  extraColumns?.map((col) => (0, import_jsx_runtime.jsx)(Table.Td, { children: col.render(att) }, col.header)),
561
660
  (0, import_jsx_runtime.jsx)(Table.Td, { children: att.uploadedBy }),
562
661
  (0, import_jsx_runtime.jsx)(Table.Td, { children: new Date(att.createdAt).toLocaleString() }),
563
- (0, import_jsx_runtime.jsx)(Table.Td, { children: (0, import_jsx_runtime.jsx)(IconDownload, { size: 16 }) })
662
+ (0, import_jsx_runtime.jsx)(Table.Td, { children: isImageFilename(att.filename) ? (0, import_jsx_runtime.jsx)(IconPhoto, { size: 16 }) : (0, import_jsx_runtime.jsx)(IconDownload, { size: 16 }) })
564
663
  ]
565
664
  }, att.id)) })]
566
665
  }), totalPages > 1 && (0, import_jsx_runtime.jsx)(Group, {
@@ -821,6 +920,18 @@ var ServerLogViewer = ({ fetchLogs: fetchLogsFn, logFiles }) => {
821
920
  ] });
822
921
  };
823
922
  //#endregion
923
+ //#region ../../../packages/common-browser/dist/VersionBadge.js
924
+ var VersionBadge = ({ version, size = "sm" }) => {
925
+ if (!version) return null;
926
+ const isGit = version.includes("/");
927
+ return (0, import_jsx_runtime.jsx)(Badge, {
928
+ size,
929
+ variant: "light",
930
+ color: isGit ? "grape" : "cyan",
931
+ children: isGit ? "git" : "npm"
932
+ });
933
+ };
934
+ //#endregion
824
935
  //#region ../../../packages/common-browser/dist/zodResolver.js
825
936
  function zodResolver(schema) {
826
937
  return (values) => {
@@ -2193,7 +2304,7 @@ var AppLayout = ({ supervisorAuth }) => {
2193
2304
  size: "xs",
2194
2305
  children: "Public read-only mode — login for full access"
2195
2306
  })
2196
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Outlet, { context: { supervisorAuth } })] }),
2307
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RouteErrorBoundary, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Outlet, { context: { supervisorAuth } }) })] }),
2197
2308
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LoginModal, {
2198
2309
  opened: loginOpen,
2199
2310
  onClose: closeLogin
@@ -2202,6 +2313,39 @@ var AppLayout = ({ supervisorAuth }) => {
2202
2313
  });
2203
2314
  };
2204
2315
  //#endregion
2316
+ //#region src/components/RootErrorPage.tsx
2317
+ var RootErrorPage = ({ error }) => {
2318
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Container, {
2319
+ size: "sm",
2320
+ py: "xl",
2321
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Stack, {
2322
+ align: "center",
2323
+ children: [
2324
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Title, {
2325
+ order: 1,
2326
+ children: "Something went wrong"
2327
+ }),
2328
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
2329
+ size: "lg",
2330
+ c: "dimmed",
2331
+ ta: "center",
2332
+ children: error.message || "An unexpected error occurred."
2333
+ }),
2334
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
2335
+ onClick: () => window.location.reload(),
2336
+ children: "Reload"
2337
+ })
2338
+ ]
2339
+ })
2340
+ });
2341
+ };
2342
+ //#endregion
2343
+ //#region src/lib/useBoomGuard.ts
2344
+ var useBoomGuard = (scope) => {
2345
+ const [searchParams] = useSearchParams();
2346
+ if (searchParams.get("boom") === scope) throw new Error(`Boom guard triggered: ${scope}`);
2347
+ };
2348
+ //#endregion
2205
2349
  //#region src/pages/admin/AdminPage.tsx
2206
2350
  var AdminPage = () => {
2207
2351
  const [data, setData] = (0, import_react.useState)(null);
@@ -2270,12 +2414,16 @@ var AdminPage = () => {
2270
2414
  children: "ERP Version"
2271
2415
  }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Table.Td, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Group, {
2272
2416
  gap: "xs",
2273
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: formatVersion(data.erpVersion) }), data.targetVersion && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Badge, {
2274
- size: "sm",
2275
- variant: "light",
2276
- color: versionsMatch(data.erpVersion, data.targetVersion) ? "green" : "red",
2277
- children: ["target: ", formatVersion(data.targetVersion)]
2278
- })]
2417
+ children: [
2418
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: formatVersion(data.erpVersion) }),
2419
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(VersionBadge, { version: data.erpVersion }),
2420
+ data.targetVersion && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Badge, {
2421
+ size: "sm",
2422
+ variant: "light",
2423
+ color: versionsMatch(data.erpVersion, data.targetVersion) ? "green" : "red",
2424
+ children: ["target: ", formatVersion(data.targetVersion)]
2425
+ })
2426
+ ]
2279
2427
  }) })] }),
2280
2428
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Table.Tr, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Table.Td, {
2281
2429
  fw: 600,
@@ -3364,19 +3512,6 @@ function formatDate$1(d) {
3364
3512
  function formatDateTime$1(d) {
3365
3513
  return `${formatDate$1(d)}T${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
3366
3514
  }
3367
- var IMAGE_EXTENSIONS = new Set([
3368
- ".jpg",
3369
- ".jpeg",
3370
- ".png",
3371
- ".gif",
3372
- ".webp",
3373
- ".svg",
3374
- ".bmp"
3375
- ]);
3376
- function isImageFilename(filename) {
3377
- const ext = filename.slice(filename.lastIndexOf(".")).toLowerCase();
3378
- return IMAGE_EXTENSIONS.has(ext);
3379
- }
3380
3515
  /** Check if a field type represents an array (e.g. "string[]") */
3381
3516
  function isArrayType(type) {
3382
3517
  return type.endsWith("[]");
@@ -5040,6 +5175,7 @@ var OrderDetail = () => {
5040
5175
  //#endregion
5041
5176
  //#region src/pages/orders/OrderList.tsx
5042
5177
  var OrderList = () => {
5178
+ useBoomGuard("orders");
5043
5179
  const navigate = useNavigate();
5044
5180
  const [searchParams, setSearchParams] = useSearchParams();
5045
5181
  const page = Number(searchParams.get("page")) || 1;
@@ -10295,6 +10431,7 @@ var WorkCenterList = () => {
10295
10431
  //#endregion
10296
10432
  //#region src/App.tsx
10297
10433
  var AppContent = () => {
10434
+ useBoomGuard("root");
10298
10435
  const [publicRead, setPublicRead] = import_react.useState(false);
10299
10436
  const [supervisorAuth, setSupervisorAuth] = import_react.useState(false);
10300
10437
  const [clientConfigLoaded, setClientConfigLoaded] = import_react.useState(false);
@@ -10445,7 +10582,10 @@ var App = () => {
10445
10582
  settings: { consistentWeeks: true },
10446
10583
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Notifications, { position: "top-right" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BrowserRouter, {
10447
10584
  basename: "/erp",
10448
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AuthProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AppContent, {}) })
10585
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AuthProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ErrorBoundary, {
10586
+ fallback: (error) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RootErrorPage, { error }),
10587
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AppContent, {})
10588
+ }) })
10449
10589
  })]
10450
10590
  })
10451
10591
  });