@7365admin1/layer-common 1.11.47 → 1.11.49

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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @iservice365/layer-common
2
2
 
3
+ ## 1.11.49
4
+
5
+ ### Patch Changes
6
+
7
+ - ef80e57: add get member with user and org
8
+
9
+ ## 1.11.48
10
+
11
+ ### Patch Changes
12
+
13
+ - 75d1ea7: add new nature business v2
14
+
3
15
  ## 1.11.47
4
16
 
5
17
  ### Patch Changes
@@ -30,7 +30,7 @@
30
30
  />
31
31
  </v-col>
32
32
 
33
- <v-col v-if="isANPR" cols="12">
33
+ <!-- <v-col v-if="isANPR" cols="12">
34
34
  <InputLabel class="text-capitalize" title="Category" required />
35
35
  <v-select
36
36
  v-model="camera.category"
@@ -38,7 +38,7 @@
38
38
  density="comfortable"
39
39
  :rules="[requiredRule]"
40
40
  />
41
- </v-col>
41
+ </v-col> -->
42
42
 
43
43
  <v-col v-if="isANPR" cols="12">
44
44
  <InputLabel class="text-capitalize" title="Type" required />
@@ -50,14 +50,14 @@
50
50
  />
51
51
  </v-col>
52
52
 
53
- <v-col v-if="isANPR" cols="12">
53
+ <!-- <v-col v-if="isANPR" cols="12">
54
54
  <InputLabel class="text-capitalize" title="Guard House" />
55
55
  <v-select
56
56
  v-model="camera.guardPost"
57
57
  :items="guardPostOptions"
58
58
  density="comfortable"
59
59
  />
60
- </v-col>
60
+ </v-col> -->
61
61
 
62
62
  <v-col v-if="isANPR" cols="12">
63
63
  <InputLabel class="text-capitalize" title="User" required />
@@ -231,6 +231,8 @@ const anprDirectionOptions = computed(() => [
231
231
  { title: "Entry", value: "entry" },
232
232
  { title: "Exit", value: "exit" },
233
233
  { title: "Both Entry and Exit", value: "both" },
234
+ { title: "Visitors", value: "visitors" },
235
+ { title: "Residents", value: "residents" },
234
236
  ]);
235
237
 
236
238
  function back() {
@@ -178,6 +178,7 @@ async function submit() {
178
178
  name: document.value.name,
179
179
  attachment: document.value.attachment,
180
180
  type: derivedType,
181
+ size: fileData?.data?.size ?? fileData?.data?.file_size ?? 0,
181
182
  status: "active",
182
183
  org: orgId,
183
184
  site: siteId,
@@ -202,6 +203,7 @@ async function submit() {
202
203
  const fileName = fileData?.data?.name || String(fileId || "");
203
204
  const derivedType = deriveTypeFromFilename(fileName);
204
205
  if (derivedType) payload.type = derivedType;
206
+ payload.size = fileData?.data?.size ?? fileData?.data?.file_size ?? 0;
205
207
  }
206
208
 
207
209
  await updateById(prop.document._id ?? "", payload);
@@ -265,21 +265,72 @@
265
265
  <v-card width="100%">
266
266
  <v-card-text style="max-height: 100vh; overflow-y: auto" class="pb-0">
267
267
  <v-row no-gutters class="mb-4">
268
- <v-col cols="12">
269
- <strong>Name:</strong> {{ selectedDocument?.name ?? "N/A" }}
268
+ <v-col cols="12" class="mb-3">
269
+ <div class="text-h6 font-weight-bold">
270
+ {{ selectedDocument?.name || "N/A" }}
271
+ </div>
270
272
  </v-col>
271
- <v-col cols="12">
272
- <strong>Type:</strong> {{ selectedDocument?.type ?? "N/A" }}
273
+
274
+ <v-col cols="12" class="mb-2">
275
+ <v-row no-gutters>
276
+ <v-col cols="6" class="text-body-1">Document</v-col>
277
+ <v-col cols="6" class="text-right text-body-1">
278
+ <NuxtLink
279
+ v-if="selectedDocument?.attachment"
280
+ :to="getFileUrl(selectedDocument.attachment)"
281
+ external
282
+ target="_blank"
283
+ >
284
+ View Document
285
+ </NuxtLink>
286
+ <span v-else>N/A</span>
287
+ </v-col>
288
+ </v-row>
289
+ </v-col>
290
+
291
+ <v-col cols="12" class="mb-2">
292
+ <v-row no-gutters>
293
+ <v-col cols="6" class="text-body-1">File Type</v-col>
294
+ <v-col cols="6" class="text-right text-body-1">{{
295
+ previewDetails.fileType
296
+ }}</v-col>
297
+ </v-row>
298
+ </v-col>
299
+
300
+ <v-col cols="12" class="mb-2">
301
+ <v-row no-gutters>
302
+ <v-col cols="6" class="text-body-1">File Size</v-col>
303
+ <v-col cols="6" class="text-right text-body-1">{{
304
+ previewDetails.fileSize
305
+ }}</v-col>
306
+ </v-row>
273
307
  </v-col>
308
+
309
+ <v-col cols="12" class="mb-2">
310
+ <v-row no-gutters>
311
+ <v-col cols="6" class="text-body-1">Date Created</v-col>
312
+ <v-col cols="6" class="text-right text-body-1">{{
313
+ previewDetails.dateCreated
314
+ }}</v-col>
315
+ </v-row>
316
+ </v-col>
317
+
318
+ <v-col cols="12" class="mb-2">
319
+ <v-row no-gutters>
320
+ <v-col cols="6" class="text-body-1">Date Modified</v-col>
321
+ <v-col cols="6" class="text-right text-body-1">{{
322
+ previewDetails.dateModified
323
+ }}</v-col>
324
+ </v-row>
325
+ </v-col>
326
+
274
327
  <v-col cols="12">
275
- <strong>Document: </strong>
276
- <NuxtLink
277
- :to="getFileUrl(selectedDocument.attachment)"
278
- external
279
- target="_blank"
280
- >
281
- View Document
282
- </NuxtLink>
328
+ <v-row no-gutters>
329
+ <v-col cols="6" class="text-body-1">Remarks</v-col>
330
+ <v-col cols="6" class="text-right text-body-1">{{
331
+ previewDetails.remarks
332
+ }}</v-col>
333
+ </v-row>
283
334
  </v-col>
284
335
  </v-row>
285
336
  </v-card-text>
@@ -511,6 +562,9 @@ const currentFolderPath = computed(() => {
511
562
  return folderStack.value.map((folder) => folder.name).join(" / ");
512
563
  });
513
564
 
565
+ const fileTypes = ref(["all", "pdf", "csv", "doc"]);
566
+ const selectedFileType = ref("all");
567
+
514
568
  const {
515
569
  data: getDocumentReq,
516
570
  refresh: getDocuments,
@@ -523,9 +577,11 @@ const {
523
577
  search: headerSearch.value,
524
578
  site: siteId,
525
579
  parentId: activeParentId.value,
580
+ type:
581
+ selectedFileType.value === "all" ? undefined : selectedFileType.value,
526
582
  }),
527
583
  {
528
- watch: [page, headerSearch, activeParentId],
584
+ watch: [page, headerSearch, activeParentId, selectedFileType],
529
585
  }
530
586
  );
531
587
 
@@ -559,12 +615,24 @@ function formatDate(value?: string | null) {
559
615
  });
560
616
  }
561
617
 
618
+ function formatBytes(bytes = 0) {
619
+ const b = Number(bytes) || 0;
620
+ if (b === 0) return "0 B";
621
+ const k = 1024;
622
+ const sizes = ["B", "KB", "MB", "GB", "TB"];
623
+ const i = Math.floor(Math.log(b) / Math.log(k));
624
+ return `${parseFloat((b / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
625
+ }
626
+
562
627
  const tableItems = computed(() => {
563
628
  return items.value.map((doc: any) => ({
564
629
  ...doc,
565
630
  _raw: doc,
566
631
  file_type: doc.type === "folder" ? "Folder" : doc.type || "N/A",
567
- file_size: doc.type === "folder" ? "—" : doc.file_size || doc.size || "0mb",
632
+ file_size:
633
+ doc.type === "folder"
634
+ ? "—"
635
+ : formatBytes(Number(doc.file_size ?? doc.size ?? 0)),
568
636
  date_created: formatDate(doc.created_at || doc.createdAt),
569
637
  date_modified: formatDate(doc.updated_at || doc.updatedAt),
570
638
  remarks: doc.remarks || "N/A",
@@ -582,8 +650,8 @@ const folderRemarks = ref("");
582
650
  const folderLoading = ref(false);
583
651
  const validFolderForm = ref(false);
584
652
 
585
- const fileTypes = ref(["All", "pdf", "csv", "doc"]);
586
- const selectedFileType = ref("All");
653
+ // fileTypes and selectedFileType are declared above to allow
654
+ // the fetch to include the selected filter.
587
655
  const filterMenu = ref(false);
588
656
  const selectedDocument = ref<TDocument>({
589
657
  _id: "",
@@ -591,6 +659,26 @@ const selectedDocument = ref<TDocument>({
591
659
  attachment: "",
592
660
  type: "",
593
661
  });
662
+
663
+ const previewDetails = computed(() => {
664
+ const document = selectedDocument.value as Record<string, any>;
665
+ const fileType =
666
+ document?.type === "folder"
667
+ ? "Folder"
668
+ : document?.type || document?.file_type || "N/A";
669
+ const fileSize =
670
+ document?.type === "folder"
671
+ ? "N/A"
672
+ : formatBytes(Number(document?.file_size ?? document?.size ?? 0));
673
+
674
+ return {
675
+ fileType,
676
+ fileSize,
677
+ dateCreated: formatDate(document?.created_at || document?.createdAt),
678
+ dateModified: formatDate(document?.updated_at || document?.updatedAt),
679
+ remarks: document?.remarks || "N/A",
680
+ };
681
+ });
594
682
  const deleteLoading = ref(false);
595
683
  const confirmDialog = ref(false);
596
684
  const selectedDocumentId = ref<string | null>(null);
@@ -603,8 +691,35 @@ function tableRowClickHandler(data: any) {
603
691
  return;
604
692
  }
605
693
 
606
- selectedDocument.value = rowData;
694
+ openPreviewDialog(rowData);
695
+ }
696
+
697
+ async function openPreviewDialog(document: TDocument) {
698
+ selectedDocument.value = document;
607
699
  previewDialog.value = true;
700
+
701
+ const documentId = String(document?._id || "");
702
+ if (!documentId) {
703
+ return;
704
+ }
705
+
706
+ try {
707
+ const response = await documentService.getById(documentId);
708
+ const fetchedDocument =
709
+ (response as any)?.item ||
710
+ (response as any)?.data ||
711
+ (response as any)?.document ||
712
+ response;
713
+
714
+ if (fetchedDocument && typeof fetchedDocument === "object") {
715
+ selectedDocument.value = {
716
+ ...document,
717
+ ...(fetchedDocument as Record<string, any>),
718
+ } as TDocument;
719
+ }
720
+ } catch (_error) {
721
+ // Keep table data in modal if detail API request fails.
722
+ }
608
723
  }
609
724
 
610
725
  function isFolderItem(document?: Partial<TDocument> & Record<string, any>) {
@@ -663,8 +778,11 @@ function setDocument({
663
778
  editDialog.value = dialog;
664
779
  selectedDocument.value = data;
665
780
  } else if (mode === "preview") {
666
- previewDialog.value = dialog;
667
- selectedDocument.value = data;
781
+ if (!dialog) {
782
+ previewDialog.value = false;
783
+ return;
784
+ }
785
+ openPreviewDialog(data);
668
786
  }
669
787
  }
670
788
 
@@ -1,13 +1,13 @@
1
1
  <template>
2
2
  <div class="d-flex flex-column">
3
3
  <v-text-field v-bind="$attrs" ref="datePickerRef" :model-value="dateFormattedReadOnly" autocomplete="off"
4
- :placeholder="placeholder" :rules="rules" style="z-index: 10" @click="openDatePicker">
4
+ :placeholder="placeholder" :rules="rules" style="z-index: 10" @click="openDatePicker" @click:clear.stop="clearDate">
5
5
  <template #append-inner>
6
6
  <v-icon icon="mdi-calendar" @click.stop="openDatePicker" />
7
7
  </template>
8
8
  </v-text-field>
9
9
  <div class="w-100 d-flex align-end ga-3 hidden-input">
10
- <input ref="dateInput" type="date" v-model="date" />
10
+ <input ref="dateInput" type="date" v-model="date" :min="min" />
11
11
  </div>
12
12
  </div>
13
13
  </template>
@@ -22,6 +22,10 @@ const prop = defineProps({
22
22
  placeholder: {
23
23
  type: String,
24
24
  default: 'DD/MM/YYYY'
25
+ },
26
+ min: {
27
+ type: String,
28
+ default: undefined
25
29
  }
26
30
  })
27
31
 
@@ -34,6 +38,11 @@ const datePickerRef = ref<HTMLInputElement | null>(null)
34
38
 
35
39
  const isInitialLoad = ref(true)
36
40
 
41
+ function clearDate() {
42
+ date.value = null
43
+ dateFormattedReadOnly.value = null
44
+ }
45
+
37
46
  function openDatePicker() {
38
47
  setTimeout(() => {
39
48
  dateInput.value?.showPicker?.()
@@ -83,6 +83,56 @@
83
83
  </v-expansion-panel-title>
84
84
 
85
85
  <v-expansion-panel-text>
86
+ <v-container fluid class="px-0">
87
+ <v-row dense>
88
+ <v-col cols="6">
89
+ <v-switch
90
+ v-model="ANPRSwitches.enableUnregistered"
91
+ :disabled="!canManageSiteSettings"
92
+ label="Enable Unregistered Vehicles"
93
+ color="primary"
94
+ hide-details
95
+ ></v-switch>
96
+ </v-col>
97
+
98
+ <v-col cols="6">
99
+ <v-switch
100
+ v-model="ANPRSwitches.openBarrierPickUpDropOff"
101
+ :disabled="!canManageSiteSettings"
102
+ label="Open Barrier for Pick-up and Drop-off"
103
+ color="primary"
104
+ hide-details
105
+ ></v-switch>
106
+ </v-col>
107
+
108
+ <v-col cols="6">
109
+ <v-switch
110
+ v-model="ANPRSwitches.vehicleSnapshot"
111
+ :disabled="!canManageSiteSettings"
112
+ label="Capture Vehicle Snapshot"
113
+ color="primary"
114
+ hide-details
115
+ ></v-switch>
116
+ </v-col>
117
+ </v-row>
118
+
119
+ <v-row v-if="canManageSiteSettings">
120
+ <v-col cols="auto">
121
+ <v-btn
122
+ variant="flat"
123
+ color="primary"
124
+ class="text-none"
125
+ :loading="isSaving"
126
+ @click="saveANPRSettings"
127
+ >
128
+ Save
129
+ </v-btn>
130
+ </v-col>
131
+ </v-row>
132
+ </v-container>
133
+
134
+ <v-divider class="my-4"></v-divider>
135
+
86
136
  <CameraMain :site="siteId" :read-only="!canManageSiteSettings"
87
137
  :guard-posts="siteData?.metadata?.guardPosts" />
88
138
  </v-expansion-panel-text>
@@ -154,9 +204,11 @@ import useFile from '../composables/useFile';
154
204
  import useSiteSettings from '../composables/useSiteSettings';
155
205
  import useUtils from '../composables/useUtils';
156
206
  import useWorkOrder from '../composables/useWorkOrder';
207
+ import useANPRSettings from '../composables/useANPRSettings';
157
208
 
158
209
  const props = defineProps({
159
210
  siteId: { type: String, required: true },
211
+ createdBy: { type: String },
160
212
  canManageSiteSettings: { type: Boolean, default: false },
161
213
  showAnprCamera: { type: Boolean, default: false },
162
214
  showCctvCamera: { type: Boolean, default: false },
@@ -173,6 +225,7 @@ const { requiredRule } = useUtils();
173
225
  const { getFileUrl } = useFile();
174
226
  const { getWorkOrderSettings, createWorkOrderSettings } = useWorkOrder();
175
227
 
228
+
176
229
  const blocks = ref(0);
177
230
  const guardPosts = ref(0);
178
231
  const gracePeriod = ref(0);
@@ -358,4 +411,56 @@ function show(message: string, color: string) {
358
411
  toast.message = message;
359
412
  toast.color = color;
360
413
  }
414
+
415
+
416
+ // ANPR Settings
417
+ const {getANPRSettingBySite, createUpdateANPRSettings} = useANPRSettings();
418
+ const isSaving = ref(false)
419
+
420
+ // Reactive object to hold the state of the 3 switches
421
+ const ANPRSwitches = ref({
422
+ enableUnregistered: false,
423
+ openBarrierPickUpDropOff: false,
424
+ vehicleSnapshot: false
425
+ })
426
+
427
+ await useLazyAsyncData(
428
+ `anpr-settings-${props.siteId}`,
429
+ async () => {
430
+ const data = await getANPRSettingBySite({
431
+ site: props.siteId,
432
+ userType: "site",
433
+ });
434
+ console.log('data', data)
435
+ if(typeof data?.ANPRSwitches == "object"){
436
+ ANPRSwitches.value = data?.ANPRSwitches
437
+ }
438
+ }
439
+ );
440
+
441
+ const saveANPRSettings = async () => {
442
+ try {
443
+ isSaving.value = true
444
+
445
+ // Replace this with your actual API endpoint / store action
446
+ // await $fetch(`/api/sites/${siteId.value}/camera-settings`, {
447
+ // method: 'POST',
448
+ // body: cameraSettings.value
449
+ // })
450
+ const data = {
451
+ site: props.siteId,
452
+ ANPRSwitches: ANPRSwitches.value,
453
+ updatedBy: props.createdBy,
454
+ userType: "site",
455
+ };
456
+ console.log('saveANPRSettings data:', data)
457
+ await createUpdateANPRSettings(data);
458
+ show("Saved successfully", "success");
459
+ } catch (error) {
460
+ console.error('Failed to save settings:', error)
461
+ } finally {
462
+ isSaving.value = false
463
+ }
464
+ }
465
+
361
466
  </script>
@@ -17,13 +17,9 @@
17
17
  <template #item.block="{ value }">
18
18
  {{ value ? `Blk ${value}` : "" }}
19
19
  </template>
20
- <!-- <template #item.status="{ value }">
21
- <v-chip :color="formatVehicleStatus(value).color" size="x-small" dark>
22
- {{ formatVehicleStatus(value).label }}
23
- </v-chip>
24
-
25
-
26
- </template> -->
20
+ <template #item.nric="{ value }">
21
+ {{ maskNRIC(value) }}
22
+ </template>
27
23
 
28
24
  <template #item.plates="{ value, item }">
29
25
  <PlateNumberDisplay :plate-numbers="value" :default-value="item.plateNumber" />
@@ -61,7 +57,12 @@
61
57
  <v-row no-gutters class="ga-1 mb-5">
62
58
 
63
59
  <template v-for="(label, key) in formattedFields" :key="key">
64
- <v-col v-if="key === 'plates'" class="d-flex flex-column ga-2">
60
+
61
+ <v-col v-if="key === 'nric' && selectedVehicleObject['nric']" cols="12">
62
+ <span class="d-flex ga-3 align-center"><strong>{{ label }}:</strong> {{ maskNRIC(selectedVehicleObject[key]) }}</span>
63
+ </v-col>
64
+
65
+ <v-col v-else-if="key === 'plates'" class="d-flex flex-column ga-2">
65
66
  <span class="d-flex ga-3 align-center"><strong>{{ label }}:</strong></span>
66
67
  <!-- <PlateNumberDisplay :plate-numbers="selectedVehicleObject[key]" show-all :default-value="selectedVehicleObject.plateNumber" /> -->
67
68
  <v-col cols="12">
@@ -186,7 +187,7 @@ const plateHeaders = [
186
187
 
187
188
 
188
189
 
189
- const { formatCamelCaseToWords, formatDate, debounce } = useUtils();
190
+ const { formatCamelCaseToWords, formatDate, debounce, maskNRIC } = useUtils();
190
191
  const { getVehicles, deleteVehicle, formatVehicleStatus, approveVehicle, updateVehicle, getSpecificVehicleById } = useVehicle();
191
192
 
192
193
  const items = ref<Array<Record<string, any>>>([]);
@@ -340,6 +340,11 @@
340
340
  :disabled="loading.checkingOut" @click="handleCheckout(selectedVisitorId as string)" />
341
341
  </span>
342
342
 
343
+ <span v-else-if="
344
+ key === 'nric' && selectedVisitorObject['nric']" class="d-flex align-center">
345
+ <strong>{{ label }}:</strong>
346
+ <span class="ml-2">{{ maskNRIC(selectedVisitorObject[key]) }}</span>
347
+ </span>
343
348
  <span v-else-if="key === 'attachments' && selectedVisitorObject?.[key]?.length > 0"
344
349
  class="d-flex flex-column">
345
350
  <strong class="w-full">{{ label }}:</strong>
@@ -349,7 +354,7 @@
349
354
  </div>
350
355
  </span>
351
356
 
352
- <span v-else-if="selectedVisitorObject[key]" class="d-flex ga-3 align-center"><strong>{{ label
357
+ <span v-else-if="selectedVisitorObject[key]" class="d-flex ga-3 align-start"><strong>{{ label
353
358
  }}:</strong>
354
359
  {{ formatValues(key, selectedVisitorObject[key]) }}
355
360
  <TooltipInfo v-if="key === 'checkOut'" text="Manual Checkout" density="compact" size="x-small" />
@@ -602,6 +607,7 @@ const {
602
607
  formatDate,
603
608
  UTCToLocalTIme,
604
609
  standardFormatDate,
610
+ maskNRIC,
605
611
  } = useUtils();
606
612
  const { formatLocation } = useSecurityUtils();
607
613
  const { getFileUrlAnpr } = useFile();
@@ -0,0 +1,27 @@
1
+ export default function () {
2
+ async function getANPRSettingBySite(data: any) {
3
+ return await useNuxtApp().$api<Record<string, any>>(
4
+ `/api/anpr-settings/get/v1`,
5
+ {
6
+ method: "POST",
7
+ body: data,
8
+ }
9
+ );
10
+ }
11
+ async function createUpdateANPRSettings(
12
+ data: any
13
+ ) {
14
+ return await useNuxtApp().$api<Record<string, any>>(
15
+ `/api/anpr-settings/v1`,
16
+ {
17
+ method: "POST",
18
+ body: data,
19
+ }
20
+ );
21
+ }
22
+
23
+ return {
24
+ getANPRSettingBySite,
25
+ createUpdateANPRSettings,
26
+ };
27
+ }
@@ -3,20 +3,28 @@ export default function useDocument() {
3
3
  page = 1,
4
4
  search = "",
5
5
  limit = 10,
6
- status = "active",
6
+ type = "all",
7
7
  site = "",
8
8
  parentId = "",
9
+ }: {
10
+ page?: number;
11
+ search?: string;
12
+ limit?: number;
13
+ type?: string;
14
+ site?: string;
15
+ parentId?: string;
9
16
  } = {}) {
17
+ const query: Record<string, any> = {
18
+ page,
19
+ search,
20
+ limit,
21
+ site,
22
+ parentId,
23
+ };
24
+
10
25
  return useNuxtApp().$api<Record<string, any>>(`/api/documents`, {
11
26
  method: "GET",
12
- query: {
13
- page,
14
- search,
15
- limit,
16
- status,
17
- site,
18
- parentId,
19
- },
27
+ query,
20
28
  });
21
29
  }
22
30
 
@@ -50,6 +50,23 @@ export default function useLocal() {
50
50
  { title: "Pool Maintenance Services", value: "pool_maintenance_services" },
51
51
  ];
52
52
 
53
+ const natureOfBusinessV2 = [
54
+ {
55
+ title: "Property Management Services",
56
+ value: "property_management_agency",
57
+ },
58
+ { title: "Security Agency", value: "security_agency" },
59
+ { title: "Cleaning Services", value: "cleaning_services" },
60
+ {
61
+ title: "M&E Services",
62
+ value: "mechanical_electrical_services",
63
+ },
64
+ { title: "Landscaping Services", value: "landscaping_services" },
65
+ { title: "Pest Control Services", value: "pest_control_services" },
66
+ { title: "Pool Maintenance Services", value: "pool_maintenance_services" },
67
+ ];
68
+
69
+
53
70
  const landingPage = useCookie("landing-page", cookieConfig);
54
71
 
55
72
  const subjects = [
@@ -125,6 +142,7 @@ export default function useLocal() {
125
142
  apps,
126
143
  headerSearch,
127
144
  natureOfBusiness,
145
+ natureOfBusinessV2,
128
146
  landingPage,
129
147
  subjects,
130
148
  };
@@ -132,6 +132,20 @@ export default function useMember() {
132
132
  )
133
133
  }
134
134
 
135
+ function getByUserTypeOrg(
136
+ user: string,
137
+ type: string,
138
+ org: string
139
+ ) {
140
+ return useNuxtApp().$api<TMember>(
141
+ `/api/members/user/${user}/app/${type}/org`,
142
+ {
143
+ method: "GET",
144
+ query: { org },
145
+ }
146
+ );
147
+ }
148
+
135
149
  return {
136
150
  members,
137
151
  member,
@@ -149,6 +163,7 @@ export default function useMember() {
149
163
  updateMemberRole,
150
164
  createMemberDirect,
151
165
  updateMemberSite,
152
- getAllByUserId
166
+ getAllByUserId,
167
+ getByUserTypeOrg
153
168
  };
154
169
  }
@@ -506,6 +506,19 @@ export default function useUtils() {
506
506
  return word[0].toUpperCase() + word.substring(1);
507
507
  }
508
508
 
509
+
510
+ function maskNRIC(value?: string) {
511
+ if(!value) return "";
512
+ if (value.length <= 4) {
513
+ return value;
514
+ }
515
+
516
+ const visiblePart = value.slice(-4);
517
+ const maskedPart = "*".repeat(value.length - 4);
518
+
519
+ return `${maskedPart}${visiblePart}`;
520
+ }
521
+
509
522
  return {
510
523
  requiredRule,
511
524
  emailRule,
@@ -552,5 +565,6 @@ export default function useUtils() {
552
565
  getImage,
553
566
  standardFormatDate,
554
567
  getCapitalized,
568
+ maskNRIC
555
569
  };
556
570
  }
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@7365admin1/layer-common",
3
3
  "license": "MIT",
4
4
  "type": "module",
5
- "version": "1.11.47",
5
+ "version": "1.11.49",
6
6
  "author": "7365admin1",
7
7
  "main": "./nuxt.config.ts",
8
8
  "publishConfig": {