@7365admin1/layer-common 3.2.2-staging.82 → 3.2.2-staging.84

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.
@@ -1,31 +1,35 @@
1
+ <!--
2
+ PHASE 3 - THE MODAL.
3
+
4
+ 520px, 16px radius and the design's overlay and shadow, with the BODY as the
5
+ only thing that scrolls so the footer never leaves the screen. Same dialog,
6
+ same slots, same close behaviour, same `persistent`.
7
+
8
+ The two `theme.name.value === ...` branches are gone, and that is a fix, not
9
+ a preference: they hard-coded `bg-white` and Vuetify's `grey-darken-4`, so
10
+ the card was painted by a literal colour instead of by the theme it is in.
11
+ `--card` is that surface in each theme.
12
+ -->
1
13
  <template>
2
- <v-dialog v-model="model" max-width="600" persistent>
3
- <v-card
4
- class="px-3 d-flex flex-column"
5
- :class="`${theme.name.value === 'light' ? 'bg-white' : ''} ${
6
- mdAndUp ? 'rounded-xl' : ''
7
- }`"
8
- :style="{ border: 'none', boxShadow: 'none' }"
9
- >
14
+ <v-dialog v-model="model" :max-width="520" persistent class="form-dialog">
15
+ <v-card class="d-flex flex-column form-dialog__card" :style="{ border: 'none' }">
10
16
  <v-toolbar
11
- :color="theme.name.value === 'dark' ? 'grey-darken-4' : 'white'"
12
- class="flex-shrink-0 mb-3"
17
+ color="transparent"
18
+ class="flex-shrink-0 form-dialog__head"
13
19
  style="position: sticky; top: 0; z-index: 10"
14
20
  >
15
21
  <slot name="title">
16
- <span class="text-h6 font-weight-medium pt-1 text-capitalize">
17
- Dialog
18
- </span>
22
+ <span class="form-dialog__title"> Dialog </span>
19
23
  </slot>
20
24
  <v-spacer />
21
- <v-btn icon="mdi-close" @click="closeDialog" />
25
+ <v-btn icon="mdi-close" variant="text" @click="closeDialog" />
22
26
  </v-toolbar>
23
27
 
24
- <v-sheet class="flex-grow-1 overflow-y-auto px-3">
28
+ <v-sheet class="flex-grow-1 overflow-y-auto px-4 form-dialog__body">
25
29
  <slot />
26
30
  </v-sheet>
27
31
 
28
- <v-sheet class="flex-shrink-0 px-3 py-2">
32
+ <v-sheet class="flex-shrink-0 px-4 py-3 form-dialog__footer">
29
33
  <slot name="footer" />
30
34
  </v-sheet>
31
35
  </v-card>
@@ -33,11 +37,6 @@
33
37
  </template>
34
38
 
35
39
  <script setup lang="ts">
36
- import { useTheme, useDisplay } from "vuetify";
37
-
38
- const theme = useTheme().global;
39
- const { mdAndUp } = useDisplay();
40
-
41
40
  const props = defineProps<{
42
41
  modelValue: boolean;
43
42
  }>();
@@ -62,4 +61,44 @@ function closeDialog() {
62
61
  :deep(.v-dialog .v-overlay__content) {
63
62
  margin: auto;
64
63
  }
64
+
65
+ .form-dialog__card {
66
+ font-family: var(--font-sans);
67
+ background: var(--card);
68
+ /* `!important` because Vuetify's own `.v-overlay__content > .v-card` rule is
69
+ more specific than a class on the card, and it is what keeps the dialog at
70
+ its factory 4px. */
71
+ border-radius: var(--r-modal) !important;
72
+ box-shadow: var(--shadow-modal);
73
+ /* Never taller than the screen it has to fit inside; the body is what
74
+ scrolls, so the Cancel/Submit footer cannot be pushed out of view. */
75
+ max-height: calc(100vh - 32px);
76
+ overflow: hidden;
77
+ }
78
+
79
+ .form-dialog__head {
80
+ border-bottom: 1px solid var(--border);
81
+ }
82
+
83
+ .form-dialog__title {
84
+ font-size: var(--fs-card-title);
85
+ font-weight: var(--fw-card-title);
86
+ color: var(--text);
87
+ text-transform: capitalize;
88
+ }
89
+
90
+ .form-dialog__body,
91
+ .form-dialog__footer {
92
+ background: var(--card);
93
+ }
94
+
95
+ .form-dialog__footer {
96
+ border-top: 1px solid var(--border);
97
+ }
98
+
99
+ @media (max-width: 599.98px) {
100
+ .form-dialog__card {
101
+ max-height: calc(100dvh - 32px);
102
+ }
103
+ }
65
104
  </style>
@@ -105,42 +105,90 @@
105
105
  />
106
106
  </v-dialog>
107
107
 
108
- <ConfirmDialog
109
- v-model="confirmDialog"
110
- :loading="deleteLoading"
111
- @submit="handleDeleteRole"
112
- >
113
- <template #title>
114
- <span class="font-weight-medium text-h5">Delete Role</span>
115
- </template>
116
-
117
- <template #description>
118
- <p class="text-subtitle-2">
119
- Are you sure you want to delete this role? This action cannot be
120
- undone.
121
- </p>
122
- </template>
123
-
124
- <template #footer>
125
- <div class="d-flex justify-center ga-3 w-100">
126
- <v-btn
127
- variant="text"
128
- @click="confirmDialog = false"
129
- :disabled="deleteLoading"
130
- >
131
- Close
132
- </v-btn>
133
- <v-btn
134
- color="primary"
135
- variant="flat"
136
- @click="handleDeleteRole"
137
- :loading="deleteLoading"
138
- >
139
- Delete Role
140
- </v-btn>
141
- </div>
142
- </template>
143
- </ConfirmDialog>
108
+ <v-dialog v-model="confirmDialog" max-width="450">
109
+ <v-card>
110
+ <v-toolbar>
111
+ <v-row no-gutters class="fill-height px-6" align="center">
112
+ <span class="font-weight-bold text-h6 text-capitalize">
113
+ Delete Role
114
+ </span>
115
+ </v-row>
116
+ </v-toolbar>
117
+
118
+ <v-card-text>
119
+ <span v-if="!hasAssignedMembers" class="text-subtitle-2">
120
+ Are you sure you want to delete this role? This action cannot be
121
+ undone.
122
+ </span>
123
+ <template v-else>
124
+ <p class="text-subtitle-2 mb-4">
125
+ There are currently
126
+ <span class="text-error font-weight-bold">
127
+ {{ deletionPreview.members.length }}
128
+ {{ deletionPreview.members.length === 1 ? "user" : "users" }}
129
+ </span>
130
+ assigned to this role. Reassign each user before deleting it so
131
+ they do not lose access.
132
+ </p>
133
+
134
+ <v-row
135
+ v-for="member in deletionPreview.members"
136
+ :key="member._id"
137
+ no-gutters
138
+ class="mb-3 pa-3 border rounded-lg"
139
+ align="center"
140
+ >
141
+ <v-col cols="12" sm="5" class="text-body-2 mb-2 mb-sm-0">
142
+ {{ member.name || member.user }}
143
+ </v-col>
144
+ <v-col cols="12" sm="7">
145
+ <v-select
146
+ v-model="memberReassignments[member._id]"
147
+ :items="deletionPreview.replacementRoles"
148
+ item-title="name"
149
+ item-value="_id"
150
+ label="Reassign role"
151
+ density="comfortable"
152
+ hide-details
153
+ />
154
+ </v-col>
155
+ </v-row>
156
+ </template>
157
+ </v-card-text>
158
+
159
+ <v-toolbar class="pa-0" density="compact">
160
+ <v-row no-gutters>
161
+ <v-col cols="6" class="pa-0">
162
+ <v-btn
163
+ block
164
+ variant="text"
165
+ class="text-none"
166
+ size="large"
167
+ height="48"
168
+ @click="closeDeleteDialog"
169
+ :disabled="deleteLoading"
170
+ >
171
+ Cancel
172
+ </v-btn>
173
+ </v-col>
174
+ <v-col cols="6" class="pa-0">
175
+ <v-btn
176
+ block
177
+ variant="flat"
178
+ color="black"
179
+ class="text-none font-weight-bold rounded-0"
180
+ height="48"
181
+ @click="handleDeleteRole"
182
+ :loading="deleteLoading"
183
+ :disabled="hasAssignedMembers && !canConfirmDeletion"
184
+ >
185
+ {{ hasAssignedMembers ? "Confirm" : "Delete" }}
186
+ </v-btn>
187
+ </v-col>
188
+ </v-row>
189
+ </v-toolbar>
190
+ </v-card>
191
+ </v-dialog>
144
192
 
145
193
  <Snackbar v-model="messageSnackbar" :text="message" :color="messageColor" />
146
194
  </v-row>
@@ -367,10 +415,49 @@ async function refreshRolesAfterUpdate(savedPermissions: string[]) {
367
415
  const confirmDialog = ref(false);
368
416
  const selectedRoleId = ref<string | null>(null);
369
417
  const deleteLoading = ref(false);
418
+ const deletionPreview = ref({
419
+ members: [] as Array<Record<string, any>>,
420
+ replacementRoles: [] as Array<Record<string, any>>,
421
+ });
422
+ const memberReassignments = ref<Record<string, string>>({});
423
+
424
+ const hasAssignedMembers = computed(
425
+ () => deletionPreview.value.members.length > 0
426
+ );
427
+
428
+ const canConfirmDeletion = computed(() =>
429
+ deletionPreview.value.members.every(
430
+ (member) => Boolean(memberReassignments.value[member._id])
431
+ )
432
+ );
370
433
 
371
- function openDeleteDialog(id: string) {
434
+ const { getDeletionPreview, deleteWithReassignments } = useRole();
435
+
436
+ async function openDeleteDialog(id: string) {
372
437
  selectedRoleId.value = id;
373
- confirmDialog.value = true;
438
+ deletionPreview.value = { members: [], replacementRoles: [] };
439
+ memberReassignments.value = {};
440
+ deleteLoading.value = true;
441
+
442
+ try {
443
+ const res = await getDeletionPreview(id);
444
+ deletionPreview.value = res.data;
445
+ confirmDialog.value = true;
446
+ } catch (error: any) {
447
+ const errorMessage =
448
+ error?.response?._data?.message || "Failed to load role assignments.";
449
+ showMessage(errorMessage, "error");
450
+ selectedRoleId.value = null;
451
+ } finally {
452
+ deleteLoading.value = false;
453
+ }
454
+ }
455
+
456
+ function closeDeleteDialog() {
457
+ confirmDialog.value = false;
458
+ selectedRoleId.value = null;
459
+ deletionPreview.value = { members: [], replacementRoles: [] };
460
+ memberReassignments.value = {};
374
461
  }
375
462
 
376
463
  function showMessage(msg: string, color: string) {
@@ -383,17 +470,23 @@ async function handleDeleteRole() {
383
470
  if (!selectedRoleId.value) return;
384
471
  deleteLoading.value = true;
385
472
  try {
386
- const res = await deleteRole(selectedRoleId.value);
387
-
388
- confirmDialog.value = false;
473
+ const reassignments = deletionPreview.value.members.map((member) => ({
474
+ memberId: member._id,
475
+ roleId: memberReassignments.value[member._id],
476
+ }));
477
+ const res = hasAssignedMembers.value
478
+ ? await deleteWithReassignments(selectedRoleId.value, reassignments)
479
+ : await deleteRole(selectedRoleId.value);
480
+
481
+ closeDeleteDialog();
389
482
  showMessage(res.message, "success");
390
483
  getRoles();
391
484
  } catch (error: any) {
392
- const errorMessage = error?.response?._data?.message;
485
+ const errorMessage =
486
+ error?.response?._data?.message || "Failed to delete role.";
393
487
  showMessage(errorMessage, "error");
394
488
  } finally {
395
489
  deleteLoading.value = false;
396
- selectedRoleId.value = null;
397
490
  }
398
491
  }
399
492
  </script>
@@ -0,0 +1,64 @@
1
+ <!--
2
+ THE STATUS CHIP.
3
+
4
+ A pill in the tone the design assigns to the status WORD, with the 6px
5
+ leading dot. The word -> tone mapping is `utils/status.ts` and is shared, so
6
+ "Open" is the same green on a work order and on a Daily Occurrence Book, and
7
+ a status the design never named comes out neutral rather than guessed.
8
+
9
+ Colour only. The chip prints the label it is given; it does not decide, map,
10
+ translate or shorten a status.
11
+ -->
12
+ <template>
13
+ <span class="status-chip" :class="[toneClass, { 'status-chip--plain': !dot }]">
14
+ <span v-if="dot" class="status-chip__dot" />
15
+ {{ label ?? status }}
16
+ </span>
17
+ </template>
18
+
19
+ <script setup lang="ts">
20
+ import { statusToneClass } from "../utils/status";
21
+
22
+ const props = defineProps({
23
+ /** The status word - what decides the colour. */
24
+ status: { type: String, default: "" },
25
+ /** What to PRINT, when that differs from the word being matched on. */
26
+ label: { type: String, default: undefined },
27
+ /** The design's tag chips (pass IDs, role names) carry no leading dot. */
28
+ dot: { type: Boolean, default: true },
29
+ });
30
+
31
+ const toneClass = computed(() => statusToneClass(props.status));
32
+ </script>
33
+
34
+ <style scoped>
35
+ .status-chip {
36
+ display: inline-flex;
37
+ align-items: center;
38
+ gap: 6px;
39
+ padding: 4px 10px;
40
+ border-radius: var(--r-pill);
41
+ font-family: var(--font-sans);
42
+ font-size: var(--fs-chip-lg);
43
+ font-weight: var(--fw-chip);
44
+ line-height: 1.4;
45
+ white-space: nowrap;
46
+ /* `--tone-bg` is set alongside the colour by the `.tone-*` class - see
47
+ assets/css/tokens.css - so one class carries both halves of the pair the
48
+ contrast was measured on. */
49
+ background: var(--tone-bg);
50
+ }
51
+
52
+ .status-chip--plain {
53
+ border-radius: var(--r-tag);
54
+ font-size: var(--fs-chip);
55
+ }
56
+
57
+ .status-chip__dot {
58
+ width: 6px;
59
+ height: 6px;
60
+ border-radius: var(--r-pill);
61
+ background: currentColor;
62
+ flex: 0 0 6px;
63
+ }
64
+ </style>
@@ -1,3 +1,18 @@
1
+ <!--
2
+ PHASE 3 - THE STANDARD TABLE CARD.
3
+
4
+ The redesign is, more than anything else, a table card: toolbar row (refresh,
5
+ optional in-card search, pagination) -> uppercase header row -> rows. This is
6
+ the shared one, so this file is where that pattern is drawn once.
7
+
8
+ VISUAL ONLY, and that matters more here than anywhere. Every column, filter,
9
+ sort, action, slot, prop, emit and page still behaves exactly as it did:
10
+ the headers are the caller's, the items are the caller's, every slot is still
11
+ forwarded to `v-data-table` untouched, and no prop's default has changed. The
12
+ empty state is the same wording `v-data-table` already showed, with the icon
13
+ the design draws above it - and it is only supplied when the caller has NOT
14
+ passed its own `no-data` slot.
15
+ -->
1
16
  <template>
2
17
  <v-row no-gutters>
3
18
  <!-- Top Actions -->
@@ -5,13 +20,17 @@
5
20
  <v-row no-gutters>
6
21
  <slot name="actions">
7
22
  <v-col cols="12">
8
- <v-row no-gutters class="ga-2">
23
+ <v-row no-gutters class="ga-2 align-center table-card__actions">
24
+ <!--
25
+ The design's primary button: the accent fill that may carry a
26
+ white label (`--accent-strong`; white on `--accent` is 3.23:1),
27
+ 40px, 10px radius, leading icon. Same click, same label.
28
+ -->
9
29
  <v-btn
10
30
  v-if="canCreate"
11
- class="text-none"
12
- rounded="pill"
13
- variant="tonal"
14
- size="large"
31
+ class="text-none table-card__primary"
32
+ variant="flat"
33
+ prepend-icon="mdi-plus"
15
34
  @click="emits('create')"
16
35
  >
17
36
  {{ createLabel }}
@@ -25,14 +44,14 @@
25
44
  max-width="300"
26
45
  append-inner-icon="mdi-magnify"
27
46
  hide-details
47
+ class="table-card__search"
28
48
  />
49
+ <!-- Ghost/secondary, as the design specifies for Scan QR Code. -->
29
50
  <v-btn
30
51
  v-if="canScanVisitorQRCode"
31
52
  text="Scan QR Code"
32
- class="text-none ml-2"
33
- rounded="pill"
34
- variant="tonal"
35
- size="large"
53
+ class="text-none ml-2 table-card__ghost"
54
+ variant="outlined"
36
55
  @click="emits('scan')"
37
56
  />
38
57
  </v-row>
@@ -45,19 +64,19 @@
45
64
  <v-col cols="12">
46
65
  <v-card
47
66
  width="100%"
48
- variant="outlined"
49
- border="thin"
50
- rounded="lg"
67
+ variant="flat"
68
+ class="table-card"
51
69
  :loading="loading"
52
70
  >
53
71
  <!-- Toolbar -->
54
72
  <v-toolbar
55
73
  density="compact"
56
- color="grey-lighten-4"
74
+ color="transparent"
75
+ class="table-card__toolbar"
57
76
  :extension-height="extensionHeight"
58
77
  >
59
78
  <template #prepend>
60
- <v-btn fab icon density="comfortable" @click="emits('refresh')">
79
+ <v-btn fab icon density="comfortable" variant="text" @click="emits('refresh')">
61
80
  <v-icon>mdi-refresh</v-icon>
62
81
  </v-btn>
63
82
  <slot name="prepend-additional" />
@@ -65,7 +84,7 @@
65
84
 
66
85
  <template #append>
67
86
  <v-row no-gutters justify="end" align="center">
68
- <span class="mr-2 text-caption text-fontgray">
87
+ <span class="mr-2 table-card__range">
69
88
  {{ pageRange }}
70
89
  </span>
71
90
  <local-pagination
@@ -96,6 +115,18 @@
96
115
  <template v-for="(_, slotName) in $slots" #[slotName]="slotProps">
97
116
  <slot :name="slotName" v-bind="slotProps" />
98
117
  </template>
118
+
119
+ <!--
120
+ The design's empty state. Same sentence the table already showed;
121
+ only supplied when the caller has not passed its own, so nobody's
122
+ existing empty state is replaced.
123
+ -->
124
+ <template v-if="!$slots['no-data']" #no-data>
125
+ <div class="table-card__empty">
126
+ <v-icon icon="mdi-magnify-close" size="32" />
127
+ <span>No data available</span>
128
+ </div>
129
+ </template>
99
130
  </v-data-table>
100
131
  <slot name="footer" />
101
132
  </v-card>
@@ -187,7 +218,134 @@ watch(
187
218
  </script>
188
219
 
189
220
  <style scoped>
190
- .v-data-table :deep(thead th) {
191
- font-weight: bold !important;
221
+ /* ------------------------------------------------------------------ */
222
+ /* The card. */
223
+ /* ------------------------------------------------------------------ */
224
+
225
+ .table-card {
226
+ font-family: var(--font-sans);
227
+ background: var(--card);
228
+ border: 1px solid var(--border);
229
+ border-radius: var(--r-card);
230
+ box-shadow: var(--shadow);
231
+ overflow: hidden;
232
+ }
233
+
234
+ /**
235
+ * The toolbar was `color="grey-lighten-4"` - a literal Vuetify grey, which is
236
+ * a near-white bar sitting on a #1c1e24 card in dark mode. It is the card's
237
+ * own surface now, with the design's rule under it.
238
+ */
239
+ .table-card__toolbar {
240
+ border-bottom: 1px solid var(--border);
241
+ }
242
+
243
+ .table-card__range {
244
+ font-size: 12px;
245
+ font-weight: 600;
246
+ color: var(--muted);
247
+ font-variant-numeric: tabular-nums;
248
+ }
249
+
250
+ /* ------------------------------------------------------------------ */
251
+ /* The header row and the rows under it. */
252
+ /* ------------------------------------------------------------------ */
253
+
254
+ .table-card :deep(thead th) {
255
+ background: var(--thead) !important;
256
+ color: var(--muted) !important;
257
+ font-size: var(--fs-table-head) !important;
258
+ font-weight: var(--fw-table-head) !important;
259
+ letter-spacing: var(--ls-table-head);
260
+ text-transform: uppercase;
261
+ padding: 10px var(--cellpad-x) !important;
262
+ height: auto !important;
263
+ border-bottom: 1px solid var(--border) !important;
264
+ white-space: nowrap;
265
+ }
266
+
267
+ .table-card :deep(tbody td) {
268
+ font-size: var(--fs-cell);
269
+ font-weight: var(--fw-cell);
270
+ color: var(--text);
271
+ padding: var(--cellpad) var(--cellpad-x) !important;
272
+ height: auto !important;
273
+ font-variant-numeric: tabular-nums;
274
+ }
275
+
276
+ .table-card :deep(tbody tr:hover > td) {
277
+ background: var(--hover);
278
+ }
279
+
280
+ /* ------------------------------------------------------------------ */
281
+ /* Empty state, in-card search, buttons and in-card tabs. */
282
+ /* ------------------------------------------------------------------ */
283
+
284
+ .table-card__empty {
285
+ display: flex;
286
+ flex-direction: column;
287
+ align-items: center;
288
+ justify-content: center;
289
+ gap: 8px;
290
+ padding: 52px 16px;
291
+ font-size: var(--fs-cell);
292
+ color: var(--muted);
293
+ }
294
+
295
+ .table-card__empty :deep(.v-icon) {
296
+ color: var(--muted);
297
+ }
298
+
299
+ /* The in-card variant: 34px, 10px radius, as drawn. */
300
+ .table-card__search :deep(.v-field) {
301
+ border-radius: var(--r-inner);
302
+ font-size: var(--fs-cell);
303
+ }
304
+
305
+ .table-card__primary {
306
+ height: var(--btn-h);
307
+ border-radius: var(--r-inner);
308
+ padding: 0 16px;
309
+ font-size: var(--fs-btn);
310
+ font-weight: var(--fw-btn-strong);
311
+ letter-spacing: 0;
312
+ background: var(--accent-strong);
313
+ color: var(--on-accent-strong);
314
+ }
315
+
316
+ .table-card__ghost {
317
+ height: var(--btn-h);
318
+ border-radius: var(--r-inner);
319
+ padding: 0 16px;
320
+ font-size: var(--fs-btn);
321
+ font-weight: var(--fw-btn);
322
+ letter-spacing: 0;
323
+ color: var(--text2);
324
+ border-color: var(--border);
325
+ background: var(--card);
326
+ }
327
+
328
+ /**
329
+ * Tabs that a screen puts in this card's extension slot: 13px, the active one
330
+ * at 800 in `--accent-text` over a 2px accent underline. Tabs elsewhere in the
331
+ * product are their own screens' and are restyled with them.
332
+ */
333
+ .table-card :deep(.v-tab) {
334
+ font-size: var(--fs-nav);
335
+ font-weight: var(--fw-cell);
336
+ letter-spacing: 0;
337
+ text-transform: none;
338
+ color: var(--muted);
339
+ min-width: 0;
340
+ }
341
+
342
+ .table-card :deep(.v-tab.v-tab--selected) {
343
+ font-weight: var(--fw-table-head);
344
+ color: var(--accent-text);
345
+ }
346
+
347
+ .table-card :deep(.v-tab .v-tab__slider) {
348
+ height: 2px;
349
+ background: var(--accent);
192
350
  }
193
351
  </style>
@@ -68,6 +68,26 @@ export default function useRole() {
68
68
  });
69
69
  }
70
70
 
71
+ function getDeletionPreview(_id: string) {
72
+ return useNuxtApp().$api<Record<string, any>>(
73
+ `/api/roles/${_id}/deletion-preview`,
74
+ { method: "GET" }
75
+ );
76
+ }
77
+
78
+ function deleteWithReassignments(
79
+ _id: string,
80
+ reassignments: Array<{ memberId: string; roleId: string }>
81
+ ) {
82
+ return useNuxtApp().$api<Record<string, any>>(
83
+ `/api/roles/${_id}/delete-with-reassignments`,
84
+ {
85
+ method: "PUT",
86
+ body: { reassignments },
87
+ }
88
+ );
89
+ }
90
+
71
91
  const role = useState<TRole>("userRole", () => {
72
92
  return {
73
93
  _id: "",
@@ -103,6 +123,8 @@ export default function useRole() {
103
123
  updateRoleById,
104
124
  updateRoleFieldById,
105
125
  deleteRole,
126
+ getDeletionPreview,
127
+ deleteWithReassignments,
106
128
  updatePermissionById,
107
129
  getRoleByUserId,
108
130
  };
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.82",
5
+ "version": "3.2.2-staging.84",
6
6
  "author": "7365admin1",
7
7
  "main": "./nuxt.config.ts",
8
8
  "publishConfig": {