@7365admin1/layer-common 3.0.23 → 3.0.25

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
+ ## 3.0.25
4
+
5
+ ### Patch Changes
6
+
7
+ - d4a5bb0: Update Layer Common Version
8
+
9
+ ## 3.0.24
10
+
11
+ ### Patch Changes
12
+
13
+ - 9eb5864: update invite service provider
14
+
3
15
  ## 3.0.23
4
16
 
5
17
  ### Patch Changes
@@ -650,7 +650,7 @@ watch(() => card.value.showAssign, async (newVal) => {
650
650
  const response = await _getBlockLevelUnitList(siteId);
651
651
  blockLevelUnitData.value = (response as any)?.data || [];
652
652
  buildingItems.value = blockLevelUnitData.value.map((b: any) => ({
653
- name: b.block,
653
+ name: `${b.block} (${b.name})`,
654
654
  value: b._id,
655
655
  }));
656
656
  } catch (error) {
@@ -676,7 +676,7 @@ watch(() => card.value.assignBuilding, (newVal) => {
676
676
  if (newVal) {
677
677
  const selectedBlock = blockLevelUnitData.value.find((b: any) => b._id === newVal);
678
678
  levelItems.value = selectedBlock?.levels?.map((l: any) => ({
679
- name: l.level,
679
+ name: l.name,
680
680
  value: l._id,
681
681
  })) || [];
682
682
  } else {
@@ -309,7 +309,7 @@ onMounted(async () => {
309
309
  if (blockLevelResult.status === "fulfilled") {
310
310
  blockLevelUnitData.value = (blockLevelResult.value as any)?.data || [];
311
311
  buildingItems.value = blockLevelUnitData.value.map((b: any) => ({
312
- name: b.block,
312
+ name: `${b.block} (${b.name})`,
313
313
  value: b._id,
314
314
  }));
315
315
  }
@@ -352,7 +352,7 @@ watch(
352
352
  const selectedBlock = blockLevelUnitData.value.find((b: any) => b._id === newVal);
353
353
  if (selectedBlock?.levels) {
354
354
  levelItems.value = selectedBlock.levels.map((l: any) => ({
355
- name: l.level,
355
+ name: l.name,
356
356
  value: l._id,
357
357
  }));
358
358
  }
@@ -152,6 +152,10 @@ const props = defineProps({
152
152
  headers: {
153
153
  type: Array as PropType<Array<Record<string, any>>>,
154
154
  default: () => [
155
+ {
156
+ title: "Block Name",
157
+ value: "block.name",
158
+ },
155
159
  {
156
160
  title: "Block",
157
161
  value: "block.block",
@@ -180,10 +184,6 @@ const props = defineProps({
180
184
  title: "Asgn. (NP)",
181
185
  value: "cardCounts.assigned.non_physical",
182
186
  },
183
- {
184
- title: "Total",
185
- value: "totalCardCount",
186
- },
187
187
  // { title: "Action", value: "action-table" },
188
188
  ],
189
189
  },
@@ -234,6 +234,7 @@ const deleteLoading = ref(false);
234
234
  const deleteError = ref("");
235
235
  const confirmDialog = ref(false);
236
236
  const searchText = ref("");
237
+ const debouncedSearch = ref("");
237
238
  const replaceDialog = ref(false);
238
239
 
239
240
  const route = useRoute();
@@ -277,16 +278,25 @@ const {
277
278
  () =>
278
279
  getUserTypeAccessCards({
279
280
  page: page.value,
280
- search: searchText.value,
281
+ search: debouncedSearch.value,
281
282
  organization: orgId.value,
282
283
  site: siteId.value,
283
284
  userType: "Visitor/Resident",
284
285
  }),
285
286
  {
286
- watch: [page, searchText],
287
+ watch: [page, debouncedSearch],
287
288
  }
288
289
  );
289
290
 
291
+ let debounceTimer: ReturnType<typeof setTimeout> | null = null;
292
+ watch(searchText, (val) => {
293
+ page.value = 1;
294
+ if (debounceTimer) clearTimeout(debounceTimer);
295
+ debounceTimer = setTimeout(() => {
296
+ debouncedSearch.value = val;
297
+ }, 400);
298
+ });
299
+
290
300
  const loading = computed(() => getAllReqStatus.value === "pending");
291
301
 
292
302
  watchEffect(() => {
@@ -427,7 +427,7 @@ watchEffect(() => {
427
427
 
428
428
  const { data: getUnitPeople, refresh: getUnitPeopleRefresh, pending: getUnitPeoplePending } = useLazyAsyncData(
429
429
  "get-unit-people",
430
- async () => await getPeopleByUnit(buildingUnit.value._id, { limit: 10, site: prop.site })
430
+ async () => await getPeopleByUnit(buildingUnit.value._id, { limit: 10, site: prop.site, type: "resident,tenant" })
431
431
  );
432
432
 
433
433
  watch(getUnitPeople, (newData: TPeople[]) => {
@@ -1355,7 +1355,16 @@
1355
1355
  :key="item.status"
1356
1356
  class="facility-booking-summary-row"
1357
1357
  >
1358
- <span class="facility-booking-summary-label">
1358
+ <button
1359
+ v-if="item.status === 'Pending'"
1360
+ class="facility-booking-summary-label facility-booking-summary-label-button"
1361
+ type="button"
1362
+ aria-label="View pending facility bookings"
1363
+ @click="goToFacilityBookingStatus(item.status)"
1364
+ >
1365
+ {{ item.status }}
1366
+ </button>
1367
+ <span v-else class="facility-booking-summary-label">
1359
1368
  {{ item.status }}
1360
1369
  </span>
1361
1370
  <span class="facility-booking-summary-dots" />
@@ -2294,6 +2303,12 @@ function openFacilityBookingDialog() {
2294
2303
  facilityBookingDialog.value = true;
2295
2304
  }
2296
2305
 
2306
+ function goToFacilityBookingStatus(status: string) {
2307
+ if (status !== "Pending" || !props.orgId || !props.site) return;
2308
+ facilityBookingDialog.value = false;
2309
+ router.push(`${dashboardPath("facility/bookings")}?tab=Pending`);
2310
+ }
2311
+
2297
2312
  function normalizeFacilityBookingStatus(status: unknown) {
2298
2313
  const value = String(status || "").trim();
2299
2314
  return value === "For Refund" ? "With Refund" : value;
@@ -4233,6 +4248,19 @@ function formatDashboardTime(value?: string) {
4233
4248
  line-height: 1.25;
4234
4249
  }
4235
4250
 
4251
+ .facility-booking-summary-label-button {
4252
+ appearance: none;
4253
+ background: transparent;
4254
+ border: 0;
4255
+ cursor: pointer;
4256
+ padding: 0;
4257
+ text-align: left;
4258
+ }
4259
+
4260
+ .facility-booking-summary-label-button:hover {
4261
+ text-decoration: underline;
4262
+ }
4263
+
4236
4264
  .facility-booking-summary-dots {
4237
4265
  border-bottom: 2px dotted #9aa3ad;
4238
4266
  display: block;
@@ -163,13 +163,47 @@
163
163
 
164
164
  <template #item.action="{ item }">
165
165
  <v-btn
166
+ v-if="!isFolderItem(item._raw)"
166
167
  variant="flat"
167
168
  class="table-download-btn text-none"
168
169
  @click.stop="downloadDocument(item._raw)"
169
- :disabled="isFolderItem(item._raw)"
170
170
  >
171
171
  Download
172
172
  </v-btn>
173
+
174
+ <v-menu
175
+ v-else
176
+ v-model="folderActionMenu[item._raw._id]"
177
+ location="bottom end"
178
+ offset="6"
179
+ >
180
+ <template #activator="{ props }">
181
+ <v-btn
182
+ v-bind="props"
183
+ icon="mdi-dots-vertical"
184
+ variant="text"
185
+ size="small"
186
+ density="comfortable"
187
+ class="folder-action-btn"
188
+ @click.stop
189
+ />
190
+ </template>
191
+
192
+ <v-list class="folder-action-list pa-0">
193
+ <v-list-item
194
+ class="folder-action-item"
195
+ @click.stop="openEditFolderDialog(item._raw)"
196
+ >
197
+ <v-list-item-title class="text-body-2">Edit</v-list-item-title>
198
+ </v-list-item>
199
+ <v-list-item
200
+ class="folder-action-item"
201
+ @click.stop="openDeleteFolderDialog(item._raw)"
202
+ >
203
+ <v-list-item-title class="text-body-2">Delete</v-list-item-title>
204
+ </v-list-item>
205
+ </v-list>
206
+ </v-menu>
173
207
  </template>
174
208
  </TableV3>
175
209
  </v-col>
@@ -251,6 +285,125 @@
251
285
  </v-card>
252
286
  </v-dialog>
253
287
 
288
+ <!-- Edit Folder Dialog -->
289
+ <v-dialog v-model="editFolderDialog" width="450" persistent>
290
+ <v-card width="100%" class="folder-dialog-card">
291
+ <v-toolbar class="pa-0 folder-dialog-toolbar" density="compact">
292
+ <v-row no-gutters class="w-100">
293
+ <v-col cols="12" class="pa-4">
294
+ <span class="font-weight-medium text-h6">Edit Folder</span>
295
+ </v-col>
296
+ </v-row>
297
+ </v-toolbar>
298
+
299
+ <v-card-text style="max-height: 100vh; overflow-y: auto" class="pa-0">
300
+ <v-form v-model="validEditFolderForm">
301
+ <v-col cols="12" class="px-6 pt-5">
302
+ <InputLabel title="Folder Title" />
303
+ <v-text-field
304
+ v-model.trim="editFolderTitle"
305
+ density="comfortable"
306
+ variant="outlined"
307
+ :rules="[(v) => !!v || 'Folder title is required']"
308
+ hide-details="auto"
309
+ />
310
+ </v-col>
311
+
312
+ <v-col cols="12" class="px-6 pb-5">
313
+ <InputLabel title="Remarks (Optional)" />
314
+ <v-textarea
315
+ v-model.trim="editFolderRemarks"
316
+ density="comfortable"
317
+ variant="outlined"
318
+ rows="3"
319
+ placeholder="Enter remarks"
320
+ hide-details
321
+ />
322
+ </v-col>
323
+ </v-form>
324
+ </v-card-text>
325
+
326
+ <v-toolbar class="pa-0" density="compact">
327
+ <v-row no-gutters class="w-100">
328
+ <v-col cols="6" class="pa-0">
329
+ <v-btn
330
+ block
331
+ variant="text"
332
+ class="text-none"
333
+ size="large"
334
+ @click="editFolderDialog = false"
335
+ height="48"
336
+ >
337
+ Cancel
338
+ </v-btn>
339
+ </v-col>
340
+ <v-col cols="6" class="pa-0">
341
+ <v-btn
342
+ block
343
+ color="black"
344
+ variant="flat"
345
+ class="text-none"
346
+ size="large"
347
+ @click="handleApplyFolderEdit"
348
+ height="48"
349
+ >
350
+ Apply
351
+ </v-btn>
352
+ </v-col>
353
+ </v-row>
354
+ </v-toolbar>
355
+ </v-card>
356
+ </v-dialog>
357
+
358
+ <!-- Delete Folder Dialog -->
359
+ <v-dialog v-model="deleteFolderDialog" width="450" persistent>
360
+ <v-card width="100%" class="folder-dialog-card">
361
+ <v-toolbar density="compact" class="pl-4 folder-dialog-toolbar">
362
+ <span class="font-weight-medium text-h6">Delete Folder</span>
363
+ </v-toolbar>
364
+
365
+ <v-card-text class="px-6 py-5">
366
+ <p class="text-subtitle-2 font-weight-medium mb-4">
367
+ Are you sure you want to permanently delete this folder?
368
+ </p>
369
+ <p class="text-body-2 text-grey-darken-1 mb-0">
370
+ This action cannot be undone and the folder will be removed
371
+ permanently from the system.
372
+ </p>
373
+ </v-card-text>
374
+
375
+ <v-toolbar density="compact" class="pa-0">
376
+ <v-row no-gutters class="w-100">
377
+ <v-col cols="6" class="pa-0">
378
+ <v-btn
379
+ tile
380
+ block
381
+ height="48"
382
+ variant="text"
383
+ class="text-none"
384
+ @click="deleteFolderDialog = false"
385
+ >
386
+ Cancel
387
+ </v-btn>
388
+ </v-col>
389
+ <v-col cols="6" class="pa-0">
390
+ <v-btn
391
+ tile
392
+ block
393
+ height="48"
394
+ color="black"
395
+ variant="flat"
396
+ class="text-none"
397
+ @click="handleDeleteFolderUiConfirm"
398
+ >
399
+ Confirm
400
+ </v-btn>
401
+ </v-col>
402
+ </v-row>
403
+ </v-toolbar>
404
+ </v-card>
405
+ </v-dialog>
406
+
254
407
  <!-- Edit Dialog -->
255
408
  <v-dialog v-model="editDialog" width="450" persistent>
256
409
  <DocumentForm
@@ -689,6 +842,15 @@ const folderTitle = ref("");
689
842
  const folderRemarks = ref("");
690
843
  const folderLoading = ref(false);
691
844
  const validFolderForm = ref(false);
845
+ const folderActionMenu = ref<Record<string, boolean>>({});
846
+ const selectedFolder = ref<(Partial<TDocument> & Record<string, any>) | null>(
847
+ null
848
+ );
849
+ const editFolderDialog = ref(false);
850
+ const deleteFolderDialog = ref(false);
851
+ const editFolderTitle = ref("");
852
+ const editFolderRemarks = ref("");
853
+ const validEditFolderForm = ref(false);
692
854
 
693
855
  // fileTypes and selectedFileType are declared above to allow
694
856
  // the fetch to include the selected filter.
@@ -849,6 +1011,40 @@ function openCreateFolderDialog() {
849
1011
  createFolderDialog.value = true;
850
1012
  }
851
1013
 
1014
+ function closeFolderActionMenu(folderId?: string) {
1015
+ if (!folderId) {
1016
+ return;
1017
+ }
1018
+
1019
+ folderActionMenu.value[folderId] = false;
1020
+ }
1021
+
1022
+ function openEditFolderDialog(
1023
+ folder: Partial<TDocument> & Record<string, any>
1024
+ ) {
1025
+ selectedFolder.value = folder;
1026
+ editFolderTitle.value = String(folder.name || "");
1027
+ editFolderRemarks.value = String(folder.remarks || "");
1028
+ editFolderDialog.value = true;
1029
+ closeFolderActionMenu(String(folder._id || ""));
1030
+ }
1031
+
1032
+ function openDeleteFolderDialog(
1033
+ folder: Partial<TDocument> & Record<string, any>
1034
+ ) {
1035
+ selectedFolder.value = folder;
1036
+ deleteFolderDialog.value = true;
1037
+ closeFolderActionMenu(String(folder._id || ""));
1038
+ }
1039
+
1040
+ function handleApplyFolderEdit() {
1041
+ editFolderDialog.value = false;
1042
+ }
1043
+
1044
+ function handleDeleteFolderUiConfirm() {
1045
+ deleteFolderDialog.value = false;
1046
+ }
1047
+
852
1048
  async function handleCreateFolder() {
853
1049
  if (!folderTitle.value) {
854
1050
  showMessage("Folder title is required", "error");
@@ -977,4 +1173,35 @@ function selectFileType(type: string) {
977
1173
  border-radius: 8px;
978
1174
  background: #fafafa;
979
1175
  }
1176
+
1177
+ .folder-action-btn {
1178
+ color: #38475a;
1179
+ }
1180
+
1181
+ .folder-action-list {
1182
+ min-width: 180px;
1183
+ border-radius: 8px;
1184
+ background: #fff;
1185
+ box-shadow: 0 10px 28px rgba(15, 23, 42, 0.22);
1186
+ overflow: hidden;
1187
+ }
1188
+
1189
+ .folder-action-item {
1190
+ min-height: 40px;
1191
+ border-bottom: 1px solid #eef0f2;
1192
+ color: #38475a;
1193
+ }
1194
+
1195
+ .folder-action-item:last-child {
1196
+ border-bottom: none;
1197
+ }
1198
+
1199
+ .folder-dialog-card {
1200
+ border-radius: 6px;
1201
+ overflow: hidden;
1202
+ }
1203
+
1204
+ .folder-dialog-toolbar {
1205
+ border-bottom: 1px solid #eef0f2;
1206
+ }
980
1207
  </style>
@@ -24,6 +24,7 @@
24
24
  <v-window-item value="patrol">
25
25
  <PatrolReportTab
26
26
  :active="activeTab === 'patrol'"
27
+ :site="site"
27
28
  :filters="filters"
28
29
  :options="options"
29
30
  @update:filters="updateFilters"
@@ -43,6 +43,7 @@ import ReportLocationHeader from "./ReportLocationHeader.vue";
43
43
 
44
44
  const props = defineProps<{
45
45
  active: boolean;
46
+ site: string;
46
47
  filters: NFCPatrolReportFilters;
47
48
  options: NFCPatrolReportFilterOptions;
48
49
  }>();
@@ -59,7 +60,7 @@ const {
59
60
  dialogRemarks,
60
61
  fetchReport,
61
62
  openRemarksDialog,
62
- } = useNFCPatrolReport(props.filters);
63
+ } = useNFCPatrolReport(props.filters, props.site);
63
64
 
64
65
  watch(
65
66
  () => [props.filters.route, props.filters.timeRange, props.filters.date, props.active],
@@ -4,6 +4,8 @@
4
4
  <v-select
5
5
  :model-value="modelValue.route"
6
6
  :items="options.routes"
7
+ item-title="title"
8
+ item-value="value"
7
9
  label="Select Route"
8
10
  variant="outlined"
9
11
  density="comfortable"
@@ -18,7 +20,7 @@
18
20
  <v-select
19
21
  :model-value="modelValue.timeRange"
20
22
  :items="options.timeRanges"
21
- label="Time Range"
23
+ label="Start Time"
22
24
  variant="outlined"
23
25
  density="comfortable"
24
26
  rounded="lg"
@@ -58,7 +58,7 @@
58
58
  <span class="text-caption font-weight-bold text-capitalize">
59
59
  permissions
60
60
  </span>
61
- <v-chip>{{ value.length }}</v-chip>
61
+ <v-chip>{{ getPermissionCount(value) }}</v-chip>
62
62
  </template>
63
63
 
64
64
  <template #item.action-table="{ item }" v-if="canDeleteRole">
@@ -145,6 +145,11 @@
145
145
  </template>
146
146
 
147
147
  <script setup lang="ts">
148
+ import {
149
+ getPermissionCount,
150
+ updateRolePermissionsInList,
151
+ } from "../utils/rolePermissionCounts";
152
+
148
153
  const props = defineProps({
149
154
  orgId: {
150
155
  type: String,
@@ -324,11 +329,29 @@ const Permissions = computed(() => {
324
329
  });
325
330
 
326
331
  function successfulUpdate() {
332
+ const savedPermissions = [...selectedPermissions.value];
333
+
327
334
  previewDialog.value = false;
328
- getRoles();
335
+ edit.value = false;
336
+ originalPermissions.value = savedPermissions;
337
+ items.value = updateRolePermissionsInList(
338
+ items.value,
339
+ roleId.value,
340
+ savedPermissions
341
+ );
342
+ void refreshRolesAfterUpdate(savedPermissions);
329
343
  useLocalSetup(props.type);
330
344
  }
331
345
 
346
+ async function refreshRolesAfterUpdate(savedPermissions: string[]) {
347
+ await getRoles();
348
+ items.value = updateRolePermissionsInList(
349
+ items.value,
350
+ roleId.value,
351
+ savedPermissions
352
+ );
353
+ }
354
+
332
355
  const confirmDialog = ref(false);
333
356
  const selectedRoleId = ref<string | null>(null);
334
357
  const deleteLoading = ref(false);
@@ -1210,6 +1210,7 @@ async function submitServiceProviderInvite() {
1210
1210
  serviceProviderInvite.value.siteName ||
1211
1211
  site.value?.name ||
1212
1212
  props.siteName,
1213
+ app: serviceProviderInvite.value.app,
1213
1214
 
1214
1215
  inviteType: existingAccount.value ? "create-org" : "organization-invite",
1215
1216
  });