@7365admin1/layer-common 4.0.1 → 4.0.3-staging.220
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 +101 -0
- package/components/AccessCardQrTagging.vue +0 -4
- package/components/AccessManagement.vue +0 -4
- package/components/BuildingManagement/buildings.vue +0 -4
- package/components/BulletinBoardManagement.vue +0 -3
- package/components/CameraForm.vue +2 -5
- package/components/CameraMain.vue +3 -6
- package/components/ClientDetailForm.vue +65 -5
- package/components/ClientMain.vue +201 -59
- package/components/DashboardMain.vue +27 -8
- package/components/DocumentManagement.vue +0 -4
- package/components/HidAccessLogDashboard.vue +114 -34
- package/components/HidReaderManagement.vue +34 -11
- package/components/HidReaderUserRoster.vue +376 -0
- package/components/HidUserEnrollment.vue +6 -0
- package/components/HidUserMapping.vue +583 -0
- package/components/IncidentReport/Authorities.vue +0 -4
- package/components/IncidentReport/IncidentInformation.vue +0 -4
- package/components/IncidentReport/IncidentInformationDownload.vue +0 -4
- package/components/IncidentReport/affectedEntities.vue +0 -4
- package/components/MemberMain.vue +20 -2
- package/components/RolePermissionFormCreate.vue +6 -1
- package/components/RolePermissionFormPreviewUpdate.vue +22 -11
- package/components/RolePermissionMain.vue +39 -4
- package/components/VehicleManagement.vue +0 -4
- package/components/VisitorManagement.vue +0 -3
- package/components/VisitorsReportPreview.vue +0 -3
- package/composables/useBulletinBoardPermission.ts +6 -1
- package/composables/useConsoleTier.ts +73 -0
- package/composables/useHidAmico.ts +18 -1
- package/composables/useHidNavigation.ts +8 -1
- package/composables/useMember.ts +0 -5
- package/composables/useSettingsPermission.ts +7 -1
- package/package.json +2 -2
- package/pages/[org]/[site]/access-mgmt/hid-user-mapping/index.vue +23 -0
- package/pages/[org]/[site]/access-mgmt/hid-users/index.vue +1 -1
- package/utils/client-subscription.test.ts +95 -0
- package/utils/console-tier.test.ts +87 -0
- package/utils/console-tier.ts +67 -0
- package/utils/data.test.ts +84 -0
- package/utils/data.ts +92 -0
|
@@ -56,7 +56,15 @@
|
|
|
56
56
|
<div class="table-card">
|
|
57
57
|
|
|
58
58
|
<!-- Toolbar row -->
|
|
59
|
-
|
|
59
|
+
<!-- Confirmations AND failures. A suspend that the server refused used
|
|
60
|
+
to reach `console.error` and nothing else, so the control read as
|
|
61
|
+
one that quietly did nothing. -->
|
|
62
|
+
<div
|
|
63
|
+
v-if="savedNote"
|
|
64
|
+
class="saved-note"
|
|
65
|
+
:class="{ 'saved-note-error': noteIsError }"
|
|
66
|
+
:role="noteIsError ? 'alert' : 'status'"
|
|
67
|
+
>
|
|
60
68
|
{{ savedNote }}
|
|
61
69
|
<button class="saved-note-close" @click="savedNote = ''">×</button>
|
|
62
70
|
</div>
|
|
@@ -266,27 +274,74 @@
|
|
|
266
274
|
</div>
|
|
267
275
|
</div>
|
|
268
276
|
|
|
269
|
-
<!--
|
|
270
|
-
|
|
277
|
+
<!--
|
|
278
|
+
SUSPEND / REACTIVATE CONFIRMATION.
|
|
279
|
+
|
|
280
|
+
One dialog for both directions, because one endpoint serves both. The
|
|
281
|
+
suspend wording says what actually happens in the owner's own terms
|
|
282
|
+
(owner decision 4) rather than "are you sure": the people who lose access
|
|
283
|
+
are not only the client's office staff, and the two things a person most
|
|
284
|
+
needs to know before pressing it are that nothing is deleted and that it
|
|
285
|
+
can be undone. Reactivating needs no warning, but it still confirms - it
|
|
286
|
+
is a real change to who can sign in.
|
|
287
|
+
-->
|
|
288
|
+
<div v-if="confirmItem" class="overlay" @click.self="cancelConfirm">
|
|
271
289
|
<div class="confirm-box">
|
|
272
|
-
<
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
290
|
+
<template v-if="confirmAction === 'suspended'">
|
|
291
|
+
<h3 class="confirm-title">Suspend {{ confirmItem?.name }}?</h3>
|
|
292
|
+
<p class="confirm-text">
|
|
293
|
+
Everyone who belongs to this client will be blocked from signing in
|
|
294
|
+
— their staff, their residents and their guards.
|
|
295
|
+
</p>
|
|
296
|
+
<p class="confirm-text">
|
|
297
|
+
Nothing is deleted. All of their data is kept.
|
|
298
|
+
</p>
|
|
299
|
+
<p class="confirm-text">
|
|
300
|
+
You can reactivate them at any time and they get straight back in.
|
|
301
|
+
</p>
|
|
302
|
+
</template>
|
|
303
|
+
|
|
304
|
+
<template v-else>
|
|
305
|
+
<h3 class="confirm-title">Reactivate {{ confirmItem?.name }}?</h3>
|
|
306
|
+
<p class="confirm-text">
|
|
307
|
+
Their staff, residents and guards will be able to sign in again.
|
|
308
|
+
</p>
|
|
309
|
+
</template>
|
|
310
|
+
|
|
311
|
+
<p v-if="confirmError" class="confirm-error" role="alert">{{ confirmError }}</p>
|
|
312
|
+
|
|
276
313
|
<div class="confirm-actions">
|
|
277
|
-
<button class="btn-cancel" @click="
|
|
278
|
-
<button
|
|
279
|
-
|
|
280
|
-
|
|
314
|
+
<button class="btn-cancel" :disabled="confirmLoading" @click="cancelConfirm">Cancel</button>
|
|
315
|
+
<button
|
|
316
|
+
:class="confirmAction === 'suspended' ? 'btn-confirm-danger' : 'btn-confirm'"
|
|
317
|
+
:disabled="confirmLoading"
|
|
318
|
+
@click="runConfirm"
|
|
319
|
+
>
|
|
320
|
+
<span v-if="confirmLoading" class="spinner spinner-light" />
|
|
321
|
+
<span v-else>{{ confirmAction === 'suspended' ? 'Suspend' : 'Reactivate' }}</span>
|
|
281
322
|
</button>
|
|
282
323
|
</div>
|
|
283
324
|
</div>
|
|
284
325
|
</div>
|
|
285
326
|
|
|
327
|
+
<!--
|
|
328
|
+
THE ROW MENU CARRIES ITS OWN THEME CLASS.
|
|
329
|
+
|
|
330
|
+
It teleports to `<body>`, which is OUTSIDE the `v-application` element
|
|
331
|
+
Vuetify puts `v-theme--light` / `v-theme--dark` on - and every design
|
|
332
|
+
token in `tokens.css` is declared under `[class*="v-theme--"]`. So
|
|
333
|
+
`background: var(--card)` resolved to NOTHING and the menu drew with a
|
|
334
|
+
fully transparent background: measured `rgba(0, 0, 0, 0)` in both themes,
|
|
335
|
+
with the table's own text showing straight through the menu items. Its
|
|
336
|
+
border, ink and hover were dead for the same reason. Naming the theme on
|
|
337
|
+
the element itself brings all of them back without moving the teleport,
|
|
338
|
+
which is what keeps the menu clear of the table's `overflow`.
|
|
339
|
+
-->
|
|
286
340
|
<Teleport to="body">
|
|
287
341
|
<div
|
|
288
342
|
v-if="openMenuId"
|
|
289
343
|
class="dropdown"
|
|
344
|
+
:class="`v-theme--${currentTheme}`"
|
|
290
345
|
:style="{ top: menuPos.top + 'px', left: menuPos.left + 'px' }"
|
|
291
346
|
>
|
|
292
347
|
<button class="dd-item" @click="viewClient(activeMenuItem)">
|
|
@@ -295,20 +350,29 @@
|
|
|
295
350
|
<button class="dd-item" @click="openSubscription(activeMenuItem)">
|
|
296
351
|
{{ activeMenuItem?.sub?.state === 'none' ? 'Set up subscription' : 'Edit subscription' }}
|
|
297
352
|
</button>
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
<
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
353
|
+
<!--
|
|
354
|
+
OWNER ONLY (owner decision 9). Ordinary Seven365 staff may view
|
|
355
|
+
clients and set up subscriptions; only the owner may stop a client's
|
|
356
|
+
people signing in. The server decides this again on every call -
|
|
357
|
+
`requirePlatformOwner` reads the session, not anything the browser
|
|
358
|
+
sends - so this hides a control it does not guard.
|
|
359
|
+
-->
|
|
360
|
+
<template v-if="isOwner">
|
|
361
|
+
<button
|
|
362
|
+
v-if="activeMenuItem?.status === 'suspended'"
|
|
363
|
+
class="dd-item"
|
|
364
|
+
@click="askConfirm(activeMenuItem, 'active')"
|
|
365
|
+
>
|
|
366
|
+
Reactivate
|
|
367
|
+
</button>
|
|
368
|
+
<button
|
|
369
|
+
v-else
|
|
370
|
+
class="dd-item danger"
|
|
371
|
+
@click="askConfirm(activeMenuItem, 'suspended')"
|
|
372
|
+
>
|
|
373
|
+
Suspend
|
|
374
|
+
</button>
|
|
375
|
+
</template>
|
|
312
376
|
</div>
|
|
313
377
|
</Teleport>
|
|
314
378
|
</div>
|
|
@@ -319,6 +383,8 @@ import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
|
|
|
319
383
|
import useOrg from '../composables/useOrg'
|
|
320
384
|
import useVerification from '../composables/useVerification'
|
|
321
385
|
import useRole from '../composables/useRole'
|
|
386
|
+
import useConsoleTier from '../composables/useConsoleTier'
|
|
387
|
+
import useThemePreference from '../composables/useThemePreference'
|
|
322
388
|
import useUtils from '../composables/useUtils'
|
|
323
389
|
import InvitationClientForm from './InvitationClientForm.vue'
|
|
324
390
|
import ClientDetailForm from './ClientDetailForm.vue'
|
|
@@ -335,6 +401,10 @@ const { getOrganizationsWithSubscription, updateStatus } = useOrg()
|
|
|
335
401
|
const { getVerifications, cancelUserInvitation } = useVerification()
|
|
336
402
|
const { getCustomerSites: getCustomerSitesByOrgId } = useCustomerSite()
|
|
337
403
|
const { getRoleById } = useRole()
|
|
404
|
+
// Owner decision 9. Only decides what is DRAWN - see `utils/console-tier.ts`.
|
|
405
|
+
const { isOwner, load: loadConsoleTier } = useConsoleTier()
|
|
406
|
+
// Only for the teleported menu - see the comment above the Teleport.
|
|
407
|
+
const { current: currentTheme } = useThemePreference()
|
|
338
408
|
// The same helper the admin app's Organizations list uses to print a nature,
|
|
339
409
|
// so both screens say the value the same way while both exist.
|
|
340
410
|
const { formatNature } = useUtils()
|
|
@@ -378,9 +448,12 @@ const selectedClient = ref<any>(null)
|
|
|
378
448
|
const subscriptionClient = ref<any>(null)
|
|
379
449
|
const savedNote = ref('')
|
|
380
450
|
|
|
381
|
-
/* suspend confirmation */
|
|
382
|
-
const
|
|
383
|
-
const
|
|
451
|
+
/* suspend / reactivate confirmation */
|
|
452
|
+
const confirmItem = ref<any>(null)
|
|
453
|
+
const confirmAction = ref<'active' | 'suspended'>('suspended')
|
|
454
|
+
const confirmLoading = ref(false)
|
|
455
|
+
const confirmError = ref('')
|
|
456
|
+
const noteIsError = ref(false)
|
|
384
457
|
|
|
385
458
|
/* ── COMPUTED ── */
|
|
386
459
|
const pageRange = computed(() => {
|
|
@@ -409,37 +482,35 @@ function parseTotalItems(pageRangeStr: string, fallback: number): number {
|
|
|
409
482
|
return match ? Number(match[1]) : fallback
|
|
410
483
|
}
|
|
411
484
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
485
|
+
/**
|
|
486
|
+
* Say what went wrong, in the words the API used where it gave any.
|
|
487
|
+
*
|
|
488
|
+
* `updateStatus` is owner-gated on the server, so the refusal an ordinary
|
|
489
|
+
* staff member gets here is a real, expected answer - not a crash. It used to
|
|
490
|
+
* land in `console.error`, which meant pressing Suspend looked exactly like
|
|
491
|
+
* pressing nothing.
|
|
492
|
+
*/
|
|
493
|
+
function apiMessage(err: any, fallback: string) {
|
|
494
|
+
return err?.response?._data?.message ?? err?.data?.message ?? fallback
|
|
420
495
|
}
|
|
421
496
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
closeMenu()
|
|
426
|
-
await fetchData()
|
|
427
|
-
} catch (err) {
|
|
428
|
-
console.error(err)
|
|
429
|
-
}
|
|
497
|
+
function showNote(text: string, isError = false) {
|
|
498
|
+
savedNote.value = text
|
|
499
|
+
noteIsError.value = isError
|
|
430
500
|
}
|
|
431
501
|
|
|
432
502
|
/* ── SUBSCRIPTION ── */
|
|
433
503
|
function openSubscription(item: any) {
|
|
434
504
|
if (!item?._id) return
|
|
435
505
|
savedNote.value = ''
|
|
506
|
+
noteIsError.value = false
|
|
436
507
|
subscriptionClient.value = item
|
|
437
508
|
closeMenu()
|
|
438
509
|
}
|
|
439
510
|
|
|
440
511
|
async function onSubscriptionSaved(message: string) {
|
|
441
512
|
subscriptionClient.value = null
|
|
442
|
-
|
|
513
|
+
showNote(message)
|
|
443
514
|
// The list derives every subscription state from the record itself, so
|
|
444
515
|
// re-reading it is all that is needed for the row to tell the truth again.
|
|
445
516
|
await fetchData()
|
|
@@ -453,25 +524,57 @@ async function onSubscriptionSaved(message: string) {
|
|
|
453
524
|
}
|
|
454
525
|
}
|
|
455
526
|
|
|
456
|
-
/* ── SUSPEND CONFIRMATION ── */
|
|
457
|
-
function
|
|
458
|
-
|
|
527
|
+
/* ── SUSPEND / REACTIVATE CONFIRMATION ── */
|
|
528
|
+
function askConfirm(item: any, action: 'active' | 'suspended') {
|
|
529
|
+
if (!item?._id) return
|
|
530
|
+
confirmItem.value = item
|
|
531
|
+
confirmAction.value = action
|
|
532
|
+
confirmError.value = ''
|
|
459
533
|
closeMenu()
|
|
460
534
|
}
|
|
461
535
|
|
|
462
|
-
function
|
|
463
|
-
if (
|
|
464
|
-
|
|
536
|
+
function cancelConfirm() {
|
|
537
|
+
if (confirmLoading.value) return
|
|
538
|
+
confirmItem.value = null
|
|
539
|
+
confirmError.value = ''
|
|
465
540
|
}
|
|
466
541
|
|
|
467
|
-
async function
|
|
468
|
-
|
|
469
|
-
|
|
542
|
+
async function runConfirm() {
|
|
543
|
+
const item = confirmItem.value
|
|
544
|
+
const action = confirmAction.value
|
|
545
|
+
if (!item?._id) return
|
|
546
|
+
|
|
547
|
+
confirmLoading.value = true
|
|
548
|
+
confirmError.value = ''
|
|
549
|
+
|
|
470
550
|
try {
|
|
471
|
-
await
|
|
472
|
-
|
|
551
|
+
await updateStatus(item._id, action)
|
|
552
|
+
confirmItem.value = null
|
|
553
|
+
|
|
554
|
+
// The row's state is derived from the record, never held here, so
|
|
555
|
+
// re-reading the list is the whole update - `describeClientSubscription`
|
|
556
|
+
// then reports "Suspended" from the subscription status the endpoint
|
|
557
|
+
// writes alongside the organisation's. The client also moves to the tab
|
|
558
|
+
// that matches its new status, which is why it leaves this one.
|
|
559
|
+
await fetchData()
|
|
560
|
+
|
|
561
|
+
showNote(
|
|
562
|
+
action === 'suspended'
|
|
563
|
+
? `${item.name} is suspended. Their staff, residents and guards can no longer sign in, and all their data is kept.`
|
|
564
|
+
: `${item.name} is active again. Their staff, residents and guards can sign in.`,
|
|
565
|
+
)
|
|
566
|
+
} catch (err: any) {
|
|
567
|
+
// Stays on the dialog. Closing it and dropping a banner would read as
|
|
568
|
+
// "done, with a note"; the action did not happen and the person is still
|
|
569
|
+
// standing in front of the decision.
|
|
570
|
+
confirmError.value = apiMessage(
|
|
571
|
+
err,
|
|
572
|
+
action === 'suspended'
|
|
573
|
+
? 'Could not suspend this client. Only the Seven365 owner may suspend a client.'
|
|
574
|
+
: 'Could not reactivate this client. Only the Seven365 owner may reactivate a client.',
|
|
575
|
+
)
|
|
473
576
|
} finally {
|
|
474
|
-
|
|
577
|
+
confirmLoading.value = false
|
|
475
578
|
}
|
|
476
579
|
}
|
|
477
580
|
|
|
@@ -574,8 +677,8 @@ function cancelView() {
|
|
|
574
677
|
}
|
|
575
678
|
|
|
576
679
|
async function submitClient() {
|
|
577
|
-
//
|
|
578
|
-
|
|
680
|
+
// `ClientDetailForm` has already saved by the time it emits - this closes the
|
|
681
|
+
// drawer and re-reads the row so the list shows what was saved.
|
|
579
682
|
viewMode.value = 'table'
|
|
580
683
|
fetchData()
|
|
581
684
|
}
|
|
@@ -672,6 +775,9 @@ async function cancelInvite(item: any) {
|
|
|
672
775
|
}
|
|
673
776
|
|
|
674
777
|
onMounted(() => {
|
|
778
|
+
// Not awaited: the list is the screen, and the tier only adds or removes two
|
|
779
|
+
// menu items on a menu nobody has opened yet.
|
|
780
|
+
loadConsoleTier()
|
|
675
781
|
fetchData()
|
|
676
782
|
document.addEventListener('click', handleOutsideClick)
|
|
677
783
|
document.addEventListener('scroll', closeMenu, true)
|
|
@@ -1170,4 +1276,40 @@ tbody td { vertical-align: middle; }
|
|
|
1170
1276
|
}
|
|
1171
1277
|
.btn-confirm-danger:hover:not(:disabled) { background: var(--err); }
|
|
1172
1278
|
.btn-confirm-danger:disabled { opacity: 0.7; cursor: not-allowed; }
|
|
1279
|
+
/* Reactivating is an ordinary action, so it is not dressed as a destructive
|
|
1280
|
+
one. Same box as the danger button so the dialog does not jump between the
|
|
1281
|
+
two directions. */
|
|
1282
|
+
.btn-confirm {
|
|
1283
|
+
display: inline-flex;
|
|
1284
|
+
align-items: center;
|
|
1285
|
+
justify-content: center;
|
|
1286
|
+
gap: 6px;
|
|
1287
|
+
min-width: 78px;
|
|
1288
|
+
padding: 8px 16px;
|
|
1289
|
+
font-size: 13px;
|
|
1290
|
+
font-weight: 500;
|
|
1291
|
+
border: none;
|
|
1292
|
+
border-radius: 8px;
|
|
1293
|
+
background: var(--accent-strong);
|
|
1294
|
+
color: var(--on-accent-strong);
|
|
1295
|
+
cursor: pointer;
|
|
1296
|
+
}
|
|
1297
|
+
.btn-confirm:disabled { opacity: 0.7; cursor: not-allowed; }
|
|
1298
|
+
.btn-cancel:disabled { opacity: 0.7; cursor: not-allowed; }
|
|
1299
|
+
/* The refusal, on the dialog it belongs to. `--err` on `--err-bg`, not a bare
|
|
1300
|
+
red on the card. */
|
|
1301
|
+
.confirm-error {
|
|
1302
|
+
margin-bottom: 16px;
|
|
1303
|
+
padding: 9px 12px;
|
|
1304
|
+
border-radius: 8px;
|
|
1305
|
+
background: var(--err-bg);
|
|
1306
|
+
color: var(--err);
|
|
1307
|
+
font-size: 13px;
|
|
1308
|
+
font-weight: 500;
|
|
1309
|
+
line-height: 1.45;
|
|
1310
|
+
}
|
|
1311
|
+
.saved-note-error {
|
|
1312
|
+
background: var(--err-bg);
|
|
1313
|
+
color: var(--err);
|
|
1314
|
+
}
|
|
1173
1315
|
</style>
|
|
@@ -1343,27 +1343,41 @@ const { userAppRole } = useLocalSetup();
|
|
|
1343
1343
|
const { downloadPDF } = usePDFDownload();
|
|
1344
1344
|
const route = useRoute();
|
|
1345
1345
|
|
|
1346
|
+
// Each app spells its own resources, and the three that render this dashboard
|
|
1347
|
+
// disagree: work orders are `work_orders` in `-security`, `workOrder` in
|
|
1348
|
+
// `-property-management` and `work_order` in this layer's `useCleaningPermission`;
|
|
1349
|
+
// visitors are `visitor-mgmt` in `-security` and `visitorManagement` in
|
|
1350
|
+
// `-property-management`. A widget asking for one spelling is invisible to a role
|
|
1351
|
+
// granted another, so every spelling in use is listed. One canonical vocabulary
|
|
1352
|
+
// would let all of these collapse back to a single string -- that is a
|
|
1353
|
+
// cross-repo change, not one this component can make.
|
|
1354
|
+
const WORK_ORDER_SEE_ALL = [
|
|
1355
|
+
"work_orders:see-all-work-orders",
|
|
1356
|
+
"workOrder:see-all-work-orders",
|
|
1357
|
+
"work_order:see-all-work-orders",
|
|
1358
|
+
];
|
|
1359
|
+
|
|
1346
1360
|
const propertyWidgetPermissions: Record<string, string[]> = {
|
|
1347
|
-
workOrders:
|
|
1361
|
+
workOrders: WORK_ORDER_SEE_ALL,
|
|
1348
1362
|
incidents: ["incident-reports:see-incident-reports"],
|
|
1349
|
-
visitors: ["visitor-mgmt:see-all-visitor"],
|
|
1363
|
+
visitors: ["visitor-mgmt:see-all-visitor", "visitorManagement:see-all-visitor"],
|
|
1350
1364
|
facilityBookings: ["facility-booking-mgmt:see-all-facility-booking"],
|
|
1351
|
-
activity:
|
|
1365
|
+
activity: WORK_ORDER_SEE_ALL,
|
|
1352
1366
|
upcomingEvents: ["event-mgmt:see-all-event"],
|
|
1353
1367
|
todayReminders: ["event-mgmt:see-all-event"],
|
|
1354
1368
|
todayAttentions: [
|
|
1355
1369
|
"incident-reports:see-incident-reports",
|
|
1356
1370
|
"facility-booking-mgmt:see-all-facility-booking",
|
|
1357
1371
|
],
|
|
1358
|
-
workOrderStatus:
|
|
1372
|
+
workOrderStatus: WORK_ORDER_SEE_ALL,
|
|
1359
1373
|
feedbacks: ["feedbacks:see-all-feedback"],
|
|
1360
1374
|
activePatrol: ["virtual-patrol:see-all-virtual-patrol-logs"],
|
|
1361
1375
|
};
|
|
1362
1376
|
|
|
1363
1377
|
const moduleWidgetPermissions: Record<string, string[]> = {
|
|
1364
|
-
openWorkOrder:
|
|
1365
|
-
workOrders:
|
|
1366
|
-
activity:
|
|
1378
|
+
openWorkOrder: WORK_ORDER_SEE_ALL,
|
|
1379
|
+
workOrders: WORK_ORDER_SEE_ALL,
|
|
1380
|
+
activity: WORK_ORDER_SEE_ALL,
|
|
1367
1381
|
taskSchedule: [
|
|
1368
1382
|
"cleaning-schedule-mgmt:see-all-schedules",
|
|
1369
1383
|
"schedule-task-mgmt:see-all-schedule-tasks",
|
|
@@ -1406,7 +1420,12 @@ function canViewWidget(key: string) {
|
|
|
1406
1420
|
? propertyWidgetPermissions[key]
|
|
1407
1421
|
: moduleWidgetPermissions[key];
|
|
1408
1422
|
|
|
1409
|
-
|
|
1423
|
+
// Fail closed. This used to return `true` while `userAppRole` was still
|
|
1424
|
+
// unresolved, so the dashboard drew every widget and then took some away --
|
|
1425
|
+
// and if the role fetch failed outright it drew every widget permanently.
|
|
1426
|
+
// Every other gate in this package returns `false` without a role; this one
|
|
1427
|
+
// now agrees with them.
|
|
1428
|
+
if (!permissions) return false;
|
|
1410
1429
|
if (permissions.includes("*")) return true;
|
|
1411
1430
|
|
|
1412
1431
|
if (
|
|
@@ -610,10 +610,6 @@
|
|
|
610
610
|
// into the consuming apps but not reliably into the layer's own components.
|
|
611
611
|
import { readListPage } from "../utils/list-page";
|
|
612
612
|
|
|
613
|
-
definePageMeta({
|
|
614
|
-
middleware: ["01-auth", "02-org"],
|
|
615
|
-
memberOnly: true,
|
|
616
|
-
});
|
|
617
613
|
const props = defineProps({
|
|
618
614
|
headers: {
|
|
619
615
|
type: Array as PropType<Array<Record<string, any>>>,
|