@eeplatform/nuxt-layer-common 1.7.47 → 1.7.49

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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @eeplatform/nuxt-layer-common
2
2
 
3
+ ## 1.7.49
4
+
5
+ ### Patch Changes
6
+
7
+ - 1afd4ee: Refactor members composable and middleware
8
+
9
+ ## 1.7.48
10
+
11
+ ### Patch Changes
12
+
13
+ - 47a0840: Revise role management
14
+
3
15
  ## 1.7.47
4
16
 
5
17
  ### Patch Changes
@@ -79,7 +79,7 @@
79
79
 
80
80
  <v-row no-gutters class="px-2 pb-2">
81
81
  <v-col
82
- v-for="item in props.apps"
82
+ v-for="item in apps"
83
83
  :key="item.title"
84
84
  cols="4"
85
85
  class="pa-2"
@@ -206,7 +206,7 @@ const props = defineProps({
206
206
  default: "Title",
207
207
  },
208
208
  defaults: {
209
- type: Array as PropType<Array<TApp>>,
209
+ type: Array as PropType<Record<string, any>[]>,
210
210
  default: () => [
211
211
  {
212
212
  title: "Account",
@@ -223,11 +223,11 @@ const props = defineProps({
223
223
  ],
224
224
  },
225
225
  apps: {
226
- type: Array as PropType<Array<TApp>>,
226
+ type: Array as PropType<Record<string, any>[]>,
227
227
  default: () => [],
228
228
  },
229
229
  others: {
230
- type: Array as PropType<Array<TApp>>,
230
+ type: Array as PropType<Record<string, any>[]>,
231
231
  default: () => [],
232
232
  },
233
233
  });
@@ -244,7 +244,7 @@ function setSearchValue() {
244
244
  search.value = _search.value;
245
245
  }
246
246
 
247
- const { redirect, drawer } = useLocal();
247
+ const { redirect, drawer, apps } = useLocal();
248
248
 
249
249
  const { APP_NAME, APP_NAME_ROUTE } = useRuntimeConfig().public;
250
250
 
@@ -0,0 +1,134 @@
1
+ <template>
2
+ <v-card width="100%" v-bind="attrs">
3
+ <v-list
4
+ v-model:selected="selected"
5
+ lines="two"
6
+ :select-strategy="attrs.readonly ? 'classic' : 'leaf'"
7
+ class="pa-0"
8
+ density="compact"
9
+ read-only
10
+ open-strategy="single"
11
+ >
12
+ <template name="prepend"></template>
13
+
14
+ <template
15
+ v-for="(keyGroup, keyGroupIndex) in props.items"
16
+ :key="keyGroupIndex"
17
+ >
18
+ <v-divider v-if="keyGroupIndex > 0"></v-divider>
19
+ <v-list-group :value="keyGroup.title" fluid>
20
+ <template v-slot:activator="{ props }">
21
+ <v-list-item v-bind="props" density="compact">
22
+ <span class="text-capitalize">
23
+ {{ keyGroup.title }}
24
+ </span>
25
+
26
+ <template #prepend>
27
+ <v-chip class="mr-2" small>
28
+ {{ selectedActionCount(String(keyGroup.key)) }}
29
+ </v-chip>
30
+ </template>
31
+ </v-list-item>
32
+ </template>
33
+
34
+ <template
35
+ v-for="(item, itemIndex) in keyGroup.children"
36
+ :key="itemIndex"
37
+ >
38
+ <v-divider></v-divider>
39
+ <v-list-item v-if="attrs.readonly" density="compact">
40
+ <template #title>
41
+ <span class="text-subtitle-2 text-capitalize pl-11">
42
+ {{ item.title }}
43
+ </span>
44
+ </template>
45
+
46
+ <template #subtitle>
47
+ <span class="text-subtitle-2 pl-11">{{
48
+ item.description
49
+ }}</span>
50
+ </template>
51
+ </v-list-item>
52
+
53
+ <v-list-item v-else :value="item.key" density="compact">
54
+ <template #title>
55
+ <span class="text-subtitle-2 text-capitalize">
56
+ {{ item.title }}
57
+ </span>
58
+ </template>
59
+
60
+ <template #subtitle>
61
+ <span class="text-subtitle-2 text-capitalize">
62
+ {{ item.description }}
63
+ </span>
64
+ </template>
65
+
66
+ <template #prepend="{ isSelected }">
67
+ <v-list-item-action start class="pl-1">
68
+ <v-checkbox-btn
69
+ :model-value="isSelected"
70
+ readonly
71
+ @click="addItem(item.key)"
72
+ ></v-checkbox-btn>
73
+ </v-list-item-action>
74
+ </template>
75
+ </v-list-item>
76
+ </template>
77
+ </v-list-group>
78
+ </template>
79
+
80
+ <slot v-if="noData" name="no-data">
81
+ <v-list-item>
82
+ <v-list-item-title>No data</v-list-item-title>
83
+ </v-list-item>
84
+ </slot>
85
+
86
+ <template name="append"></template>
87
+ </v-list>
88
+ </v-card>
89
+ </template>
90
+
91
+ <script setup lang="ts">
92
+ const selected = defineModel<Array<string>>({ default: [] });
93
+ const attrs = useAttrs();
94
+
95
+ function addItem(key: string) {
96
+ if (selected.value.includes(key)) {
97
+ // remove item from selected without reassigning the array
98
+ const index = selected.value.indexOf(key);
99
+ if (index > -1) {
100
+ selected.value.splice(index, 1);
101
+ }
102
+ } else {
103
+ selected.value.push(key);
104
+ }
105
+ }
106
+
107
+ type TListGroup = {
108
+ title: string;
109
+ key: string;
110
+ children: { title: string; key: string; description: string }[];
111
+ };
112
+
113
+ const props = defineProps({
114
+ items: {
115
+ // Array of object
116
+ type: Array as PropType<TListGroup[]>,
117
+ required: true,
118
+ default: () => [],
119
+ },
120
+ message: {
121
+ type: String,
122
+ default: "",
123
+ },
124
+ });
125
+
126
+ const selectedActionCount = (resource: string) => {
127
+ return selected.value.filter((permission) => permission.startsWith(resource))
128
+ .length;
129
+ };
130
+
131
+ const noData = computed(() => {
132
+ return Object.keys(props.items).length === 0;
133
+ });
134
+ </script>
@@ -0,0 +1,327 @@
1
+ <template>
2
+ <v-card width="100%">
3
+ <v-toolbar>
4
+ <v-row no-gutters class="fill-height px-6" align="center">
5
+ <span class="font-weight-bold text-h5 text-capitalize">
6
+ {{ localProps.title }}
7
+ </span>
8
+ </v-row>
9
+ </v-toolbar>
10
+
11
+ <v-card-text style="max-height: 100vh; overflow-y: auto">
12
+ <v-form v-model="valid">
13
+ <v-row no-gutters>
14
+ <v-col v-if="isOrg" cols="12" class="mb-1">
15
+ <v-row no-gutters>
16
+ <InputLabel
17
+ class="text-capitalize"
18
+ title="App"
19
+ :required="isMutable"
20
+ />
21
+ <v-col cols="12">
22
+ <v-select
23
+ v-model="role.app"
24
+ :items="apps"
25
+ item-title="name"
26
+ item-value="code"
27
+ :readonly="!isMutable"
28
+ :loading="loadingApps"
29
+ ></v-select>
30
+ </v-col>
31
+ </v-row>
32
+ </v-col>
33
+
34
+ <v-col cols="12" class="mb-1">
35
+ <v-row no-gutters>
36
+ <InputLabel
37
+ class="text-capitalize"
38
+ title="Name"
39
+ :required="isMutable"
40
+ />
41
+ <v-col cols="12">
42
+ <v-text-field
43
+ v-model="role.name"
44
+ :rules="isMutable ? [requiredRule] : []"
45
+ :readonly="!isMutable"
46
+ ></v-text-field>
47
+ </v-col>
48
+ </v-row>
49
+ </v-col>
50
+
51
+ <v-col cols="12" class="mb-1">
52
+ <v-row no-gutters>
53
+ <InputLabel
54
+ class="text-capitalize"
55
+ title="Permissions"
56
+ :required="isMutable"
57
+ />
58
+ <v-col cols="12">
59
+ <ListGroupSelection
60
+ v-model="role.permissions"
61
+ :items="permissions"
62
+ :readonly="!isMutable"
63
+ variant="outlined"
64
+ border="thin"
65
+ :loading="loadingPermissions"
66
+ />
67
+ <v-input
68
+ :messages="errorPermissionSelection"
69
+ :error="!!errorPermissionSelection"
70
+ ></v-input>
71
+ </v-col>
72
+ </v-row>
73
+ </v-col>
74
+
75
+ <v-col cols="12" class="mb-1">
76
+ <v-row no-gutters>
77
+ <InputLabel class="text-capitalize" title="Description" />
78
+ <v-col cols="12">
79
+ <v-textarea
80
+ v-model="role.description"
81
+ rows="2"
82
+ no-resize
83
+ :readonly="!isMutable"
84
+ ></v-textarea>
85
+ </v-col>
86
+ </v-row>
87
+ </v-col>
88
+ </v-row>
89
+ </v-form>
90
+
91
+ <v-alert
92
+ v-if="message"
93
+ type="error"
94
+ variant="flat"
95
+ closable
96
+ position="absolute"
97
+ location="bottom"
98
+ style="bottom: 48px"
99
+ @click:close="message = ''"
100
+ width="100%"
101
+ tile
102
+ class="text-caption"
103
+ >
104
+ {{ message }}
105
+ </v-alert>
106
+ </v-card-text>
107
+
108
+ <v-toolbar density="compact">
109
+ <v-row no-gutters>
110
+ <v-col v-if="isMutable" cols="6">
111
+ <v-btn
112
+ tile
113
+ block
114
+ variant="text"
115
+ class="text-none"
116
+ size="48"
117
+ @click="emits('cancel')"
118
+ >
119
+ Cancel
120
+ </v-btn>
121
+ </v-col>
122
+
123
+ <v-col v-if="localProps.mode === 'view'" cols="6">
124
+ <v-btn
125
+ tile
126
+ block
127
+ variant="text"
128
+ class="text-none"
129
+ size="48"
130
+ @click="emits('close')"
131
+ >
132
+ Close
133
+ </v-btn>
134
+ </v-col>
135
+
136
+ <v-col v-if="localProps.mode === 'view'" cols="6">
137
+ <v-menu>
138
+ <template #activator="{ props }">
139
+ <v-btn
140
+ block
141
+ variant="flat"
142
+ color="black"
143
+ class="text-none"
144
+ height="48"
145
+ v-bind="props"
146
+ tile
147
+ >
148
+ More actions
149
+ </v-btn>
150
+ </template>
151
+
152
+ <v-list class="pa-0">
153
+ <v-list-item @click="emits('edit')">
154
+ <v-list-item-title class="text-subtitle-2">
155
+ Edit Role
156
+ </v-list-item-title>
157
+ </v-list-item>
158
+
159
+ <v-list-item @click="emits('delete')" class="text-red">
160
+ <v-list-item-title class="text-subtitle-2">
161
+ Delete Role
162
+ </v-list-item-title>
163
+ </v-list-item>
164
+ </v-list>
165
+ </v-menu>
166
+ </v-col>
167
+
168
+ <v-col v-if="isMutable" cols="6">
169
+ <v-btn
170
+ tile
171
+ block
172
+ variant="flat"
173
+ color="black"
174
+ class="text-none"
175
+ size="48"
176
+ @click="emits('submit')"
177
+ :disabled="!valid"
178
+ >
179
+ {{ localProps.mode === "add" ? "Add Role" : "Save Changes" }}
180
+ </v-btn>
181
+ </v-col>
182
+ </v-row>
183
+ </v-toolbar>
184
+ </v-card>
185
+ </template>
186
+
187
+ <script setup lang="ts">
188
+ const emits = defineEmits(["submit", "cancel", "close", "edit", "delete"]);
189
+
190
+ const isMutable = computed(() => ["add", "edit"].includes(localProps.mode));
191
+
192
+ const localProps = defineProps({
193
+ title: {
194
+ type: String,
195
+ default: "Role Form",
196
+ },
197
+ app: {
198
+ type: String,
199
+ default: "",
200
+ },
201
+ mode: {
202
+ type: String,
203
+ default: "add",
204
+ },
205
+ });
206
+
207
+ const role = defineModel<TRole>({
208
+ default: () => useRole().role.value,
209
+ });
210
+
211
+ const errorPermissionSelection = computed(() =>
212
+ requireListRule(role.value.permissions ?? [])
213
+ );
214
+
215
+ const message = defineModel("message", { default: "" });
216
+
217
+ const valid = ref(false);
218
+
219
+ const { requiredRule, requireListRule } = useUtils();
220
+
221
+ const apps = ref<TApp[]>([]);
222
+
223
+ const { getAll: getAllApps } = useApps();
224
+
225
+ const isOrg = computed(() => localProps.app === "org");
226
+
227
+ const {
228
+ data: appsData,
229
+ status: appsReqStatus,
230
+ refresh: refreshApps,
231
+ } = await useLazyAsyncData(
232
+ `role-form-get-apps-${localProps.app}`,
233
+ () =>
234
+ getAllApps({
235
+ limit: 20,
236
+ }),
237
+ {
238
+ immediate: isOrg.value,
239
+ }
240
+ );
241
+
242
+ const loadingApps = computed(() => appsReqStatus.value === "pending");
243
+
244
+ watchEffect(() => {
245
+ if (appsData.value) {
246
+ apps.value = appsData.value.items;
247
+ }
248
+ });
249
+
250
+ type PermChild = {
251
+ title: string;
252
+ key: string;
253
+ description: string;
254
+ };
255
+
256
+ type PermGroup = {
257
+ title: string;
258
+ key: string;
259
+ children: PermChild[];
260
+ };
261
+
262
+ const permissions = ref<PermGroup[]>([]);
263
+
264
+ const { getAllPerm } = usePermission();
265
+
266
+ const {
267
+ data: permissionsData,
268
+ status: permissionsReqStatus,
269
+ refresh: refreshPermissions,
270
+ } = await useLazyAsyncData(
271
+ `role-form-get-all-permission-${role.value.app}`,
272
+ () =>
273
+ getAllPerm({
274
+ app: role.value.app,
275
+ limit: 200,
276
+ }),
277
+ {
278
+ watch: [() => role.value.app],
279
+ }
280
+ );
281
+
282
+ const loadingPermissions = computed(
283
+ () => permissionsReqStatus.value === "pending"
284
+ );
285
+
286
+ function groupPermissions(permissions: TPerm[]): PermGroup[] {
287
+ const groups: Record<string, PermGroup> = {};
288
+
289
+ for (const perm of permissions) {
290
+ if (perm.status !== "active") continue;
291
+
292
+ const groupKey = perm.group;
293
+
294
+ if (!groups[groupKey]) {
295
+ groups[groupKey] = {
296
+ title: groupKey,
297
+ key: groupKey,
298
+ children: [],
299
+ };
300
+ }
301
+
302
+ groups[groupKey].children.push({
303
+ title: perm.name,
304
+ key: perm.key,
305
+ description: perm.description,
306
+ });
307
+ }
308
+
309
+ return Object.values(groups);
310
+ }
311
+
312
+ watchEffect(() => {
313
+ if (
314
+ permissionsData.value &&
315
+ permissionsData.value &&
316
+ permissionsData.value.items
317
+ ) {
318
+ let items = permissionsData.value.items;
319
+ if (localProps.mode === "view") {
320
+ items = permissionsData.value.items.filter((p: TPerm) =>
321
+ role.value.permissions?.includes(p.key)
322
+ );
323
+ }
324
+ permissions.value = groupPermissions(items);
325
+ }
326
+ });
327
+ </script>