@7365admin1/layer-common 4.35.1-staging.384 → 4.36.0
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 +10 -4
- package/assets/css/primitives.css +0 -43
- package/assets/css/robot-hero.css +2 -2
- package/components/AccessCardAddForm.vue +4 -5
- package/components/AccessCardAssignToUnitForm.vue +2 -3
- package/components/AccessCardPreviewDialog.vue +3 -4
- package/components/AppSelect.vue +3 -77
- package/components/CameraForm.vue +6 -20
- package/components/DashboardMain.vue +60 -66
- package/components/Dialog/UpdateMoreAction.vue +1 -11
- package/components/DocumentForm.vue +112 -407
- package/components/DocumentManagement.vue +12 -213
- package/components/EntryPass/QrTemplatePreview.vue +25 -21
- package/components/EntryPassInformation.vue +24 -119
- package/components/Facility/BookingSetup.vue +0 -63
- package/components/HidAccessLogDashboard.vue +17 -74
- package/components/HidIntercomManagement.vue +14 -180
- package/components/HidQrCodeConfiguration.vue +195 -17
- package/components/HidReaderManagement.vue +0 -398
- package/components/HidReaderUserRoster.vue +3 -44
- package/components/HidUserEnrollment.vue +155 -845
- package/components/InvitationClientForm.vue +1 -19
- package/components/Layout/NavigationDrawer.vue +1 -4
- package/components/NotificationBell.vue +7 -59
- package/components/Robot/BuildingMap.vue +9 -16
- package/components/Robot/LiftQueue.vue +4 -9
- package/components/Robot/MapEditor.vue +8 -1
- package/components/RolePermissionFormCreate.vue +3 -4
- package/components/RolePermissionFormPreviewUpdate.vue +5 -12
- package/components/RolePermissionMain.vue +0 -1
- package/components/ServiceProviderInvitationPrompt.vue +155 -0
- package/components/ServiceProviderMain.vue +30 -178
- package/components/VisitorForm.vue +0 -12
- package/components/VisitorManagement.vue +10 -285
- package/components/VisitorSocketPopUp.vue +1 -2
- package/composables/useANPRSettings.ts +15 -45
- package/composables/useAccessManagement.ts +0 -5
- package/composables/useCleaningPermission.ts +0 -2
- package/composables/useCommonPermission.ts +0 -32
- package/composables/useDocument.ts +0 -7
- package/composables/useFacility.ts +0 -11
- package/composables/useHidAmico.ts +0 -159
- package/composables/useHidNavigation.ts +1 -25
- package/composables/usePeople.ts +0 -19
- package/composables/usePropertyPermission.ts +0 -19
- package/composables/useRobotBuilding.ts +3 -22
- package/composables/useRobots.ts +0 -13
- package/composables/useSecurityPermission.ts +0 -19
- package/composables/useServiceProvider.ts +0 -3
- package/composables/useServiceProviderInvitation.ts +3 -101
- package/composables/useSiteEntryPassSettings.ts +0 -26
- package/composables/useWebUsb.ts +11 -14
- package/package.json +2 -2
- package/pages/[org]/[site]/access-mgmt/administrator/index.vue +1 -1
- package/pages/[org]/[site]/access-mgmt/hid-cards/index.vue +1 -1
- package/pages/[org]/[site]/access-mgmt/hid-readers/index.vue +1 -1
- package/plugins/API.ts +0 -11
- package/types/document.d.ts +0 -14
- package/types/facility.d.ts +0 -4
- package/types/people.d.ts +1 -28
- package/utils/module-routes.ts +1 -8
- package/utils/nav-sections.ts +2 -8
- package/utils/notification-target.ts +8 -277
- package/utils/permission-labels.ts +2 -20
- package/components/ClientInvitations.vue +0 -291
- package/components/HidAccessPermissions.vue +0 -424
- package/components/HidProfileQrCode.vue +0 -332
- package/composables/useHidReaderSelection.ts +0 -50
- package/composables/useSiteCategory.ts +0 -45
- package/pages/[org]/[site]/access-mgmt/hid-qr-code/index.vue +0 -23
- package/utils/dashboard-widget-prefs.ts +0 -54
- package/utils/hid-permission-assignments.ts +0 -190
- package/utils/hid-reader-selection.ts +0 -52
- package/utils/occupancy-role.ts +0 -109
- package/utils/robot-fleet-core.ts +0 -57
- package/utils/sp-inviter-row.ts +0 -86
|
@@ -1,291 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div>
|
|
3
|
-
<PageHeader
|
|
4
|
-
title="Client invitations"
|
|
5
|
-
subtitle="Other organisations asking your company to provide services at their sites."
|
|
6
|
-
/>
|
|
7
|
-
|
|
8
|
-
<AppCard class="table-card">
|
|
9
|
-
<CardToolbar>
|
|
10
|
-
<template #tabs>
|
|
11
|
-
<button
|
|
12
|
-
v-for="t in tabs"
|
|
13
|
-
:key="t.value"
|
|
14
|
-
type="button"
|
|
15
|
-
role="tab"
|
|
16
|
-
class="app-tab"
|
|
17
|
-
:class="{ 'app-tab--active': tab === t.value }"
|
|
18
|
-
:aria-selected="tab === t.value"
|
|
19
|
-
@click="tab = t.value"
|
|
20
|
-
>
|
|
21
|
-
{{ t.label }}<span v-if="counts[t.value]"> ({{ counts[t.value] }})</span>
|
|
22
|
-
</button>
|
|
23
|
-
</template>
|
|
24
|
-
</CardToolbar>
|
|
25
|
-
|
|
26
|
-
<div class="pa-4 d-flex flex-column ga-3">
|
|
27
|
-
<div v-if="loading" class="screen-hint">Loading…</div>
|
|
28
|
-
<div v-else-if="loadError" class="text-error" role="alert">{{ loadError }}</div>
|
|
29
|
-
<div v-else-if="!items.length" class="screen-hint">{{ emptyLine }}</div>
|
|
30
|
-
|
|
31
|
-
<AppCard
|
|
32
|
-
v-for="item in items"
|
|
33
|
-
:key="item._id"
|
|
34
|
-
class="client-invite"
|
|
35
|
-
:class="{ 'client-invite--linked': item._id === linked }"
|
|
36
|
-
>
|
|
37
|
-
<div class="d-flex align-start ga-3 pa-4">
|
|
38
|
-
<v-avatar color="primary" variant="tonal" size="44">
|
|
39
|
-
<v-icon>mdi-office-building-marker-outline</v-icon>
|
|
40
|
-
</v-avatar>
|
|
41
|
-
<div class="flex-grow-1">
|
|
42
|
-
<div class="screen-card-title">
|
|
43
|
-
{{ item.orgName }} has invited your company to {{ item.siteName }}
|
|
44
|
-
</div>
|
|
45
|
-
<div class="screen-hint client-invite__meta">
|
|
46
|
-
<span v-if="item.service">Services: {{ serviceLabel(item.service) }} · </span>
|
|
47
|
-
<span v-if="item.invitedByName">Invited by {{ item.invitedByName }} · </span>
|
|
48
|
-
<span>Sent {{ day(item.createdAt) }}</span>
|
|
49
|
-
</div>
|
|
50
|
-
</div>
|
|
51
|
-
<StatusChip :status="item.status" :label="item.statusLabel" />
|
|
52
|
-
</div>
|
|
53
|
-
|
|
54
|
-
<div class="px-4 pb-4 client-invite__body">
|
|
55
|
-
{{ stateLine(item) }}
|
|
56
|
-
</div>
|
|
57
|
-
|
|
58
|
-
<!-- Buttons only while the invitation is waiting for an answer. -->
|
|
59
|
-
<div v-if="item.status === 'pending'" class="d-flex align-center ga-2 px-4 pb-4">
|
|
60
|
-
<v-btn
|
|
61
|
-
variant="flat"
|
|
62
|
-
class="screen-btn-primary"
|
|
63
|
-
:loading="working[item._id] === 'accept'"
|
|
64
|
-
:disabled="Boolean(working[item._id])"
|
|
65
|
-
@click="answer(item, 'accept')"
|
|
66
|
-
>
|
|
67
|
-
Accept
|
|
68
|
-
</v-btn>
|
|
69
|
-
<v-btn
|
|
70
|
-
variant="outlined"
|
|
71
|
-
class="screen-btn-ghost"
|
|
72
|
-
:loading="working[item._id] === 'decline'"
|
|
73
|
-
:disabled="Boolean(working[item._id])"
|
|
74
|
-
@click="answer(item, 'decline')"
|
|
75
|
-
>
|
|
76
|
-
Decline
|
|
77
|
-
</v-btn>
|
|
78
|
-
</div>
|
|
79
|
-
|
|
80
|
-
<div v-else-if="item.status === 'complete'" class="px-4 pb-4">
|
|
81
|
-
<v-btn
|
|
82
|
-
v-if="item.needsSiteLink"
|
|
83
|
-
variant="flat"
|
|
84
|
-
class="screen-btn-primary"
|
|
85
|
-
:loading="working[item._id] === 'link'"
|
|
86
|
-
:disabled="Boolean(working[item._id])"
|
|
87
|
-
@click="addToSites(item)"
|
|
88
|
-
>
|
|
89
|
-
Add {{ item.siteName }} to your Sites
|
|
90
|
-
</v-btn>
|
|
91
|
-
<NuxtLink v-else-if="item.onThisOrg" :to="`/org/${org}/sites`">Open Sites</NuxtLink>
|
|
92
|
-
<NuxtLink
|
|
93
|
-
v-else-if="item.providerOrgId && item.providerOrgId !== org"
|
|
94
|
-
:to="`/org/${item.providerOrgId}/sites`"
|
|
95
|
-
>
|
|
96
|
-
Open {{ item.providerOrgName || "that company" }}'s Sites
|
|
97
|
-
</NuxtLink>
|
|
98
|
-
</div>
|
|
99
|
-
|
|
100
|
-
<div v-if="errors[item._id]" class="px-4 pb-4 text-error client-invite__body" role="alert">
|
|
101
|
-
{{ errors[item._id] }}
|
|
102
|
-
</div>
|
|
103
|
-
</AppCard>
|
|
104
|
-
</div>
|
|
105
|
-
</AppCard>
|
|
106
|
-
</div>
|
|
107
|
-
</template>
|
|
108
|
-
|
|
109
|
-
<script setup lang="ts">
|
|
110
|
-
/**
|
|
111
|
-
* "Client invitations" (owner request 2026-09-24): where a service-provider
|
|
112
|
-
* company answers invitations from its clients, in four tabs, with one plain
|
|
113
|
-
* sentence per card saying what happened. Accept / Decline appear only while
|
|
114
|
-
* an invitation is waiting - an answered one has no buttons at all.
|
|
115
|
-
*
|
|
116
|
-
* Accepting adds the site to the company ON SCREEN (`org`), which is what makes
|
|
117
|
-
* it appear on this company's Sites list and in the service app's site
|
|
118
|
-
* switcher. The server refuses a company the person is not a member of.
|
|
119
|
-
*/
|
|
120
|
-
import type { TReceivedServiceProviderInvitation } from "../composables/useServiceProviderInvitation";
|
|
121
|
-
|
|
122
|
-
const props = defineProps({
|
|
123
|
-
/** The organisation on screen - the company that accepts. */
|
|
124
|
-
org: { type: String, required: true },
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
type Tab = "pending" | "accepted" | "declined" | "expired";
|
|
128
|
-
|
|
129
|
-
const tabs: Array<{ value: Tab; label: string }> = [
|
|
130
|
-
{ value: "pending", label: "Pending" },
|
|
131
|
-
{ value: "accepted", label: "Accepted" },
|
|
132
|
-
{ value: "declined", label: "Declined" },
|
|
133
|
-
{ value: "expired", label: "Expired" },
|
|
134
|
-
];
|
|
135
|
-
|
|
136
|
-
const route = useRoute();
|
|
137
|
-
const {
|
|
138
|
-
getReceivedInvitations,
|
|
139
|
-
getInvitation,
|
|
140
|
-
acceptInvitation,
|
|
141
|
-
declineInvitation,
|
|
142
|
-
linkInvitationSite,
|
|
143
|
-
} = useServiceProviderInvitation();
|
|
144
|
-
|
|
145
|
-
const tab = ref<Tab>("pending");
|
|
146
|
-
const items = ref<TReceivedServiceProviderInvitation[]>([]);
|
|
147
|
-
const counts = ref<Record<string, number>>({});
|
|
148
|
-
const loading = ref(false);
|
|
149
|
-
const loadError = ref("");
|
|
150
|
-
const working = reactive<Record<string, string>>({});
|
|
151
|
-
const errors = reactive<Record<string, string>>({});
|
|
152
|
-
|
|
153
|
-
/** The invitation the bell linked to (`?invite=<id>`), outlined. */
|
|
154
|
-
const linked = computed(() => {
|
|
155
|
-
const value = String(route.query?.invite ?? "").trim();
|
|
156
|
-
return /^[a-f0-9]{24}$/i.test(value) ? value : "";
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
const emptyLine = computed(
|
|
160
|
-
() =>
|
|
161
|
-
({
|
|
162
|
-
pending: "Nothing is waiting for your answer.",
|
|
163
|
-
accepted: "You have not accepted any invitations yet.",
|
|
164
|
-
declined: "You have not declined any invitations.",
|
|
165
|
-
expired: "No expired or withdrawn invitations.",
|
|
166
|
-
})[tab.value],
|
|
167
|
-
);
|
|
168
|
-
|
|
169
|
-
function day(value?: string | null) {
|
|
170
|
-
if (!value) return "";
|
|
171
|
-
const d = new Date(value);
|
|
172
|
-
return Number.isNaN(d.getTime())
|
|
173
|
-
? ""
|
|
174
|
-
: d.toLocaleDateString("en-GB", { day: "numeric", month: "short", year: "numeric" });
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/** " on 24 Sep 2026", or nothing when the date is unknown. */
|
|
178
|
-
function on(value?: string | null) {
|
|
179
|
-
const text = day(value);
|
|
180
|
-
return text ? ` on ${text}` : "";
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
function serviceLabel(value: string) {
|
|
184
|
-
return value.replace(/[-_]/g, " ").replace(/^\w/, (c) => c.toUpperCase());
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/** One sentence: where the invitation stands and what, if anything, to do. */
|
|
188
|
-
function stateLine(item: TReceivedServiceProviderInvitation) {
|
|
189
|
-
switch (item.status) {
|
|
190
|
-
case "pending":
|
|
191
|
-
return `Waiting for you. Accepting adds ${item.siteName} to this company's Sites list${
|
|
192
|
-
item.expireAt ? ` — please answer by ${day(item.expireAt)}` : ""
|
|
193
|
-
}.`;
|
|
194
|
-
case "complete":
|
|
195
|
-
if (item.onThisOrg) {
|
|
196
|
-
return `Accepted${on(item.answeredAt)} — ${item.siteName} is on your Sites list as a site you serve for ${item.orgName}.`;
|
|
197
|
-
}
|
|
198
|
-
if (item.needsSiteLink) {
|
|
199
|
-
return "This invitation was accepted, but the site wasn't added to your Sites. Add it now.";
|
|
200
|
-
}
|
|
201
|
-
if (!item.providerOrgId || item.providerOrgId === props.org) {
|
|
202
|
-
return `Accepted${on(item.answeredAt)}, but ${item.siteName} is not on this company's Sites list. Please contact Seven365 support so we can add it.`;
|
|
203
|
-
}
|
|
204
|
-
return `Accepted${on(item.answeredAt)} for ${
|
|
205
|
-
item.providerOrgName || "another of your companies"
|
|
206
|
-
} — the site is on that company's Sites list, not this one's.`;
|
|
207
|
-
case "declined":
|
|
208
|
-
return `Declined${on(item.answeredAt)}. ${item.orgName} has been told.`;
|
|
209
|
-
case "cancelled":
|
|
210
|
-
return `${item.orgName} withdrew this invitation${on(item.answeredAt)}.`;
|
|
211
|
-
default:
|
|
212
|
-
return `This invitation expired${on(item.expireAt)} without an answer. Ask ${item.orgName} to send a new one if you still want this site.`;
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
async function load() {
|
|
217
|
-
loading.value = true;
|
|
218
|
-
loadError.value = "";
|
|
219
|
-
try {
|
|
220
|
-
const res = await getReceivedInvitations({ org: props.org, status: tab.value });
|
|
221
|
-
items.value = Array.isArray(res?.items) ? res.items : [];
|
|
222
|
-
counts.value = res?.counts ?? {};
|
|
223
|
-
} catch (err: any) {
|
|
224
|
-
items.value = [];
|
|
225
|
-
loadError.value = err?.data?.message || "Could not load your client invitations. Please try again.";
|
|
226
|
-
} finally {
|
|
227
|
-
loading.value = false;
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
async function answer(item: TReceivedServiceProviderInvitation, action: "accept" | "decline") {
|
|
232
|
-
errors[item._id] = "";
|
|
233
|
-
working[item._id] = action;
|
|
234
|
-
try {
|
|
235
|
-
if (action === "accept") await acceptInvitation(item._id, props.org);
|
|
236
|
-
else await declineInvitation(item._id);
|
|
237
|
-
// Follow the invitation to the tab it now belongs to (the tab watcher
|
|
238
|
-
// reloads the list).
|
|
239
|
-
tab.value = action === "accept" ? "accepted" : "declined";
|
|
240
|
-
} catch (err: any) {
|
|
241
|
-
errors[item._id] =
|
|
242
|
-
err?.data?.message ||
|
|
243
|
-
(action === "accept" ? "Could not accept this invitation." : "Could not decline this invitation.");
|
|
244
|
-
} finally {
|
|
245
|
-
working[item._id] = "";
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
/** Accepted but never added to the Sites list: add it now, then show it. */
|
|
250
|
-
async function addToSites(item: TReceivedServiceProviderInvitation) {
|
|
251
|
-
errors[item._id] = "";
|
|
252
|
-
working[item._id] = "link";
|
|
253
|
-
try {
|
|
254
|
-
await linkInvitationSite(item._id, props.org);
|
|
255
|
-
await load();
|
|
256
|
-
} catch (err: any) {
|
|
257
|
-
errors[item._id] = err?.data?.message || "Could not add this site to your Sites.";
|
|
258
|
-
} finally {
|
|
259
|
-
working[item._id] = "";
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
// Opened from the bell: start on the tab the linked invitation is in.
|
|
264
|
-
if (linked.value) {
|
|
265
|
-
try {
|
|
266
|
-
const one: any = await getInvitation(linked.value, props.org);
|
|
267
|
-
if (one?.tab && tabs.some((t) => t.value === one.tab)) tab.value = one.tab;
|
|
268
|
-
} catch {
|
|
269
|
-
// Not ours or gone: the page still lists the rest.
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
await load();
|
|
273
|
-
watch(tab, load);
|
|
274
|
-
</script>
|
|
275
|
-
|
|
276
|
-
<style scoped>
|
|
277
|
-
.client-invite--linked {
|
|
278
|
-
outline: 2px solid rgb(var(--v-theme-primary));
|
|
279
|
-
outline-offset: 2px;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
.client-invite :deep(.screen-card-title) {
|
|
283
|
-
display: block;
|
|
284
|
-
line-height: 1.35;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
.client-invite__meta,
|
|
288
|
-
.client-invite__body {
|
|
289
|
-
font-size: var(--fs-cell);
|
|
290
|
-
}
|
|
291
|
-
</style>
|