@7365admin1/layer-common 1.11.37 → 1.11.39
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 +12 -0
- package/components/CameraForm.vue +58 -13
- package/components/CameraMain.vue +19 -3
- package/components/ClientMain.vue +91 -70
- package/components/InvitationClientForm.vue +37 -19
- package/components/InvitationForm.vue +35 -5
- package/components/Layout/NavigationDrawer.vue +49 -26
- package/components/RolePermissionFormCreate.vue +106 -46
- package/components/ScheduleTaskForm.vue +168 -18
- package/components/ScheduleTaskMain.vue +115 -4
- package/components/VehicleForm.vue +6 -1
- package/components/VehicleManagement.vue +27 -17
- package/components/VisitorManagement.vue +4 -4
- package/composables/useMember.ts +16 -0
- package/composables/useSiteSettings.ts +1 -0
- package/composables/useVehicle.ts +14 -1
- package/composables/useWorkOrder.ts +13 -7
- package/package.json +1 -1
- package/types/camera.d.ts +1 -0
- package/types/org.d.ts +1 -0
|
@@ -24,7 +24,11 @@
|
|
|
24
24
|
</v-row>
|
|
25
25
|
</v-col>
|
|
26
26
|
|
|
27
|
-
<v-col
|
|
27
|
+
<v-col
|
|
28
|
+
v-if="!props.hideApp && props.adminState && APP === 'organization'"
|
|
29
|
+
cols="12"
|
|
30
|
+
class="mt-2"
|
|
31
|
+
>
|
|
28
32
|
<v-row no-gutters>
|
|
29
33
|
<InputLabel class="text-capitalize" title="App" required />
|
|
30
34
|
<v-col cols="12">
|
|
@@ -55,7 +59,7 @@
|
|
|
55
59
|
</v-row>
|
|
56
60
|
</v-col>
|
|
57
61
|
|
|
58
|
-
<v-col v-if="hasSite" cols="12">
|
|
62
|
+
<v-col v-if="hasSite && props.adminState" cols="12">
|
|
59
63
|
<v-row no-gutters>
|
|
60
64
|
<InputLabel class="text-capitalize" title="Site" />
|
|
61
65
|
<v-col cols="12">
|
|
@@ -139,6 +143,8 @@ import useUser from '../composables/useUser';
|
|
|
139
143
|
import useUtils from '../composables/useUtils';
|
|
140
144
|
|
|
141
145
|
const APP = useRuntimeConfig().public.APP;
|
|
146
|
+
|
|
147
|
+
|
|
142
148
|
const props = defineProps({
|
|
143
149
|
title: {
|
|
144
150
|
type: String,
|
|
@@ -177,13 +183,21 @@ const props = defineProps({
|
|
|
177
183
|
metadata: { app: "organization", role: "" },
|
|
178
184
|
}),
|
|
179
185
|
},
|
|
186
|
+
adminState: {
|
|
187
|
+
type: Boolean,
|
|
188
|
+
default: true
|
|
189
|
+
},
|
|
190
|
+
hideApp: {
|
|
191
|
+
type: Boolean,
|
|
192
|
+
default: false
|
|
193
|
+
}
|
|
180
194
|
});
|
|
181
195
|
|
|
182
196
|
const emit = defineEmits(["cancel", "success", "success:create-more"]);
|
|
183
197
|
|
|
184
198
|
const validForm = ref(false);
|
|
185
199
|
const form = ref<HTMLFormElement | null>(null);
|
|
186
|
-
const app = computed(() => useRuntimeConfig().public.APP ?? "");
|
|
200
|
+
// const app = computed(() => useRuntimeConfig().public.APP ?? "");
|
|
187
201
|
|
|
188
202
|
const loading = reactive({
|
|
189
203
|
submittingForm: false,
|
|
@@ -200,8 +214,24 @@ const invite = ref<Record<string, any>>({
|
|
|
200
214
|
isMemberInvite: false,
|
|
201
215
|
});
|
|
202
216
|
|
|
203
|
-
|
|
204
|
-
|
|
217
|
+
watch(
|
|
218
|
+
() => [props.hideApp, props.adminState, props.app, props.org],
|
|
219
|
+
() => {
|
|
220
|
+
invite.value.org = props.org || ""
|
|
221
|
+
|
|
222
|
+
if (props.hideApp) {
|
|
223
|
+
invite.value.app = "property_management_agency"
|
|
224
|
+
return
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
invite.value.app = props.adminState
|
|
228
|
+
? (props.app || "")
|
|
229
|
+
: "property_management_agency"
|
|
230
|
+
},
|
|
231
|
+
{ immediate: true }
|
|
232
|
+
)
|
|
233
|
+
// invite.value.app = props.app;
|
|
234
|
+
// invite.value.org = props.org ?? "";
|
|
205
235
|
|
|
206
236
|
if (props.mode === "edit") {
|
|
207
237
|
invite.value.email = props.invite.email;
|
|
@@ -1,45 +1,68 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<v-navigation-drawer
|
|
2
|
+
<v-navigation-drawer
|
|
3
|
+
v-model="drawer"
|
|
4
|
+
permanent
|
|
5
|
+
floating
|
|
6
|
+
class="bg-primary"
|
|
7
|
+
>
|
|
3
8
|
<v-list>
|
|
4
9
|
<v-list-item>
|
|
5
10
|
<v-list-item-title class="text-h6 text-white">
|
|
6
11
|
{{ props.title || APP_NAME }}
|
|
7
12
|
</v-list-item-title>
|
|
8
13
|
</v-list-item>
|
|
14
|
+
|
|
9
15
|
<slot name="action" />
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
16
|
+
|
|
17
|
+
<!-- NAVIGATION ITEMS -->
|
|
18
|
+
<v-tooltip
|
|
19
|
+
v-for="(item, index) in props.navigationItems"
|
|
20
|
+
:key="`${item.title}-${index}`"
|
|
21
|
+
location="right"
|
|
13
22
|
>
|
|
14
|
-
<
|
|
15
|
-
v-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
23
|
+
<template #activator="{ props: tooltipProps }">
|
|
24
|
+
<div v-bind="tooltipProps">
|
|
25
|
+
<NavigationItem
|
|
26
|
+
:title="item.title"
|
|
27
|
+
:icon="item.icon"
|
|
28
|
+
:route="item.route"
|
|
29
|
+
:link="item.link"
|
|
30
|
+
:class="{
|
|
31
|
+
'opacity-50 pointer-events-none': item.disabled
|
|
32
|
+
}"
|
|
33
|
+
@click="() => handleClick(item)"
|
|
34
|
+
/>
|
|
35
|
+
</div>
|
|
36
|
+
</template>
|
|
37
|
+
|
|
38
|
+
<!-- TOOLTIP ONLY WHEN DISABLED -->
|
|
39
|
+
<span v-if="item.disabled">
|
|
40
|
+
Please complete onboarding first
|
|
41
|
+
</span>
|
|
42
|
+
</v-tooltip>
|
|
43
|
+
|
|
44
|
+
<!-- SERVICES SLOT -->
|
|
45
|
+
<slot name="services" />
|
|
32
46
|
</v-list>
|
|
33
47
|
</v-navigation-drawer>
|
|
34
48
|
</template>
|
|
35
49
|
|
|
36
50
|
<script setup lang="ts">
|
|
37
51
|
const props = defineProps({
|
|
38
|
-
navigationItems: { type: Array
|
|
52
|
+
navigationItems: { type: Array, required: true },
|
|
39
53
|
title: { type: String, default: "" }
|
|
40
|
-
})
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
const emit = defineEmits(['navigate'])
|
|
41
57
|
|
|
42
|
-
const { drawer } = useLocal()
|
|
58
|
+
const { drawer } = useLocal()
|
|
59
|
+
const { APP_NAME } = useRuntimeConfig().public
|
|
43
60
|
|
|
44
|
-
|
|
61
|
+
/**
|
|
62
|
+
* GLOBAL CLICK CONTROL
|
|
63
|
+
*/
|
|
64
|
+
const handleClick = (item: any) => {
|
|
65
|
+
if (item.disabled) return
|
|
66
|
+
emit('navigate', item)
|
|
67
|
+
}
|
|
45
68
|
</script>
|
|
@@ -26,17 +26,11 @@
|
|
|
26
26
|
</v-row>
|
|
27
27
|
</v-col>
|
|
28
28
|
|
|
29
|
-
<v-col cols="12" class="mt-2">
|
|
29
|
+
<v-col v-if="props.siteState" cols="12" class="mt-2">
|
|
30
30
|
<v-row no-gutters>
|
|
31
31
|
<InputLabel title="Site" />
|
|
32
32
|
<v-col cols="12">
|
|
33
|
-
<v-text-field
|
|
34
|
-
:model-value="siteName"
|
|
35
|
-
density="comfortable"
|
|
36
|
-
readonly
|
|
37
|
-
disabled
|
|
38
|
-
:loading="loadSite"
|
|
39
|
-
/>
|
|
33
|
+
<v-text-field :model-value="siteName" density="comfortable" readonly disabled :loading="loadSite" />
|
|
40
34
|
</v-col>
|
|
41
35
|
</v-row>
|
|
42
36
|
</v-col>
|
|
@@ -53,12 +47,21 @@
|
|
|
53
47
|
</v-radio-group>
|
|
54
48
|
</v-col>
|
|
55
49
|
|
|
50
|
+
<v-col cols="12" class="mt-2">
|
|
51
|
+
<v-btn
|
|
52
|
+
size="small"
|
|
53
|
+
variant="tonal"
|
|
54
|
+
class="text-none mb-2"
|
|
55
|
+
@click="() => toggleSelectAll(!isAllSelected)"
|
|
56
|
+
>
|
|
57
|
+
{{ isAllSelected ? 'Unselect All' : 'Select All' }}
|
|
58
|
+
</v-btn>
|
|
59
|
+
</v-col>
|
|
60
|
+
|
|
56
61
|
<v-col cols="12" class="mt-3">
|
|
57
62
|
<v-card variant="outlined" border="thin" rounded="lg">
|
|
58
63
|
<template
|
|
59
|
-
v-for="(
|
|
60
|
-
actions, resourceKey, resourceIndex
|
|
61
|
-
) in props.permissions"
|
|
64
|
+
v-for="(actions, resourceKey, resourceIndex) in safePermissions"
|
|
62
65
|
:key="resourceKey"
|
|
63
66
|
>
|
|
64
67
|
<v-divider v-if="resourceIndex > 0" />
|
|
@@ -94,18 +97,14 @@
|
|
|
94
97
|
<!-- Expanded checkboxes when toggled on -->
|
|
95
98
|
<template v-if="isCategoryEnabled(String(resourceKey))">
|
|
96
99
|
<template
|
|
97
|
-
v-for="(
|
|
100
|
+
v-for="([actionKey, action]) in Object.entries(actions || {})"
|
|
98
101
|
:key="actionKey"
|
|
99
102
|
>
|
|
100
103
|
<v-divider />
|
|
101
104
|
<v-list-item density="compact" :ripple="false">
|
|
102
105
|
<template #prepend>
|
|
103
106
|
<v-checkbox-btn
|
|
104
|
-
:model-value="
|
|
105
|
-
selectedPermissions.includes(
|
|
106
|
-
`${String(resourceKey)}:${String(actionKey)}`
|
|
107
|
-
)
|
|
108
|
-
"
|
|
107
|
+
:model-value="permState.selected.includes(`${String(resourceKey)}:${String(actionKey)}`)"
|
|
109
108
|
@update:model-value="
|
|
110
109
|
toggleAction(
|
|
111
110
|
String(resourceKey),
|
|
@@ -204,59 +203,90 @@ const props = defineProps({
|
|
|
204
203
|
type: String,
|
|
205
204
|
default: "app",
|
|
206
205
|
},
|
|
206
|
+
siteState: {
|
|
207
|
+
type: Boolean,
|
|
208
|
+
default: true,
|
|
209
|
+
},
|
|
207
210
|
});
|
|
208
211
|
|
|
209
212
|
const emit = defineEmits(["cancel", "success", "success:create-more"]);
|
|
210
213
|
|
|
211
214
|
const validForm = ref(false);
|
|
212
215
|
const name = ref("");
|
|
213
|
-
const selectedPermissions = ref<Array<string>>([]);
|
|
214
216
|
const platform = ref("web");
|
|
215
217
|
const disable = ref(false);
|
|
216
218
|
const message = ref("");
|
|
217
219
|
|
|
218
|
-
const { requiredRule
|
|
220
|
+
const { requiredRule } = useUtils();
|
|
219
221
|
const { createRole } = useRole();
|
|
220
222
|
const { getSiteById } = useSite();
|
|
221
223
|
|
|
222
|
-
|
|
224
|
+
// Gom selected + enabled vào 1 shallowRef duy nhất
|
|
225
|
+
// để chỉ trigger 1 render cycle khi update cả 2
|
|
226
|
+
const permState = shallowRef({
|
|
227
|
+
selected: [] as string[],
|
|
228
|
+
enabled: [] as string[],
|
|
229
|
+
})
|
|
223
230
|
|
|
224
|
-
function
|
|
225
|
-
return
|
|
226
|
-
.length;
|
|
231
|
+
function isCategoryEnabled(resource: string) {
|
|
232
|
+
return permState.value.enabled.includes(resource)
|
|
227
233
|
}
|
|
228
234
|
|
|
229
|
-
function
|
|
230
|
-
return
|
|
235
|
+
function selectedActionCount(resource: string) {
|
|
236
|
+
return permState.value.selected.filter(p => p.startsWith(`${resource}:`)).length
|
|
231
237
|
}
|
|
232
238
|
|
|
233
239
|
function toggleCategory(resource: string, value: boolean | null) {
|
|
234
|
-
const
|
|
240
|
+
const { selected, enabled } = permState.value
|
|
235
241
|
if (value) {
|
|
236
|
-
|
|
242
|
+
if (!enabled.includes(resource)) {
|
|
243
|
+
permState.value = { selected, enabled: [...enabled, resource] }
|
|
244
|
+
}
|
|
237
245
|
} else {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
);
|
|
246
|
+
permState.value = {
|
|
247
|
+
selected: selected.filter(p => !p.startsWith(`${resource}:`)),
|
|
248
|
+
enabled: enabled.filter(r => r !== resource),
|
|
249
|
+
}
|
|
243
250
|
}
|
|
244
|
-
enabledCategories.value = updated;
|
|
245
251
|
}
|
|
246
252
|
|
|
247
253
|
function toggleAction(resource: string, action: string, value: boolean | null) {
|
|
248
|
-
|
|
254
|
+
if (value !== true && value !== false) return
|
|
255
|
+
const key = `${resource}:${action}`
|
|
256
|
+
const { selected, enabled } = permState.value
|
|
249
257
|
if (value) {
|
|
250
|
-
if (!
|
|
251
|
-
|
|
258
|
+
if (!selected.includes(key)) {
|
|
259
|
+
permState.value = { selected: [...selected, key], enabled }
|
|
252
260
|
}
|
|
253
261
|
} else {
|
|
254
|
-
|
|
255
|
-
(p) => p !== key
|
|
256
|
-
);
|
|
262
|
+
permState.value = { selected: selected.filter(p => p !== key), enabled }
|
|
257
263
|
}
|
|
258
264
|
}
|
|
259
265
|
|
|
266
|
+
function toggleSelectAll(value: boolean) {
|
|
267
|
+
if (!value) {
|
|
268
|
+
permState.value = { selected: [], enabled: [] }
|
|
269
|
+
return
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const perms = safePermissions.value
|
|
273
|
+
if (!perms) return
|
|
274
|
+
|
|
275
|
+
const allKeys: string[] = []
|
|
276
|
+
const categories: string[] = []
|
|
277
|
+
|
|
278
|
+
for (const [resource, actions] of Object.entries(perms)) {
|
|
279
|
+
if (!resource || !actions || typeof actions !== 'object') continue
|
|
280
|
+
categories.push(resource)
|
|
281
|
+
for (const action of Object.keys(actions)) {
|
|
282
|
+
if (action) allKeys.push(`${resource}:${action}`)
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// 1 assignment duy nhất = 1 render cycle, không crash
|
|
287
|
+
permState.value = { selected: allKeys, enabled: categories }
|
|
288
|
+
}
|
|
289
|
+
|
|
260
290
|
const siteName = ref("");
|
|
261
291
|
const loadSite = ref(false);
|
|
262
292
|
|
|
@@ -286,7 +316,7 @@ async function submit() {
|
|
|
286
316
|
const platformValue = platform.value === "web" ? "website" : platform.value;
|
|
287
317
|
await createRole({
|
|
288
318
|
name: name.value,
|
|
289
|
-
permissions:
|
|
319
|
+
permissions: permState.value.selected,
|
|
290
320
|
type: props.type,
|
|
291
321
|
org: props.org,
|
|
292
322
|
site: props.siteId,
|
|
@@ -301,11 +331,41 @@ async function submit() {
|
|
|
301
331
|
}
|
|
302
332
|
|
|
303
333
|
function cancel() {
|
|
304
|
-
name.value = ""
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
emit("cancel");
|
|
334
|
+
name.value = ""
|
|
335
|
+
permState.value = { selected: [], enabled: [] }
|
|
336
|
+
platform.value = "web"
|
|
337
|
+
message.value = ""
|
|
338
|
+
emit("cancel")
|
|
310
339
|
}
|
|
340
|
+
|
|
341
|
+
const safePermissions = computed(() => {
|
|
342
|
+
const p = props.permissions || {}
|
|
343
|
+
const cleaned: Record<string, any> = {}
|
|
344
|
+
|
|
345
|
+
for (const [k, v] of Object.entries(p)) {
|
|
346
|
+
if (v && typeof v === 'object' && !Array.isArray(v)) {
|
|
347
|
+
cleaned[k] = v
|
|
348
|
+
} else {
|
|
349
|
+
cleaned[k] = {}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
return cleaned
|
|
354
|
+
})
|
|
355
|
+
|
|
356
|
+
const isAllSelected = computed(() => {
|
|
357
|
+
const perms = safePermissions.value
|
|
358
|
+
const allKeys: string[] = []
|
|
359
|
+
|
|
360
|
+
Object.entries(perms).forEach(([resource, actions]) => {
|
|
361
|
+
if (!actions) return
|
|
362
|
+
Object.keys(actions).forEach((action) => {
|
|
363
|
+
if (action) allKeys.push(`${resource}:${action}`)
|
|
364
|
+
})
|
|
365
|
+
})
|
|
366
|
+
|
|
367
|
+
return allKeys.length > 0 &&
|
|
368
|
+
allKeys.every(k => permState.value.selected.includes(k))
|
|
369
|
+
})
|
|
370
|
+
|
|
311
371
|
</script>
|