@7365admin1/layer-common 4.82.1-staging.474 → 4.82.1-staging.475
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.
|
@@ -149,6 +149,9 @@ const props = defineProps({
|
|
|
149
149
|
});
|
|
150
150
|
|
|
151
151
|
const movedFromEquipment = useRoute().query.moved === "equipment";
|
|
152
|
+
// A notification link (`?storeId=&tab=`, NotificationBell): open that store on that tab, once.
|
|
153
|
+
const linkQuery = useRoute().query;
|
|
154
|
+
const linked = { storeId: String(linkQuery.storeId ?? ""), tab: String(linkQuery.tab ?? "") };
|
|
152
155
|
|
|
153
156
|
const { getStores, createStore: apiCreateStore, getRequests, getReport } = useInventory();
|
|
154
157
|
const { getSites } = useSite();
|
|
@@ -227,7 +230,10 @@ async function loadStores() {
|
|
|
227
230
|
loadError.value = "";
|
|
228
231
|
try {
|
|
229
232
|
stores.value = (await getStores(props.siteId)) ?? [];
|
|
230
|
-
|
|
233
|
+
const fromLink = stores.value.find((s) => s._id === linked.storeId);
|
|
234
|
+
linked.storeId = "";
|
|
235
|
+
if (fromLink) storeId.value = fromLink._id;
|
|
236
|
+
else if (!stores.value.some((s) => s._id === storeId.value)) {
|
|
231
237
|
// A service provider working at a property store sees it first.
|
|
232
238
|
storeId.value = stores.value[0]?._id ?? "";
|
|
233
239
|
}
|
|
@@ -295,7 +301,10 @@ async function createStore() {
|
|
|
295
301
|
}
|
|
296
302
|
|
|
297
303
|
watch(storeId, () => {
|
|
298
|
-
tab
|
|
304
|
+
// The linked tab only when this person has it (tabs are hidden, never greyed).
|
|
305
|
+
const wanted = linked.tab;
|
|
306
|
+
linked.tab = "";
|
|
307
|
+
tab.value = wanted && tabs.value.some((t) => t.value === wanted) ? wanted : "stock";
|
|
299
308
|
refreshCounts();
|
|
300
309
|
probeCompanyUsage();
|
|
301
310
|
});
|
|
@@ -127,6 +127,13 @@
|
|
|
127
127
|
<v-btn variant="text" class="text-none" @click="viewFromToast">View</v-btn>
|
|
128
128
|
</template>
|
|
129
129
|
</v-snackbar>
|
|
130
|
+
|
|
131
|
+
<v-snackbar v-model="nowhere.show" location="top right" :timeout="6000" color="surface">
|
|
132
|
+
<div class="d-flex align-center ga-3">
|
|
133
|
+
<v-icon icon="mdi-information-outline" color="primary" />
|
|
134
|
+
<div class="text-body-2">{{ nowhere.text }}</div>
|
|
135
|
+
</div>
|
|
136
|
+
</v-snackbar>
|
|
130
137
|
</div>
|
|
131
138
|
</template>
|
|
132
139
|
|
|
@@ -137,7 +144,7 @@ import {
|
|
|
137
144
|
timeAgo,
|
|
138
145
|
type TInboxItem,
|
|
139
146
|
} from "../utils/notification-inbox";
|
|
140
|
-
import { appUrlsFrom, chooseNavigation, notificationTarget } from "../utils/notification-target";
|
|
147
|
+
import { appUrlsFrom, chooseNavigation, noTargetMessage, notificationTarget } from "../utils/notification-target";
|
|
141
148
|
|
|
142
149
|
/**
|
|
143
150
|
* The top-bar notification feed for the web, in every signed-in app.
|
|
@@ -180,6 +187,8 @@ const markingAll = ref(false);
|
|
|
180
187
|
const items = ref<Array<Record<string, any>>>([]);
|
|
181
188
|
const unread = ref(0);
|
|
182
189
|
const toast = reactive({ show: false, title: "", body: "" });
|
|
190
|
+
/** Said when a clicked row has no page to open - a click is never a silent nothing. */
|
|
191
|
+
const nowhere = reactive({ show: false, text: "" });
|
|
183
192
|
|
|
184
193
|
const userId = computed(() => String(currentUser.value?._id ?? ""));
|
|
185
194
|
const badgeLabel = computed(() => badgeText(unread.value));
|
|
@@ -341,7 +350,10 @@ async function goToSubject(item: Record<string, any>) {
|
|
|
341
350
|
hasPage: hasLocalPage,
|
|
342
351
|
appUrls: appUrls(),
|
|
343
352
|
});
|
|
344
|
-
if (!nav)
|
|
353
|
+
if (!nav) {
|
|
354
|
+
Object.assign(nowhere, { show: true, text: noTargetMessage(item as any) });
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
345
357
|
|
|
346
358
|
open.value = false;
|
|
347
359
|
if (nav.type === "href") {
|
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.82.1-staging.
|
|
5
|
+
"version": "4.82.1-staging.475",
|
|
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.",
|
|
@@ -45,7 +45,7 @@ export type TTargetRow = {
|
|
|
45
45
|
* (core `withNotificationKind`); `site` stands in for `siteId` on rows kept
|
|
46
46
|
* unscoped on purpose. Older rows have neither and are read from `module`.
|
|
47
47
|
*/
|
|
48
|
-
params?: { id?: string; org?: string; kind?: string; site?: string } | null;
|
|
48
|
+
params?: { id?: string; org?: string; kind?: string; site?: string; storeId?: string } | null;
|
|
49
49
|
};
|
|
50
50
|
|
|
51
51
|
/** What the surrounding app knows that the stored row does not. */
|
|
@@ -127,9 +127,26 @@ export function notificationKind(item: TTargetRow | null | undefined): string {
|
|
|
127
127
|
return "";
|
|
128
128
|
}
|
|
129
129
|
if (module === "other") {
|
|
130
|
-
|
|
130
|
+
if (/(accepted|declined) your invitation$/i.test(title)) return "serviceProviderInviteAnswer";
|
|
131
|
+
// Inventory alerts sent before core had an inventory module were stored as "other".
|
|
132
|
+
return /request waiting|request (was )?(approved|rejected)|low (on )?stock|stock handed to you|hand-over (confirmed|declined)/i.test(title)
|
|
133
|
+
? "inventory"
|
|
134
|
+
: "";
|
|
131
135
|
}
|
|
132
|
-
|
|
136
|
+
// Older rows spelled the work order module with a hyphen.
|
|
137
|
+
return module === "work-order" ? "workOrder" : module;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Which inventory tab an alert is about. Alerts carry the store id but no tab,
|
|
142
|
+
* so the tab follows the alert's fixed title (API-core inventory-notify.service)
|
|
143
|
+
* - the same rule the mobile apps use. Values are the web tabs' own names.
|
|
144
|
+
*/
|
|
145
|
+
export function inventoryTabForAlert(title: string): "requests" | "stock" | "mine" | "" {
|
|
146
|
+
if (/stock handed to you|hand-over (confirmed|declined)/i.test(title)) return "mine";
|
|
147
|
+
if (/request waiting|request (was )?(approved|rejected)/i.test(title)) return "requests";
|
|
148
|
+
if (/low (on )?stock/i.test(title)) return "stock";
|
|
149
|
+
return "";
|
|
133
150
|
}
|
|
134
151
|
|
|
135
152
|
/**
|
|
@@ -182,6 +199,8 @@ const SITE_PAGES: Record<string, TSitePage[]> = {
|
|
|
182
199
|
{ apps: ["security"], page: () => "virtual-patrol/cctv" },
|
|
183
200
|
{ apps: ["property-management"], page: () => "cctv" },
|
|
184
201
|
],
|
|
202
|
+
// A robot at the site raised an alarm (core safety alert, category robotAlert).
|
|
203
|
+
robotAlert: [{ apps: ["security", "property-management", "hygiene"], page: () => "robot-management/alerts" }],
|
|
185
204
|
// hygiene has its Service Providers page too since 2026-09-28
|
|
186
205
|
serviceProviderInviteAnswer: [{ apps: SERVICE_APPS, page: () => "service-providers" }],
|
|
187
206
|
};
|
|
@@ -214,7 +233,12 @@ function sitePageTarget(
|
|
|
214
233
|
const list = apps === "any" ? [home, ...SERVICE_APPS.filter((a) => a !== home)] : apps;
|
|
215
234
|
for (const app of list) candidates.push({ app, path: base + page(id) });
|
|
216
235
|
}
|
|
217
|
-
return { candidates };
|
|
236
|
+
if (kind !== "inventory") return { candidates };
|
|
237
|
+
// Open the alert's own store on the tab it is about (InventoryMain reads ?storeId=&tab=).
|
|
238
|
+
const storeId = String(item.params?.storeId ?? item.params?.id ?? "").trim();
|
|
239
|
+
const tab = inventoryTabForAlert(String(item.title ?? ""));
|
|
240
|
+
const query: Record<string, string> = { ...(storeId ? { storeId } : {}), ...(tab ? { tab } : {}) };
|
|
241
|
+
return Object.keys(query).length ? { candidates, query } : { candidates };
|
|
218
242
|
}
|
|
219
243
|
|
|
220
244
|
/** Seven365 staff pages, in the organisation app. */
|
|
@@ -294,6 +318,21 @@ export function notificationTarget(
|
|
|
294
318
|
};
|
|
295
319
|
}
|
|
296
320
|
|
|
321
|
+
/** Kinds that only the iService365 phone apps can open (residents' own screens). */
|
|
322
|
+
const PHONE_ONLY_KINDS = new Set(["prelovedMarketplace", "marketplace-order"]);
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* What the bell says when a click has nowhere to go, so a click is never a
|
|
326
|
+
* silent nothing. A link grants nothing either way: the page opened still
|
|
327
|
+
* checks the person may see it.
|
|
328
|
+
*/
|
|
329
|
+
export function noTargetMessage(item: TTargetRow | null | undefined): string {
|
|
330
|
+
if (PHONE_ONLY_KINDS.has(notificationKind(item))) {
|
|
331
|
+
return "This notification opens in the iService365 app on your phone.";
|
|
332
|
+
}
|
|
333
|
+
return "There is no page to open for this notification here. It has been marked as read.";
|
|
334
|
+
}
|
|
335
|
+
|
|
297
336
|
/**
|
|
298
337
|
* The full URL for a target whose page lives in another web app, or "" when
|
|
299
338
|
* there is none to build (no path, or that app's URL is not configured).
|