@7365admin1/layer-common 3.2.8-staging.204 → 3.2.8-staging.205
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/components/ClientMain.vue +45 -8
- package/package.json +1 -1
- package/utils/client-nature.test.ts +77 -0
|
@@ -101,23 +101,48 @@
|
|
|
101
101
|
<thead>
|
|
102
102
|
<tr>
|
|
103
103
|
<th>Organization Name</th>
|
|
104
|
-
<th>
|
|
104
|
+
<th>Nature</th>
|
|
105
105
|
<th>Sites</th>
|
|
106
106
|
<th>Subscription</th>
|
|
107
107
|
<th>Billing Cycle</th>
|
|
108
108
|
<th>Start Date</th>
|
|
109
109
|
<th>End Date</th>
|
|
110
|
-
|
|
110
|
+
<!--
|
|
111
|
+
NO STATUS COLUMN. The table is fetched with the status the
|
|
112
|
+
chosen tab asks for (`fetchOrgs(tab)` -> `status` -> the
|
|
113
|
+
aggregation's `$match`), so on the Active tab every row said
|
|
114
|
+
"active" and on Suspended every row said "suspended" - a
|
|
115
|
+
column that repeated the tab the reader was standing on and
|
|
116
|
+
could never say anything else. Its 138px is what pays for the
|
|
117
|
+
Nature column, and the table now fits its viewport instead of
|
|
118
|
+
scrolling sideways.
|
|
119
|
+
-->
|
|
111
120
|
<th>Actions</th>
|
|
112
121
|
</tr>
|
|
113
122
|
</thead>
|
|
114
123
|
<tbody>
|
|
115
124
|
<tr v-if="!loading && items.length === 0">
|
|
116
|
-
<td colspan="
|
|
125
|
+
<td colspan="8" class="empty">No records found.</td>
|
|
117
126
|
</tr>
|
|
118
127
|
<tr v-for="item in items" :key="item._id">
|
|
119
|
-
|
|
120
|
-
|
|
128
|
+
<!--
|
|
129
|
+
WHO THEY ARE, then WHAT KIND OF BUSINESS. The email used to
|
|
130
|
+
hold a column of its own; it identifies the same client the
|
|
131
|
+
name does, so it sits under the name and the column it was
|
|
132
|
+
using goes to the nature - which nothing on this screen said
|
|
133
|
+
before, and which is the one thing the admin app's
|
|
134
|
+
Organizations list showed that this one did not.
|
|
135
|
+
-->
|
|
136
|
+
<td class="td-name">
|
|
137
|
+
{{ item.name }}
|
|
138
|
+
<span class="td-name-sub">{{ item.email }}</span>
|
|
139
|
+
</td>
|
|
140
|
+
<!-- A blank cell reads as a broken screen, so the one case
|
|
141
|
+
with nothing to print says so. `--text2`, not `--muted`:
|
|
142
|
+
this is text to be read, and `--muted` measures 3.24:1. -->
|
|
143
|
+
<td :class="{ 'td-nature-none': !item.nature }">
|
|
144
|
+
{{ item.nature ? formatNature(item.nature) : 'Not recorded' }}
|
|
145
|
+
</td>
|
|
121
146
|
<td>
|
|
122
147
|
<span class="sites-badge">{{ item.sites }}</span>
|
|
123
148
|
</td>
|
|
@@ -161,9 +186,6 @@
|
|
|
161
186
|
</span>
|
|
162
187
|
</td>
|
|
163
188
|
</template>
|
|
164
|
-
<td>
|
|
165
|
-
<span :class="['status-chip', item.status]">{{ item.status }}</span>
|
|
166
|
-
</td>
|
|
167
189
|
<td>
|
|
168
190
|
<div class="action-wrap">
|
|
169
191
|
<button class="dots-btn" @click.stop="toggleMenu(item._id, $event)">
|
|
@@ -297,6 +319,7 @@ import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
|
|
|
297
319
|
import useOrg from '../composables/useOrg'
|
|
298
320
|
import useVerification from '../composables/useVerification'
|
|
299
321
|
import useRole from '../composables/useRole'
|
|
322
|
+
import useUtils from '../composables/useUtils'
|
|
300
323
|
import InvitationClientForm from './InvitationClientForm.vue'
|
|
301
324
|
import ClientDetailForm from './ClientDetailForm.vue'
|
|
302
325
|
import ClientSubscriptionForm from './ClientSubscriptionForm.vue'
|
|
@@ -312,6 +335,9 @@ const { getOrganizationsWithSubscription, updateStatus } = useOrg()
|
|
|
312
335
|
const { getVerifications, cancelUserInvitation } = useVerification()
|
|
313
336
|
const { getCustomerSites: getCustomerSitesByOrgId } = useCustomerSite()
|
|
314
337
|
const { getRoleById } = useRole()
|
|
338
|
+
// The same helper the admin app's Organizations list uses to print a nature,
|
|
339
|
+
// so both screens say the value the same way while both exist.
|
|
340
|
+
const { formatNature } = useUtils()
|
|
315
341
|
|
|
316
342
|
/* ── CONSTANTS ── */
|
|
317
343
|
const APP_LIST = [
|
|
@@ -947,6 +973,17 @@ tbody td { vertical-align: middle; }
|
|
|
947
973
|
/* Owner decision 8: an end date has passed and somebody has to look at it. */
|
|
948
974
|
.needs-attention { color: var(--warn); font-weight: 500; }
|
|
949
975
|
.td-name { font-weight: 500; color: var(--text); }
|
|
976
|
+
/* The email, second line of the same cell. `--text2` is the design's reading
|
|
977
|
+
token for secondary text; `--muted`, which the email used while it had its
|
|
978
|
+
own column, is a decoration weight and measures below AA as body copy. */
|
|
979
|
+
.td-nature-none { color: var(--text2); }
|
|
980
|
+
.td-name-sub {
|
|
981
|
+
display: block;
|
|
982
|
+
margin-top: 2px;
|
|
983
|
+
font-size: 12px;
|
|
984
|
+
font-weight: 400;
|
|
985
|
+
color: var(--text2);
|
|
986
|
+
}
|
|
950
987
|
.td-muted { color: var(--muted); font-size: 13px; }
|
|
951
988
|
|
|
952
989
|
.sites-badge {
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@7365admin1/layer-common",
|
|
3
3
|
"license": "MIT",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "3.2.8-staging.
|
|
5
|
+
"version": "3.2.8-staging.205",
|
|
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.",
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { test } from "node:test";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* THE CLIENT LIST'S "NATURE" COLUMN.
|
|
6
|
+
*
|
|
7
|
+
* The column prints `formatNature(org.nature)`, the same helper the admin app's
|
|
8
|
+
* Organizations list uses - so the two screens say a nature the same way while
|
|
9
|
+
* both exist, and the console keeps saying it after that screen is retired.
|
|
10
|
+
*
|
|
11
|
+
* `nature` is a REQUIRED enum on the organisation model
|
|
12
|
+
* (`iservice365-core/src/models/organization.model.ts` `allowedNatures`), so
|
|
13
|
+
* what this asserts is that every legal value comes out as something a person
|
|
14
|
+
* reads - not a raw `security_agency`.
|
|
15
|
+
*
|
|
16
|
+
* `useUtils()` is a Nuxt composable: it opens with `useState("search", ...)`,
|
|
17
|
+
* which only exists inside a Nuxt app. One stub makes the factory constructible
|
|
18
|
+
* here; `formatNature` itself touches nothing but its argument.
|
|
19
|
+
*/
|
|
20
|
+
(globalThis as any).useState = (_key: string, init: () => any) => ({
|
|
21
|
+
value: init(),
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const { formatNature } = (await import("../composables/useUtils.ts")).default();
|
|
25
|
+
|
|
26
|
+
/** `allowedNatures`, copied from the core model, in the model's own order. */
|
|
27
|
+
const ALLOWED_NATURES = [
|
|
28
|
+
"real_estate_developer",
|
|
29
|
+
"property_management_agency",
|
|
30
|
+
"security_agency",
|
|
31
|
+
"cleaning_services",
|
|
32
|
+
"mechanical_electrical_services",
|
|
33
|
+
"landscaping_services",
|
|
34
|
+
"pest_control_services",
|
|
35
|
+
"pool_maintenance_services",
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
test("every nature the organisation model allows prints as words", () => {
|
|
39
|
+
assert.deepEqual(
|
|
40
|
+
ALLOWED_NATURES.map((v) => formatNature(v)),
|
|
41
|
+
[
|
|
42
|
+
"Real Estate Developer",
|
|
43
|
+
"Property Management Agency",
|
|
44
|
+
"Security Agency",
|
|
45
|
+
"Cleaning Services",
|
|
46
|
+
"Mechanical Electrical Services",
|
|
47
|
+
"Landscaping Services",
|
|
48
|
+
"Pest Control Services",
|
|
49
|
+
"Pool Maintenance Services",
|
|
50
|
+
],
|
|
51
|
+
);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("no allowed nature is left as its stored value", () => {
|
|
55
|
+
for (const value of ALLOWED_NATURES) {
|
|
56
|
+
const label = formatNature(value);
|
|
57
|
+
assert.notEqual(label, value, `${value} was printed raw`);
|
|
58
|
+
assert.ok(!label.includes("_"), `${value} kept an underscore`);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("an organisation with no nature yields nothing, so the column says so itself", () => {
|
|
63
|
+
// The cell renders `nature ? formatNature(nature) : 'Not recorded'`. This is
|
|
64
|
+
// the half that decides the fallback is needed: the helper answers "" rather
|
|
65
|
+
// than a label, and a blank cell reads as a broken screen.
|
|
66
|
+
assert.equal(formatNature(""), "");
|
|
67
|
+
assert.equal(formatNature(undefined as unknown as string), "");
|
|
68
|
+
assert.equal(formatNature(null as unknown as string), "");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("a value the model does not list is still printed, not swallowed", () => {
|
|
72
|
+
// `customer.service.ts` creates property-owner orgs with `property_owner`,
|
|
73
|
+
// which is NOT in `allowedNatures`. Those rows appear in the Client List, and
|
|
74
|
+
// the column showing what they are is the point - so an unknown value is
|
|
75
|
+
// formatted like any other rather than blanked.
|
|
76
|
+
assert.equal(formatNature("property_owner"), "Property Owner");
|
|
77
|
+
});
|