@7365admin1/layer-common 3.2.2-staging.91 → 3.2.2-staging.93

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.
@@ -28,16 +28,17 @@ const SECURITY = [
28
28
  ].map((title) => ({ title }));
29
29
 
30
30
  /**
31
- * THE RULE THAT MATTERS. A label is decoration; the menu is the product. If
32
- * this ever fails, someone has taught the sidebar to reorder itself.
31
+ * THE RULE THAT MATTERS. Grouping may reorder the rail; it may never change
32
+ * the menu. Same objects, same count - nothing added, hidden or duplicated.
33
33
  */
34
- test("the menu comes back in the order it went in, item for item", () => {
34
+ test("every menu item comes back exactly once, and nothing else does", () => {
35
35
  const out = withSections(SECURITY);
36
36
 
37
37
  assert.equal(out.length, SECURITY.length);
38
- out.forEach((entry, i) => {
39
- assert.equal(entry.item, SECURITY[i], `position ${i}`);
40
- });
38
+ assert.deepEqual(
39
+ new Set(out.map((e) => e.item)),
40
+ new Set(SECURITY)
41
+ );
41
42
  });
42
43
 
43
44
  test("Security is labelled the way the design draws it", () => {
@@ -102,10 +103,10 @@ test("an unmapped FIRST item prints no heading rather than a blank one", () => {
102
103
 
103
104
  /**
104
105
  * The applications do not agree on order: Landscape lists Feedbacks after its
105
- * equipment modules. The heading is printed again rather than left off, so no
106
- * item ever sits under a heading that is not its own.
106
+ * equipment modules. Those two Feedbacks/Work Orders items are collected under
107
+ * ONE SERVICE DESK heading instead of printing the heading twice.
107
108
  */
108
- test("a section that comes back later is labelled again, not orphaned", () => {
109
+ test("a section that comes back later is grouped, not labelled twice", () => {
109
110
  const out = withSections([
110
111
  { title: "Feedbacks" },
111
112
  { title: "Equipment Items" },
@@ -113,8 +114,66 @@ test("a section that comes back later is labelled again, not orphaned", () => {
113
114
  ]);
114
115
 
115
116
  assert.deepEqual(
116
- out.map((e) => e.section),
117
- ["SERVICE DESK", "EQUIPMENT", "SERVICE DESK"]
117
+ out.map((e) => `${e.section}|${e.item.title}`),
118
+ [
119
+ "SERVICE DESK|Feedbacks",
120
+ "|Work Orders",
121
+ "EQUIPMENT|Equipment Items",
122
+ ]
123
+ );
124
+ });
125
+
126
+ /**
127
+ * Property Management is the screen that showed the defect: its own menu order
128
+ * printed SERVICE DESK, PROPERTY and SECURITY OPERATIONS twice each.
129
+ */
130
+ test("Property Management prints each heading exactly once", () => {
131
+ const PM = [
132
+ "Dashboard",
133
+ "Service Providers",
134
+ "Feedbacks",
135
+ "Work Orders",
136
+ "Facility Mgmt",
137
+ "Pass & Key Mgmt",
138
+ "HID Face Reader",
139
+ "Online Forms",
140
+ "NFC Patrol",
141
+ "Bulletin Board",
142
+ "Bulletin Videos",
143
+ "Visitor Mgmt",
144
+ "Document Mgmt",
145
+ "Access Mgmt",
146
+ ].map((title) => ({ title }));
147
+
148
+ const out = withSections(PM);
149
+ const headings = out.map((e) => e.section).filter(Boolean);
150
+
151
+ assert.deepEqual(headings, [
152
+ "OVERVIEW",
153
+ "SERVICE DESK",
154
+ "PROPERTY",
155
+ "SECURITY OPERATIONS",
156
+ "COMMUNITY",
157
+ ]);
158
+ assert.equal(new Set(headings).size, headings.length);
159
+ assert.deepEqual(
160
+ out.map((e) => e.item.title),
161
+ [
162
+ "Dashboard",
163
+ "Service Providers",
164
+ "Feedbacks",
165
+ "Work Orders",
166
+ "Online Forms",
167
+ "Facility Mgmt",
168
+ "Pass & Key Mgmt",
169
+ "Visitor Mgmt",
170
+ "Document Mgmt",
171
+ "Access Mgmt",
172
+ "HID Face Reader",
173
+ "NFC Patrol",
174
+ "Bulletin Board",
175
+ "Bulletin Videos",
176
+ ]
118
177
  );
119
178
  });
120
179
 
@@ -1,29 +1,33 @@
1
1
  /**
2
- * THE GROUPED SIDEBAR - section labels, without touching the menu.
2
+ * THE GROUPED SIDEBAR - one heading per section, each printed once.
3
3
  *
4
4
  * The design draws the sidebar in labelled sections (OVERVIEW, SERVICE DESK,
5
5
  * PROPERTY, SECURITY OPERATIONS, COMMUNITY, WORKFORCE, ADMINISTRATION). Each
6
6
  * application builds its own menu array in its `layouts/default.vue`, gated
7
- * item by item on permissions, and that array is the product's navigation.
7
+ * item by item on permissions.
8
8
  *
9
- * So this file adds a LABEL and nothing else:
9
+ * The applications do not agree on the order they list their modules in
10
+ * (Property Management interleaves five sections; Landscape puts Feedbacks
11
+ * after Equipment). Walking that order and printing a heading whenever the
12
+ * section changed - what this file used to do - made Property Management draw
13
+ * SERVICE DESK, PROPERTY and SECURITY OPERATIONS TWICE each. It reads as a
14
+ * bug, so the items are now GROUPED:
10
15
  *
11
- * - the order of the menu is the order the application gave us. Nothing is
12
- * moved, sorted, merged, hidden or renamed. A label is inserted when the
13
- * section CHANGES as we walk that existing order, which means the label is
14
- * always true of the items under it.
15
- * - a title this map has never heard of inherits the section above it, so a
16
- * new menu item added by any of the eleven applications appears exactly
17
- * where its author put it, under the heading it follows, rather than
18
- * disappearing or forcing a stray heading.
19
- * - the first item always gets its section printed, even if that section is
20
- * "" (nothing) - a leading blank label is simply not rendered.
16
+ * - every item is collected under its section, and each heading is printed
17
+ * exactly once, in the order the design lists the sections (which is the
18
+ * order they are declared in the map below).
19
+ * - inside a section the application's own order is preserved, so items that
20
+ * belong together stay in the sequence their author chose.
21
+ * - a title this map has never heard of inherits the section of the item
22
+ * above it, so a new menu item added by any of the eleven applications is
23
+ * grouped with its neighbours rather than disappearing or forcing a stray
24
+ * heading.
25
+ * - an item with no section at all (an unmapped FIRST item) leads the rail
26
+ * with no heading, exactly as before.
21
27
  *
22
- * A section may therefore appear twice in one sidebar. That is deliberate: the
23
- * applications do not all list their modules in the same order (Landscape puts
24
- * Feedbacks after Equipment; Property Management interleaves five sections),
25
- * and printing the heading again is honest, where suppressing it would leave a
26
- * run of items sitting under a heading that is not theirs.
28
+ * This changes what ORDER the items are drawn in and nothing else. No item is
29
+ * added, hidden, renamed, re-routed or re-gated - the array that comes out
30
+ * holds the same objects as the array that went in.
27
31
  */
28
32
 
29
33
  export const NAV_SECTIONS: Record<string, string> = {
@@ -110,19 +114,38 @@ export const NAV_SECTIONS: Record<string, string> = {
110
114
 
111
115
  export type TSectionedNavItem<T> = { item: T; section: string };
112
116
 
117
+ /** The design's section order - the order they are declared above. */
118
+ export const SECTION_ORDER: string[] = [
119
+ ...new Set(Object.values(NAV_SECTIONS)),
120
+ ];
121
+
113
122
  /**
114
- * Walks the menu IN THE ORDER GIVEN and tags each item with the section label
115
- * to print above it, or "" for no label. Same length, same order, same objects.
123
+ * Groups the menu by section and tags each item with the heading to print
124
+ * above it, or "" when the heading is already printed. Same length, same
125
+ * objects; only the order changes.
116
126
  */
117
127
  export function withSections<T extends { title?: string }>(
118
128
  items: T[]
119
129
  ): Array<TSectionedNavItem<T>> {
130
+ // An unmapped title inherits the section of the item above it.
120
131
  let current = "";
132
+ const resolved = items.map((item, index) => {
133
+ current = NAV_SECTIONS[String(item?.title ?? "")] ?? current;
134
+ return { item, section: current, index };
135
+ });
136
+
137
+ // Sectionless items (an unmapped first item) rank -1 and lead the rail.
138
+ // Inside a section the application's own order is kept.
139
+ const grouped = [...resolved].sort(
140
+ (a, b) =>
141
+ SECTION_ORDER.indexOf(a.section) - SECTION_ORDER.indexOf(b.section) ||
142
+ a.index - b.index
143
+ );
121
144
 
122
- return items.map((item, index) => {
123
- const section = NAV_SECTIONS[String(item?.title ?? "")] ?? current;
124
- const changed = index === 0 || section !== current;
125
- current = section;
126
- return { item, section: changed ? section : "" };
145
+ let printed = "";
146
+ return grouped.map(({ item, section }) => {
147
+ const label = section === printed ? "" : section;
148
+ printed = section;
149
+ return { item, section: label };
127
150
  });
128
151
  }