@7365admin1/layer-common 4.65.0 → 4.65.2
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 +22 -4
- package/assets/css/primitives.css +13 -0
- package/components/DashboardMain.vue +14 -7
- package/components/InventoryItemDetail.vue +2 -1
- package/components/InventoryReportsTab.vue +4 -1
- package/components/InventorySettingsTab.vue +2 -5
- package/components/InventoryStockTab.vue +2 -1
- package/components/NotificationBell.vue +1 -1
- package/components/NotificationSettings.vue +1 -1
- package/components/Robot/MiniMap.vue +1 -1
- package/package.json +1 -1
- package/utils/dashboard.ts +11 -0
- package/utils/inventory-store-label.ts +27 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# @iservice365/layer-common
|
|
2
2
|
|
|
3
|
+
## 4.65.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- dbd4245: Inventory Stock Movement: the Received column labels opening stock received in the month ("incl. N opening stock").
|
|
8
|
+
|
|
9
|
+
## 4.65.1
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 5df1751: Signed-in QA sweep fixes (plain words and readable controls):
|
|
14
|
+
- Inventory Settings names every property it supplies (was "Current property" when the organisation's site list did not include it).
|
|
15
|
+
- Inventory item lines label the packing ("Code 001 · Bulb · Packing: 25") instead of a bare number.
|
|
16
|
+
- Dashboard Low Stock tile shows 0 and "Every item is above its alert level" instead of "No data to display".
|
|
17
|
+
- Dashboard activity chart y-axis shows distinct whole numbers (was "0 0 0 1 1 1"); the Customize entry is called "Activity chart" and describes what it draws.
|
|
18
|
+
- A plain on/off switch now has a visible off track in light mode.
|
|
19
|
+
- Bell panel title "Notifications"; notification settings say "iService365"; the robot mini-map no longer shows "(placeholder)".
|
|
20
|
+
|
|
3
21
|
## 4.65.0
|
|
4
22
|
|
|
5
23
|
### Minor Changes
|
|
@@ -1755,11 +1773,11 @@
|
|
|
1755
1773
|
`utils/console-tier.ts` + `composables/useConsoleTier.ts` mirror the server's
|
|
1756
1774
|
own rule from two endpoints the console already calls, unprojected:
|
|
1757
1775
|
|
|
1758
|
-
|
|
1759
|
-
|
|
1776
|
+
GET /api/members/user/:user/app/admin the Seven365 staff membership
|
|
1777
|
+
GET /api/roles/id/:role that membership's role document
|
|
1760
1778
|
|
|
1761
|
-
|
|
1762
|
-
|
|
1779
|
+
owner = member.type === "admin" && role.type === "admin" && role.default === true
|
|
1780
|
+
staff = member.type === "admin" && role.type === "admin"
|
|
1763
1781
|
|
|
1764
1782
|
`role.default` is the marker because it is the only property of a platform
|
|
1765
1783
|
staff role no API caller can set - `role.controller.ts` validates create and
|
|
@@ -939,6 +939,19 @@ select option {
|
|
|
939
939
|
background: var(--accent);
|
|
940
940
|
}
|
|
941
941
|
|
|
942
|
+
/*
|
|
943
|
+
* A plain `v-switch` (no `.app-switch`). Vuetify paints its OFF track with the
|
|
944
|
+
* theme's surface-variant, which this theme sets to near-white (251,251,248),
|
|
945
|
+
* so on a white card an off switch showed only its thumb and did not read as
|
|
946
|
+
* a switch at all (Inventory > Settings, Site Settings, Online Forms...). Give
|
|
947
|
+
* the off track a visible tint in both themes; when ON, Vuetify's `bg-*`
|
|
948
|
+
* colour class (`!important`) still wins, so the checked colour is unchanged.
|
|
949
|
+
*/
|
|
950
|
+
.v-switch:not(.app-switch) .v-switch__track {
|
|
951
|
+
background-color: rgba(var(--v-theme-on-surface), 0.26);
|
|
952
|
+
opacity: 1;
|
|
953
|
+
}
|
|
954
|
+
|
|
942
955
|
.app-switch .v-selection-control__input {
|
|
943
956
|
position: absolute;
|
|
944
957
|
inset: 0;
|
|
@@ -31,7 +31,9 @@
|
|
|
31
31
|
<span class="text-blue-grey">Balance: {{ item.qty }}</span>
|
|
32
32
|
</div>
|
|
33
33
|
</div>
|
|
34
|
-
<div v-else class="print-kpi-meta mt-2">
|
|
34
|
+
<div v-else class="print-kpi-meta mt-2">
|
|
35
|
+
{{ card.raw?.basis === "below-alert-level" ? "0 · every item is above its alert level" : "No supplies recorded" }}
|
|
36
|
+
</div>
|
|
35
37
|
</template>
|
|
36
38
|
<template v-else>
|
|
37
39
|
<div class="print-kpi-value">{{ card.value }}</div>
|
|
@@ -401,6 +403,11 @@
|
|
|
401
403
|
<span class="text-medium-emphasis">Balance: {{ item.qty }}</span>
|
|
402
404
|
</div>
|
|
403
405
|
</div>
|
|
406
|
+
<!-- Nothing low: say so with the number, not "No data" - that reads as "not set up". -->
|
|
407
|
+
<div v-else-if="card.raw?.basis === 'below-alert-level'" class="d-flex flex-column">
|
|
408
|
+
<span class="text-h5 font-weight-bold text-high-emphasis">0</span>
|
|
409
|
+
<span class="text-body-2 text-medium-emphasis">Every item is above its alert level</span>
|
|
410
|
+
</div>
|
|
404
411
|
<DashboardEmptyState v-else compact />
|
|
405
412
|
</template>
|
|
406
413
|
</AppKpiTile>
|
|
@@ -1230,7 +1237,7 @@ import type { CookieOptions } from "#app";
|
|
|
1230
1237
|
// Explicitly, not by auto-import: every other component in this layer reaches
|
|
1231
1238
|
// its utils by relative path (`StatusChip.vue`, `CameraWall.vue`,
|
|
1232
1239
|
// `plugins/vuetify.ts`), and a bare call resolved to nothing when driven.
|
|
1233
|
-
import { buildWorkOrderStatus } from "../utils/dashboard";
|
|
1240
|
+
import { buildWorkOrderStatus, chartTopValue } from "../utils/dashboard";
|
|
1234
1241
|
import { permissionsDenied } from "../utils/module-routes";
|
|
1235
1242
|
import { grantsPermission } from "../utils/permission-spellings";
|
|
1236
1243
|
import { workerTerm, workerTitle } from "../utils/worker-terms";
|
|
@@ -2155,8 +2162,8 @@ const customizeWidgets = computed(() => {
|
|
|
2155
2162
|
},
|
|
2156
2163
|
{
|
|
2157
2164
|
key: "activity",
|
|
2158
|
-
title: "
|
|
2159
|
-
description: "
|
|
2165
|
+
title: "Activity chart",
|
|
2166
|
+
description: "Work orders opened and closed over the chosen period.",
|
|
2160
2167
|
},
|
|
2161
2168
|
{
|
|
2162
2169
|
key: "upcomingEvents",
|
|
@@ -2217,8 +2224,8 @@ const customizeWidgets = computed(() => {
|
|
|
2217
2224
|
},
|
|
2218
2225
|
{
|
|
2219
2226
|
key: "activity",
|
|
2220
|
-
title: "
|
|
2221
|
-
description: "
|
|
2227
|
+
title: "Activity chart",
|
|
2228
|
+
description: "Work orders opened and closed over the chosen period.",
|
|
2222
2229
|
},
|
|
2223
2230
|
{
|
|
2224
2231
|
key: "activePatrol",
|
|
@@ -2714,7 +2721,7 @@ const secondarySeries = computed(() =>
|
|
|
2714
2721
|
const primaryLegendLabel = computed(() => activityChart.value.primaryLabel);
|
|
2715
2722
|
const secondaryLegendLabel = computed(() => activityChart.value.secondaryLabel);
|
|
2716
2723
|
const chartMax = computed(() =>
|
|
2717
|
-
|
|
2724
|
+
chartTopValue([...primarySeries.value, ...secondarySeries.value])
|
|
2718
2725
|
);
|
|
2719
2726
|
const hasChartData = computed(
|
|
2720
2727
|
() => primarySeries.value.some(Boolean) || secondarySeries.value.some(Boolean)
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
<div class="flex-grow-1">
|
|
13
13
|
<div class="text-h5 mb-1" :class="item.lowStock ? 'text-error' : ''">{{ item.onHand }} {{ item.unit }} in store</div>
|
|
14
14
|
<div class="text-body-2 text-medium-emphasis">
|
|
15
|
-
{{ [item
|
|
15
|
+
{{ [inventoryItemMeta(item), item.reorderLevel ? `Alert at ${item.reorderLevel}` : "", item.unitCost !== undefined ? `$${Number(item.unitCost).toFixed(2)} each` : ""].filter(Boolean).join(" · ") }}
|
|
16
16
|
</div>
|
|
17
17
|
<div v-if="item.description" class="text-body-2 mt-2">{{ item.description }}</div>
|
|
18
18
|
</div>
|
|
@@ -77,6 +77,7 @@
|
|
|
77
77
|
import useInventory from "../composables/useInventory";
|
|
78
78
|
import useFile from "../composables/useFile";
|
|
79
79
|
import useUtils from "../composables/useUtils";
|
|
80
|
+
import { inventoryItemMeta } from "../utils/inventory-store-label";
|
|
80
81
|
|
|
81
82
|
const show = defineModel<boolean>({ default: false });
|
|
82
83
|
const props = defineProps({
|
|
@@ -112,6 +112,9 @@
|
|
|
112
112
|
<template #item.locationType="{ value }">{{ value === "tenant_unit" ? "Tenant unit" : "Common area" }}</template>
|
|
113
113
|
<template #item.unitCost="{ value }">{{ money(value) }}</template>
|
|
114
114
|
<template #item.totalCost="{ value }">{{ money(value) }}</template>
|
|
115
|
+
<template #item.received="{ item }">
|
|
116
|
+
{{ item.received }}<span v-if="item.receivedOpening" class="d-block text-caption text-medium-emphasis">incl. {{ item.receivedOpening }} opening stock</span>
|
|
117
|
+
</template>
|
|
115
118
|
<template #item.receivedCost="{ value }">{{ money(value) }}</template>
|
|
116
119
|
<template #item.usedCost="{ value }">{{ money(value) }}</template>
|
|
117
120
|
<template #item.siteUsedCost="{ value }">{{ money(value) }}</template>
|
|
@@ -181,7 +184,7 @@ const barWidth = (v: number) => {
|
|
|
181
184
|
};
|
|
182
185
|
|
|
183
186
|
const help: Record<string, string> = {
|
|
184
|
-
movement: "For each item: stock at the start of the month, what came in, what was issued and returned, count differences, and stock at the end.",
|
|
187
|
+
movement: "For each item: stock at the start of the month, what came in (deliveries and opening stock), what was issued and returned, count differences, and stock at the end.",
|
|
185
188
|
usage: "Every recorded usage: where it was used, for which tenant, by whom, and the cost. Use this for tenant billing. Tick records and mark them checked once verified.",
|
|
186
189
|
technicians: "For each person: stock they held at the start, took, used, returned, and hold at the end of the month.",
|
|
187
190
|
purchases: "Every delivery received, and opening stock entered with new items (Type: Opening stock), with totals by month and by supplier.",
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
<script setup lang="ts">
|
|
40
40
|
import useInventory from "../composables/useInventory";
|
|
41
41
|
import useUtils from "../composables/useUtils";
|
|
42
|
+
import { inventorySiteOptions } from "../utils/inventory-store-label";
|
|
42
43
|
|
|
43
44
|
const props = defineProps({
|
|
44
45
|
store: { type: Object as PropType<TInventoryStore>, required: true },
|
|
@@ -57,11 +58,7 @@ const sites = ref<string[]>([]);
|
|
|
57
58
|
const showInResidentApp = ref(false);
|
|
58
59
|
const showCostInResidentApp = ref(false);
|
|
59
60
|
|
|
60
|
-
const siteItems = computed(() =>
|
|
61
|
-
const known = new Map(props.orgSites.map((s) => [s._id, s.name]));
|
|
62
|
-
for (const id of props.store.sites) if (!known.has(id)) known.set(id, "Current property");
|
|
63
|
-
return [...known].map(([_id, name]) => ({ _id, name }));
|
|
64
|
-
});
|
|
61
|
+
const siteItems = computed(() => inventorySiteOptions(props.orgSites, props.store));
|
|
65
62
|
|
|
66
63
|
function reset() {
|
|
67
64
|
name.value = props.store.name;
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
</v-avatar>
|
|
68
68
|
<div>
|
|
69
69
|
<div class="font-weight-medium">{{ item.name }}</div>
|
|
70
|
-
<div class="text-caption text-medium-emphasis">{{
|
|
70
|
+
<div class="text-caption text-medium-emphasis">{{ inventoryItemMeta(item) }}</div>
|
|
71
71
|
</div>
|
|
72
72
|
</div>
|
|
73
73
|
</template>
|
|
@@ -102,6 +102,7 @@
|
|
|
102
102
|
import useInventory from "../composables/useInventory";
|
|
103
103
|
import useFile from "../composables/useFile";
|
|
104
104
|
import useUtils from "../composables/useUtils";
|
|
105
|
+
import { inventoryItemMeta } from "../utils/inventory-store-label";
|
|
105
106
|
|
|
106
107
|
const props = defineProps({
|
|
107
108
|
store: { type: Object as PropType<TInventoryStore>, required: true },
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
<v-col cols="12" md="8" class="screen-hint intro-copy">
|
|
10
10
|
Choose what you want to hear about, and how. These settings are yours
|
|
11
|
-
alone and apply everywhere you use
|
|
11
|
+
alone and apply everywhere you use iService365 — on this site and on your
|
|
12
12
|
phone.
|
|
13
13
|
</v-col>
|
|
14
14
|
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
</RobotFloorPlan>
|
|
50
50
|
|
|
51
51
|
<div class="hero-mini__foot">
|
|
52
|
-
<span>{{ shownMap ? `Robot map · ${shownMap.label}` : "
|
|
52
|
+
<span>{{ shownMap ? `Robot map · ${shownMap.label}` : "Sample layout, not this building's map" }}</span>
|
|
53
53
|
<span v-if="type === 'security'"><span class="hero-mini__legend" aria-hidden="true" />Patrol routes</span>
|
|
54
54
|
<span v-if="showRoutes && hasRoutes"><span class="hero-mini__legend hero-mini__legend--lane" aria-hidden="true" />Robot lanes</span>
|
|
55
55
|
<span v-if="floor !== null" class="hero-mini__count">{{ robots.length }} on {{ floorName(floor) }}</span>
|
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.65.
|
|
5
|
+
"version": "4.65.2",
|
|
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.",
|
package/utils/dashboard.ts
CHANGED
|
@@ -51,3 +51,14 @@ export function buildWorkOrderStatus(
|
|
|
51
51
|
{ label: "Pending", value: pending, percent: (pending / max) * 100, tone: "warn" },
|
|
52
52
|
];
|
|
53
53
|
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* The activity chart's top value. The six y-axis labels are max x 0, .2 ... 1
|
|
57
|
+
* rounded, so a raw max of 1 printed "0 0 0 1 1 1" - the same number on four
|
|
58
|
+
* lines. Rounding the top up to a multiple of 5 makes every label a distinct
|
|
59
|
+
* whole number (0 1 2 3 4 5, 0 2 4 6 8 10, ...).
|
|
60
|
+
*/
|
|
61
|
+
export function chartTopValue(values: number[]) {
|
|
62
|
+
const max = Math.max(0, ...values.filter((v) => Number.isFinite(v)));
|
|
63
|
+
return Math.max(5, Math.ceil(max / 5) * 5);
|
|
64
|
+
}
|
|
@@ -18,3 +18,30 @@ export function inventoryStoreLabel(store: TStoreLike, all: TStoreLike[]) {
|
|
|
18
18
|
const sameKind = all.filter((s) => s.ownerType === store.ownerType).length;
|
|
19
19
|
return sameKind > 1 && store.orgName ? `${kind} · ${store.orgName}` : kind;
|
|
20
20
|
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The properties a store can be set to supply, by name. The organisation's own
|
|
24
|
+
* site list does not always contain the store's current property (a manager
|
|
25
|
+
* can run a store at a site another organisation owns), so the store's own
|
|
26
|
+
* resolved names fill the gap - a chip must never read "Current property"
|
|
27
|
+
* when the store already knows it is Seventh Condominium.
|
|
28
|
+
*/
|
|
29
|
+
export function inventorySiteOptions(
|
|
30
|
+
orgSites: { _id: string; name: string }[],
|
|
31
|
+
store: { sites: string[]; siteNames?: { _id: string; name?: string }[] },
|
|
32
|
+
) {
|
|
33
|
+
const known = new Map(orgSites.map((s) => [s._id, s.name]));
|
|
34
|
+
for (const id of store.sites) {
|
|
35
|
+
if (!known.has(id)) known.set(id, store.siteNames?.find((s) => s._id === id)?.name || "Another property");
|
|
36
|
+
}
|
|
37
|
+
return [...known].map(([_id, name]) => ({ _id, name }));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
type TItemLike = { code?: string; category?: string; packing?: string };
|
|
41
|
+
|
|
42
|
+
/** "Code 001 · Bulb · Packing: 25" - each figure says what it is, never a bare number. */
|
|
43
|
+
export function inventoryItemMeta(item: TItemLike) {
|
|
44
|
+
return [item.code && `Code ${item.code}`, item.category, item.packing && `Packing: ${item.packing}`]
|
|
45
|
+
.filter(Boolean)
|
|
46
|
+
.join(" · ");
|
|
47
|
+
}
|