Skip to content

Commit 6708d32

Browse files
committed
feat: roles crud
1 parent 9cb46a6 commit 6708d32

File tree

12 files changed

+325
-488
lines changed

12 files changed

+325
-488
lines changed

.stylelintrc.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
"@stylistic/stylelint-config"
66
],
77
"plugins": [
8-
"stylelint-use-logical-spec",
98
"@stylistic/stylelint-plugin"
109
],
1110
"overrides": [
@@ -30,7 +29,6 @@
3029
}
3130
],
3231
"@stylistic/indentation": 2,
33-
"liberty/use-logical-spec": true,
3432
"selector-class-pattern": null,
3533
"color-function-notation": null,
3634
"annotation-no-unknown": [

app/assets/locale/en.json

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,5 +137,24 @@
137137
"Users": "Users",
138138
"Are you sure you want to delete this shortcut?": "Are you sure you want to delete this shortcut?",
139139
"Account settings updated successfully": "Account settings updated successfully",
140-
"Stripe customer self-service portal is not currently available!": "Stripe customer self-service portal is not currently available!"
140+
"Stripe customer self-service portal is not currently available!": "Stripe customer self-service portal is not currently available!",
141+
"Edit Permission": "Edit Permission",
142+
"Create Permission": "Create Permission",
143+
"Be careful!": "Be careful!",
144+
"By modifying permission, you might break the system permissions functionality. Please ensure you\\'re absolutely certain before proceeding.": "By modifying permission, you might break the system permissions functionality. Please ensure you\\'re absolutely certain before proceeding.",
145+
"Select Permission": "Select Permission",
146+
"Create": "Create",
147+
"Read": "Read",
148+
"Update": "Update",
149+
"Delete": "Delete",
150+
"Manage": "Manage",
151+
"Fill Module Name": "Fill Module Name",
152+
"Select Scope": "Select Scope",
153+
"All": "All",
154+
"Creator": "Creator",
155+
"Custom": "Custom",
156+
"Fill Scope Value": "Fill Scope Value",
157+
"Submit": "Submit",
158+
"Total {number} permissions": "Total {number} permissions",
159+
"Edit Role": "Edit Role"
141160
}

app/assets/locale/vi.json

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,5 +137,24 @@
137137
"Users": "Người dùng",
138138
"Are you sure you want to delete this shortcut?": "Bạn có chắc chắn muốn xóa phím tắt này không?",
139139
"Account settings updated successfully": "Thông tin tài khoản đã được cập nhật thành công",
140-
"Stripe customer self-service portal is not currently available!": "Cổng tự phục vụ của thanh toán điện tử Stripe hiện không khả dụng!"
140+
"Stripe customer self-service portal is not currently available!": "Cổng tự phục vụ của thanh toán điện tử Stripe hiện không khả dụng!",
141+
"Edit Permission": "Chỉnh sửa quyền",
142+
"Create Permission": "Tạo quyền",
143+
"Be careful!": "Hãy cẩn thận!",
144+
"By modifying permission, you might break the system permissions functionality. Please ensure you\\'re absolutely certain before proceeding.": "Bằng cách chỉnh sửa quyền, bạn có thể làm hỏng chức năng quyền của hệ thống. Vui lòng đảm bảo bạn hoàn toàn chắc chắn trước khi tiếp tục.",
145+
"Select Permission": "Chọn quyền",
146+
"Create": "Tạo",
147+
"Read": "Đọc",
148+
"Update": "Cập nhật",
149+
"Delete": "Xóa",
150+
"Manage": "Quản lý",
151+
"Fill Module Name": "Điền tên mô-đun",
152+
"Select Scope": "Chọn phạm vi",
153+
"All": "Tất cả",
154+
"Creator": "Người tạo",
155+
"Custom": "Tùy chỉnh",
156+
"Fill Scope Value": "Điền giá trị phạm vi",
157+
"Submit": "Gửi",
158+
"Total {number} permissions": "Tổng cộng {number} quyền",
159+
"Edit Role": "Chỉnh sửa vai trò"
141160
}

app/components/permissions/AddEditPermissionDialog.vue

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
<script setup lang="ts">
22
import type { VForm } from 'vuetify/components/VForm'
3-
import { useRoleStore } from '@base/stores/admin/role'
43
54
import type { sysPermissionTable } from '@base/server/db/schemas/sys_permissions.schema'
65
import type { InferSelectModel } from 'drizzle-orm'
7-
import { usePermissionStore } from '@base/stores/admin/permission'
86
import { cloneDeep } from 'lodash-es'
97
import { PermissionAction, PermissionScope } from '@base/server/db/schemas'
108
import { requiredValidator } from '#imports'
@@ -63,12 +61,10 @@ async function handleSubmit() {
6361
const { valid } = await formTemplate.value.validate()
6462
6563
if (valid) {
66-
if (formData.value.id) {
64+
if (formData.value.id)
6765
emit('edit', formData.value)
68-
}
69-
else {
66+
else
7067
emit('create', formData.value)
71-
}
7268
}
7369
}
7470
}

app/components/roles/AddEditRoleDialog.vue

Lines changed: 75 additions & 161 deletions
Original file line numberDiff line numberDiff line change
@@ -1,213 +1,127 @@
11
<script setup lang="ts">
2-
import { cloneDeep, debounce } from 'lodash-es'
2+
import { debounce, values } from 'lodash-es'
33
import { VForm } from 'vuetify/components/VForm'
4-
import { usePermissionStore } from '~/stores/admin/permission'
5-
import type { PivotRolePermission, Role } from '~/stores/admin/role'
6-
7-
interface Props {
8-
isDialogVisible: boolean
9-
roleData?: Partial<PivotRolePermission>
10-
}
11-
interface Emit {
12-
(e: 'update:isDialogVisible', value: boolean): void
13-
(e: 'update:rolePermissions', value: Partial<PivotRolePermission>): void
14-
}
15-
16-
const props = defineProps<Props>()
17-
const emit = defineEmits<Emit>()
18-
19-
const permissionStore = usePermissionStore()
20-
const { permissionList } = storeToRefs(permissionStore)
21-
const { fetchPermissions } = permissionStore
22-
23-
const refPermissionForm = ref<VForm>()
24-
const isFormValid = ref(false)
25-
26-
const localRoleData = ref<Partial<PivotRolePermission>>({
27-
id: '',
28-
name: '',
29-
permissions: [],
30-
})
31-
32-
// 👉 Fetch Query
33-
const queryKeyword = ref('')
34-
const queryOptions = ref<ParsedFilterQuery> ({
35-
keyword: '',
36-
keywordLower: '',
37-
sortBy: 'action',
38-
sortAsc: true,
39-
limit: 10,
40-
page: 1,
41-
withCount: true,
42-
})
43-
44-
const handleSearch = debounce((keyword: string) => {
45-
queryOptions.value.keyword = keyword
46-
}, 1000)
47-
48-
watch(queryKeyword, (newValue) => {
49-
handleSearch(newValue)
50-
})
51-
52-
watch(queryOptions.value, async () => {
53-
await fetchPermissions(queryOptions.value)
54-
})
55-
56-
// 👉 select all and none
57-
const isSelectAll = computed(() => {
58-
return (
59-
localRoleData.value.permissions?.length === permissionList.value.length
60-
)
4+
import { usePermissionStore } from '@base/stores/admin/permission'
5+
import type { Permission } from '@base/stores/admin/permission'
6+
import type { PivotRolePermission, RoleWithPermissions } from '~/stores/admin/role'
7+
import { requiredValidator } from '#imports'
8+
9+
const props = defineProps<{
10+
role?: RoleWithPermissions | null
11+
permissions: Permission[]
12+
}>()
13+
14+
const emit = defineEmits<{
15+
(e: 'edit', payload: ReturnType<typeof getDefaultFormData>): void
16+
(e: 'create', payload: ReturnType<typeof getDefaultFormData>): void
17+
}>()
18+
19+
const modelValue = defineModel<boolean>({
20+
default: false,
6121
})
6222
63-
function handleCheckAllPermissions() {
64-
localRoleData.value.permissions = [...permissionList.value]
65-
}
66-
67-
function handleCheckNonePermissions() {
68-
localRoleData.value.permissions = []
69-
}
70-
71-
// 👉 Submit
72-
function onReset() {
73-
emit('update:isDialogVisible', false)
74-
75-
refPermissionForm.value?.reset()
76-
77-
localRoleData.value = {
23+
function getDefaultFormData(): { id: string, name: string, permissions: string[] } {
24+
return {
7825
id: '',
7926
name: '',
8027
permissions: [],
8128
}
8229
}
8330
84-
function onSubmit() {
85-
refPermissionForm.value?.validate().then(({ valid }) => {
86-
if (valid) {
87-
emit('update:rolePermissions', props.roleData
88-
? localRoleData.value
89-
: {
90-
name: localRoleData.value.name,
91-
permissions: localRoleData.value.permissions,
92-
})
93-
94-
onReset()
95-
}
96-
})
97-
}
31+
const formData = ref(getDefaultFormData())
32+
33+
syncRef(computed(() => props.role), formData, {
34+
direction: 'ltr',
35+
transform: {
36+
ltr(left) {
37+
if (left) {
38+
return {
39+
id: left.id,
40+
name: left.name,
41+
permissions: left.permissions.map(p => p.permission.id),
42+
}
43+
}
44+
45+
return getDefaultFormData()
46+
},
47+
},
48+
})
9849
99-
watch(() => props.roleData, (newRoleData) => {
100-
if (newRoleData) {
101-
localRoleData.value = {
102-
id: newRoleData.id,
103-
name: newRoleData.name,
104-
permissions: permissionList.value.filter(permission =>
105-
newRoleData.permissions?.some(p => p.id === permission.id),
106-
),
50+
watch(modelValue, (value) => {
51+
if (!value) {
52+
formData.value = {
53+
id: '',
54+
name: '',
55+
permissions: [],
10756
}
10857
}
109-
}, { immediate: true })
58+
})
11059
111-
onMounted(async () => {
112-
await fetchPermissions()
60+
const formTemplate = useTemplateRef('formRef')
11361
114-
if (props.roleData) {
115-
localRoleData.value.permissions = permissionList.value.filter(permission =>
116-
props.roleData?.permissions?.some(p =>
117-
p.id === permission.id,
118-
),
119-
)
62+
async function handleSubmit() {
63+
try {
64+
if (formTemplate.value) {
65+
const { valid } = await formTemplate.value.validate()
66+
67+
if (valid) {
68+
if (formData.value.id)
69+
emit('edit', formData.value)
70+
else
71+
emit('create', formData.value)
72+
}
73+
}
12074
}
121-
})
75+
catch {}
76+
}
12277
</script>
12378

12479
<template>
12580
<VDialog
81+
v-model="modelValue"
12682
:width="$vuetify.display.smAndDown ? '100%' : 900"
127-
:model-value="props.isDialogVisible"
128-
@update:model-value="onReset"
12983
>
13084
<VCard class="pa-sm-11 pa-3">
13185
<!-- 👉 dialog close btn -->
13286
<DialogCloseBtn
13387
variant="text"
13488
size="default"
135-
@click="onReset"
89+
@click="modelValue = false"
13690
/>
13791

13892
<VCardText>
13993
<!-- 👉 Title -->
14094
<div class="text-center mb-10">
14195
<h4 class="text-h4 mb-2">
142-
{{ !props.roleData ? 'Add' : 'Edit' }} Role
96+
{{ role ? $t('Edit Role') : $t('Create Role') }}
14397
</h4>
14498
</div>
14599

146100
<!-- 👉 Form -->
147-
<VForm ref="refPermissionForm" v-model="isFormValid" @submit.prevent>
101+
<VForm ref="formRef" @submit.prevent="handleSubmit">
148102
<!-- 👉 Role name -->
149103
<VTextField
150-
v-model="localRoleData.name"
104+
v-model="formData.name"
151105
:rules="[requiredValidator]"
152106
class="mb-6"
153107
label="Role Name"
154108
placeholder="Enter Role Name"
155109
/>
156110

157-
<!-- 👉 Role filter -->
158-
<VRow>
159-
<VCol cols="12" md="4" class="d-flex align-center">
160-
<h5 class="text-h5">
161-
Permissions
162-
</h5>
163-
</VCol>
164-
165-
<VCol cols="12" md="8">
166-
<div class="d-flex align-center gap-3 flex-wrap">
167-
<p>
168-
Select:
169-
</p>
170-
171-
<VBtn
172-
:color="isSelectAll ? 'primary' : 'secondary'"
173-
variant="text"
174-
text="All"
175-
@click="handleCheckAllPermissions"
176-
/>
177-
178-
<VBtn
179-
:color="localRoleData?.permissions?.length === 0 ? 'primary' : 'secondary'"
180-
variant="text"
181-
text="None"
182-
@click="handleCheckNonePermissions"
183-
/>
184-
185-
<div class="flex-grow-1">
186-
<VTextField
187-
v-model="queryKeyword"
188-
placeholder="Search"
189-
density="compact"
190-
prepend-inner-icon="ri-search-line"
191-
/>
192-
</div>
193-
</div>
194-
</VCol>
195-
</VRow>
196-
197111
<!-- 👉 Role Permissions -->
198112
<div class="mt-6 d-flex flex-wrap gap-3 permission-container">
199-
<div v-for="permission in permissionList" :key="permission.id" class="d-flex align-center gap-2 flex-wrap">
113+
<div v-for="permission in permissions" :key="permission.id" class="d-flex align-center gap-2 flex-wrap">
200114
<VLabel>
201115
<VCheckbox
202-
v-model="localRoleData.permissions"
203-
:value="permission"
116+
v-model="formData.permissions"
117+
:value="permission.id"
204118
class="border pa-2"
205-
:class="{ 'border border-primary text-primary': localRoleData?.permissions && localRoleData?.permissions.some(p => p.id === permission.id) }"
119+
:class="{ 'border border-primary text-primary': formData.permissions.includes(permission.id) }"
206120
multiple
207121
>
208122
<template #label>
209-
<span class="pr-3" :class="{ 'text-primary': localRoleData?.permissions && localRoleData?.permissions.some(p => p.id === permission.id) }">
210-
{{ permission.action }} : {{ permission.subject }}
123+
<span class="pr-3" :class="{ 'text-primary': formData.permissions.includes(permission.id) }">
124+
{{ permission.action }}:{{ permission.subject }}
211125
</span>
212126
</template>
213127
</VCheckbox>
@@ -217,16 +131,16 @@ onMounted(async () => {
217131

218132
<!-- 👉 Actions button -->
219133
<div class="d-flex align-center justify-center gap-3 mt-6">
220-
<VBtn @click="onSubmit">
221-
Submit
134+
<VBtn type="submit">
135+
{{ role ? $t('Update Role') : $t('Create Role') }}
222136
</VBtn>
223137

224138
<VBtn
225139
color="secondary"
226140
variant="outlined"
227-
@click="onReset"
141+
@click="modelValue = false"
228142
>
229-
Cancel
143+
{{ $t('Cancel') }}
230144
</VBtn>
231145
</div>
232146
</VForm>

0 commit comments

Comments
 (0)