Skip to content

Commit 1d2df85

Browse files
committed
permissions selection with multiselect
1 parent da33b51 commit 1d2df85

File tree

4 files changed

+47
-37
lines changed

4 files changed

+47
-37
lines changed

components.d.ts

-10
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,16 @@ declare module 'vue' {
1414
DynamicForm: typeof import('./src/components/DynamicForm.vue')['default']
1515
ErrorBox: typeof import('./src/components/ErrorBox.vue')['default']
1616
Header: typeof import('./src/components/Header.vue')['default']
17-
HeroiconsAdjustmentsVertical: typeof import('~icons/heroicons/adjustments-vertical')['default']
1817
HeroiconsArrowDown20Solid: typeof import('~icons/heroicons/arrow-down20-solid')['default']
1918
HeroiconsArrowPath: typeof import('~icons/heroicons/arrow-path')['default']
2019
HeroiconsBars3Solid: typeof import('~icons/heroicons/bars3-solid')['default']
2120
HeroiconsBoltSolid: typeof import('~icons/heroicons/bolt-solid')['default']
2221
HeroiconsChevronUpDown20Solid: typeof import('~icons/heroicons/chevron-up-down20-solid')['default']
2322
HeroiconsClipboard: typeof import('~icons/heroicons/clipboard')['default']
24-
HeroiconsCloudArrowDownSolid: typeof import('~icons/heroicons/cloud-arrow-down-solid')['default']
2523
HeroiconsCog6Tooth20Solid: typeof import('~icons/heroicons/cog6-tooth20-solid')['default']
2624
HeroiconsDocumentTextSolid: typeof import('~icons/heroicons/document-text-solid')['default']
2725
HeroiconsGlobeAlt: typeof import('~icons/heroicons/globe-alt')['default']
2826
HeroiconsHome20Solid: typeof import('~icons/heroicons/home20-solid')['default']
29-
HeroiconsInformationCircleSolid: typeof import('~icons/heroicons/information-circle-solid')['default']
3027
HeroiconsLink20Solid: typeof import('~icons/heroicons/link20-solid')['default']
3128
HeroiconsMagnifyingGlass20Solid: typeof import('~icons/heroicons/magnifying-glass20-solid')['default']
3229
HeroiconsMicrophoneSolid: typeof import('~icons/heroicons/microphone-solid')['default']
@@ -51,17 +48,13 @@ declare module 'vue' {
5148
Pagination: typeof import('./src/components/Pagination.vue')['default']
5249
PhArrowCounterClockwiseBold: typeof import('~icons/ph/arrow-counter-clockwise-bold')['default']
5350
PhBrainFill: typeof import('~icons/ph/brain-fill')['default']
54-
PhCaretLeftFill: typeof import('~icons/ph/caret-left-fill')['default']
55-
PhCaretRightFill: typeof import('~icons/ph/caret-right-fill')['default']
5651
PhChatCenteredDots: typeof import('~icons/ph/chat-centered-dots')['default']
5752
PhChats: typeof import('~icons/ph/chats')['default']
5853
PhExportBold: typeof import('~icons/ph/export-bold')['default']
5954
PhFileFill: typeof import('~icons/ph/file-fill')['default']
6055
PhFiles: typeof import('~icons/ph/files')['default']
6156
PhFloppyDiskBold: typeof import('~icons/ph/floppy-disk-bold')['default']
6257
PhInfo: typeof import('~icons/ph/info')['default']
63-
PhLightbulbFilamentFill: typeof import('~icons/ph/lightbulb-filament-fill')['default']
64-
PhListMagnifyingGlass: typeof import('~icons/ph/list-magnifying-glass')['default']
6558
PhNut: typeof import('~icons/ph/nut')['default']
6659
PhPencilFill: typeof import('~icons/ph/pencil-fill')['default']
6760
PhPlugFill: typeof import('~icons/ph/plug-fill')['default']
@@ -70,15 +63,12 @@ declare module 'vue' {
7063
PhTextbox: typeof import('~icons/ph/textbox')['default']
7164
PhToolbox: typeof import('~icons/ph/toolbox')['default']
7265
PhTrashFill: typeof import('~icons/ph/trash-fill')['default']
73-
PhUser: typeof import('~icons/ph/user')['default']
74-
PhUserFill: typeof import('~icons/ph/user-fill')['default']
7566
RouterLink: typeof import('vue-router')['RouterLink']
7667
RouterView: typeof import('vue-router')['RouterView']
7768
SelectBox: typeof import('./src/components/SelectBox.vue')['default']
7869
SidePanel: typeof import('./src/components/SidePanel.vue')['default']
7970
ThemeButton: typeof import('./src/components/ThemeButton.vue')['default']
8071
TransitionChild: typeof import('@headlessui/vue')['TransitionChild']
8172
TransitionRoot: typeof import('@headlessui/vue')['TransitionRoot']
82-
UseImage: typeof import('@vueuse/components')['UseImage']
8373
}
8474
}

package.json

+4-2
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@
4545
"vue": "^3.4.31",
4646
"vue-component-type-helpers": "^2.0.26",
4747
"vue-router": "^4.4.0",
48-
"vue3-apexcharts": "^1.5.3"
48+
"vue3-apexcharts": "^1.5.3",
49+
"vue3-select-component": "^0.5.2"
4950
},
5051
"devDependencies": {
5152
"@iconify-json/heroicons": "^1.1.21",
@@ -80,6 +81,7 @@
8081
"unplugin-vue-components": "^0.27.2",
8182
"vite": "^5.3.3",
8283
"vitest": "^2.0.0",
83-
"vue-tsc": "^2.0.26"
84+
"vue-tsc": "^2.0.26",
85+
"vue3-select-component": "^0.5.2"
8486
}
8587
}

pnpm-lock.yaml

+12
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/views/SettingsView.vue

+31-25
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import { startCase, lowerCase, cloneDeep } from 'lodash'
44
import { apiClient, tryRequest } from '@services/ApiService'
55
import ModalBox from '@components/ModalBox.vue'
66
import SidePanel from '@components/SidePanel.vue'
7-
import type { Status, UserCreate, UserResponse, UserUpdate } from 'ccat-api'
7+
import type { AuthPermission, Status, UserCreate, UserResponse, UserUpdate } from 'ccat-api'
8+
import type { Option } from 'vue3-select-component'
9+
import VueSelect from 'vue3-select-component'
810
911
const getStatus = async () => {
1012
const result = await tryRequest(apiClient?.api?.status.home(), 'Getting Cheshire Cat status', 'Unable to fetch Cheshire Cat status')
@@ -55,6 +57,10 @@ const createOrUpdateUser = () => {
5557
else createUser(userInfo as UserCreate)
5658
editPanel.value?.togglePanel()
5759
}
60+
61+
const permissionsOptions = computed(() => Object.values(availablePerms.value)[0].map(permission => ({ label: permission, value: permission })))
62+
63+
const fillAll = (resource: string) => currentUser.value!.permissions![resource] = availablePerms.value[resource]
5864
</script>
5965

6066
<template>
@@ -135,18 +141,20 @@ const createOrUpdateUser = () => {
135141
</button>
136142
</div>-->
137143
<div class="tooltip tooltip-left" data-tip="Edit">
138-
<button class="btn btn-square btn-info btn-xs" :disabled="cannot('EDIT', 'USERS')" @click="() => {
139-
currentUser = cloneDeep(item)
140-
editPanel?.togglePanel()
141-
}">
144+
<button class="btn btn-square btn-info btn-xs" :disabled="cannot('EDIT', 'USERS')"
145+
@click="() => {
146+
currentUser = cloneDeep(item)
147+
editPanel?.togglePanel()
148+
}">
142149
<ph-pencil-fill class="size-4" />
143150
</button>
144151
</div>
145152
<div class="tooltip tooltip-left" data-tip="Delete">
146-
<button :disabled="cannot('DELETE', 'USERS')" class="btn btn-square btn-error btn-xs" @click="() => {
147-
currentUser = cloneDeep(item)
148-
deleteModal?.toggleModal()
149-
}">
153+
<button :disabled="cannot('DELETE', 'USERS')"
154+
class="btn btn-square btn-error btn-xs" @click="() => {
155+
currentUser = cloneDeep(item)
156+
deleteModal?.toggleModal()
157+
}">
150158
<ph-trash-fill class="size-4" />
151159
</button>
152160
</div>
@@ -192,22 +200,20 @@ const createOrUpdateUser = () => {
192200
<div class="label py-0">
193201
<span class="label-text">{{ startCase(lowerCase(r)) }}</span>
194202
</div>
195-
<div class="flex items-center gap-2">
196-
<div v-for="p in l" :key="p" class="form-control">
197-
<label class="label cursor-pointer gap-2 py-1">
198-
<input :checked="currentUser?.permissions?.[r]?.includes(p)" type="checkbox"
199-
class="checkbox-primary checkbox checkbox-xs rounded" @click="() => {
200-
if (currentUser?.permissions?.[r]?.includes(p)) {
201-
currentUser.permissions[r] = currentUser.permissions[r].filter((perm: string) => perm !== p)
202-
} else if (currentUser?.permissions?.[r]) {
203-
currentUser!.permissions![r].push(p)
204-
} else {
205-
currentUser!.permissions![r] = [p]
206-
}
207-
}" />
208-
<span class="label-text">{{ p }}</span>
209-
</label>
210-
</div>
203+
<div class="label py-0">
204+
<VueSelect :modelValue="currentUser?.permissions?.[r] ?? []" :options="permissionsOptions"
205+
is-multi :placeholder="`Select permission`" class="w-full" @update:modelValue="(options: AuthPermission[]) => {
206+
if (options.length < (currentUser!.permissions?.[r]?.length || 0)) return
207+
208+
if (!currentUser!.permissions || !(r in currentUser!.permissions))
209+
currentUser!.permissions = { [r]: options } as Record<string, AuthPermission[]>
210+
else currentUser!.permissions![r] = [...new Set([...currentUser!.permissions![r], ...options])]
211+
}" @option-deselected="(option: Option<AuthPermission>) => {
212+
if (currentUser!.permissions && (r in currentUser!.permissions)) {
213+
currentUser!.permissions![r] = currentUser!.permissions![r].filter(p => p !== option.value)
214+
}
215+
}" />
216+
<button class="btn btn-outline btn-info btn-xs ml-1" @click="fillAll(r)">All</button>
211217
</div>
212218
</label>
213219
</div>

0 commit comments

Comments
 (0)