|
@@ -0,0 +1,263 @@
|
|
|
|
|
+<template>
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <DefaultHeaderPage title="Grupos de Permissões" :show-filter-icon="false" />
|
|
|
|
|
+ <div class="q-px-sm">
|
|
|
|
|
+ <DefaultTable
|
|
|
|
|
+ ref="tableRef"
|
|
|
|
|
+ title="Lista de Grupos de Permissões"
|
|
|
|
|
+ :columns="columns"
|
|
|
|
|
+ :api-call="getUserTypes"
|
|
|
|
|
+ description="grupos"
|
|
|
|
|
+ :add-item="canAdd"
|
|
|
|
|
+ add-item-label="Novo Grupo"
|
|
|
|
|
+ @on-add-item="openGroup()"
|
|
|
|
|
+ >
|
|
|
|
|
+ <template #body-cell-actions="{ row }">
|
|
|
|
|
+ <q-td>
|
|
|
|
|
+ <div class="row no-wrap justify-center q-gutter-sm">
|
|
|
|
|
+ <q-btn
|
|
|
|
|
+ v-if="canEdit"
|
|
|
|
|
+ outline
|
|
|
|
|
+ icon="mdi-shield-key-outline"
|
|
|
|
|
+ color="primary"
|
|
|
|
|
+ @click="openPermissions(row)"
|
|
|
|
|
+ >
|
|
|
|
|
+ <q-tooltip>Permissões</q-tooltip>
|
|
|
|
|
+ </q-btn>
|
|
|
|
|
+ <q-btn
|
|
|
|
|
+ v-if="canEdit"
|
|
|
|
|
+ outline
|
|
|
|
|
+ icon="mdi-pencil-outline"
|
|
|
|
|
+ color="primary"
|
|
|
|
|
+ @click="openGroup(row)"
|
|
|
|
|
+ >
|
|
|
|
|
+ <q-tooltip>Editar grupo</q-tooltip>
|
|
|
|
|
+ </q-btn>
|
|
|
|
|
+ <q-btn
|
|
|
|
|
+ v-if="canDelete"
|
|
|
|
|
+ outline
|
|
|
|
|
+ icon="mdi-trash-can-outline"
|
|
|
|
|
+ color="negative"
|
|
|
|
|
+ @click="remove(row)"
|
|
|
|
|
+ >
|
|
|
|
|
+ <q-tooltip>Remover grupo</q-tooltip>
|
|
|
|
|
+ </q-btn>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </q-td>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </DefaultTable>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <q-dialog v-model="groupDialog">
|
|
|
|
|
+ <q-card style="min-width: 440px">
|
|
|
|
|
+ <DefaultDialogHeader
|
|
|
|
|
+ :title="editingGroup ? 'Editar Grupo' : 'Novo Grupo'"
|
|
|
|
|
+ @close="groupDialog = false"
|
|
|
|
|
+ />
|
|
|
|
|
+ <q-form @submit="saveGroup">
|
|
|
|
|
+ <q-card-section class="column q-gutter-md">
|
|
|
|
|
+ <DefaultInput
|
|
|
|
|
+ v-model="form.label"
|
|
|
|
|
+ label="Nome"
|
|
|
|
|
+ outlined
|
|
|
|
|
+ :rules="[(value) => !!value || 'Campo obrigatório']"
|
|
|
|
|
+ />
|
|
|
|
|
+ <DefaultInput
|
|
|
|
|
+ v-model="form.slug"
|
|
|
|
|
+ label="Código"
|
|
|
|
|
+ outlined
|
|
|
|
|
+ hint="Ex.: ATENDIMENTO"
|
|
|
|
|
+ :rules="[
|
|
|
|
|
+ (value) =>
|
|
|
|
|
+ /^[A-Z0-9_]+$/.test(value) ||
|
|
|
|
|
+ 'Use letras maiúsculas, números e underline',
|
|
|
|
|
+ ]"
|
|
|
|
|
+ />
|
|
|
|
|
+ </q-card-section>
|
|
|
|
|
+ <q-card-actions align="right">
|
|
|
|
|
+ <q-btn v-close-popup label="Cancelar" outline color="primary" />
|
|
|
|
|
+ <q-btn
|
|
|
|
|
+ label="Salvar"
|
|
|
|
|
+ type="submit"
|
|
|
|
|
+ color="primary"
|
|
|
|
|
+ :loading="saving"
|
|
|
|
|
+ />
|
|
|
|
|
+ </q-card-actions>
|
|
|
|
|
+ </q-form>
|
|
|
|
|
+ </q-card>
|
|
|
|
|
+ </q-dialog>
|
|
|
|
|
+
|
|
|
|
|
+ <q-dialog v-model="permissionsDialog" full-height>
|
|
|
|
|
+ <q-card class="column no-wrap" style="width: 760px; max-width: 95vw">
|
|
|
|
|
+ <DefaultDialogHeader
|
|
|
|
|
+ :title="`Permissões: ${editingGroup?.label ?? ''}`"
|
|
|
|
|
+ @close="permissionsDialog = false"
|
|
|
|
|
+ />
|
|
|
|
|
+ <q-card-section class="col scroll q-pa-none">
|
|
|
|
|
+ <q-inner-loading :showing="loadingPermissions" />
|
|
|
|
|
+ <q-list separator>
|
|
|
|
|
+ <q-item v-for="menu in visibleMenus" :key="menu.scope">
|
|
|
|
|
+ <q-item-section
|
|
|
|
|
+ ><q-item-label>{{ menu.label }}</q-item-label></q-item-section
|
|
|
|
|
+ >
|
|
|
|
|
+ <q-item-section side>
|
|
|
|
|
+ <div class="row q-gutter-sm">
|
|
|
|
|
+ <q-checkbox
|
|
|
|
|
+ v-for="level in levelsFor(menu.scope)"
|
|
|
|
|
+ :key="level.key"
|
|
|
|
|
+ :label="level.label"
|
|
|
|
|
+ :model-value="hasBit(menu.scope, level.bit)"
|
|
|
|
|
+ @update:model-value="toggleBit(menu.scope, level.bit)"
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </q-item-section>
|
|
|
|
|
+ </q-item>
|
|
|
|
|
+ </q-list>
|
|
|
|
|
+ </q-card-section>
|
|
|
|
|
+ <q-card-actions align="right" class="q-pa-md">
|
|
|
|
|
+ <q-btn v-close-popup label="Cancelar" outline color="primary" />
|
|
|
|
|
+ <q-btn
|
|
|
|
|
+ label="Salvar"
|
|
|
|
|
+ color="primary"
|
|
|
|
|
+ :loading="saving"
|
|
|
|
|
+ @click="savePermissions"
|
|
|
|
|
+ />
|
|
|
|
|
+ </q-card-actions>
|
|
|
|
|
+ </q-card>
|
|
|
|
|
+ </q-dialog>
|
|
|
|
|
+ </div>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script setup>
|
|
|
|
|
+import { computed, ref, useTemplateRef } from "vue";
|
|
|
|
|
+import { useQuasar } from "quasar";
|
|
|
|
|
+import DefaultHeaderPage from "src/components/layout/DefaultHeaderPage.vue";
|
|
|
|
|
+import DefaultTable from "src/components/defaults/DefaultTable.vue";
|
|
|
|
|
+import DefaultDialogHeader from "src/components/defaults/DefaultDialogHeader.vue";
|
|
|
|
|
+import DefaultInput from "src/components/defaults/DefaultInput.vue";
|
|
|
|
|
+import { permissionStore } from "src/stores/permission";
|
|
|
|
|
+import { MENUS, LEVELS, normalizeBits } from "./permission_menus";
|
|
|
|
|
+import {
|
|
|
|
|
+ getUserTypes,
|
|
|
|
|
+ createUserType,
|
|
|
|
|
+ updateUserType,
|
|
|
|
|
+ deleteUserType,
|
|
|
|
|
+ getUserTypePermissions,
|
|
|
|
|
+ updateUserTypePermissions,
|
|
|
|
|
+} from "src/api/user_type";
|
|
|
|
|
+
|
|
|
|
|
+const $q = useQuasar();
|
|
|
|
|
+const access = permissionStore();
|
|
|
|
|
+const tableRef = useTemplateRef("tableRef");
|
|
|
|
|
+const canAdd = computed(() =>
|
|
|
|
|
+ access.getAccess("franchisee_permissions", "add"),
|
|
|
|
|
+);
|
|
|
|
|
+const canEdit = computed(() =>
|
|
|
|
|
+ access.getAccess("franchisee_permissions", "edit"),
|
|
|
|
|
+);
|
|
|
|
|
+const canDelete = computed(() =>
|
|
|
|
|
+ access.getAccess("franchisee_permissions", "delete"),
|
|
|
|
|
+);
|
|
|
|
|
+const columns = [
|
|
|
|
|
+ {
|
|
|
|
|
+ name: "label",
|
|
|
|
|
+ label: "Nome",
|
|
|
|
|
+ field: "label",
|
|
|
|
|
+ align: "left",
|
|
|
|
|
+ sortable: true,
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ name: "slug",
|
|
|
|
|
+ label: "Código",
|
|
|
|
|
+ field: "slug",
|
|
|
|
|
+ align: "left",
|
|
|
|
|
+ sortable: true,
|
|
|
|
|
+ },
|
|
|
|
|
+ { name: "actions", label: "Ações", align: "center", required: true },
|
|
|
|
|
+];
|
|
|
|
|
+const groupDialog = ref(false);
|
|
|
|
|
+const permissionsDialog = ref(false);
|
|
|
|
|
+const editingGroup = ref(null);
|
|
|
|
|
+const saving = ref(false);
|
|
|
|
|
+const loadingPermissions = ref(false);
|
|
|
|
|
+const form = ref({ label: "", slug: "" });
|
|
|
|
|
+const permissions = ref({});
|
|
|
|
|
+
|
|
|
|
|
+const openGroup = (group = null) => {
|
|
|
|
|
+ editingGroup.value = group;
|
|
|
|
|
+ form.value = { label: group?.label ?? "", slug: group?.slug ?? "" };
|
|
|
|
|
+ groupDialog.value = true;
|
|
|
|
|
+};
|
|
|
|
|
+const saveGroup = async () => {
|
|
|
|
|
+ saving.value = true;
|
|
|
|
|
+ try {
|
|
|
|
|
+ editingGroup.value
|
|
|
|
|
+ ? await updateUserType(editingGroup.value.id, form.value)
|
|
|
|
|
+ : await createUserType(form.value);
|
|
|
|
|
+ groupDialog.value = false;
|
|
|
|
|
+ tableRef.value?.refresh();
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ saving.value = false;
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const remove = (group) =>
|
|
|
|
|
+ $q
|
|
|
|
|
+ .dialog({
|
|
|
|
|
+ title: "Confirmar exclusão",
|
|
|
|
|
+ message: `Remover o grupo “${group.label}”?`,
|
|
|
|
|
+ cancel: true,
|
|
|
|
|
+ })
|
|
|
|
|
+ .onOk(async () => {
|
|
|
|
|
+ await deleteUserType(group.id);
|
|
|
|
|
+ tableRef.value?.refresh();
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+const visibleMenus = computed(() =>
|
|
|
|
|
+ MENUS.filter((menu) => permissions.value[menu.scope]),
|
|
|
|
|
+);
|
|
|
|
|
+
|
|
|
|
|
+const levelsFor = (scope) =>
|
|
|
|
|
+ LEVELS.filter((level) => permissions.value[scope].available_bits & level.bit);
|
|
|
|
|
+
|
|
|
|
|
+const hasBit = (scope, bit) => !!(permissions.value[scope]?.bits & bit);
|
|
|
|
|
+
|
|
|
|
|
+const toggleBit = (scope, bit) => {
|
|
|
|
|
+ permissions.value[scope].bits = normalizeBits(
|
|
|
|
|
+ permissions.value[scope].bits ^ bit,
|
|
|
|
|
+ );
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const openPermissions = async (group) => {
|
|
|
|
|
+ editingGroup.value = group;
|
|
|
|
|
+ permissionsDialog.value = true;
|
|
|
|
|
+ loadingPermissions.value = true;
|
|
|
|
|
+ try {
|
|
|
|
|
+ const payload = await getUserTypePermissions(group.id);
|
|
|
|
|
+ permissions.value = Object.fromEntries(
|
|
|
|
|
+ payload.permissions.map((item) => [
|
|
|
|
|
+ item.scope,
|
|
|
|
|
+ { ...item, bits: normalizeBits(item.bits) },
|
|
|
|
|
+ ]),
|
|
|
|
|
+ );
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ loadingPermissions.value = false;
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const savePermissions = async () => {
|
|
|
|
|
+ saving.value = true;
|
|
|
|
|
+ try {
|
|
|
|
|
+ await updateUserTypePermissions(
|
|
|
|
|
+ editingGroup.value.id,
|
|
|
|
|
+ Object.values(permissions.value).map((item) => ({
|
|
|
|
|
+ permission_id: item.id,
|
|
|
|
|
+ bits: item.bits,
|
|
|
|
|
+ })),
|
|
|
|
|
+ );
|
|
|
|
|
+ permissionsDialog.value = false;
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ saving.value = false;
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+</script>
|