@7365admin1/layer-common 3.2.1 → 3.2.2-staging.100

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 (123) hide show
  1. package/.changeset/camera-wall-gated-endpoint.md +47 -0
  2. package/.changeset/camera-wall-plain-english.md +60 -0
  3. package/.changeset/camera-wall-selection-and-guidance.md +65 -0
  4. package/.changeset/dark-primary-contrast.md +39 -0
  5. package/.changeset/dashboard-dark-theme-contrast.md +15 -0
  6. package/.changeset/notification-preferences.md +17 -0
  7. package/.changeset/pm-sidebar-grouping-and-list-scaffold.md +27 -0
  8. package/.changeset/service-provider-invitation-actions.md +28 -0
  9. package/assets/css/primitives.css +680 -0
  10. package/assets/css/screens.css +541 -0
  11. package/assets/css/shell.css +258 -0
  12. package/assets/css/tokens.css +220 -0
  13. package/components/AccessCardQrTagging.vue +1 -1
  14. package/components/AccessManagement.vue +53 -72
  15. package/components/AppButton.vue +52 -0
  16. package/components/AppCard.vue +20 -0
  17. package/components/AppDateField.vue +35 -0
  18. package/components/AppField.vue +46 -0
  19. package/components/AppKpiTile.vue +65 -0
  20. package/components/AppSegmented.vue +38 -0
  21. package/components/AppSelect.vue +203 -0
  22. package/components/AreaChecklistHistoryMain.vue +6 -2
  23. package/components/AreaMain.vue +9 -17
  24. package/components/AttendanceMain.vue +4 -3
  25. package/components/Avatar/Main.vue +54 -2
  26. package/components/BillingMain.vue +26 -30
  27. package/components/BuildingManagement/buildings.vue +27 -38
  28. package/components/BuildingManagement/units.vue +26 -42
  29. package/components/BulletinBoardManagement.vue +23 -10
  30. package/components/CameraWall.vue +1034 -0
  31. package/components/CameraWallTile.vue +818 -0
  32. package/components/CardToolbar.vue +37 -0
  33. package/components/CleaningScheduleMain.vue +29 -46
  34. package/components/ClientMain.vue +90 -78
  35. package/components/DashboardEmptyState.vue +11 -1
  36. package/components/DashboardMain.vue +349 -275
  37. package/components/DashboardPanel.vue +1 -1
  38. package/components/DocumentManagement.vue +129 -93
  39. package/components/EntryPassInformation.vue +4 -4
  40. package/components/EntryPassMain.vue +49 -27
  41. package/components/EquipmentItemMain.vue +4 -6
  42. package/components/EquipmentManagementMain.vue +9 -7
  43. package/components/FeedbackMain.vue +42 -56
  44. package/components/FormDialog.vue +60 -21
  45. package/components/HidAccessLogDashboard.vue +119 -44
  46. package/components/HidCameraCaptureDialog.vue +199 -0
  47. package/components/HidIntercomManagement.vue +825 -202
  48. package/components/HidQrCodeConfiguration.vue +927 -93
  49. package/components/HidServiceSettingsPanel.vue +20 -1
  50. package/components/HidUserEnrollment.vue +454 -106
  51. package/components/InvitationMain.vue +46 -61
  52. package/components/Layout/Header.vue +289 -37
  53. package/components/Layout/NavigationDrawer.vue +340 -30
  54. package/components/ManageChecklistMain.vue +17 -17
  55. package/components/MemberMain.vue +79 -60
  56. package/components/MyAttendanceMain.vue +3 -5
  57. package/components/NavigationItem.vue +9 -0
  58. package/components/Nfc/NFCPatrolRouteMain.vue +116 -103
  59. package/components/NotificationSettings.vue +361 -0
  60. package/components/OnlineFormConfigurationForm.vue +620 -224
  61. package/components/OnlineFormFill.vue +36 -8
  62. package/components/OnlineFormsConfiguration.vue +7 -3
  63. package/components/PageHeader.vue +43 -0
  64. package/components/PassInformation.vue +104 -45
  65. package/components/QrTemplate/CreditCardLandscape.vue +19 -12
  66. package/components/QrTemplate/PrintDialog.vue +209 -196
  67. package/components/RolePermissionMain.vue +184 -91
  68. package/components/ScanVisitorQRCode.vue +35 -7
  69. package/components/ScheduleAreaMain.vue +15 -49
  70. package/components/ScheduleTaskMain.vue +23 -26
  71. package/components/ScheduleTastTicketMain.vue +18 -43
  72. package/components/ServiceProviderInvitationPrompt.vue +150 -0
  73. package/components/ServiceProviderMain.vue +346 -157
  74. package/components/SiteSettings.vue +25 -10
  75. package/components/SpecificAttr.vue +10 -2
  76. package/components/StatusChip.vue +93 -0
  77. package/components/SwitchContext.vue +33 -1
  78. package/components/TableHygiene.vue +29 -46
  79. package/components/TableList.vue +1 -1
  80. package/components/TableListSecondary.vue +1 -1
  81. package/components/TableMain.vue +161 -77
  82. package/components/TableV3.vue +61 -66
  83. package/components/UnitMain.vue +9 -17
  84. package/components/VehicleManagement.vue +20 -9
  85. package/components/VisitorForm.vue +294 -78
  86. package/components/VisitorManagement.vue +221 -71
  87. package/components/VisitorSocketPopUp.vue +56 -2
  88. package/components/WorkOrder/Main.vue +42 -51
  89. package/composables/useComment.ts +3 -3
  90. package/composables/useHidAmico.ts +151 -0
  91. package/composables/useHidNavigation.ts +0 -7
  92. package/composables/useLocalAuth.ts +9 -36
  93. package/composables/useNotificationPreference.ts +87 -0
  94. package/composables/useOnlineFormPages.ts +2 -0
  95. package/composables/useOrg.ts +1 -1
  96. package/composables/useRole.ts +22 -0
  97. package/composables/useServiceProviderInvitation.ts +145 -0
  98. package/composables/useSipWebPhone.ts +336 -0
  99. package/composables/useSiteSettings.ts +50 -0
  100. package/composables/useThemePreference.ts +63 -0
  101. package/composables/useVisitor.ts +6 -1
  102. package/composables/useVisitorSocket.ts +26 -0
  103. package/composables/useWebUsb.ts +127 -37
  104. package/nuxt.config.ts +31 -0
  105. package/package.json +4 -1
  106. package/pages/[org]/[site]/access-mgmt/intercom/index.vue +1 -1
  107. package/pages/notification-settings.vue +19 -0
  108. package/plugins/secure-member.client.ts +21 -56
  109. package/plugins/vuetify.ts +68 -9
  110. package/test/visitor-socket.test.mjs +36 -0
  111. package/types/org.d.ts +2 -0
  112. package/types/site.d.ts +71 -0
  113. package/utils/camera-wall.test.ts +571 -0
  114. package/utils/camera-wall.ts +926 -0
  115. package/utils/nav-sections.test.ts +190 -0
  116. package/utils/nav-sections.ts +151 -0
  117. package/utils/status.test.ts +77 -0
  118. package/utils/status.ts +90 -0
  119. package/utils/theme.test.ts +534 -0
  120. package/utils/theme.ts +294 -0
  121. package/components/HidIdentityMapping.vue +0 -975
  122. package/middleware/member.ts +0 -4
  123. package/pages/[org]/[site]/access-mgmt/identity-mapping/index.vue +0 -23
@@ -247,6 +247,40 @@
247
247
  {{ resolveText(block.text) }}
248
248
  </p>
249
249
 
250
+ <!-- ORDERED LIST -->
251
+ <div v-else-if="block.type === 'ordered-list'" class="mb-3">
252
+ <div
253
+ v-if="block.title"
254
+ class="text-body-2 font-weight-bold mb-1"
255
+ style="text-decoration: underline; text-underline-offset: 3px"
256
+ >{{ block.title }}</div>
257
+ <ol style="padding-left: 22px; margin: 0">
258
+ <li
259
+ v-for="(item, itemIdx) in block.items"
260
+ :key="itemIdx"
261
+ class="text-body-2 mb-1"
262
+ style="line-height: 1.55; color: #333"
263
+ >{{ item }}</li>
264
+ </ol>
265
+ </div>
266
+
267
+ <!-- UNORDERED LIST -->
268
+ <div v-else-if="block.type === 'unordered-list'" class="mb-3">
269
+ <div
270
+ v-if="block.title"
271
+ class="text-body-2 font-weight-bold mb-1"
272
+ style="text-decoration: underline; text-underline-offset: 3px"
273
+ >{{ block.title }}</div>
274
+ <ul style="padding-left: 22px; margin: 0">
275
+ <li
276
+ v-for="(item, itemIdx) in block.items"
277
+ :key="itemIdx"
278
+ class="text-body-2 mb-1"
279
+ style="line-height: 1.55; color: #333"
280
+ >{{ item }}</li>
281
+ </ul>
282
+ </div>
283
+
250
284
  <!-- DIVIDER -->
251
285
  <v-divider v-else-if="block.type === 'divider'" class="my-4" />
252
286
 
@@ -433,6 +467,7 @@ const props = defineProps({
433
467
  approvalMode: { type: Boolean, default: false },
434
468
  initialManagementValues: { type: Object as PropType<Record<string, string>>, default: () => ({}) },
435
469
  formType: { type: String, default: "" },
470
+ paymentAmount: { type: Number, default: 0 },
436
471
  /** Optional custom submit handler. Receives the collected form values.
437
472
  * If omitted the component submits to /api/online-forms automatically. */
438
473
  submitFn: {
@@ -596,15 +631,8 @@ const submissionStatusColor = computed(() => {
596
631
  }
597
632
  });
598
633
 
599
- const PAYMENT_REQUIRED_FORM_TYPES = new Set([
600
- "Application for Electronic Access Card",
601
- "Application for Booking of BBQ Pit",
602
- "Application for Moving In/Out & Delivery",
603
- "Application for Main Entrance RFID Transponder",
604
- ]);
605
-
606
634
  const requiresPayment = computed(() => {
607
- if (!PAYMENT_REQUIRED_FORM_TYPES.has(props.formType)) return false;
635
+ if (props.paymentAmount <= 0) return false;
608
636
  if (props.formType === "Application for Main Entrance RFID Transponder") {
609
637
  return !!(props.initialValues.reasonLost || props.initialValues.reasonDamaged);
610
638
  }
@@ -22,7 +22,7 @@
22
22
  rounded="lg"
23
23
  :loading="loading"
24
24
  >
25
- <v-toolbar density="compact" color="grey-lighten-4">
25
+ <v-toolbar density="compact" class="px-3 border-b-sm" color="transparent">
26
26
  <template #prepend>
27
27
  <v-btn
28
28
  fab
@@ -88,7 +88,7 @@
88
88
  >
89
89
 
90
90
  <!-- Create Dialog -->
91
- <v-dialog v-model="createDialog" width="450" persistent>
91
+ <v-dialog v-model="createDialog" width="720" persistent>
92
92
  <OnlineFormConfigurationForm
93
93
  mode="add"
94
94
  @cancel="createDialog = false"
@@ -97,7 +97,7 @@
97
97
  </v-dialog>
98
98
 
99
99
  <!-- Edit Dialog -->
100
- <v-dialog v-model="editDialog" width="450" persistent>
100
+ <v-dialog v-model="editDialog" width="720" persistent>
101
101
  <OnlineFormConfigurationForm
102
102
  mode="edit"
103
103
  @cancel="editDialog = false"
@@ -113,6 +113,7 @@
113
113
  :definition="selectedFormDefinition"
114
114
  :site-id="siteId"
115
115
  :org-id="orgId"
116
+ :payment-amount="selectedPaymentAmount"
116
117
  readonly
117
118
  @cancel="previewDialog = false"
118
119
  >
@@ -336,6 +337,9 @@ const selectedFormDefinition = ref<any>(null);
336
337
  const deleteLoading = ref(false);
337
338
  const confirmDialog = ref(false);
338
339
  const selectedOnlineFormId = ref<string | null>(null);
340
+ const selectedPaymentAmount = computed(() =>
341
+ Number(selectedOnlineForm.value?.fields?.paymentAmount ?? 0)
342
+ );
339
343
 
340
344
  function parseFieldsToInputs(fields: Record<string, string>): any[] {
341
345
  return Object.keys(fields)
@@ -0,0 +1,43 @@
1
+ <!--
2
+ THE PAGE TITLE ROW.
3
+
4
+ The design opens EVERY screen with this row: the page's name at 22px/800,
5
+ and the screen's primary action on the right of the same line. The product
6
+ has never had it - the page name lived only in the browser tab and in the
7
+ navigation rail - which is the single most visible difference between the
8
+ staging site and the handoff.
9
+
10
+ It is a label and a slot. It fetches nothing, decides nothing and navigates
11
+ nowhere; the action passed into it is the screen's own button.
12
+ -->
13
+ <template>
14
+ <div class="app-page-head">
15
+ <!-- A caller with actions but no page name (the shared table card, when the
16
+ screen has not given it a title) gets just the action group, at the
17
+ left, rather than an empty heading holding a line of blank space. -->
18
+ <div v-if="title || $slots.title || subtitle || $slots.subtitle">
19
+ <h1 class="app-page-head__title">
20
+ <slot name="title">{{ title }}</slot>
21
+ </h1>
22
+
23
+ <!-- The design's second line: a location, a count, a date range. Only
24
+ drawn when a screen supplies one. -->
25
+ <div v-if="subtitle || $slots.subtitle" class="app-page-head__sub">
26
+ <v-icon v-if="subtitleIcon" :icon="subtitleIcon" size="15" />
27
+ <slot name="subtitle">{{ subtitle }}</slot>
28
+ </div>
29
+ </div>
30
+
31
+ <div v-if="$slots.actions" class="app-page-head__actions">
32
+ <slot name="actions" />
33
+ </div>
34
+ </div>
35
+ </template>
36
+
37
+ <script setup lang="ts">
38
+ defineProps({
39
+ title: { type: String, default: "" },
40
+ subtitle: { type: String, default: "" },
41
+ subtitleIcon: { type: String, default: "" },
42
+ });
43
+ </script>
@@ -8,8 +8,8 @@
8
8
  :height="40"
9
9
  text="Scan QR Code"
10
10
  class="text-capitalize"
11
- disabled
12
11
  prepend-icon="mdi-qrcode"
12
+ @click="handleScanQRCode"
13
13
  />
14
14
  <v-autocomplete
15
15
  v-model="selectedPass"
@@ -88,6 +88,11 @@
88
88
  </v-card-text>
89
89
  </v-card>
90
90
  </v-row>
91
+ <visitor-pass-key-q-r-scanner
92
+ :dialog="dialog.scanQRCode"
93
+ v-model:scannedValue="scannedValue"
94
+ @close-dialog="dialog.scanQRCode = false"
95
+ />
91
96
  <Snackbar v-model="messageSnackbar" :text="message" :color="messageColor" />
92
97
  </template>
93
98
 
@@ -95,46 +100,36 @@
95
100
  import type { PropType } from "vue";
96
101
  import type { ValidationRule } from "vuetify/lib/types.mjs";
97
102
  import usePassKey from "../composables/usePassKey";
98
- import useKey from "../composables/useKey";
99
- import { string } from "zod/v4";
103
+ import { errorConverter } from "../utils/data";
104
+ import VisitorPassKeyQRScanner from "./VisitorPassKeyQRScanner.vue";
100
105
 
101
- const props = defineProps({
102
- passRules: {
103
- type: Array as PropType<ValidationRule[]>,
104
- default: [],
105
- },
106
- countRules: {
107
- type: Array as PropType<ValidationRule[]>,
108
- default: [],
109
- },
110
- site: {
111
- type: String,
112
- required: true,
113
- },
114
- type: {
115
- type: String as PropType<TVisitorType>,
116
- required: true,
117
- },
118
- contractorType: {
119
- type: String,
120
- default: "",
121
- },
122
- passKeys: {
123
- type: Array,
124
- default: [],
125
- },
126
- hideKeys: {
127
- type: Boolean,
128
- default: false,
129
- },
130
- clearable: {
131
- type: Boolean,
132
- default: false,
133
- },
106
+ type PassInformationProps = {
107
+ passRules?: ValidationRule[];
108
+ countRules?: ValidationRule[];
109
+ site: string;
110
+ type: TVisitorType;
111
+ contractorType?: string;
112
+ passKeys?: unknown[];
113
+ hideKeys?: boolean;
114
+ clearable?: boolean;
115
+ pass?: TPassKeyPayload[];
116
+ keys?: TPassKeyPayload[];
117
+ };
118
+
119
+ const props = withDefaults(defineProps<PassInformationProps>(), {
120
+ passRules: [],
121
+ countRules: [],
122
+ contractorType: "",
123
+ passKeys: [],
124
+ hideKeys: false,
125
+ clearable: false,
126
+ pass: [],
127
+ keys: [],
134
128
  });
135
129
 
136
- const pass = defineModel<TPassKeyPayload[]>("pass", { default: [] });
137
- const keys = defineModel<TPassKeyPayload[]>("keys", { default: [] });
130
+ const emit = defineEmits(["update:pass", "update:keys"]);
131
+ const pass = ref<TPassKeyPayload[]>(props.pass);
132
+ const keys = ref<TPassKeyPayload[]>(props.keys);
138
133
  const selectedPass = ref<string>("");
139
134
  const selectedKeys = ref<string[]>([]);
140
135
  const passInput = ref("");
@@ -144,6 +139,11 @@ const keyItems = ref<any[]>([]);
144
139
  const selectedType = ref<"qr-pass" | "nfc-card">();
145
140
  const count = ref(1);
146
141
 
142
+ const dialog = reactive({
143
+ scanQRCode: false,
144
+ });
145
+ const scannedValue = ref("");
146
+
147
147
  const { getPassKeysByPageSearch } = usePassKey();
148
148
 
149
149
  const typeItems = [
@@ -179,6 +179,23 @@ function showMessage(msg: string, color: string) {
179
179
  messageSnackbar.value = true;
180
180
  }
181
181
 
182
+ function getItemLabel(item: any) {
183
+ return (item?.prefixAndName ?? item?.raw?.prefixAndName ?? "")
184
+ .toString()
185
+ .trim();
186
+ }
187
+
188
+ function handleScanQRCode() {
189
+ scannedValue.value = "";
190
+ dialog.scanQRCode = true;
191
+ if (passItems.value.length === 0) {
192
+ fetchPasses();
193
+ }
194
+ if (!props.hideKeys && keyItems.value.length === 0) {
195
+ fetchKeys();
196
+ }
197
+ }
198
+
182
199
  const isPassListLoading = ref(false);
183
200
 
184
201
  async function fetchPasses() {
@@ -207,7 +224,7 @@ const isKeyListLoading = ref(false);
207
224
  async function fetchKeys() {
208
225
  isKeyListLoading.value = true;
209
226
  try {
210
- const keys: any = await getPassKeysByPageSearch({
227
+ const result: any = await getPassKeysByPageSearch({
211
228
  page: 1,
212
229
  sites: [props.site],
213
230
  limit: 10000,
@@ -223,7 +240,7 @@ async function fetchKeys() {
223
240
  return false;
224
241
  });
225
242
 
226
- const filteredArray = keys.items.filter((item: any) => {
243
+ const filteredArray = result.items.filter((item: any) => {
227
244
  if (Array.isArray(props?.passKeys) && props.passKeys.length) {
228
245
  return !props.passKeys.includes(item._id);
229
246
  }
@@ -231,18 +248,60 @@ async function fetchKeys() {
231
248
  });
232
249
  keyItems.value = [...previouslySelectedKeys, ...filteredArray];
233
250
  } catch (error) {
234
- showMessage(errorConverter(error as string), "error");
251
+ showMessage(errorConverter(error), "error");
235
252
  } finally {
236
253
  isKeyListLoading.value = false;
237
254
  }
238
255
  }
239
256
 
240
257
  watch(selectedPass, (newVal) => {
241
- pass.value = [{ keyId: newVal }];
258
+ const updated = newVal ? [{ keyId: newVal }] : [];
259
+ pass.value = updated;
260
+ emit("update:pass", updated);
242
261
  });
243
262
 
244
263
  watch(selectedKeys, (newVal) => {
245
- keys.value = newVal.map((key) => ({ keyId: key }));
264
+ const updated = newVal.map((key) => ({ keyId: key }));
265
+ keys.value = updated;
266
+ emit("update:keys", updated);
267
+ });
268
+
269
+ watch(scannedValue, async (newValue) => {
270
+ if (!newValue) return;
271
+
272
+ const scanned = String(newValue).trim();
273
+ if (!scanned) return;
274
+
275
+ if (passItems.value.length === 0) {
276
+ await fetchPasses();
277
+ }
278
+ if (!props.hideKeys && keyItems.value.length === 0) {
279
+ await fetchKeys();
280
+ }
281
+
282
+ const matchedPass = passItems.value.find((item) => {
283
+ return getItemLabel(item).toLowerCase() === scanned.toLowerCase();
284
+ });
285
+
286
+ if (matchedPass && matchedPass._id) {
287
+ selectedPass.value = matchedPass._id;
288
+ return;
289
+ }
290
+
291
+ if (!props.hideKeys) {
292
+ const matchedKey = keyItems.value.find((item) => {
293
+ return getItemLabel(item).toLowerCase() === scanned.toLowerCase();
294
+ });
295
+
296
+ if (matchedKey && matchedKey._id) {
297
+ if (!selectedKeys.value.includes(matchedKey._id)) {
298
+ selectedKeys.value = [...selectedKeys.value, matchedKey._id];
299
+ }
300
+ return;
301
+ }
302
+ }
303
+
304
+ showMessage(`Scanned pass or key "${scanned}" not found`, "error");
246
305
  });
247
306
 
248
307
  //prevent negative value;
@@ -254,10 +313,10 @@ watch(count, (newCount) => {
254
313
 
255
314
  onMounted(() => {
256
315
  if (pass.value.length > 0) {
257
- selectedPass.value = pass.value[0].keyId;
316
+ selectedPass.value = pass.value[0]?.keyId ?? "";
258
317
  }
259
318
  if (keys.value.length > 0) {
260
- selectedKeys.value = keys.value.map((k) => k.keyId);
319
+ selectedKeys.value = keys.value.map((k) => k.keyId ?? "");
261
320
  }
262
321
  });
263
322
  </script>
@@ -33,7 +33,7 @@
33
33
 
34
34
  <qrcode-vue
35
35
  v-if="templateQrCode.CCFrontQREnabled"
36
- :value="`${prefix}0${+printRangeStart + +card.number - 1}`"
36
+ :value="getPassNumberValue(card.number)"
37
37
  :size="templateQrCode.CCFrontQRCodeSize"
38
38
  level="H"
39
39
  :style="{
@@ -53,9 +53,7 @@
53
53
  }"
54
54
  class="position-absolute text-center"
55
55
  >
56
- Pass Number: {{ prefix }}0{{
57
- +printRangeStart + +card.number - 1
58
- }}
56
+ Pass Number: {{ getPassNumberValue(card.number) }}
59
57
  </div>
60
58
  </v-card>
61
59
 
@@ -74,7 +72,7 @@
74
72
 
75
73
  <qrcode-vue
76
74
  v-if="templateQrCode.CCBackQREnabled"
77
- :value="`${prefix}0${+printRangeStart + +card.number - 1}`"
75
+ :value="getPassNumberValue(card.number)"
78
76
  :size="templateQrCode.CCBackQRCodeSize"
79
77
  level="H"
80
78
  :style="{
@@ -93,9 +91,7 @@
93
91
  }"
94
92
  class="position-absolute text-center"
95
93
  >
96
- Pass Number: {{ prefix }}0{{
97
- +printRangeStart + +card.number - 1
98
- }}
94
+ Pass Number: {{ getPassNumberValue(card.number) }}
99
95
  </div>
100
96
  </v-card>
101
97
  </v-col>
@@ -113,11 +109,22 @@ const props = defineProps({
113
109
  paginatedCardData: Array,
114
110
  templateQrCode: Object,
115
111
  prefix: String,
116
- printRangeStart: Number,
117
- });
118
- onMounted(() => {
119
- console.log("templateQrCode 11", props.templateQrCode.CCOrientation);
112
+ printRangeStart: [String, Number],
120
113
  });
114
+
115
+ const getPassNumberValue = (position) => {
116
+ const rawStartValue = `${props.printRangeStart ?? ""}`.trim();
117
+ const startMatch = rawStartValue.match(/(\d+)$/);
118
+ const startValue = startMatch?.[1] ?? "0";
119
+ const startNumber = Number(startValue);
120
+ const sequenceNumber = startNumber + Number(position) - 1;
121
+ const cleanPrefix = `${props.prefix ?? ""}`.replace(/\d+$/, "");
122
+ const width = startValue.length;
123
+ const formattedSequence =
124
+ width > 0 ? `${sequenceNumber}`.padStart(width, "0") : `${sequenceNumber}`;
125
+
126
+ return `${cleanPrefix}${formattedSequence}`;
127
+ };
121
128
  </script>
122
129
 
123
130
  <style scoped>