Browse Source

feat(permissions): refactor permission management with new dialogs and components

ebagabee 1 week ago
parent
commit
3241c369c7

+ 29 - 225
src/pages/permissions/PermissionsPage.vue

@@ -8,38 +8,21 @@
         :columns="columns"
         :api-call="getEditableUserTypes"
         description="grupos"
+        :female="false"
         :add-item="canAdd"
         add-item-label="Novo Grupo"
-        @on-add-item="openGroup()"
+        @on-add-item="onAdd"
       >
         <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)"
-              >
+            <div class="row no-wrap justify-center" style="gap: 8px">
+              <q-btn v-if="canEdit" outline icon="mdi-shield-key-outline" style="width: 36px" color="secondary" @click.stop="onEditPermissions(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-btn v-if="canEdit && !row.is_system" outline icon="mdi-pencil-outline" style="width: 36px" color="secondary" @click.stop="onEdit(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-btn v-if="canDelete && !row.is_system" outline icon="mdi-trash-can-outline" style="width: 36px" color="negative" @click.stop="onDelete(row)">
                 <q-tooltip>Remover grupo</q-tooltip>
               </q-btn>
             </div>
@@ -47,219 +30,40 @@
         </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 { computed, defineAsyncComponent, 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 { getUserTypes, deleteUserType } from "src/api/user_type";
 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 AddEditPermissionGroupDialog = defineAsyncComponent(() => import("src/pages/permissions/components/AddEditPermissionGroupDialog.vue"));
+const PermissionGroupDialog = defineAsyncComponent(() => import("src/pages/permissions/components/PermissionGroupDialog.vue"));
 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 access = permissionStore();
+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 getEditableUserTypes = async () => (await getUserTypes()).filter((group) => !group.is_system);
 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 },
+  { 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", field: null, 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 getEditableUserTypes = async () =>
-  (await getUserTypes()).filter((group) => !group.is_system);
-
-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;
-  }
-};
+const refresh = () => tableRef.value?.refresh();
+const onAdd = () => $q.dialog({ component: AddEditPermissionGroupDialog }).onOk(refresh);
+const onEdit = (group) => $q.dialog({ component: AddEditPermissionGroupDialog, componentProps: { group } }).onOk(refresh);
+const onEditPermissions = (group) => $q.dialog({ component: PermissionGroupDialog, componentProps: { group } });
+const onDelete = (group) => $q.dialog({
+  title: "Confirmar exclusão",
+  message: `Remover o grupo "${group.label}"?`,
+  cancel: { label: "Cancelar", outline: true, color: "secondary" },
+  ok: { label: "Remover", color: "negative" },
+  persistent: true,
+}).onOk(async () => { await deleteUserType(group.id); refresh(); });
 </script>

+ 44 - 0
src/pages/permissions/components/AddEditPermissionGroupDialog.vue

@@ -0,0 +1,44 @@
+<template>
+  <q-dialog ref="dialogRef" @hide="onDialogHide">
+    <q-card style="min-width: 440px; max-width: 90vw">
+      <DefaultDialogHeader :title="isEdit ? 'Editar Grupo de Permissões' : 'Novo Grupo de Permissões'" @close="onDialogCancel" />
+      <q-form ref="formRef" @submit="onSave">
+        <q-card-section class="column q-gutter-md">
+          <DefaultInput v-model="form.label" label="Nome" autofocus :rules="[inputRules.required]" @update:model-value="onLabelChange" />
+          <DefaultInput v-model="form.slug" label="Código" hint="Identificador único. Ex.: FINANCEIRO" :rules="[inputRules.required, slugRule]" @update:model-value="onSlugTouched" />
+        </q-card-section>
+        <q-card-actions align="right">
+          <q-btn label="Cancelar" color="negative" outline @click="onDialogCancel" />
+          <q-btn label="Salvar" color="secondary" type="submit" :loading="loading" />
+        </q-card-actions>
+      </q-form>
+    </q-card>
+  </q-dialog>
+</template>
+
+<script setup>
+import { computed, ref, useTemplateRef } from "vue";
+import { useDialogPluginComponent } from "quasar";
+import { useInputRules } from "src/composables/useInputRules";
+import { useSubmitHandler } from "src/composables/useSubmitHandler";
+import { createUserType, updateUserType } from "src/api/user_type";
+import DefaultDialogHeader from "src/components/defaults/DefaultDialogHeader.vue";
+import DefaultInput from "src/components/defaults/DefaultInput.vue";
+
+const { group } = defineProps({ group: { type: Object, default: null } });
+defineEmits([...useDialogPluginComponent.emits]);
+const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } = useDialogPluginComponent();
+const { inputRules } = useInputRules();
+const formRef = useTemplateRef("formRef");
+const isEdit = computed(() => !!group);
+const form = ref({ label: group?.label ?? "", slug: group?.slug ?? "" });
+const slugTouched = ref(isEdit.value);
+const toSlug = (value) => (value ?? "").normalize("NFD").replace(/\p{Diacritic}/gu, "").toUpperCase().replace(/[^A-Z0-9]+/g, "_").replace(/^_+|_+$/g, "");
+const slugRule = (value) => /^[A-Z0-9_]+$/.test(value ?? "") || "Use apenas letras maiúsculas, números e underline.";
+const onLabelChange = (value) => { if (!slugTouched.value) form.value.slug = toSlug(value); };
+const onSlugTouched = (value) => { slugTouched.value = true; form.value.slug = toSlug(value); };
+const { loading, execute } = useSubmitHandler({ formRef, onSuccess: () => onDialogOK() });
+const onSave = () => execute(() => isEdit.value
+  ? updateUserType(group.id, form.value)
+  : createUserType(form.value));
+</script>

+ 114 - 0
src/pages/permissions/components/PermissionGroupDialog.vue

@@ -0,0 +1,114 @@
+<template>
+  <q-dialog ref="dialogRef" full-height @hide="onDialogHide">
+    <q-card class="column no-wrap" style="width: 100%; max-width: 95vw; height: 100%">
+      <DefaultDialogHeader :title="`Permissões: ${group.label}`" @close="onDialogCancel" />
+      <q-card-section class="col column no-wrap q-pa-none" style="min-height: 0">
+        <div class="row items-stretch col no-wrap" style="min-height: 0">
+          <div class="col-12 col-md-6 column no-wrap" style="min-height: 0">
+            <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 menus" :key="item.scope" clickable :active="selectedScope === item.scope" active-class="bg-orange-1 text-secondary" @click="selectedScope = item.scope">
+                <q-item-section avatar><q-icon :name="item.icon" color="dark" /></q-item-section>
+                <q-item-section>
+                  <q-item-label>{{ item.label }}</q-item-label>
+                  <q-item-label caption><PermissionLevelBadges :bits="bitsFor(item.scope)" /></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="bitsFor(item.scope)" :available-bits="availableBitsFor(item.scope)" @update:bits="(value) => setBits(item.scope, value)" />
+                  </q-btn>
+                </q-item-section>
+              </q-item>
+            </q-list>
+          </div>
+          <q-separator vertical />
+          <div class="col column no-wrap" style="min-height: 0">
+            <div class="text-center text-subtitle1 text-weight-medium q-pa-sm">Detalhes</div>
+            <q-separator />
+            <div v-if="!selected" class="col column flex-center text-center text-grey-7 q-pa-md">
+              <div>Nenhum menu selecionado!</div>
+              <div class="text-caption">← Selecione um menu à esquerda para visualizar suas permissões.</div>
+            </div>
+            <q-list v-else separator class="col scroll">
+              <q-item>
+                <q-item-section avatar><q-icon :name="selected.icon" color="dark" /></q-item-section>
+                <q-item-section>
+                  <q-item-label>{{ selected.label }}</q-item-label>
+                  <q-item-label caption><PermissionLevelBadges :bits="bitsFor(selected.scope)" /></q-item-label>
+                </q-item-section>
+                <q-item-section side>
+                  <q-btn flat round dense icon="mdi-pencil-outline" color="dark">
+                    <PermissionLevelMenu :bits="bitsFor(selected.scope)" :available-bits="availableBitsFor(selected.scope)" @update:bits="(value) => setBits(selected.scope, value)" />
+                  </q-btn>
+                </q-item-section>
+              </q-item>
+            </q-list>
+          </div>
+        </div>
+      </q-card-section>
+      <q-separator />
+      <q-card-actions align="right" class="q-pa-md">
+        <q-btn label="Cancelar" color="negative" outline @click="onDialogCancel" />
+        <q-btn label="Salvar" color="secondary" :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 "./PermissionLevelBadges.vue";
+import PermissionLevelMenu from "./PermissionLevelMenu.vue";
+import { getUserTypePermissions, updateUserTypePermissions } from "src/api/user_type";
+import { FRANCHISEE_PERMISSION_MENUS, applyComputedLevels } from "../permission_menus";
+
+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 selectedScope = ref(null);
+const permissionIdByScope = ref({});
+const bitsByScope = ref({});
+const availableBitsByScope = ref({});
+const menus = computed(() => FRANCHISEE_PERMISSION_MENUS.filter((menu) => permissionIdByScope.value[menu.scope] !== undefined));
+const selected = computed(() => menus.value.find((menu) => menu.scope === selectedScope.value) ?? null);
+const bitsFor = (scope) => bitsByScope.value[scope] ?? 0;
+const availableBitsFor = (scope) => availableBitsByScope.value[scope] ?? 0;
+const setBits = (scope, value) => { bitsByScope.value[scope] = value; };
+
+const fetchPermissions = async () => {
+  loading.value = true;
+  try {
+    const payload = await getUserTypePermissions(group.id);
+    const byScope = Object.fromEntries(payload.permissions.map((permission) => [permission.scope, permission]));
+    for (const menu of FRANCHISEE_PERMISSION_MENUS) {
+      const permission = byScope[menu.scope];
+      if (!permission) continue;
+      permissionIdByScope.value[menu.scope] = permission.id;
+      availableBitsByScope.value[menu.scope] = permission.available_bits;
+      bitsByScope.value[menu.scope] = applyComputedLevels(permission.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.id, Object.entries(permissionIdByScope.value).map(([scope, id]) => ({ permission_id: id, bits: bitsByScope.value[scope] ?? 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>

+ 12 - 0
src/pages/permissions/components/PermissionLevelBadges.vue

@@ -0,0 +1,12 @@
+<template>
+  <span v-if="!active.length" class="text-grey-7">Sem acesso</span>
+  <span v-else class="row inline items-center" style="gap: 4px">
+    <q-badge v-for="level in active" :key="level.key" color="secondary" outline :label="level.label" />
+  </span>
+</template>
+<script setup>
+import { computed } from "vue";
+import { PERMISSION_LEVELS } from "src/pages/permissions/permission_menus";
+const { bits } = defineProps({ bits: { type: Number, default: 0 } });
+const active = computed(() => PERMISSION_LEVELS.filter((level) => (bits & level.bit) !== 0));
+</script>

+ 24 - 0
src/pages/permissions/components/PermissionLevelMenu.vue

@@ -0,0 +1,24 @@
+<template>
+  <q-menu anchor="bottom right" self="top right">
+    <q-list style="min-width: 200px">
+      <q-item-label header class="q-pb-xs">Permissões</q-item-label>
+      <q-item v-for="level in availableLevels" :key="level.key" v-ripple="!level.computed" :clickable="!level.computed" @click="toggle(level.bit)">
+        <q-item-section side><q-checkbox :model-value="has(level.bit)" :disable="level.computed" dense @update:model-value="toggle(level.bit)" @click.stop /></q-item-section>
+        <q-item-section><q-item-label :class="{ 'text-grey-7': level.computed }">{{ level.label }}</q-item-label></q-item-section>
+        <q-tooltip v-if="level.computed">{{ level.computedHint }}</q-tooltip>
+      </q-item>
+    </q-list>
+  </q-menu>
+</template>
+<script setup>
+import { computed } from "vue";
+import { PERMISSION_LEVELS, applyComputedLevels } from "src/pages/permissions/permission_menus";
+const { bits, availableBits } = defineProps({ bits: { type: Number, default: 0 }, availableBits: { type: Number, default: 511 } });
+const emit = defineEmits(["update:bits"]);
+const availableLevels = computed(() => PERMISSION_LEVELS.filter((level) => (availableBits & level.bit) !== 0));
+const has = (bit) => (bits & bit) !== 0;
+const toggle = (bit) => {
+  if (PERMISSION_LEVELS.find((level) => level.bit === bit)?.computed) return;
+  emit("update:bits", applyComputedLevels(has(bit) ? bits & ~bit : bits | bit));
+};
+</script>

+ 25 - 21
src/pages/permissions/permission_menus.js

@@ -1,24 +1,28 @@
-export const LEVELS = Object.freeze([
-  { key: "view", label: "Visualizar", bit: 1 },
-  { key: "add", label: "Cadastrar", bit: 2 },
-  { key: "edit", label: "Editar", bit: 4 },
-  { key: "delete", label: "Excluir", bit: 8 },
+export const PERMISSION_BITS = Object.freeze({ menu: 256, view: 1, add: 2, edit: 4, delete: 8 });
+
+export const PERMISSION_LEVELS = Object.freeze([
+  { key: "menu", label: "Menu", bit: PERMISSION_BITS.menu, computed: true, computedHint: "Ativado automaticamente por Visualizar." },
+  { key: "view", label: "Visualizar", bit: PERMISSION_BITS.view },
+  { key: "add", label: "Cadastrar", bit: PERMISSION_BITS.add },
+  { key: "edit", label: "Editar", bit: PERMISSION_BITS.edit },
+  { key: "delete", label: "Excluir", bit: PERMISSION_BITS.delete },
 ]);
 
-export const MENUS = Object.freeze([
-  ["franchisee_dashboard", "Dashboard"],
-  ["franchisee_students", "Alunos"],
-  ["franchisee_contracts", "Contratos"],
-  ["franchisee_classes", "Agenda / Aulas"],
-  ["franchisee_financial", "Financeiro"],
-  ["franchisee_packages", "Pacotes"],
-  ["franchisee_users", "Usuários"],
-  ["franchisee_permissions", "Permissões"],
-  ["franchisee_unit", "Unidade"],
-  ["franchisee_integrations", "APIs"],
-  ["franchisee_support", "Suporte"],
-  ["franchisee_activities", "Atividades"],
-  ["franchisee_orders", "Pedidos"],
-].map(([scope, label]) => ({ scope, label })));
+export const applyComputedLevels = (bits) => bits & PERMISSION_BITS.view
+  ? bits | PERMISSION_BITS.menu
+  : bits & ~PERMISSION_BITS.menu;
 
-export const normalizeBits = (bits) => (bits & 1 ? bits | 256 : bits & ~256);
+export const FRANCHISEE_PERMISSION_MENUS = Object.freeze([
+  { scope: "franchisee_dashboard", label: "Dashboard", icon: "mdi-poll" },
+  { scope: "franchisee_students", label: "Alunos", icon: "mdi-account-outline" },
+  { scope: "franchisee_contracts", label: "Contratos", icon: "mdi-file-document-outline" },
+  { scope: "franchisee_classes", label: "Agenda / Aulas", icon: "mdi-calendar" },
+  { scope: "franchisee_financial", label: "Financeiro", icon: "mdi-finance" },
+  { scope: "franchisee_packages", label: "Pacotes", icon: "mdi-package-variant-closed" },
+  { scope: "franchisee_users", label: "Usuários", icon: "mdi-account-multiple-outline" },
+  { scope: "franchisee_permissions", label: "Permissões", icon: "mdi-shield-account-outline" },
+  { scope: "franchisee_unit", label: "Unidade", icon: "mdi-cog-outline" },
+  { scope: "franchisee_support", label: "Suporte", icon: "mdi-lifebuoy" },
+  { scope: "franchisee_activities", label: "Atividades", icon: "mdi-clipboard-list-outline" },
+  { scope: "franchisee_orders", label: "Pedidos", icon: "mdi-cart-outline" },
+]);