@7365admin1/layer-common 3.2.2-staging.147 → 3.2.2-staging.149

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.
@@ -10,12 +10,17 @@
10
10
  <v-row no-gutters>
11
11
  <v-col v-if="value || readOnly" cols="12">
12
12
  <v-row no-gutters>
13
+ <!-- The strip is one control split in two: the file name, and the
14
+ cross that clears it. `rounded=""` on the read-only card was
15
+ not "no radius" - Vuetify's `useRounded` treats an empty string
16
+ as `true` and adds `.rounded`, i.e. its own 4px. The design's
17
+ radius is stated here instead, on both ends. -->
13
18
  <v-card
14
19
  variant="outlined"
15
20
  border="thin"
16
- class="px-4 text-caption font-weight-medium"
17
- :rounded="readOnly ? '' : '0 s'"
18
- height="36"
21
+ class="btn-input-file__name"
22
+ :class="{ 'btn-input-file__name--solo': readOnly }"
23
+ height="40"
19
24
  @click="emit('click:view')"
20
25
  >
21
26
  <v-row no-gutters class="fill-height" align="center">
@@ -26,24 +31,28 @@
26
31
  v-if="!readOnly"
27
32
  variant="outlined"
28
33
  border="s-0 thin"
29
- rounded="0 e"
30
- class="px-4 d-inline"
31
- height="36"
34
+ class="btn-input-file__clear"
35
+ height="40"
32
36
  @click="emit('click:close')"
33
37
  >
34
38
  <v-row no-gutters class="fill-height" align="center">
35
- <v-icon size="small"> mdi-close </v-icon>
39
+ <v-icon size="18"> mdi-close </v-icon>
36
40
  </v-row>
37
41
  </v-card>
38
42
  </v-row>
39
43
  </v-col>
40
44
 
41
45
  <v-col v-else cols="12">
46
+ <!-- This arm is the `v-else` of `v-if="value || readOnly"`, so
47
+ `value` is always falsy by the time it renders: the old
48
+ `:prepend-icon="value ? '' : ..."` had an arm that could never be
49
+ taken, and the icon is unconditional. `screen-btn-ghost` is the
50
+ design's 40px bordered button while keeping Vuetify's `loading`
51
+ spinner, which the upload needs and `AppButton` does not have. -->
42
52
  <v-btn
43
- variant="outlined"
44
- border="thin"
45
- class="text-none"
46
- :prepend-icon="value ? '' : 'mdi-tray-arrow-up'"
53
+ variant="flat"
54
+ class="text-none screen-btn-ghost"
55
+ prepend-icon="mdi-tray-arrow-up"
47
56
  :disabled="loading"
48
57
  :loading="loading"
49
58
  @click="selectAttachment"
@@ -137,3 +146,36 @@ const errorMessage = computed(() => {
137
146
  return null;
138
147
  });
139
148
  </script>
149
+
150
+ <style scoped>
151
+ /* The design's control box on the attachment strip: 40px, `--r-inner` on the
152
+ outer corners only (the two cards butt together), a `--line` edge and the
153
+ 13.5px/500 cell type - not Vuetify's `text-caption` scale and 4px radius.
154
+ `:deep` because the box being styled is the `v-card` root Vuetify renders. */
155
+ .btn-input-file__name,
156
+ .btn-input-file__clear {
157
+ font-family: var(--font-sans);
158
+ font-size: var(--fs-cell);
159
+ font-weight: var(--fw-cell);
160
+ color: var(--text2);
161
+ border-color: var(--border);
162
+ }
163
+
164
+ .btn-input-file__name {
165
+ padding-inline: 14px;
166
+ border-start-start-radius: var(--r-inner);
167
+ border-end-start-radius: var(--r-inner);
168
+ }
169
+
170
+ /* Read-only: there is no cross beside it, so it is a single rounded box. */
171
+ .btn-input-file__name--solo {
172
+ border-radius: var(--r-inner);
173
+ }
174
+
175
+ .btn-input-file__clear {
176
+ display: inline;
177
+ padding-inline: 12px;
178
+ border-start-end-radius: var(--r-inner);
179
+ border-end-end-radius: var(--r-inner);
180
+ }
181
+ </style>
@@ -1683,27 +1683,52 @@ const finalDashboardData = computed<Record<string, any>>(() => {
1683
1683
 
1684
1684
  // ─── Card Value Configs ───────────────────────────────────────────────────────
1685
1685
 
1686
+ /**
1687
+ * THE KPI TILE PALETTE.
1688
+ *
1689
+ * Was eleven hand-picked hexes restated in four places and blind to the theme:
1690
+ * the SAME orange glyph on the SAME pink tint in light and in dark, because a
1691
+ * literal cannot follow a theme. Its worst pairing measured 1.36:1 and the one
1692
+ * that actually shipped measured 1.74:1 - the lowest readings in the package,
1693
+ * on the first screen every account type opens.
1694
+ *
1695
+ * The handoff names no colour for a KPI tile. It DOES name four status tones
1696
+ * and their soft fills, and the hexes that were here were already those four
1697
+ * hues (`#1d5f99` blue, `#b73535` red, `#d28722` amber, `#23824f` green). So
1698
+ * each pair is re-pointed at the token it was imitating rather than given a
1699
+ * new colour. That also makes each tile the design's own chip pairing, which
1700
+ * is the ruling this project already follows.
1701
+ */
1702
+ const KPI_TONES = {
1703
+ info: { color: "var(--info)", tint: "var(--info-bg)" },
1704
+ err: { color: "var(--err)", tint: "var(--err-bg)" },
1705
+ ok: { color: "var(--ok)", tint: "var(--ok-bg)" },
1706
+ warn: { color: "var(--warn)", tint: "var(--warn-bg)" },
1707
+ } as const;
1708
+
1686
1709
  const defaultCardValues: TDashboardCardValue[] = [
1687
1710
  {
1688
1711
  key: "openWorkOrder",
1689
1712
  periodKey: "period",
1690
1713
  title: "Open Work Order",
1691
1714
  icon: "mdi-briefcase",
1692
- color: "#0b2f47",
1715
+ /** The same tile is `#1d5f99` blue in the other two branches - info. */
1716
+ color: KPI_TONES.info.color,
1693
1717
  },
1694
1718
  {
1695
1719
  key: "taskCompleted",
1696
1720
  periodKey: "period",
1697
1721
  title: "Task Completed",
1698
1722
  icon: "mdi-clipboard-check-outline",
1699
- color: "#f4a340",
1723
+ /** `utils/status.ts`: "completed" is `ok`. This was the 1.74:1 orange. */
1724
+ color: KPI_TONES.ok.color,
1700
1725
  },
1701
1726
  {
1702
1727
  key: "supplyAlert",
1703
1728
  periodKey: "period",
1704
1729
  title: "Supply Alert",
1705
1730
  icon: "mdi-alert",
1706
- color: "#d32f2f",
1731
+ color: KPI_TONES.err.color,
1707
1732
  },
1708
1733
  ];
1709
1734
 
@@ -1713,28 +1738,28 @@ const propertyCardValues: TDashboardCardValue[] = [
1713
1738
  periodKey: "period",
1714
1739
  title: "Open Work Order",
1715
1740
  icon: "mdi-briefcase",
1716
- color: "#1d5f99",
1741
+ color: KPI_TONES.info.color,
1717
1742
  },
1718
1743
  {
1719
1744
  key: "incidents",
1720
1745
  periodKey: "period",
1721
1746
  title: "Open Incidents",
1722
1747
  icon: "mdi-alert",
1723
- color: "#b73535",
1748
+ color: KPI_TONES.err.color,
1724
1749
  },
1725
1750
  {
1726
1751
  key: "visitors",
1727
1752
  periodKey: "period",
1728
1753
  title: "Visitors",
1729
1754
  icon: "mdi-account-multiple",
1730
- color: "#d28722",
1755
+ color: KPI_TONES.warn.color,
1731
1756
  },
1732
1757
  {
1733
1758
  key: "facilityBookings",
1734
1759
  periodKey: "period",
1735
1760
  title: "Facility Booking",
1736
1761
  icon: "mdi-office-building",
1737
- color: "#23824f",
1762
+ color: KPI_TONES.ok.color,
1738
1763
  },
1739
1764
  ];
1740
1765
 
@@ -1744,28 +1769,28 @@ const securityCardValues: TDashboardCardValue[] = [
1744
1769
  periodKey: "period",
1745
1770
  title: "Open Work Order",
1746
1771
  icon: "mdi-briefcase",
1747
- color: "#1d5f99",
1772
+ color: KPI_TONES.info.color,
1748
1773
  },
1749
1774
  {
1750
1775
  key: "incidents",
1751
1776
  periodKey: "period",
1752
1777
  title: "Open Incidents",
1753
1778
  icon: "mdi-alert",
1754
- color: "#b73535",
1779
+ color: KPI_TONES.err.color,
1755
1780
  },
1756
1781
  {
1757
1782
  key: "visitors",
1758
1783
  periodKey: "period",
1759
1784
  title: "Visitors",
1760
1785
  icon: "mdi-account-multiple",
1761
- color: "#d28722",
1786
+ color: KPI_TONES.warn.color,
1762
1787
  },
1763
1788
  {
1764
1789
  key: "patrolCompliance",
1765
1790
  periodKey: "period",
1766
1791
  title: "Patrol Compliance",
1767
1792
  icon: "mdi-office-building",
1768
- color: "#23824f",
1793
+ color: KPI_TONES.ok.color,
1769
1794
  },
1770
1795
  ];
1771
1796
 
@@ -2094,8 +2119,8 @@ function getPropertyKpiCards() {
2094
2119
  key: "workOrders",
2095
2120
  title: "Open Work Order",
2096
2121
  icon: "mdi-briefcase",
2097
- color: "#1d5f99",
2098
- tint: "#d7ecff",
2122
+ color: KPI_TONES.info.color,
2123
+ tint: KPI_TONES.info.tint,
2099
2124
  value: formatNumber(toNumber(workOrderMetric?.count)),
2100
2125
  percentage: toNumber(workOrderMetric?.percentage),
2101
2126
  meta: getMetricMeta(workOrderMetric),
@@ -2106,8 +2131,8 @@ function getPropertyKpiCards() {
2106
2131
  key: "incidents",
2107
2132
  title: "Open Incidents",
2108
2133
  icon: "mdi-alert",
2109
- color: "#b73535",
2110
- tint: "#ffdede",
2134
+ color: KPI_TONES.err.color,
2135
+ tint: KPI_TONES.err.tint,
2111
2136
  value: formatNumber(toNumber(incidentMetric?.count)),
2112
2137
  percentage: toNumber(incidentMetric?.percentage),
2113
2138
  meta: getMetricMeta(incidentMetric),
@@ -2118,8 +2143,8 @@ function getPropertyKpiCards() {
2118
2143
  key: "visitors",
2119
2144
  title: "Visitors",
2120
2145
  icon: "mdi-account-multiple",
2121
- color: "#d28722",
2122
- tint: "#ffe4bd",
2146
+ color: KPI_TONES.warn.color,
2147
+ tint: KPI_TONES.warn.tint,
2123
2148
  value: formatNumber(toNumber(visitorMetric?.count)),
2124
2149
  percentage: toNumber(visitorMetric?.percentage),
2125
2150
  meta: getMetricMeta(visitorMetric),
@@ -2137,8 +2162,8 @@ function getPropertyKpiCards() {
2137
2162
  key: "facilityBookings",
2138
2163
  title: "Facility Booking",
2139
2164
  icon: "mdi-office-building",
2140
- color: "#23824f",
2141
- tint: "#c8f3da",
2165
+ color: KPI_TONES.ok.color,
2166
+ tint: KPI_TONES.ok.tint,
2142
2167
  value: formatNumber(toNumber(facilityMetric?.count)),
2143
2168
  percentage: toNumber(facilityMetric?.percentage),
2144
2169
  meta: getMetricMeta(facilityMetric),
@@ -2172,8 +2197,8 @@ function getSecurityKpiCards() {
2172
2197
  key: "workOrders",
2173
2198
  title: "Open Work Order",
2174
2199
  icon: "mdi-briefcase",
2175
- color: "#1d5f99",
2176
- tint: "#d7ecff",
2200
+ color: KPI_TONES.info.color,
2201
+ tint: KPI_TONES.info.tint,
2177
2202
  value: formatNumber(toNumber(workOrderMetric?.count)),
2178
2203
  percentage: toNumber(workOrderMetric?.percentage),
2179
2204
  meta: getMetricMeta(workOrderMetric),
@@ -2184,8 +2209,8 @@ function getSecurityKpiCards() {
2184
2209
  key: "incidents",
2185
2210
  title: "Open Incidents",
2186
2211
  icon: "mdi-alert",
2187
- color: "#b73535",
2188
- tint: "#ffdede",
2212
+ color: KPI_TONES.err.color,
2213
+ tint: KPI_TONES.err.tint,
2189
2214
  value: formatNumber(toNumber(incidentMetric?.count)),
2190
2215
  percentage: toNumber(incidentMetric?.percentage),
2191
2216
  meta: getMetricMeta(incidentMetric),
@@ -2196,8 +2221,8 @@ function getSecurityKpiCards() {
2196
2221
  key: "visitors",
2197
2222
  title: "Visitors",
2198
2223
  icon: "mdi-account-multiple",
2199
- color: "#d28722",
2200
- tint: "#ffe4bd",
2224
+ color: KPI_TONES.warn.color,
2225
+ tint: KPI_TONES.warn.tint,
2201
2226
  value: formatNumber(toNumber(visitorMetric?.count)),
2202
2227
  percentage: toNumber(visitorMetric?.percentage),
2203
2228
  meta: getMetricMeta(visitorMetric),
@@ -2215,8 +2240,8 @@ function getSecurityKpiCards() {
2215
2240
  key: "patrolCompliance",
2216
2241
  title: "Patrol Compliance",
2217
2242
  icon: "mdi-office-building",
2218
- color: "#23824f",
2219
- tint: "#c8f3da",
2243
+ color: KPI_TONES.ok.color,
2244
+ tint: KPI_TONES.ok.tint,
2220
2245
  value: `${toNumber(patrolMetric?.count)}%`,
2221
2246
  percentage: toNumber(patrolMetric?.percentage),
2222
2247
  meta: getMetricMeta(patrolMetric),
@@ -3607,18 +3632,33 @@ async function exportDashboard(format: "PDF" | "Word") {
3607
3632
 
3608
3633
  // ─── Card Color Helpers ───────────────────────────────────────────────────────
3609
3634
 
3635
+ /** The rotation an unstyled `cardValues` entry falls into - same four tones. */
3636
+ const KPI_ROTATION = [
3637
+ KPI_TONES.info,
3638
+ KPI_TONES.err,
3639
+ KPI_TONES.ok,
3640
+ KPI_TONES.warn,
3641
+ ] as const;
3642
+
3610
3643
  function getCardColor(index: number) {
3611
- return ["#155178", "#9f2f29", "#1f8a56", "#b96d17"][index % 4];
3644
+ return KPI_ROTATION[index % KPI_ROTATION.length].color;
3612
3645
  }
3613
3646
 
3647
+ /**
3648
+ * A tint for whatever colour the caller gave. `props.cardValues` comes from
3649
+ * eleven applications and still arrives as a raw hex in places, so the hex
3650
+ * matching stays - the token names are matched alongside it, not instead.
3651
+ */
3614
3652
  function getCardTint(color: string, index: number) {
3615
- const tints = ["#dbeeff", "#f8c6c3", "#d8f5e5", "#ffe7c2"];
3616
- if (!color) return tints[index % tints.length];
3653
+ if (!color) return KPI_ROTATION[index % KPI_ROTATION.length].tint;
3617
3654
  const n = color.toLowerCase();
3618
- if (n.includes("f44336") || n.includes("d32f2f")) return "#f8c6c3";
3619
- if (n.includes("4caf50") || n.includes("43a047")) return "#d8f5e5";
3620
- if (n.includes("ff9800") || n.includes("ffa726")) return "#ffe7c2";
3621
- return "#dbeeff";
3655
+ if (n.includes("--err") || n.includes("f44336") || n.includes("d32f2f"))
3656
+ return KPI_TONES.err.tint;
3657
+ if (n.includes("--ok") || n.includes("4caf50") || n.includes("43a047"))
3658
+ return KPI_TONES.ok.tint;
3659
+ if (n.includes("--warn") || n.includes("ff9800") || n.includes("ffa726"))
3660
+ return KPI_TONES.warn.tint;
3661
+ return KPI_TONES.info.tint;
3622
3662
  }
3623
3663
 
3624
3664
  // ─── Generic Helpers ─────────────────────────────────────────────────────────
@@ -1,6 +1,6 @@
1
1
  <template>
2
2
  <div class="d-flex flex-column">
3
- <v-text-field v-bind="$attrs" ref="datePickerRef" :model-value="dateFormattedReadOnly" autocomplete="off"
3
+ <v-text-field v-bind="$attrs" class="filter-field" ref="datePickerRef" :model-value="dateFormattedReadOnly" autocomplete="off"
4
4
  :placeholder="placeholder" :rules="rules" style="z-index: 10" @click="openDatePicker" @click:clear.stop="clearDate">
5
5
  <template #append-inner>
6
6
  <v-icon icon="mdi-calendar" @click.stop="openDatePicker" />
@@ -2,6 +2,7 @@
2
2
  <div class="d-flex flex-column">
3
3
  <v-text-field
4
4
  v-bind="$attrs"
5
+ class="filter-field"
5
6
  ref="dateTimePickerRef"
6
7
  :model-value="dateTimeFormattedReadOnly"
7
8
  autocomplete="off"
@@ -22,7 +22,7 @@
22
22
  <template v-else>
23
23
  <v-file-upload v-model="uploadFiles" density="compact" @update:model-value="handleUpdateValue"
24
24
  :loading="processing" :disabled="processing" :height="height" :title="title" :accept="accept"
25
- :name="`upload_images`" class="text-caption w-100" clearable :multiple="multiple">
25
+ :name="`upload_images`" class="w-100 file-v2__drop" clearable :multiple="multiple">
26
26
  <template #item="{ props: itemProps, file }">
27
27
  <v-file-upload-item v-bind="itemProps" lines="one" nav @click="handleClickItem(file)">
28
28
  <template #prepend>
@@ -30,8 +30,19 @@
30
30
  </template>
31
31
 
32
32
  <!-- delete button NOT shown in view mode -->
33
- <template #clear="{ props: clearProps }">
34
- <v-btn v-if="!viewMode" color="primary" @click.stop="handleRemove(file)"></v-btn>
33
+ <!-- This button had NO icon and NO label, so it drew as an empty
34
+ slab, and `color="primary"` filled that slab with the design's
35
+ FOREGROUND token (the link ink) rather than a fill. It is the
36
+ remove control for one attached file: a plain close glyph. -->
37
+ <template #clear>
38
+ <v-btn
39
+ v-if="!viewMode"
40
+ icon="mdi-close"
41
+ variant="text"
42
+ size="small"
43
+ aria-label="Remove file"
44
+ @click.stop="handleRemove(file)"
45
+ />
35
46
  </template>
36
47
  </v-file-upload-item>
37
48
  </template>
@@ -39,7 +50,7 @@
39
50
  </template>
40
51
 
41
52
  <v-row no-gutters class="w-100" v-if="errorMessage">
42
- <p class="text-error w-100 text-center text-subtitle-2">{{ errorMessage }}</p>
53
+ <p class="text-error w-100 text-center file-v2__error">{{ errorMessage }}</p>
43
54
  </v-row>
44
55
 
45
56
  <ImageCarousel v-model="showImageCarousel" :files="idsArray" :active-file-id="activeImageId" />
@@ -224,12 +235,34 @@ async function downloadFile(id: string, filename: string) {
224
235
 
225
236
 
226
237
  <style scoped>
238
+ /* The drop zone on the design's surface: `--r-inner`, a `--border` edge and the
239
+ 13.5px cell type, instead of Vuetify's 4px radius and `text-caption` scale.
240
+ `:deep` because every box below is rendered by `v-file-upload`. */
241
+ .file-v2__drop {
242
+ font-family: var(--font-sans);
243
+ font-size: var(--fs-cell);
244
+ border-radius: var(--r-inner);
245
+ border-color: var(--border);
246
+ }
247
+
248
+ .file-v2__drop :deep(.v-file-upload-item) {
249
+ border-radius: var(--r-inner);
250
+ }
251
+
227
252
  * :deep(.v-file-upload-title) {
228
- font-size: 1rem;
229
- font-weight: 500;
253
+ font-family: var(--font-sans);
254
+ font-size: var(--fs-card-title);
255
+ font-weight: var(--fw-card-title);
256
+ color: var(--text);
230
257
  }
231
258
 
232
259
  * :deep(.v-file-upload-items) {
233
260
  min-width: 100%;
234
261
  }
262
+
263
+ .file-v2__error {
264
+ font-family: var(--font-sans);
265
+ font-size: var(--fs-cell);
266
+ font-weight: var(--fw-cell);
267
+ }
235
268
  </style>
@@ -8,7 +8,7 @@
8
8
  item-title="dial_code"
9
9
  item-value="code"
10
10
  hide-details
11
- class="px-0"
11
+ class="px-0 filter-field"
12
12
  :density="density"
13
13
  style="max-width: 110px"
14
14
  :rules="[...props.rules]"
@@ -31,6 +31,7 @@
31
31
 
32
32
  <v-mask-input
33
33
  v-model="input"
34
+ class="filter-field"
34
35
  :mask="currentMask"
35
36
  :rules="[...props.rules, validatePhone]"
36
37
  ref="maskRef"
@@ -1,6 +1,7 @@
1
1
  <template>
2
2
  <v-text-field
3
3
  v-model.trim="model"
4
+ class="filter-field"
4
5
  :rules="rules"
5
6
  :maxlength="maxlength"
6
7
  :placeholder="placeholder"
@@ -113,6 +113,7 @@ const modifyDatePart = (
113
113
  <template>
114
114
  <v-text-field
115
115
  ref="inputRef"
116
+ class="filter-field"
116
117
  v-model="dateValue"
117
118
  placeholder="MM/DD/YYYY"
118
119
  @input="formatDate"
@@ -102,6 +102,7 @@ const restoreCursor = () => {
102
102
  ref="inputRef"
103
103
  v-model="formattedValue"
104
104
  v-bind="attrs"
105
+ class="filter-field"
105
106
  type="text"
106
107
  @keydown="handleKeyDown"
107
108
  >
@@ -2,6 +2,7 @@
2
2
  <v-text-field
3
3
  id="_password"
4
4
  v-model="password"
5
+ class="filter-field"
5
6
  placeholder="Password"
6
7
  :type="showPassword ? 'text' : 'password'"
7
8
  :append-inner-icon="showPassword ? 'mdi-eye-off' : 'mdi-eye'"
@@ -1,6 +1,7 @@
1
1
  <template>
2
2
  <v-text-field
3
3
  v-model="model"
4
+ class="filter-field"
4
5
  :rules="rules"
5
6
  :maxlength="maxlength"
6
7
  :placeholder="placeholder"
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.2-staging.147",
5
+ "version": "3.2.2-staging.149",
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.",