feat(admin): migrate groups page to vue 3 composable

pull/5698/head
Nicolas Giard 2 years ago
parent 1eefdb06e6
commit 7e344fc6fa
No known key found for this signature in database
GPG Key ID: 85061B8F9D55B7C8

@ -341,7 +341,7 @@ exports.up = async knex => {
// -> GENERATE IDS // -> GENERATE IDS
const groupAdminId = uuid() const groupAdminId = uuid()
const groupGuestId = '10000000-0000-4000-0000-000000000001' const groupGuestId = '10000000-0000-4000-8000-000000000001'
const siteId = uuid() const siteId = uuid()
const authModuleId = uuid() const authModuleId = uuid()
const userAdminId = uuid() const userAdminId = uuid()

@ -9,7 +9,7 @@ extend type Query {
): [Group] ): [Group]
groupById( groupById(
id: Int! id: UUID!
): Group ): Group
} }
@ -19,22 +19,25 @@ extend type Mutation {
): GroupResponse ): GroupResponse
updateGroup( updateGroup(
id: Int! id: UUID!
patch: GroupUpdateInput! name: String!
redirectOnLogin: String!
permissions: [String]!
rules: [GroupRuleInput]!
): DefaultResponse ): DefaultResponse
deleteGroup( deleteGroup(
id: Int! id: UUID!
): DefaultResponse ): DefaultResponse
assignUserToGroup( assignUserToGroup(
groupId: Int! groupId: UUID!
userId: Int! userId: UUID!
): DefaultResponse ): DefaultResponse
unassignUserFromGroup( unassignUserFromGroup(
groupId: Int! groupId: UUID!
userId: Int! userId: UUID!
): DefaultResponse ): DefaultResponse
} }
@ -48,46 +51,60 @@ type GroupResponse {
} }
type Group { type Group {
id: Int id: UUID
name: String name: String
isSystem: Boolean isSystem: Boolean
redirectOnLogin: String redirectOnLogin: String
redirectOnFirstLogin: String
redirectOnLogout: String
permissions: [String] permissions: [String]
pageRules: [PageRule] rules: [GroupRule]
users: [UserMinimal] users(
page: Int
pageSize: Int
orderBy: UserOrderBy
orderByDirection: OrderByDirection
# Filter by name / email
filter: String
): [UserMinimal]
userCount: Int
createdAt: Date createdAt: Date
updatedAt: Date updatedAt: Date
} }
type PageRule { type GroupRule {
id: String id: UUID
deny: Boolean name: String
match: PageRuleMatch mode: GroupRuleMode
match: GroupRuleMatch
roles: [String] roles: [String]
path: String path: String
locales: [String] locales: [String]
sites: [UUID]
} }
input GroupUpdateInput { input GroupRuleInput {
id: UUID!
name: String! name: String!
redirectOnLogin: String! mode: GroupRuleMode!
permissions: [String]! match: GroupRuleMatch!
pageRules: [PageRuleInput]!
}
input PageRuleInput {
id: String!
deny: Boolean!
match: PageRuleMatch!
roles: [String]! roles: [String]!
path: String! path: String!
locales: [String]! locales: [String]!
sites: [UUID]
}
enum GroupRuleMode {
ALLOW
DENY
FORCEALLOW
} }
enum PageRuleMatch { enum GroupRuleMatch {
START START
EXACT EXACT
END END
REGEX REGEX
TAG TAG
TAGALL
} }

@ -4,12 +4,12 @@ q-page.admin-groups
.col-auto .col-auto
img.admin-icon.animated.fadeInLeft(src='/_assets/icons/fluent-people.svg') img.admin-icon.animated.fadeInLeft(src='/_assets/icons/fluent-people.svg')
.col.q-pl-md .col.q-pl-md
.text-h5.text-primary.animated.fadeInLeft {{ $t('admin.groups.title') }} .text-h5.text-primary.animated.fadeInLeft {{ t('admin.groups.title') }}
.text-subtitle1.text-grey.animated.fadeInLeft.wait-p2s {{ $t('admin.groups.subtitle') }} .text-subtitle1.text-grey.animated.fadeInLeft.wait-p2s {{ t('admin.groups.subtitle') }}
.col-auto.flex.items-center .col-auto.flex.items-center
q-input.denser.q-mr-sm( q-input.denser.q-mr-sm(
outlined outlined
v-model='search' v-model='state.search'
dense dense
:class='$q.dark.isActive ? `bg-dark` : `bg-white`' :class='$q.dark.isActive ? `bg-dark` : `bg-white`'
) )
@ -28,12 +28,12 @@ q-page.admin-groups
flat flat
color='secondary' color='secondary'
@click='load' @click='load'
:loading='loading > 0' :loading='state.loading > 0'
) )
q-btn( q-btn(
unelevated unelevated
icon='las la-plus' icon='las la-plus'
:label='$t(`admin.groups.create`)' :label='t(`admin.groups.create`)'
color='primary' color='primary'
@click='createGroup' @click='createGroup'
) )
@ -42,15 +42,15 @@ q-page.admin-groups
.col-12 .col-12
q-card.shadow-1 q-card.shadow-1
q-table( q-table(
:rows='groups' :rows='state.groups'
:columns='headers' :columns='headers'
row-key='id' row-key='id'
flat flat
hide-header hide-header
hide-bottom hide-bottom
:rows-per-page-options='[0]' :rows-per-page-options='[0]'
:loading='loading > 0' :loading='state.loading > 0'
:filter='search' :filter='state.search'
) )
template(v-slot:body-cell-id='props') template(v-slot:body-cell-id='props')
q-td(:props='props') q-td(:props='props')
@ -71,7 +71,7 @@ q-page.admin-groups
:color='$q.dark.isActive ? `dark-6` : `grey-2`' :color='$q.dark.isActive ? `dark-6` : `grey-2`'
:text-color='$q.dark.isActive ? `white` : `grey-8`' :text-color='$q.dark.isActive ? `white` : `grey-8`'
dense dense
) {{$t('admin.groups.usersCount', { count: props.value })}} ) {{t('admin.groups.usersCount', { count: props.value })}}
template(v-slot:body-cell-edit='props') template(v-slot:body-cell-edit='props')
q-td(:props='props') q-td(:props='props')
q-btn.acrylic-btn.q-mr-sm( q-btn.acrylic-btn.q-mr-sm(
@ -79,7 +79,7 @@ q-page.admin-groups
:to='`/_admin/groups/` + props.row.id' :to='`/_admin/groups/` + props.row.id'
icon='las la-pen' icon='las la-pen'
color='indigo' color='indigo'
:label='$t(`common.actions.edit`)' :label='t(`common.actions.edit`)'
no-caps no-caps
) )
q-btn.acrylic-btn( q-btn.acrylic-btn(
@ -91,136 +91,178 @@ q-page.admin-groups
) )
</template> </template>
<script> <script setup>
import gql from 'graphql-tag' import gql from 'graphql-tag'
import cloneDeep from 'lodash/cloneDeep' import cloneDeep from 'lodash/cloneDeep'
import { createMetaMixin } from 'quasar' import { useI18n } from 'vue-i18n'
import { sync } from 'vuex-pathify' import { useMeta, useQuasar } from 'quasar'
import { computed, onBeforeUnmount, onMounted, reactive, watch } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { useAdminStore } from 'src/stores/admin'
import GroupCreateDialog from '../components/GroupCreateDialog.vue' import GroupCreateDialog from '../components/GroupCreateDialog.vue'
import GroupDeleteDialog from '../components/GroupDeleteDialog.vue' import GroupDeleteDialog from '../components/GroupDeleteDialog.vue'
export default { // QUASAR
mixins: [
createMetaMixin(function () { const $q = useQuasar()
return {
title: this.$t('admin.groups.title') // STORES
}
}) const adminStore = useAdminStore()
],
data () { // ROUTER
return {
groups: [], const router = useRouter()
loading: 0, const route = useRoute()
search: ''
} // I18N
},
computed: { const { t } = useI18n()
overlay: sync('admin/overlay', false),
headers () { // META
return [
{ useMeta({
align: 'center', title: t('admin.groups.title')
field: 'id', })
name: 'id',
sortable: false, // DATA
style: 'width: 20px'
}, const state = reactive({
{ groups: [],
label: this.$t('common.field.name'), loading: 0,
align: 'left', search: ''
field: 'name', })
name: 'name',
sortable: true const headers = [
}, {
{ align: 'center',
label: this.$t('admin.groups.userCount'), field: 'id',
align: 'center', name: 'id',
field: 'userCount', sortable: false,
name: 'usercount', style: 'width: 20px'
sortable: false,
style: 'width: 150px'
},
{
label: '',
align: 'right',
field: 'edit',
name: 'edit',
sortable: false,
style: 'width: 250px'
}
]
}
},
watch: {
overlay (newValue, oldValue) {
if (newValue === '' && oldValue === 'GroupEditOverlay') {
this.$router.push('/_admin/groups')
this.load()
}
},
$route: 'checkOverlay'
}, },
mounted () { {
this.checkOverlay() label: t('common.field.name'),
this.load() align: 'left',
field: 'name',
name: 'name',
sortable: true
}, },
beforeUnmount () { {
this.overlay = '' label: t('admin.groups.userCount'),
align: 'center',
field: 'userCount',
name: 'usercount',
sortable: false,
style: 'width: 150px'
}, },
methods: { {
async load () { label: '',
this.loading++ align: 'right',
this.$q.loading.show() field: 'edit',
const resp = await this.$apollo.query({ name: 'edit',
query: gql` sortable: false,
query getGroups { style: 'width: 250px'
groups { }
id ]
name
isSystem watch(() => adminStore.overlay, (newValue, oldValue) => {
userCount if (newValue === '' && oldValue === 'GroupEditOverlay') {
createdAt router.push('/_admin/groups')
updatedAt load()
} }
})
watch(() => route, () => {
checkOverlay()
})
// METHODS
async function load () {
state.loading++
$q.loading.show()
try {
const resp = await APOLLO_CLIENT.query({
query: gql`
query getGroups {
groups {
id
name
isSystem
userCount
createdAt
updatedAt
} }
`,
fetchPolicy: 'network-only'
})
this.groups = cloneDeep(resp?.data?.groups)
this.$q.loading.hide()
this.loading--
},
checkOverlay () {
if (this.$route.params && this.$route.params.id) {
this.$store.set('admin/overlayOpts', { id: this.$route.params.id })
this.$store.set('admin/overlay', 'GroupEditOverlay')
} else {
this.$store.set('admin/overlay', '')
}
},
createGroup () {
this.$q.dialog({
component: GroupCreateDialog
}).onOk(() => {
this.load()
})
},
editGroup (gr) {
this.$router.push(`/_admin/groups/${gr.id}`)
},
deleteGroup (gr) {
this.$q.dialog({
component: GroupDeleteDialog,
componentProps: {
group: gr
} }
}).onOk(() => { `,
this.load() fetchPolicy: 'network-only'
}) })
} state.groups = cloneDeep(resp?.data?.groups)
} catch (err) {
$q.notify({
type: 'negative',
message: 'Failed to load groups.',
caption: err.message
})
} }
$q.loading.hide()
state.loading--
}
function checkOverlay () {
if (route.params && route.params.id) {
adminStore.$patch({
overlayOpts: { id: route.params.id },
overlay: 'GroupEditOverlay'
})
} else {
adminStore.$patch({
overlay: ''
})
}
}
function createGroup () {
$q.dialog({
component: GroupCreateDialog
}).onOk(() => {
load()
})
}
function editGroup (gr) {
router.push(`/_admin/groups/${gr.id}`)
}
function deleteGroup (gr) {
$q.dialog({
component: GroupDeleteDialog,
componentProps: {
group: gr
}
}).onOk(() => {
load()
})
} }
// MOUNTED
onMounted(() => {
checkOverlay()
load()
})
// BEFORE UNMOUNT
onBeforeUnmount(() => {
adminStore.$patch({
overlay: ''
})
})
</script> </script>
<style lang='scss'> <style lang='scss'>

@ -41,7 +41,7 @@ const routes = [
// { path: ':siteid/theme', component: () => import('../pages/AdminTheme.vue') }, // { path: ':siteid/theme', component: () => import('../pages/AdminTheme.vue') },
// -> Users // -> Users
// { path: 'auth', component: () => import('../pages/AdminAuth.vue') }, // { path: 'auth', component: () => import('../pages/AdminAuth.vue') },
// { path: 'groups/:id?/:section?', component: () => import('../pages/AdminGroups.vue') }, { path: 'groups/:id?/:section?', component: () => import('../pages/AdminGroups.vue') },
// { path: 'users/:id?/:section?', component: () => import('../pages/AdminUsers.vue') }, // { path: 'users/:id?/:section?', component: () => import('../pages/AdminUsers.vue') },
// -> System // -> System
// { path: 'api', component: () => import('../pages/AdminApi.vue') }, // { path: 'api', component: () => import('../pages/AdminApi.vue') },

Loading…
Cancel
Save