@7365admin1/layer-common 4.39.1 → 4.40.1
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
|
+
## 4.40.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 783ffe2: Edit Camera: the device password is write-only. On edit the Password field is optional, starts blank with the placeholder "Saved · leave blank to keep", and is sent only when a new one is typed, so an admin can switch a camera's Status without re-typing its login. A status-only change now enables Submit. Password stays required when adding a camera.
|
|
8
|
+
|
|
9
|
+
## 4.40.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- 3a96438: Client invitations: an accepted invitation whose site never reached the company's Sites list now says so in plain words and offers "Add <site> to your Sites" (`linkInvitationSite`, `PUT /service-provider-invitations/:id/link-site`). Needs core 3.100.0.
|
|
14
|
+
|
|
3
15
|
## 4.39.1
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
|
@@ -1453,11 +1465,11 @@
|
|
|
1453
1465
|
`utils/console-tier.ts` + `composables/useConsoleTier.ts` mirror the server's
|
|
1454
1466
|
own rule from two endpoints the console already calls, unprojected:
|
|
1455
1467
|
|
|
1456
|
-
|
|
1457
|
-
|
|
1468
|
+
GET /api/members/user/:user/app/admin the Seven365 staff membership
|
|
1469
|
+
GET /api/roles/id/:role that membership's role document
|
|
1458
1470
|
|
|
1459
|
-
|
|
1460
|
-
|
|
1471
|
+
owner = member.type === "admin" && role.type === "admin" && role.default === true
|
|
1472
|
+
staff = member.type === "admin" && role.type === "admin"
|
|
1461
1473
|
|
|
1462
1474
|
`role.default` is the marker because it is the only property of a platform
|
|
1463
1475
|
staff role no API caller can set - `role.controller.ts` validates create and
|
|
@@ -161,14 +161,21 @@
|
|
|
161
161
|
</v-col>
|
|
162
162
|
|
|
163
163
|
<v-col v-if="isANPR" cols="12">
|
|
164
|
-
|
|
164
|
+
<!-- Write-only: the saved password is never sent to the browser, so
|
|
165
|
+
on edit a blank field keeps it. Required only for a new camera. -->
|
|
166
|
+
<InputLabel
|
|
167
|
+
class="text-capitalize"
|
|
168
|
+
title="Password"
|
|
169
|
+
:required="!isEdit"
|
|
170
|
+
/>
|
|
165
171
|
<v-text-field
|
|
166
172
|
v-model.trim="camera.password"
|
|
167
173
|
type="password"
|
|
174
|
+
autocomplete="new-password"
|
|
168
175
|
class="filter-field"
|
|
169
176
|
density="comfortable"
|
|
170
|
-
:rules="[requiredRule]"
|
|
171
|
-
placeholder="Enter password"
|
|
177
|
+
:rules="isEdit ? [] : [requiredRule]"
|
|
178
|
+
:placeholder="isEdit ? 'Saved · leave blank to keep' : 'Enter password'"
|
|
172
179
|
/>
|
|
173
180
|
</v-col>
|
|
174
181
|
|
|
@@ -419,8 +426,12 @@ const camera = ref<TSiteCamera>({
|
|
|
419
426
|
|
|
420
427
|
camera.value.site = prop.site;
|
|
421
428
|
|
|
422
|
-
|
|
429
|
+
const isEdit = prop.mode === "edit";
|
|
430
|
+
|
|
431
|
+
if (isEdit) {
|
|
423
432
|
camera.value = JSON.parse(JSON.stringify(prop.camera));
|
|
433
|
+
// Never pre-filled: blank means "keep the saved password".
|
|
434
|
+
camera.value.password = "";
|
|
424
435
|
}
|
|
425
436
|
camera.value.site = prop.site;
|
|
426
437
|
camera.value.type = prop.type;
|
|
@@ -576,7 +587,9 @@ const hasChanges = computed(() => {
|
|
|
576
587
|
return (
|
|
577
588
|
camera.value.host !== prop.camera.host ||
|
|
578
589
|
camera.value.username !== prop.camera.username ||
|
|
579
|
-
|
|
590
|
+
// A typed password is a change; blank keeps the saved one.
|
|
591
|
+
!!camera.value.password ||
|
|
592
|
+
camera.value.status !== prop.camera.status ||
|
|
580
593
|
camera.value.category !== prop.camera.category ||
|
|
581
594
|
camera.value.guardPost !== prop.camera.guardPost ||
|
|
582
595
|
camera.value.direction !== prop.camera.direction ||
|
|
@@ -895,7 +908,8 @@ async function save(
|
|
|
895
908
|
const res = await updateSiteCamera(camera.value._id ?? "", {
|
|
896
909
|
...addressPayload(),
|
|
897
910
|
username: camera.value.username,
|
|
898
|
-
|
|
911
|
+
// Sent only when typed; left out, the server keeps the saved one.
|
|
912
|
+
...(camera.value.password ? { password: camera.value.password } : {}),
|
|
899
913
|
category: camera.value.category,
|
|
900
914
|
guardPost: camera.value.guardPost,
|
|
901
915
|
direction: camera.value.direction,
|
|
@@ -78,7 +78,17 @@
|
|
|
78
78
|
</div>
|
|
79
79
|
|
|
80
80
|
<div v-else-if="item.status === 'complete'" class="px-4 pb-4">
|
|
81
|
-
<
|
|
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>
|
|
82
92
|
<NuxtLink
|
|
83
93
|
v-else-if="item.providerOrgId && item.providerOrgId !== org"
|
|
84
94
|
:to="`/org/${item.providerOrgId}/sites`"
|
|
@@ -124,8 +134,13 @@ const tabs: Array<{ value: Tab; label: string }> = [
|
|
|
124
134
|
];
|
|
125
135
|
|
|
126
136
|
const route = useRoute();
|
|
127
|
-
const {
|
|
128
|
-
|
|
137
|
+
const {
|
|
138
|
+
getReceivedInvitations,
|
|
139
|
+
getInvitation,
|
|
140
|
+
acceptInvitation,
|
|
141
|
+
declineInvitation,
|
|
142
|
+
linkInvitationSite,
|
|
143
|
+
} = useServiceProviderInvitation();
|
|
129
144
|
|
|
130
145
|
const tab = ref<Tab>("pending");
|
|
131
146
|
const items = ref<TReceivedServiceProviderInvitation[]>([]);
|
|
@@ -180,6 +195,9 @@ function stateLine(item: TReceivedServiceProviderInvitation) {
|
|
|
180
195
|
if (item.onThisOrg) {
|
|
181
196
|
return `Accepted${on(item.answeredAt)} — ${item.siteName} is on your Sites list as a site you serve for ${item.orgName}.`;
|
|
182
197
|
}
|
|
198
|
+
if (item.needsSiteLink) {
|
|
199
|
+
return "This invitation was accepted, but the site wasn't added to your Sites. Add it now.";
|
|
200
|
+
}
|
|
183
201
|
if (!item.providerOrgId || item.providerOrgId === props.org) {
|
|
184
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.`;
|
|
185
203
|
}
|
|
@@ -228,6 +246,20 @@ async function answer(item: TReceivedServiceProviderInvitation, action: "accept"
|
|
|
228
246
|
}
|
|
229
247
|
}
|
|
230
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
|
+
|
|
231
263
|
// Opened from the bell: start on the tab the linked invitation is in.
|
|
232
264
|
if (linked.value) {
|
|
233
265
|
try {
|
|
@@ -48,6 +48,8 @@ export type TReceivedServiceProviderInvitation = {
|
|
|
48
48
|
providerOrgName?: string;
|
|
49
49
|
/** Accepted AND on the Sites list of the company on screen. */
|
|
50
50
|
onThisOrg?: boolean;
|
|
51
|
+
/** Accepted, but the site is on no company's Sites list: offer to add it. */
|
|
52
|
+
needsSiteLink?: boolean;
|
|
51
53
|
};
|
|
52
54
|
|
|
53
55
|
/** The words on screen. Kept beside the server's own list on purpose — a
|
|
@@ -171,6 +173,17 @@ export default function useServiceProviderInvitation() {
|
|
|
171
173
|
});
|
|
172
174
|
}
|
|
173
175
|
|
|
176
|
+
/**
|
|
177
|
+
* An accepted invitation whose site never reached this company's Sites
|
|
178
|
+
* list: add it now. Safe to repeat - the server adds it once.
|
|
179
|
+
*/
|
|
180
|
+
function linkInvitationSite(id: string, org: string) {
|
|
181
|
+
return useNuxtApp().$api<{ message: string }>(`${base}/${id}/link-site`, {
|
|
182
|
+
method: "PUT",
|
|
183
|
+
body: { org },
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
174
187
|
/**
|
|
175
188
|
* "Client invitations": one tab of the invitations sent to the signed-in
|
|
176
189
|
* account, plus the count in every tab. `org` is the company on screen.
|
|
@@ -200,6 +213,7 @@ export default function useServiceProviderInvitation() {
|
|
|
200
213
|
getInvitation,
|
|
201
214
|
declineInvitation,
|
|
202
215
|
acceptInvitation,
|
|
216
|
+
linkInvitationSite,
|
|
203
217
|
getReceivedInvitations,
|
|
204
218
|
};
|
|
205
219
|
}
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@7365admin1/layer-common",
|
|
3
3
|
"license": "MIT",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "4.
|
|
5
|
+
"version": "4.40.1",
|
|
6
6
|
"author": "7365admin1",
|
|
7
7
|
"main": "./nuxt.config.ts",
|
|
8
8
|
"//files": "What a consumer extending this layer actually loads. Without this npm ships the whole working tree - the changesets, the CI workflows, the render harness in tools/ and any scratch directory that happened to exist at publish time. Nuxt resolves a layer by directory, so every runtime directory below has to stay listed; adding a new top-level runtime directory means adding it here too.",
|