|
|
@@ -0,0 +1,222 @@
|
|
|
+<template>
|
|
|
+ <q-dialog ref="dialogRef" @hide="onDialogHide">
|
|
|
+ <q-card style="width: 1100px; max-width: 95vw">
|
|
|
+ <DefaultDialogHeader
|
|
|
+ :title="`Permissões — ${group.label}`"
|
|
|
+ @close="onDialogCancel"
|
|
|
+ />
|
|
|
+
|
|
|
+ <q-card-section class="q-pa-none">
|
|
|
+ <div class="row items-stretch" style="min-height: 440px">
|
|
|
+ <!-- Menu -->
|
|
|
+ <div class="col-12 col-md-6 column">
|
|
|
+ <div class="text-center text-subtitle1 text-weight-medium q-pa-sm">
|
|
|
+ Menu
|
|
|
+ </div>
|
|
|
+ <q-separator />
|
|
|
+
|
|
|
+ <q-inner-loading :showing="loading" />
|
|
|
+
|
|
|
+ <q-list v-if="!loading" separator class="col scroll">
|
|
|
+ <q-item
|
|
|
+ v-for="item in roots"
|
|
|
+ :key="item.id"
|
|
|
+ clickable
|
|
|
+ :active="selectedId === item.id"
|
|
|
+ active-class="bg-blue-1 text-primary"
|
|
|
+ @click="selectedId = item.id"
|
|
|
+ >
|
|
|
+ <q-item-section avatar>
|
|
|
+ <q-icon :name="iconFor(item.scope)" color="dark" />
|
|
|
+ </q-item-section>
|
|
|
+
|
|
|
+ <q-item-section>
|
|
|
+ <q-item-label>{{ item.description }}</q-item-label>
|
|
|
+ <q-item-label caption>
|
|
|
+ <PermissionLevelBadges :bits="bitsById[item.id]" />
|
|
|
+ </q-item-label>
|
|
|
+ </q-item-section>
|
|
|
+
|
|
|
+ <q-item-section side>
|
|
|
+ <q-btn
|
|
|
+ flat
|
|
|
+ round
|
|
|
+ dense
|
|
|
+ icon="mdi-pencil-outline"
|
|
|
+ color="dark"
|
|
|
+ @click.stop
|
|
|
+ >
|
|
|
+ <q-tooltip>Editar permissões</q-tooltip>
|
|
|
+ <PermissionLevelMenu
|
|
|
+ :bits="bitsById[item.id]"
|
|
|
+ :available-bits="item.available_bits"
|
|
|
+ @update:bits="(value) => (bitsById[item.id] = value)"
|
|
|
+ />
|
|
|
+ </q-btn>
|
|
|
+ </q-item-section>
|
|
|
+ </q-item>
|
|
|
+ </q-list>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <q-separator vertical />
|
|
|
+
|
|
|
+ <!-- SubCategorias -->
|
|
|
+ <div class="col column">
|
|
|
+ <div class="text-center text-subtitle1 text-weight-medium q-pa-sm">
|
|
|
+ SubCategorias
|
|
|
+ </div>
|
|
|
+ <q-separator />
|
|
|
+
|
|
|
+ <div
|
|
|
+ v-if="!selected"
|
|
|
+ class="col column flex-center text-center text-foreground q-pa-md"
|
|
|
+ >
|
|
|
+ <div>Nenhum menu selecionado!</div>
|
|
|
+ <div class="text-caption">
|
|
|
+ ← Selecione um menu à esquerda para carregar as subcategorias.
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div
|
|
|
+ v-else-if="!children.length"
|
|
|
+ class="col column flex-center text-center text-foreground q-pa-md"
|
|
|
+ >
|
|
|
+ <div>Nenhuma subcategoria</div>
|
|
|
+ <div class="text-caption">
|
|
|
+ O menu "{{ selected.description }}" não possui subcategorias.
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <q-list v-else separator class="col scroll">
|
|
|
+ <q-item v-for="child in children" :key="child.id">
|
|
|
+ <q-item-section>
|
|
|
+ <q-item-label>{{ child.description }}</q-item-label>
|
|
|
+ <q-item-label caption>
|
|
|
+ <PermissionLevelBadges :bits="bitsById[child.id]" />
|
|
|
+ </q-item-label>
|
|
|
+ </q-item-section>
|
|
|
+
|
|
|
+ <q-item-section side>
|
|
|
+ <q-btn
|
|
|
+ flat
|
|
|
+ round
|
|
|
+ dense
|
|
|
+ icon="mdi-pencil-outline"
|
|
|
+ color="dark"
|
|
|
+ >
|
|
|
+ <q-tooltip>Editar permissões</q-tooltip>
|
|
|
+ <PermissionLevelMenu
|
|
|
+ :bits="bitsById[child.id]"
|
|
|
+ :available-bits="child.available_bits"
|
|
|
+ @update:bits="(value) => (bitsById[child.id] = value)"
|
|
|
+ />
|
|
|
+ </q-btn>
|
|
|
+ </q-item-section>
|
|
|
+ </q-item>
|
|
|
+ </q-list>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </q-card-section>
|
|
|
+
|
|
|
+ <q-separator />
|
|
|
+
|
|
|
+ <q-card-actions align="right">
|
|
|
+ <q-btn label="Cancelar" color="primary" outline @click="onDialogCancel" />
|
|
|
+ <q-btn
|
|
|
+ label="Salvar"
|
|
|
+ color="primary"
|
|
|
+ :loading="saving"
|
|
|
+ :disable="loading"
|
|
|
+ @click="onSave"
|
|
|
+ />
|
|
|
+ </q-card-actions>
|
|
|
+ </q-card>
|
|
|
+ </q-dialog>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+import { computed, onMounted, ref } from "vue";
|
|
|
+import { Notify, useDialogPluginComponent } from "quasar";
|
|
|
+import DefaultDialogHeader from "src/components/defaults/DefaultDialogHeader.vue";
|
|
|
+import PermissionLevelBadges from "src/pages/permissions/components/PermissionLevelBadges.vue";
|
|
|
+import PermissionLevelMenu from "src/pages/permissions/components/PermissionLevelMenu.vue";
|
|
|
+import {
|
|
|
+ getUserTypePermissions,
|
|
|
+ updateUserTypePermissions,
|
|
|
+} from "src/api/user_type";
|
|
|
+import { permissionScopeIcon } from "src/pages/permissions/permission_scopes";
|
|
|
+
|
|
|
+const { group } = defineProps({
|
|
|
+ group: {
|
|
|
+ type: Object,
|
|
|
+ required: true,
|
|
|
+ },
|
|
|
+});
|
|
|
+
|
|
|
+defineEmits([...useDialogPluginComponent.emits]);
|
|
|
+
|
|
|
+const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } =
|
|
|
+ useDialogPluginComponent();
|
|
|
+
|
|
|
+const loading = ref(false);
|
|
|
+const saving = ref(false);
|
|
|
+const permissions = ref([]);
|
|
|
+const bitsById = ref({});
|
|
|
+const selectedId = ref(null);
|
|
|
+
|
|
|
+const roots = computed(() =>
|
|
|
+ permissions.value.filter((p) => p.parent_id === null),
|
|
|
+);
|
|
|
+
|
|
|
+const selected = computed(
|
|
|
+ () => permissions.value.find((p) => p.id === selectedId.value) ?? null,
|
|
|
+);
|
|
|
+
|
|
|
+const children = computed(() =>
|
|
|
+ permissions.value.filter((p) => p.parent_id === selectedId.value),
|
|
|
+);
|
|
|
+
|
|
|
+const iconFor = (scope) => permissionScopeIcon(scope);
|
|
|
+
|
|
|
+const fetchPermissions = async () => {
|
|
|
+ loading.value = true;
|
|
|
+ try {
|
|
|
+ const payload = await getUserTypePermissions(group.slug);
|
|
|
+ permissions.value = payload.permissions;
|
|
|
+ bitsById.value = Object.fromEntries(
|
|
|
+ payload.permissions.map((p) => [p.id, p.bits]),
|
|
|
+ );
|
|
|
+ } catch {
|
|
|
+ Notify.create({
|
|
|
+ message: "Não foi possível carregar as permissões do grupo.",
|
|
|
+ type: "negative",
|
|
|
+ });
|
|
|
+ } finally {
|
|
|
+ loading.value = false;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const onSave = async () => {
|
|
|
+ saving.value = true;
|
|
|
+ try {
|
|
|
+ await updateUserTypePermissions(
|
|
|
+ group.slug,
|
|
|
+ permissions.value.map((p) => ({
|
|
|
+ permission_id: p.id,
|
|
|
+ bits: bitsById.value[p.id] ?? 0,
|
|
|
+ })),
|
|
|
+ );
|
|
|
+ Notify.create({ message: "Permissões atualizadas.", type: "positive" });
|
|
|
+ onDialogOK();
|
|
|
+ } catch {
|
|
|
+ Notify.create({
|
|
|
+ message: "Não foi possível salvar as permissões.",
|
|
|
+ type: "negative",
|
|
|
+ });
|
|
|
+ } finally {
|
|
|
+ saving.value = false;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+onMounted(fetchPermissions);
|
|
|
+</script>
|