Просмотр исходного кода

feat(classes): update API routes to franchisee-specific endpoints and enhance permission checks for class management

ebagabee 1 неделя назад
Родитель
Сommit
3f22cf54d1

+ 7 - 7
src/api/class.js

@@ -1,38 +1,38 @@
 import api from "src/api";
 
 export const getClasses = async (params = {}) => {
-  const { data } = await api.get("/class", { params });
+  const { data } = await api.get("/franchisee/classes", { params });
   return data.payload;
 };
 
 export const getClass = async (id) => {
-  const { data } = await api.get(`/class/${id}`);
+  const { data } = await api.get(`/franchisee/classes/${id}`);
   return data.payload;
 };
 
 export const createClass = async (payload) => {
-  const { data } = await api.post("/class", payload);
+  const { data } = await api.post("/franchisee/classes", payload);
   return data.payload;
 };
 
 export const updateClass = async (payload, id) => {
-  const { data } = await api.put(`/class/${id}`, payload);
+  const { data } = await api.put(`/franchisee/classes/${id}`, payload);
   return data.payload;
 };
 
 export const deleteClass = async (id) => {
-  const { data } = await api.delete(`/class/${id}`);
+  const { data } = await api.delete(`/franchisee/classes/${id}`);
   return data.payload;
 };
 
 // Alunos com contrato ativo no pacote da aula + presença registrada
 export const getClassStudents = async (id) => {
-  const { data } = await api.get(`/class/${id}/students`);
+  const { data } = await api.get(`/franchisee/classes/${id}/students`);
   return data.payload;
 };
 
 // Salva a lista de presença da aula em lote
 export const saveClassAttendance = async (id, attendances) => {
-  const { data } = await api.post(`/class/${id}/attendances`, { attendances });
+  const { data } = await api.post(`/franchisee/classes/${id}/attendances`, { attendances });
   return data.payload;
 };

+ 10 - 0
src/pages/classes/ClassPage.vue

@@ -41,6 +41,7 @@
         <!-- Direita -->
         <div class="row items-center no-wrap justify-end">
           <q-btn
+            v-if="canAddClass"
             color="primary"
             icon="mdi-plus"
             label="Nova Atividade"
@@ -92,6 +93,7 @@ import MonthGrid from "./components/MonthGrid.vue";
 import DayTimeline from "./components/DayTimeline.vue";
 import { getClasses } from "src/api/class";
 import { userStore } from "src/stores/user";
+import { permissionStore } from "src/stores/permission";
 
 const ClassAttendanceDialog = defineAsyncComponent(
   () => import("./components/ClassAttendanceDialog.vue"),
@@ -102,6 +104,14 @@ const AddEditClassDialog = defineAsyncComponent(
 
 const $q = useQuasar();
 const { selectedUnit } = userStore();
+const permissions = permissionStore();
+const canAdd = computed(() =>
+  permissions.getAccess("franchisee_classes", "add"),
+);
+const canViewPackages = computed(() =>
+  permissions.getAccess("franchisee_packages", "view"),
+);
+const canAddClass = computed(() => canAdd.value && canViewPackages.value);
 
 const view = ref("month"); // 'month' | 'day'
 const currentDate = ref(new Date());

+ 10 - 2
src/pages/classes/components/AddEditClassDialog.vue

@@ -80,6 +80,7 @@
 
               <!-- Instrutor (Neurotrainer da unidade) -->
               <DefaultSelect
+                v-if="canViewUsers"
                 v-model="form.instructor"
                 label="Neurotrainer"
                 :options="instructorOptions"
@@ -138,6 +139,7 @@ import DefaultInputDatePicker from "src/components/defaults/DefaultInputDatePick
 import { createClass, updateClass } from "src/api/class";
 import { getUnitPackages } from "src/api/package";
 import { getUsersByUnit } from "src/api/user";
+import { permissionStore } from "src/stores/permission";
 
 defineEmits([...useDialogPluginComponent.emits]);
 
@@ -151,6 +153,8 @@ const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } =
   useDialogPluginComponent();
 
 const $q = useQuasar();
+const permissions = permissionStore();
+const canViewUsers = permissions.getAccess("franchisee_users", "view");
 
 const formRef = ref(null);
 const saving = ref(false);
@@ -185,11 +189,15 @@ const loadPackages = async () => {
 };
 
 const loadInstructors = async () => {
+  if (!canViewUsers) return;
+
   loadingInstructors.value = true;
   try {
     const users = await getUsersByUnit();
     instructorOptions.value = (users ?? []).filter(
-      (u) => u.user_type === "NEUROTRAINER",
+      (u) =>
+        u.user_type_system_key === "NEUROTRAINER" ||
+        u.user_type === "NEUROTRAINER",
     );
   } catch (e) {
     console.error("Erro ao carregar neurotrainers:", e);
@@ -229,6 +237,6 @@ const onSubmit = async () => {
 
 onMounted(() => {
   loadPackages();
-  loadInstructors();
+  if (canViewUsers) loadInstructors();
 });
 </script>

+ 9 - 1
src/pages/classes/components/ClassAttendanceDialog.vue

@@ -66,6 +66,7 @@
                 <td class="text-center">
                   <div class="row no-wrap items-center justify-center" style="gap: 6px">
                     <q-btn
+                      :disable="!canEdit"
                       round
                       unelevated
                       size="sm"
@@ -77,6 +78,7 @@
                       <q-tooltip>Presente</q-tooltip>
                     </q-btn>
                     <q-btn
+                      :disable="!canEdit"
                       round
                       unelevated
                       size="sm"
@@ -93,13 +95,13 @@
                 <!-- Justificar: só habilita para faltas -->
                 <td class="text-center">
                   <q-btn
+                    :disable="!canEdit || row.in_class"
                     no-caps
                     unelevated
                     size="sm"
                     :color="row.justified ? 'primary' : 'secondary'"
                     text-color="white"
                     :label="row.justified ? 'Justificada' : 'Justificar'"
-                    :disable="row.in_class"
                     @click="openJustify(row)"
                   >
                     <q-tooltip v-if="row.in_class">
@@ -126,6 +128,7 @@
         <q-card-actions align="right" class="q-px-md q-pb-md">
           <q-btn outline color="primary" label="Cancelar" no-caps @click="onDialogCancel" />
           <q-btn
+            v-if="canEdit"
             color="primary"
             label="Salvar"
             no-caps
@@ -147,6 +150,7 @@ import { format, parseISO } from "date-fns";
 import DefaultDialogHeader from "src/components/defaults/DefaultDialogHeader.vue";
 import JustifyAttendanceDialog from "./JustifyAttendanceDialog.vue";
 import { getClassStudents, saveClassAttendance } from "src/api/class";
+import { permissionStore } from "src/stores/permission";
 
 defineEmits([...useDialogPluginComponent.emits]);
 
@@ -158,6 +162,10 @@ const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } =
   useDialogPluginComponent();
 
 const $q = useQuasar();
+const permissions = permissionStore();
+const canEdit = computed(() =>
+  permissions.getAccess("franchisee_classes", "edit"),
+);
 
 const students = ref([]);
 const loading = ref(false);

+ 1 - 0
src/router/routes/class.route.js

@@ -6,6 +6,7 @@ export default [
     meta: {
       title: { value: "Agenda / Aulas", translate: false },
       requireAuth: true,
+      requiredPermission: "franchisee_classes",
       breadcrumbs: [
         { name: "ClassPage", title: "Aulas" },
       ],

+ 2 - 1
src/stores/navigation.js

@@ -37,7 +37,8 @@ export const navigationStore = defineStore("navigation", () => {
       name: "ClassPage",
       icon: "mdi-calendar",
       disable: false,
-      permission: true,
+      permission: false,
+      permissionScope: "franchisee_classes",
     },
     {
       type: "expansive",