@7365admin1/layer-common 4.64.4 → 4.65.1

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.
Files changed (35) hide show
  1. package/CHANGELOG.md +50 -4
  2. package/assets/css/primitives.css +13 -0
  3. package/components/AccessCardAddForm.vue +5 -4
  4. package/components/AccessCardAssignToUnitForm.vue +3 -2
  5. package/components/AccessCardPreviewDialog.vue +4 -3
  6. package/components/AreaMain.vue +33 -30
  7. package/components/BuildingManagement/units.vue +30 -21
  8. package/components/CameraBulkImportDialog.vue +41 -12
  9. package/components/DashboardMain.vue +14 -7
  10. package/components/EntryPass/QrTemplatePreview.vue +21 -25
  11. package/components/EntryPassInformation.vue +119 -24
  12. package/components/ImportExcelDialog.vue +136 -0
  13. package/components/InventoryImportDialog.vue +33 -104
  14. package/components/InventoryItemDetail.vue +3 -2
  15. package/components/InventoryItemDialog.vue +62 -7
  16. package/components/InventoryReportsTab.vue +2 -1
  17. package/components/InventorySettingsTab.vue +2 -5
  18. package/components/InventoryStockTab.vue +13 -4
  19. package/components/NotificationBell.vue +1 -1
  20. package/components/NotificationSettings.vue +1 -1
  21. package/components/Robot/MiniMap.vue +1 -1
  22. package/components/UnitMain.vue +30 -28
  23. package/components/VehicleBulkAddForm.vue +30 -16
  24. package/composables/useAccessManagement.ts +5 -0
  25. package/composables/useImportTemplates.ts +24 -0
  26. package/composables/useInventory.ts +12 -5
  27. package/composables/useSiteEntryPassSettings.ts +26 -0
  28. package/composables/useWebUsb.ts +14 -11
  29. package/package.json +1 -1
  30. package/types/inventory.d.ts +2 -0
  31. package/utils/camera-csv.ts +2 -0
  32. package/utils/dashboard.ts +11 -0
  33. package/utils/import-result.ts +62 -0
  34. package/utils/inventory-store-label.ts +27 -0
  35. package/utils/vehicle-bulk-sheet.ts +59 -3
@@ -97,6 +97,8 @@ export type UploadedSheet = {
97
97
  matchesTemplate: boolean;
98
98
  /** The heading line as the file spells it - for telling a person what it has. */
99
99
  header: string[];
100
+ /** A template example row was left in and was not read. */
101
+ exampleRowIgnored?: boolean;
100
102
  };
101
103
 
102
104
  /**
@@ -138,14 +140,16 @@ export function mapUploadedGrid(grid: string[][]): UploadedSheet {
138
140
  // Every line under the header is somebody's car. Nothing is dropped for
139
141
  // looking like something else - two people in one household can have the
140
142
  // same details but for the plate, and a repeated row is the person's to fix.
141
- const rows = lines
142
- .slice(1)
143
+ const body = lines.slice(1);
144
+ // The example row of a template the server made is marked beside it; it is nobody's car.
145
+ const rows = body
146
+ .filter((line) => !isExampleLine(line))
143
147
  .map((line) =>
144
148
  BULK_SHEET_HEADINGS.map((_, column) => String(line[column] ?? "").trim()),
145
149
  )
146
150
  .filter((line) => !isEmptyLine(line));
147
151
 
148
- return { rows, matchesTemplate: true, header };
152
+ return { rows, matchesTemplate: true, header, exampleRowIgnored: body.some(isExampleLine) };
149
153
  }
150
154
 
151
155
  /**
@@ -385,3 +389,55 @@ export async function xlsxToGrid(data: ArrayBuffer | Uint8Array): Promise<string
385
389
  export function isWorkbookName(name: string): boolean {
386
390
  return /\.(xlsx|xlsm)$/i.test(String(name ?? ""));
387
391
  }
392
+
393
+ /* ------------------------------ live templates ----------------------------- */
394
+
395
+ /** Beside the example row of every template the server makes. */
396
+ export const TEMPLATE_EXAMPLE_MARK = "Example – delete this row";
397
+
398
+ /** Does this line carry the template's example-row mark? */
399
+ export function isExampleLine(cells: string[]): boolean {
400
+ return (cells ?? []).some((cell) => String(cell ?? "").includes(TEMPLATE_EXAMPLE_MARK));
401
+ }
402
+
403
+ /**
404
+ * The hidden "_meta" sheet of a template the server made: which kind of
405
+ * template it is and the property it was made for (`kind`, `site`,
406
+ * `siteName`). Empty for any other workbook.
407
+ */
408
+ export async function xlsxMeta(data: ArrayBuffer | Uint8Array): Promise<Record<string, string>> {
409
+ const bytes = data instanceof Uint8Array ? data : new Uint8Array(data);
410
+ const entries = readZipEntries(bytes);
411
+ const book = entries.get("xl/workbook.xml");
412
+ const rels = entries.get("xl/_rels/workbook.xml.rels");
413
+ if (!book || !rels) return {};
414
+ const sheet = (await readPart(bytes, book)).match(/<sheet\b[^>]*\bname="_meta"[^>]*>/)?.[0];
415
+ const rid = sheet?.match(/\br:id="([^"]+)"/)?.[1];
416
+ if (!rid) return {};
417
+ const relation = ((await readPart(bytes, rels)).match(/<Relationship\b[^>]*>/g) ?? []).find((tag) => tag.includes(` Id="${rid}"`));
418
+ const target = relation?.match(/\bTarget="([^"]+)"/)?.[1];
419
+ const part = target ? entries.get(target.startsWith("/") ? target.slice(1) : `xl/${target}`) : undefined;
420
+ if (!part) return {};
421
+ const sharedEntry = entries.get("xl/sharedStrings.xml");
422
+ const strings = sharedEntry ? sharedStringsOf(await readPart(bytes, sharedEntry)) : [];
423
+ const meta: Record<string, string> = {};
424
+ for (const line of sheetToGrid(await readPart(bytes, part), strings).slice(1)) {
425
+ if (line[0]) meta[String(line[0])] = String(line[1] ?? "");
426
+ }
427
+ return meta;
428
+ }
429
+
430
+ /**
431
+ * Why a template may not be used here, or "" when it may: a template made for
432
+ * another property, or a different kind of template. A workbook without the
433
+ * hidden sheet (an older template, a person's own file) is allowed.
434
+ */
435
+ export function templateRefusal(meta: Record<string, string>, expected: { kind: string; site: string }): string {
436
+ if (meta.kind && meta.kind !== expected.kind) {
437
+ return "This is a different kind of template. Download the template on this screen and copy your rows into it.";
438
+ }
439
+ if (meta.site && expected.site && meta.site !== expected.site) {
440
+ return `This file was made for ${meta.siteName ? `"${meta.siteName}"` : "another property"}. Download the template on this screen and copy your rows into it.`;
441
+ }
442
+ return "";
443
+ }