@7365admin1/layer-common 3.2.1-staging.60 → 3.2.1-staging.62

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.
@@ -30,21 +30,28 @@
30
30
  <v-row no-gutters>
31
31
  <InputLabel title="Site" />
32
32
  <v-col cols="12">
33
- <v-text-field :model-value="siteName" density="comfortable" readonly disabled :loading="loadSite" />
33
+ <v-text-field
34
+ :model-value="siteName"
35
+ density="comfortable"
36
+ readonly
37
+ disabled
38
+ :loading="loadSite"
39
+ />
34
40
  </v-col>
35
41
  </v-row>
36
42
  </v-col>
37
43
 
38
- <v-col cols="12" class="mt-1">
39
- <v-radio-group
40
- v-model="platform"
41
- inline
42
- color="primary"
43
- hide-details
44
- >
45
- <v-radio label="Website" value="web" />
46
- <v-radio label="Mobile" value="mobile" />
47
- </v-radio-group>
44
+ <v-col cols="12" class="mt-3">
45
+ <div class="d-flex align-center ga-4 text-caption">
46
+ <span class="d-flex align-center ga-2">
47
+ <v-icon color="amber-darken-2" size="14">mdi-circle</v-icon>
48
+ Web and Mobile
49
+ </span>
50
+ <span class="d-flex align-center ga-2">
51
+ <v-icon color="blue-darken-2" size="14">mdi-circle</v-icon>
52
+ Web only
53
+ </span>
54
+ </div>
48
55
  </v-col>
49
56
 
50
57
  <v-col cols="12" class="mt-2">
@@ -54,7 +61,7 @@
54
61
  class="text-none mb-2"
55
62
  @click="() => toggleSelectAll(!isAllSelected)"
56
63
  >
57
- {{ isAllSelected ? 'Unselect All' : 'Select All' }}
64
+ {{ isAllSelected ? "Unselect All" : "Select All" }}
58
65
  </v-btn>
59
66
  </v-col>
60
67
 
@@ -69,7 +76,12 @@
69
76
  <!-- Category row -->
70
77
  <v-list-item density="compact" :ripple="false">
71
78
  <template #prepend>
72
- <v-chip class="mr-3" size="small" label>
79
+ <v-chip
80
+ class="mr-3"
81
+ size="small"
82
+ label
83
+ :color="resourceColor(String(resourceKey))"
84
+ >
73
85
  {{ selectedActionCount(String(resourceKey)) }}
74
86
  </v-chip>
75
87
  </template>
@@ -97,14 +109,18 @@
97
109
  <!-- Expanded checkboxes when toggled on -->
98
110
  <template v-if="isCategoryEnabled(String(resourceKey))">
99
111
  <template
100
- v-for="([actionKey, action]) in Object.entries(actions || {})"
112
+ v-for="[actionKey, action] in Object.entries(actions || {})"
101
113
  :key="actionKey"
102
114
  >
103
115
  <v-divider />
104
116
  <v-list-item density="compact" :ripple="false">
105
117
  <template #prepend>
106
118
  <v-checkbox-btn
107
- :model-value="permState.selected.includes(`${String(resourceKey)}:${String(actionKey)}`)"
119
+ :model-value="
120
+ permState.selected.includes(
121
+ `${String(resourceKey)}:${String(actionKey)}`
122
+ )
123
+ "
108
124
  @update:model-value="
109
125
  toggleAction(
110
126
  String(resourceKey),
@@ -207,17 +223,20 @@ const props = defineProps({
207
223
  type: Boolean,
208
224
  default: true,
209
225
  },
226
+ webOnlyResources: {
227
+ type: Array as PropType<string[]>,
228
+ default: () => [],
229
+ },
210
230
  cancelText: {
211
231
  type: String,
212
- default: "Cancel"
213
- }
232
+ default: "Cancel",
233
+ },
214
234
  });
215
235
 
216
236
  const emit = defineEmits(["cancel", "success", "success:create-more"]);
217
237
 
218
238
  const validForm = ref(false);
219
239
  const name = ref("");
220
- const platform = ref("web");
221
240
  const disable = ref(false);
222
241
  const message = ref("");
223
242
 
@@ -229,73 +248,81 @@ const { getSiteById } = useSite();
229
248
  const permState = shallowRef({
230
249
  selected: [] as string[],
231
250
  enabled: [] as string[],
232
- })
251
+ });
233
252
 
234
253
  const resourceLabels: Record<string, string> = {
235
254
  "access-card-mgmt": "Access Mgmt",
236
255
  };
237
256
 
238
257
  function formatResourceLabel(key: string): string {
239
- return resourceLabels[key] ?? key.replace(/-/g, " ");
258
+ const label = resourceLabels[key] ?? key.replace(/-/g, " ");
259
+ return props.webOnlyResources.includes(key) ? `${label} (Web Only)` : label;
260
+ }
261
+
262
+ function resourceColor(resource: string) {
263
+ return props.webOnlyResources.includes(resource)
264
+ ? "blue-darken-2"
265
+ : "amber-darken-2";
240
266
  }
241
267
 
242
268
  function isCategoryEnabled(resource: string) {
243
- return permState.value.enabled.includes(resource)
269
+ return permState.value.enabled.includes(resource);
244
270
  }
245
271
 
246
272
  function selectedActionCount(resource: string) {
247
- return permState.value.selected.filter(p => p.startsWith(`${resource}:`)).length
273
+ return permState.value.selected.filter((p) => p.startsWith(`${resource}:`))
274
+ .length;
248
275
  }
249
276
 
250
277
  function toggleCategory(resource: string, value: boolean | null) {
251
- const { selected, enabled } = permState.value
278
+ const { selected, enabled } = permState.value;
252
279
  if (value) {
253
280
  if (!enabled.includes(resource)) {
254
- permState.value = { selected, enabled: [...enabled, resource] }
281
+ permState.value = { selected, enabled: [...enabled, resource] };
255
282
  }
256
283
  } else {
257
284
  permState.value = {
258
- selected: selected.filter(p => !p.startsWith(`${resource}:`)),
259
- enabled: enabled.filter(r => r !== resource),
260
- }
285
+ selected: selected.filter((p) => !p.startsWith(`${resource}:`)),
286
+ enabled: enabled.filter((r) => r !== resource),
287
+ };
261
288
  }
262
289
  }
263
290
 
264
291
  function toggleAction(resource: string, action: string, value: boolean | null) {
265
- if (value !== true && value !== false) return
266
- const key = `${resource}:${action}`
267
- const { selected, enabled } = permState.value
292
+ if (value !== true && value !== false) return;
293
+ const key = `${resource}:${action}`;
294
+ const { selected, enabled } = permState.value;
268
295
  if (value) {
269
296
  if (!selected.includes(key)) {
270
- permState.value = { selected: [...selected, key], enabled }
297
+ permState.value = { selected: [...selected, key], enabled };
271
298
  }
272
299
  } else {
273
- permState.value = { selected: selected.filter(p => p !== key), enabled }
300
+ permState.value = { selected: selected.filter((p) => p !== key), enabled };
274
301
  }
275
302
  }
276
303
 
277
304
  function toggleSelectAll(value: boolean) {
278
305
  if (!value) {
279
- permState.value = { selected: [], enabled: [] }
280
- return
306
+ permState.value = { selected: [], enabled: [] };
307
+ return;
281
308
  }
282
309
 
283
- const perms = safePermissions.value
284
- if (!perms) return
310
+ const perms = safePermissions.value;
311
+ if (!perms) return;
285
312
 
286
- const allKeys: string[] = []
287
- const categories: string[] = []
313
+ const allKeys: string[] = [];
314
+ const categories: string[] = [];
288
315
 
289
316
  for (const [resource, actions] of Object.entries(perms)) {
290
- if (!resource || !actions || typeof actions !== 'object') continue
291
- categories.push(resource)
317
+ if (!resource || !actions || typeof actions !== "object") continue;
318
+ categories.push(resource);
292
319
  for (const action of Object.keys(actions)) {
293
- if (action) allKeys.push(`${resource}:${action}`)
320
+ if (action) allKeys.push(`${resource}:${action}`);
294
321
  }
295
322
  }
296
323
 
297
324
  // 1 assignment to trigger reactivity only once
298
- permState.value = { selected: allKeys, enabled: categories }
325
+ permState.value = { selected: allKeys, enabled: categories };
299
326
  }
300
327
 
301
328
  const siteName = ref("");
@@ -324,14 +351,13 @@ watch(
324
351
  async function submit() {
325
352
  disable.value = true;
326
353
  try {
327
- const platformValue = platform.value === "web" ? "website" : platform.value;
328
354
  await createRole({
329
355
  name: name.value,
330
356
  permissions: permState.value.selected,
331
357
  type: props.type,
332
358
  org: props.org,
333
359
  site: props.siteId,
334
- platform: platformValue,
360
+ platform: "website",
335
361
  });
336
362
  emit("success");
337
363
  } catch (error: any) {
@@ -342,41 +368,41 @@ async function submit() {
342
368
  }
343
369
 
344
370
  function cancel() {
345
- name.value = ""
346
- permState.value = { selected: [], enabled: [] }
347
- platform.value = "web"
348
- message.value = ""
349
- emit("cancel")
371
+ name.value = "";
372
+ permState.value = { selected: [], enabled: [] };
373
+ message.value = "";
374
+ emit("cancel");
350
375
  }
351
376
 
352
377
  const safePermissions = computed(() => {
353
- const p = props.permissions || {}
354
- const cleaned: Record<string, any> = {}
378
+ const p = props.permissions || {};
379
+ const cleaned: Record<string, any> = {};
355
380
 
356
381
  for (const [k, v] of Object.entries(p)) {
357
- if (v && typeof v === 'object' && !Array.isArray(v)) {
358
- cleaned[k] = v
382
+ if (v && typeof v === "object" && !Array.isArray(v)) {
383
+ cleaned[k] = v;
359
384
  } else {
360
- cleaned[k] = {}
385
+ cleaned[k] = {};
361
386
  }
362
387
  }
363
388
 
364
- return cleaned
365
- })
389
+ return cleaned;
390
+ });
366
391
 
367
392
  const isAllSelected = computed(() => {
368
- const perms = safePermissions.value
369
- const allKeys: string[] = []
393
+ const perms = safePermissions.value;
394
+ const allKeys: string[] = [];
370
395
 
371
396
  Object.entries(perms).forEach(([resource, actions]) => {
372
- if (!actions) return
397
+ if (!actions) return;
373
398
  Object.keys(actions).forEach((action) => {
374
- if (action) allKeys.push(`${resource}:${action}`)
375
- })
376
- })
377
-
378
- return allKeys.length > 0 &&
379
- allKeys.every(k => permState.value.selected.includes(k))
380
- })
399
+ if (action) allKeys.push(`${resource}:${action}`);
400
+ });
401
+ });
381
402
 
403
+ return (
404
+ allKeys.length > 0 &&
405
+ allKeys.every((k) => permState.value.selected.includes(k))
406
+ );
407
+ });
382
408
  </script>
@@ -11,18 +11,104 @@
11
11
  <v-form v-model="validForm" :disabled="disable">
12
12
  <v-row no-gutters>
13
13
  <v-col cols="12" class="mt-4">
14
- <InputListGroupSelection
15
- v-model="definedModel"
16
- :items="props.permissions"
17
- variant="outlined"
18
- border="thin"
19
- :error-messages="errorMessages"
20
- :readonly="!edit"
21
- :hint="
22
- edit && 'Select permissions'
23
- "
24
- persistent-hint
25
- />
14
+ <div class="d-flex align-center ga-4 text-caption mb-3">
15
+ <span class="d-flex align-center ga-2">
16
+ <v-icon color="amber-darken-2" size="14">mdi-circle</v-icon>
17
+ Web and Mobile
18
+ </span>
19
+ <span class="d-flex align-center ga-2">
20
+ <v-icon color="blue-darken-2" size="14">mdi-circle</v-icon>
21
+ Web only
22
+ </span>
23
+ </div>
24
+ <v-btn
25
+ v-if="edit"
26
+ size="small"
27
+ variant="tonal"
28
+ class="text-none mb-2"
29
+ @click="toggleSelectAll(!isAllSelected)"
30
+ >
31
+ {{ isAllSelected ? "Unselect All" : "Select All" }}
32
+ </v-btn>
33
+ <v-card variant="outlined" border="thin" rounded="lg">
34
+ <template
35
+ v-for="(actions, resourceKey, resourceIndex) in safePermissions"
36
+ :key="resourceKey"
37
+ >
38
+ <v-divider v-if="resourceIndex > 0" />
39
+ <v-list-item density="compact" :ripple="false">
40
+ <template #prepend>
41
+ <v-chip
42
+ class="mr-3"
43
+ size="small"
44
+ label
45
+ :color="resourceColor(String(resourceKey))"
46
+ >
47
+ {{ selectedActionCount(String(resourceKey)) }}
48
+ </v-chip>
49
+ </template>
50
+ <v-list-item-title
51
+ class="text-capitalize text-subtitle-2 font-weight-medium"
52
+ >
53
+ {{ formatResourceLabel(String(resourceKey)) }}
54
+ </v-list-item-title>
55
+ <template #append>
56
+ <v-switch
57
+ :model-value="isCategoryEnabled(String(resourceKey))"
58
+ :disabled="!edit"
59
+ color="green"
60
+ density="compact"
61
+ hide-details
62
+ inset
63
+ @update:model-value="
64
+ toggleCategory(String(resourceKey), $event)
65
+ "
66
+ />
67
+ </template>
68
+ </v-list-item>
69
+ <template v-if="isCategoryEnabled(String(resourceKey))">
70
+ <template
71
+ v-for="[actionKey, action] in Object.entries(actions || {})"
72
+ :key="actionKey"
73
+ >
74
+ <v-divider />
75
+ <v-list-item density="compact" :ripple="false">
76
+ <template #prepend>
77
+ <v-checkbox-btn
78
+ :model-value="
79
+ definedModel.includes(
80
+ `${String(resourceKey)}:${String(actionKey)}`
81
+ )
82
+ "
83
+ :disabled="!edit"
84
+ color="primary"
85
+ density="compact"
86
+ class="mr-2"
87
+ @update:model-value="
88
+ toggleAction(
89
+ String(resourceKey),
90
+ String(actionKey),
91
+ $event
92
+ )
93
+ "
94
+ />
95
+ </template>
96
+ <v-list-item-title
97
+ class="text-subtitle-2 font-weight-medium text-capitalize"
98
+ >
99
+ {{ String(actionKey).replace(/-/g, " ") }}
100
+ </v-list-item-title>
101
+ <v-list-item-subtitle class="text-caption">
102
+ {{ action.description }}
103
+ </v-list-item-subtitle>
104
+ </v-list-item>
105
+ </template>
106
+ </template>
107
+ </template>
108
+ </v-card>
109
+ <div v-if="errorMessages" class="text-caption text-error mt-2">
110
+ {{ errorMessages }}
111
+ </div>
26
112
  </v-col>
27
113
 
28
114
  <v-col v-if="edit" cols="12" class="my-2">
@@ -85,7 +171,7 @@
85
171
  color="black"
86
172
  class="text-none"
87
173
  size="large"
88
- :disabled="!validForm"
174
+ :disabled="disable"
89
175
  @click="edit = true"
90
176
  >
91
177
  Edit
@@ -99,7 +185,8 @@
99
185
  color="black"
100
186
  class="text-none"
101
187
  size="large"
102
- :disabled="!validForm"
188
+ :disabled="Boolean(errorMessages) || disable"
189
+ :loading="disable"
103
190
  @click="submit()"
104
191
  >
105
192
  Submit
@@ -111,8 +198,8 @@
111
198
  </template>
112
199
 
113
200
  <script setup lang="ts">
114
- import useRole from '../composables/useRole';
115
- import useUtils from '../composables/useUtils';
201
+ import useRole from "../composables/useRole";
202
+ import useUtils from "../composables/useUtils";
116
203
 
117
204
  const definedModel = defineModel<Array<string>>({
118
205
  default: () => [],
@@ -135,11 +222,97 @@ const props = defineProps({
135
222
  type: Object,
136
223
  default: () => ({}),
137
224
  },
225
+ webOnlyResources: {
226
+ type: Array as PropType<string[]>,
227
+ default: () => [],
228
+ },
138
229
  });
139
230
 
140
231
  const validForm = ref(false);
141
232
  const message = ref("");
142
233
  const disable = ref(false);
234
+ const expandedResources = ref<string[]>([]);
235
+
236
+ const resourceLabels: Record<string, string> = {
237
+ "access-card-mgmt": "Access Mgmt",
238
+ };
239
+
240
+ function formatResourceLabel(key: string) {
241
+ const label = resourceLabels[key] ?? key.replace(/-/g, " ");
242
+ return props.webOnlyResources.includes(key) ? `${label} (Web Only)` : label;
243
+ }
244
+
245
+ function resourceColor(resource: string) {
246
+ return props.webOnlyResources.includes(resource)
247
+ ? "blue-darken-2"
248
+ : "amber-darken-2";
249
+ }
250
+
251
+ function selectedActionCount(resource: string) {
252
+ return definedModel.value.filter((permission) =>
253
+ permission.startsWith(`${resource}:`)
254
+ ).length;
255
+ }
256
+
257
+ function isCategoryEnabled(resource: string) {
258
+ return (
259
+ expandedResources.value.includes(resource) ||
260
+ selectedActionCount(resource) > 0
261
+ );
262
+ }
263
+
264
+ function toggleCategory(resource: string, value: boolean | null) {
265
+ if (!edit.value || value === null) return;
266
+ if (value) {
267
+ if (!expandedResources.value.includes(resource))
268
+ expandedResources.value.push(resource);
269
+ return;
270
+ }
271
+ expandedResources.value = expandedResources.value.filter(
272
+ (item) => item !== resource
273
+ );
274
+ definedModel.value = definedModel.value.filter(
275
+ (permission) => !permission.startsWith(`${resource}:`)
276
+ );
277
+ }
278
+
279
+ function toggleAction(resource: string, action: string, value: boolean | null) {
280
+ if (!edit.value || value === null) return;
281
+ const permission = `${resource}:${action}`;
282
+ if (value && !definedModel.value.includes(permission)) {
283
+ definedModel.value = [...definedModel.value, permission];
284
+ } else if (!value) {
285
+ definedModel.value = definedModel.value.filter(
286
+ (item) => item !== permission
287
+ );
288
+ }
289
+ }
290
+
291
+ const safePermissions = computed(() => props.permissions || {});
292
+
293
+ const isAllSelected = computed(() => {
294
+ const keys = Object.entries(safePermissions.value).flatMap(
295
+ ([resource, actions]) =>
296
+ Object.keys(actions || {}).map((action) => `${resource}:${action}`)
297
+ );
298
+ return (
299
+ keys.length > 0 && keys.every((key) => definedModel.value.includes(key))
300
+ );
301
+ });
302
+
303
+ function toggleSelectAll(value: boolean) {
304
+ if (!value) {
305
+ definedModel.value = [];
306
+ expandedResources.value = [];
307
+ return;
308
+ }
309
+ const permissions = Object.entries(safePermissions.value).flatMap(
310
+ ([resource, actions]) =>
311
+ Object.keys(actions || {}).map((action) => `${resource}:${action}`)
312
+ );
313
+ definedModel.value = permissions;
314
+ expandedResources.value = Object.keys(safePermissions.value);
315
+ }
143
316
 
144
317
  const emit = defineEmits(["success", "cancel"]);
145
318
 
@@ -84,6 +84,7 @@
84
84
  :permissions="props.permissions"
85
85
  :org="props.orgId"
86
86
  :site-id="props.siteId"
87
+ :web-only-resources="props.webOnlyResources"
87
88
  @success="success()"
88
89
  :type="props.type"
89
90
  @success:create-more="getRoles()"
@@ -100,6 +101,7 @@
100
101
  v-model:edit="edit"
101
102
  :name="name"
102
103
  :id="roleId"
104
+ :web-only-resources="props.webOnlyResources"
103
105
  />
104
106
  </v-dialog>
105
107
 
@@ -168,6 +170,10 @@ const props = defineProps({
168
170
  required: true,
169
171
  default: () => ({}),
170
172
  },
173
+ webOnlyResources: {
174
+ type: Array as PropType<string[]>,
175
+ default: () => [],
176
+ },
171
177
  headers: {
172
178
  type: Array as PropType<Array<Record<string, any>>>,
173
179
  default: () => [
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.1-staging.60",
5
+ "version": "3.2.1-staging.62",
6
6
  "author": "7365admin1",
7
7
  "main": "./nuxt.config.ts",
8
8
  "publishConfig": {