Skip to content

Commit 900886d

Browse files
HaoNguyen-Ronimrim12
authored andcommitted
feat: admin crud
1 parent 04c13a0 commit 900886d

15 files changed

+279
-145
lines changed
+88-84
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
<script setup lang="ts">
2+
import { cloneDeep, debounce } from 'lodash-es'
23
import { VForm } from 'vuetify/components/VForm'
34
import { usePermissionStore } from '~/stores/admin/permission'
5+
import type { PivotRolePermission, Role } from '~/stores/admin/role'
46
57
interface Props {
68
isDialogVisible: boolean
7-
roleData?: Partial<Role>
9+
roleData?: Partial<PivotRolePermission>
810
}
911
interface Emit {
1012
(e: 'update:isDialogVisible', value: boolean): void
11-
(e: 'update:rolePermissions', value: Partial<Role>): void
13+
(e: 'update:rolePermissions', value: Partial<PivotRolePermission>): void
1214
}
1315
1416
const props = defineProps<Props>()
@@ -21,89 +23,61 @@ const { fetchPermissions } = permissionStore
2123
const refPermissionForm = ref<VForm>()
2224
const isFormValid = ref(false)
2325
24-
const isSelectAll = ref(false)
25-
const localRoleData = ref<Partial<Role>>({
26+
const localRoleData = ref<Partial<PivotRolePermission>>({
2627
id: '',
2728
name: '',
29+
permissions: [],
2830
})
2931
30-
const permissions = [
31-
{
32-
action: 'Read',
33-
subject: 'All',
34-
value: {
35-
action: 'read',
36-
subject: 'all',
37-
},
38-
},
39-
{
40-
action: 'Read',
41-
subject: 'Category',
42-
value: {
43-
action: 'read',
44-
subject: 'category',
45-
},
46-
},
47-
{
48-
action: 'Create',
49-
subject: 'All',
50-
value: {
51-
action: 'create',
52-
subject: 'all',
53-
},
54-
},
55-
]
56-
57-
// // select all
58-
// watch(isSelectAll, (val) => {
59-
// permissions.value = permissions.value.map(permission => ({
60-
// ...permission,
61-
// read: val,
62-
// write: val,
63-
// create: val,
64-
// }))
65-
// })
66-
67-
// // if Indeterminate is false, then set isSelectAll to false
68-
// watch(isIndeterminate, () => {
69-
// if (!isIndeterminate.value)
70-
// isSelectAll.value = false
71-
// })
72-
73-
// // if all permissions are checked, then set isSelectAll to true
74-
// watch(permissions, () => {
75-
// if (checkedCount.value === (permissions.value.length * 3))
76-
// isSelectAll.value = true
77-
// }, { deep: true })
78-
79-
// // if rolePermissions is not empty, then set permissions
80-
// watch(() => props, () => {
81-
// if (props.rolePermissions && props.rolePermissions.permissions.length) {
82-
// role.value = props.rolePermissions.name
83-
// permissions.value = permissions.value.map((permission) => {
84-
// const rolePermission = props.rolePermissions?.permissions.find(item => item.name === permission.name)
85-
86-
// if (rolePermission) {
87-
// return {
88-
// ...permission,
89-
// ...rolePermission,
90-
// }
91-
// }
92-
93-
// return permission
94-
// })
95-
// }
96-
// })
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)
9747
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+
)
61+
})
62+
63+
function handleCheckAllPermissions() {
64+
localRoleData.value.permissions = [...permissionList.value]
65+
}
66+
67+
function handleCheckNonePermissions() {
68+
localRoleData.value.permissions = []
69+
}
70+
71+
// 👉 Submit
9872
function onReset() {
9973
emit('update:isDialogVisible', false)
10074
101-
isSelectAll.value = false
10275
refPermissionForm.value?.reset()
10376
10477
localRoleData.value = {
10578
id: '',
10679
name: '',
80+
permissions: [],
10781
}
10882
}
10983
@@ -114,6 +88,7 @@ function onSubmit() {
11488
? localRoleData.value
11589
: {
11690
name: localRoleData.value.name,
91+
permissions: localRoleData.value.permissions,
11792
})
11893
11994
onReset()
@@ -123,12 +98,26 @@ function onSubmit() {
12398
12499
watch(() => props.roleData, (newRoleData) => {
125100
if (newRoleData) {
126-
localRoleData.value = { ...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+
),
107+
}
127108
}
128109
}, { immediate: true })
129110
130-
onMounted(() => {
131-
fetchPermissions()
111+
onMounted(async () => {
112+
await fetchPermissions()
113+
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+
)
120+
}
132121
})
133122
</script>
134123

@@ -169,7 +158,7 @@ onMounted(() => {
169158
<VRow>
170159
<VCol cols="12" md="4" class="d-flex align-center">
171160
<h5 class="text-h5">
172-
Role Permissions
161+
Permissions
173162
</h5>
174163
</VCol>
175164

@@ -180,20 +169,22 @@ onMounted(() => {
180169
</p>
181170

182171
<VBtn
183-
color="secondary"
172+
:color="isSelectAll ? 'primary' : 'secondary'"
184173
variant="text"
185174
text="All"
175+
@click="handleCheckAllPermissions"
186176
/>
187177

188178
<VBtn
189-
color="secondary"
179+
:color="localRoleData?.permissions?.length === 0 ? 'primary' : 'secondary'"
190180
variant="text"
191181
text="None"
182+
@click="handleCheckNonePermissions"
192183
/>
193184

194185
<div class="flex-grow-1">
195186
<VTextField
196-
v-model="search"
187+
v-model="queryKeyword"
197188
placeholder="Search"
198189
density="compact"
199190
prepend-inner-icon="ri-search-line"
@@ -204,12 +195,22 @@ onMounted(() => {
204195
</VRow>
205196

206197
<!-- 👉 Role Permissions -->
207-
<div class="mt-6 d-flex flex-wrap gap-3">
208-
<div v-for="permission in permissionList" :key="permission.id" class="d-flex align-center gap-2 flex-wrap border px-3 py-2">
198+
<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">
209200
<VLabel>
210-
<VCheckboxBtn />
211-
212-
{{ permission.action }}:{{ permission.subject }}
201+
<VCheckbox
202+
v-model="localRoleData.permissions"
203+
:value="permission"
204+
class="border pa-2"
205+
:class="{ 'border border-primary text-primary': localRoleData?.permissions && localRoleData?.permissions.some(p => p.id === permission.id) }"
206+
multiple
207+
>
208+
<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 }}
211+
</span>
212+
</template>
213+
</VCheckbox>
213214
</VLabel>
214215
</div>
215216
</div>
@@ -235,4 +236,7 @@ onMounted(() => {
235236
</template>
236237

237238
<style lang="scss" scoped>
239+
.permission-container {
240+
min-block-size: 150px;
241+
}
238242
</style>

app/components/roles/RoleCards.vue

+9-1
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ function editPermission(value: RoleDetails) {
195195
</script>
196196

197197
<template>
198-
<VRow>
198+
<VRow class="card-container">
199199
<!-- 👉 Roles -->
200200
<VCol
201201
v-for="item in roles"
@@ -309,3 +309,11 @@ function editPermission(value: RoleDetails) {
309309
v-model:role-permissions="roleDetail"
310310
/>
311311
</template>
312+
313+
<style lang="scss" scoped>
314+
.card-container {
315+
max-height: 600px;
316+
overflow-y: auto;
317+
margin-bottom: 2rem;
318+
}
319+
</style>

app/pages/test-role.vue

+4-6
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,10 @@ const { roleList, totalRoles, roleDetail } = storeToRefs(roleStore)
2020
const { fetchRoles, fetchRoleDetail, createRole, updateRole, deleteRole, joinRolePermissions } = roleStore
2121
2222
const currentRoleId = ref<string>('')
23-
const currentRoleData = ref<Partial<Role>>({
23+
const currentRoleData = ref<Partial<PivotRolePermission>>({
2424
id: '',
2525
name: '',
26+
permissions: [],
2627
})
2728
const currentDialogAction = ref<DrawerActionTypes>('add')
2829
const currentDialogConfig = ref<RoleDialog>({
@@ -58,6 +59,7 @@ async function handleOpenEditDialog(roleId: string) {
5859
currentRoleData.value = {
5960
id: roleDetail.value?.id,
6061
name: roleDetail.value?.name,
62+
permissions: cloneDeep(roleDetail.value?.permissions),
6163
}
6264
6365
currentDialogConfig.value = {
@@ -67,7 +69,7 @@ async function handleOpenEditDialog(roleId: string) {
6769
}
6870
6971
// 👉 Create or Update
70-
async function handleRoleChange(data: Partial<Role>) {
72+
async function handleRoleChange(data: Partial<PivotRolePermission>) {
7173
if (currentDialogAction.value === DRAWER_ACTION_TYPES.EDIT) {
7274
const { id, ...body } = data
7375
@@ -215,7 +217,3 @@ useLazyAsyncData(
215217
/>
216218
</div>
217219
</template>
218-
219-
<style lang="scss" scoped>
220-
221-
</style>

app/pages/test-user.vue

+9-1
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,15 @@ useLazyAsyncData(
305305
</div>
306306
<!-- 👉 Add user button -->
307307
<VBtn @click="handleOpenAddDrawer()">
308-
Add New User
308+
<template v-if="$vuetify.display.xs">
309+
<VIcon icon="ri-add-line" />
310+
</template>
311+
<template v-else-if="$vuetify.display.mdAndDown">
312+
<span>Add</span> <VIcon icon="ri-add-line" />
313+
</template>
314+
<template v-else>
315+
Add New User
316+
</template>
309317
</VBtn>
310318
</div>
311319
</VCardText>

app/stores/admin/permission.ts

+7-2
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
import type { InferSelectModel } from 'drizzle-orm'
22
import type { ParsedFilterQuery } from '@base/server/utils/filter'
33
import type { sysPermissionTable } from '@base/server/db/schemas/sys_permissions.schema'
4+
import type { RolePermission } from './role'
45

56
export type Permission = InferSelectModel<typeof sysPermissionTable>
67

8+
export interface PermissionWithRelations extends Permission {
9+
rolePermission: RolePermission[]
10+
}
11+
712
export const usePermissionStore = defineStore('permission', () => {
8-
const permissionList = ref<Permission[]>([])
13+
const permissionList = ref<PermissionWithRelations[]>([])
914
const totalPermissions = ref(0)
10-
const permissionDetail = ref<Permission | null>(null)
15+
const permissionDetail = ref<PermissionWithRelations | null>(null)
1116

1217
async function fetchPermissions(options?: ParsedFilterQuery) {
1318
try {

app/stores/admin/role.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
11
import type { InferSelectModel } from 'drizzle-orm'
22
import type { sysRoleTable } from '@base/server/db/schemas/sys_roles.schema'
33
import type { ParsedFilterQuery } from '@base/server/utils/filter'
4+
import type { Permission } from './permission'
5+
import type { sysRolePermissionTable } from '~~/server/db/schemas'
46

57
export type Role = InferSelectModel<typeof sysRoleTable>
8+
export type RolePermission = InferSelectModel<typeof sysRolePermissionTable>
9+
export interface PivotRolePermission extends Partial<Role> {
10+
permissions: Permission[]
11+
}
612

713
export const useRoleStore = defineStore('role', () => {
814
const roleList = ref<Role[]>([])
915
const totalRoles = ref<number>(0)
10-
const roleDetail = ref<Role | null>(null)
16+
const roleDetail = ref<RoleWithRelations | null>(null)
1117

1218
async function fetchRoles(options?: ParsedFilterQuery) {
1319
try {
@@ -29,7 +35,7 @@ export const useRoleStore = defineStore('role', () => {
2935
method: 'GET',
3036
})
3137

32-
roleDetail.value = response.data
38+
roleDetail.value = response
3339
}
3440
catch (error) {
3541
console.error('Error fetching role detail:', error)

0 commit comments

Comments
 (0)