Jelajahi Sumber

feat(api): update endpoints to use franchisee-specific routes for dashboard, holidays, students, contracts, and media
feat(permissions): enhance permission checks for contracts and student-related actions
fix(navigation): adjust navigation permissions for ContractsPage

ebagabee 1 Minggu lalu
induk
melakukan
32ac68fc45

+ 6 - 0
src/api/franchisee_dashboard.js

@@ -0,0 +1,6 @@
+import api from "src/api";
+
+export const getDashboardSummary = async () => {
+  const { data } = await api.get("/franchisee/dashboard/summary");
+  return data.payload;
+};

+ 4 - 4
src/api/holiday.js

@@ -1,21 +1,21 @@
 import api from "src/api";
 import api from "src/api";
 
 
 export const getHolidays = async () => {
 export const getHolidays = async () => {
-  const { data } = await api.get("/holiday");
+  const { data } = await api.get("/franchisee/dashboard/holidays");
   return data.payload;
   return data.payload;
 };
 };
 
 
 export const createHoliday = async (holiday) => {
 export const createHoliday = async (holiday) => {
-  const { data } = await api.post("/holiday", holiday);
+  const { data } = await api.post("/franchisee/dashboard/holidays", holiday);
   return data.payload;
   return data.payload;
 };
 };
 
 
 export const updateHoliday = async (holiday, id) => {
 export const updateHoliday = async (holiday, id) => {
-  const { data } = await api.put(`/holiday/${id}`, holiday);
+  const { data } = await api.put(`/franchisee/dashboard/holidays/${id}`, holiday);
   return data.payload;
   return data.payload;
 };
 };
 
 
 export const deleteHoliday = async (id) => {
 export const deleteHoliday = async (id) => {
-  const { data } = await api.delete(`/holiday/${id}`);
+  const { data } = await api.delete(`/franchisee/dashboard/holidays/${id}`);
   return data.payload;
   return data.payload;
 };
 };

+ 6 - 6
src/api/student.js

@@ -1,31 +1,31 @@
 import api from "src/api";
 import api from "src/api";
 
 
 export const getStudents = async (params = {}) => {
 export const getStudents = async (params = {}) => {
-  const { data } = await api.get("/student", { params });
+  const { data } = await api.get("/franchisee/students", { params });
   return data.payload;
   return data.payload;
 };
 };
 
 
 export const getStudent = async (id) => {
 export const getStudent = async (id) => {
-  const { data } = await api.get(`/student/${id}`);
+  const { data } = await api.get(`/franchisee/students/${id}`);
   return data.payload;
   return data.payload;
 };
 };
 
 
 export const createStudent = async (student) => {
 export const createStudent = async (student) => {
-  const { data } = await api.post("/student", student);
+  const { data } = await api.post("/franchisee/students", student);
   return data.payload;
   return data.payload;
 };
 };
 
 
 export const updateStudent = async (payload, id) => {
 export const updateStudent = async (payload, id) => {
   if (payload instanceof FormData) {
   if (payload instanceof FormData) {
     payload.append('_method', 'PUT');
     payload.append('_method', 'PUT');
-    const { data } = await api.post(`/student/${id}`, payload);
+    const { data } = await api.post(`/franchisee/students/${id}`, payload);
     return data.payload;
     return data.payload;
   }
   }
-  const { data } = await api.put(`/student/${id}`, payload);
+  const { data } = await api.put(`/franchisee/students/${id}`, payload);
   return data.payload;
   return data.payload;
 };
 };
 
 
 export const deleteStudent = async (id) => {
 export const deleteStudent = async (id) => {
-  const { data } = await api.delete(`/student/${id}`);
+  const { data } = await api.delete(`/franchisee/students/${id}`);
   return data.payload;
   return data.payload;
 };
 };

+ 11 - 11
src/api/studentContract.js

@@ -1,57 +1,57 @@
 import api from "src/api";
 import api from "src/api";
 
 
 export const getStudentContractById = async (id) => {
 export const getStudentContractById = async (id) => {
-  const { data } = await api.get(`/student-contract/${id}`);
+  const { data } = await api.get(`/franchisee/contracts/${id}`);
   return data.payload;
   return data.payload;
 };
 };
 
 
 export const getStudentContracts = async (studentId) => {
 export const getStudentContracts = async (studentId) => {
-  const { data } = await api.get("/student-contract", { params: { student_id: studentId } });
+  const { data } = await api.get("/franchisee/contracts", { params: { student_id: studentId } });
   return data.payload;
   return data.payload;
 };
 };
 
 
 export const getAllContracts = async () => {
 export const getAllContracts = async () => {
-  const { data } = await api.get("/student-contract");
+  const { data } = await api.get("/franchisee/contracts");
   return data.payload;
   return data.payload;
 };
 };
 
 
 export const createStudentContract = async (payload) => {
 export const createStudentContract = async (payload) => {
-  const { data } = await api.post("/student-contract", payload);
+  const { data } = await api.post("/franchisee/contracts", payload);
   return data.payload;
   return data.payload;
 };
 };
 
 
 export const updateStudentContract = async (id, payload) => {
 export const updateStudentContract = async (id, payload) => {
-  const { data } = await api.put(`/student-contract/${id}`, payload);
+  const { data } = await api.put(`/franchisee/contracts/${id}`, payload);
   return data.payload;
   return data.payload;
 };
 };
 
 
 export const attachContractFile = async (id, formData) => {
 export const attachContractFile = async (id, formData) => {
-  const { data } = await api.post(`/student-contract/${id}/file`, formData, {
+  const { data } = await api.post(`/franchisee/contracts/${id}/file`, formData, {
     headers: { "Content-Type": "multipart/form-data" },
     headers: { "Content-Type": "multipart/form-data" },
   });
   });
   return data.payload;
   return data.payload;
 };
 };
 
 
 export const getContractInstallments = async (id) => {
 export const getContractInstallments = async (id) => {
-  const { data } = await api.get(`/student-contract/${id}/installments`);
+  const { data } = await api.get(`/franchisee/contracts/${id}/installments`);
   return data.payload;
   return data.payload;
 };
 };
 
 
 export const freezeContract = async (id, months) => {
 export const freezeContract = async (id, months) => {
-  const { data } = await api.post(`/student-contract/${id}/freeze`, { months });
+  const { data } = await api.post(`/franchisee/contracts/${id}/freeze`, { months });
   return data.payload;
   return data.payload;
 };
 };
 
 
 export const cancelContract = async (id) => {
 export const cancelContract = async (id) => {
-  const { data } = await api.post(`/student-contract/${id}/cancel`);
+  const { data } = await api.post(`/franchisee/contracts/${id}/cancel`);
   return data.payload;
   return data.payload;
 };
 };
 
 
 export const reactivateContract = async (id) => {
 export const reactivateContract = async (id) => {
-  const { data } = await api.post(`/student-contract/${id}/reactivate`);
+  const { data } = await api.post(`/franchisee/contracts/${id}/reactivate`);
   return data.payload;
   return data.payload;
 };
 };
 
 
 export const deleteStudentContract = async (id) => {
 export const deleteStudentContract = async (id) => {
-  await api.delete(`/student-contract/${id}`);
+  await api.delete(`/franchisee/contracts/${id}`);
 };
 };

+ 4 - 4
src/api/studentResponsible.js

@@ -1,21 +1,21 @@
 import api from "src/api";
 import api from "src/api";
 
 
 export const getStudentResponsible = async (studentId) => {
 export const getStudentResponsible = async (studentId) => {
-  const { data } = await api.get(`/student/${studentId}/responsible`);
+  const { data } = await api.get(`/franchisee/students/${studentId}/responsible`);
   return data.payload;
   return data.payload;
 };
 };
 
 
 export const createStudentResponsible = async (payload) => {
 export const createStudentResponsible = async (payload) => {
-  const { data } = await api.post("/student-responsible", payload);
+  const { data } = await api.post("/franchisee/students/responsibles", payload);
   return data.payload;
   return data.payload;
 };
 };
 
 
 export const updateStudentResponsible = async (id, payload) => {
 export const updateStudentResponsible = async (id, payload) => {
-  const { data } = await api.put(`/student-responsible/${id}`, payload);
+  const { data } = await api.put(`/franchisee/students/responsibles/${id}`, payload);
   return data.payload;
   return data.payload;
 };
 };
 
 
 export const deleteStudentResponsible = async (id) => {
 export const deleteStudentResponsible = async (id) => {
-  const { data } = await api.delete(`/student-responsible/${id}`);
+  const { data } = await api.delete(`/franchisee/students/responsibles/${id}`);
   return data.payload;
   return data.payload;
 };
 };

+ 10 - 5
src/api/student_media.js

@@ -1,17 +1,22 @@
 import api from "src/api";
 import api from "src/api";
 
 
 export const getStudentMedias = async (studentId) => {
 export const getStudentMedias = async (studentId) => {
-  const { data } = await api.get("/student-media", { params: { student_id: studentId } });
+  const { data } = await api.get("/franchisee/students/medias", { params: { student_id: studentId } });
   return data.payload;
   return data.payload;
 };
 };
 
 
 export const getContractMedias = async (contractId) => {
 export const getContractMedias = async (contractId) => {
-  const { data } = await api.get("/student-media", { params: { student_contract_id: contractId } });
+  const { data } = await api.get("/franchisee/contracts/medias", { params: { student_contract_id: contractId } });
   return data.payload;
   return data.payload;
 };
 };
 
 
+export const deleteContractMedia = async (id) => {
+  const { data } = await api.delete(`/franchisee/contracts/medias/${id}`);
+  return data;
+};
+
 export const createStudentMedia = async (formData) => {
 export const createStudentMedia = async (formData) => {
-  const { data } = await api.post("/student-media", formData, {
+  const { data } = await api.post("/franchisee/students/medias", formData, {
     headers: { "Content-Type": "multipart/form-data" },
     headers: { "Content-Type": "multipart/form-data" },
   });
   });
   return data.payload;
   return data.payload;
@@ -19,13 +24,13 @@ export const createStudentMedia = async (formData) => {
 
 
 export const updateStudentMedia = async (id, formData) => {
 export const updateStudentMedia = async (id, formData) => {
   formData.append("_method", "PUT");
   formData.append("_method", "PUT");
-  const { data } = await api.post(`/student-media/${id}`, formData, {
+  const { data } = await api.post(`/franchisee/students/medias/${id}`, formData, {
     headers: { "Content-Type": "multipart/form-data" },
     headers: { "Content-Type": "multipart/form-data" },
   });
   });
   return data.payload;
   return data.payload;
 };
 };
 
 
 export const deleteStudentMedia = async (id) => {
 export const deleteStudentMedia = async (id) => {
-  const { data } = await api.delete(`/student-media/${id}`);
+  const { data } = await api.delete(`/franchisee/students/medias/${id}`);
   return data;
   return data;
 };
 };

+ 6 - 0
src/pages/contracts/ContractsPage.vue

@@ -63,6 +63,7 @@
               @click.prevent.stop="handleView(row)"
               @click.prevent.stop="handleView(row)"
             />
             />
             <q-btn
             <q-btn
+              v-if="canEdit"
               outline
               outline
               icon="mdi-dots-vertical"
               icon="mdi-dots-vertical"
               style="width: 36px"
               style="width: 36px"
@@ -113,6 +114,7 @@ import DefaultHeaderPage from "src/components/layout/DefaultHeaderPage.vue";
 import DefaultTable from "src/components/defaults/DefaultTable.vue";
 import DefaultTable from "src/components/defaults/DefaultTable.vue";
 import DashboardStatCard from "src/components/charts/DashboardStatCard.vue";
 import DashboardStatCard from "src/components/charts/DashboardStatCard.vue";
 import { userStore } from "src/stores/user";
 import { userStore } from "src/stores/user";
+import { permissionStore } from "src/stores/permission";
 import ContractActionConfirmDialog from "src/pages/students/components/ContractActionConfirmDialog.vue";
 import ContractActionConfirmDialog from "src/pages/students/components/ContractActionConfirmDialog.vue";
 import FreezeContractDialog from "src/pages/students/components/FreezeContractDialog.vue";
 import FreezeContractDialog from "src/pages/students/components/FreezeContractDialog.vue";
 import ViewContractDialog from "src/pages/students/components/ViewContractDialog.vue";
 import ViewContractDialog from "src/pages/students/components/ViewContractDialog.vue";
@@ -124,6 +126,10 @@ import {
 
 
 const $q = useQuasar();
 const $q = useQuasar();
 const store = userStore();
 const store = userStore();
+const permissions = permissionStore();
+const canEdit = computed(() =>
+  permissions.getAccess("franchisee_contracts", "edit"),
+);
 const rows = ref([]);
 const rows = ref([]);
 const isLoading = ref(false);
 const isLoading = ref(false);
 
 

+ 25 - 39
src/pages/dashboard/DashboardPage.vue

@@ -85,7 +85,7 @@
             <q-separator />
             <q-separator />
             <q-card-section class="q-pt-md column q-gutter-sm">
             <q-card-section class="q-pt-md column q-gutter-sm">
               <q-btn
               <q-btn
-                v-if="canAdd"
+                v-if="canAddContracts"
                 unelevated
                 unelevated
                 color="primary"
                 color="primary"
                 label="Criar contrato"
                 label="Criar contrato"
@@ -94,7 +94,7 @@
                 @click="onCriarContrato"
                 @click="onCriarContrato"
               />
               />
               <q-btn
               <q-btn
-                v-if="canEdit"
+                v-if="canEditClasses"
                 unelevated
                 unelevated
                 color="primary"
                 color="primary"
                 label="Registrar presença"
                 label="Registrar presença"
@@ -103,7 +103,7 @@
                 @click="onRegistrarPresenca"
                 @click="onRegistrarPresenca"
               />
               />
               <q-btn
               <q-btn
-                v-if="canAdd"
+                v-if="canAddOrders"
                 unelevated
                 unelevated
                 color="primary"
                 color="primary"
                 label="Novo pedido"
                 label="Novo pedido"
@@ -207,8 +207,7 @@ import AniversariantesCard from "src/components/charts/AniversariantesCard.vue";
 import FeriadosDialog from "./components/FeriadosDialog.vue";
 import FeriadosDialog from "./components/FeriadosDialog.vue";
 import FeriadosEditDialog from "./components/FeriadosEditDialog.vue";
 import FeriadosEditDialog from "./components/FeriadosEditDialog.vue";
 import { getHolidays } from "src/api/holiday";
 import { getHolidays } from "src/api/holiday";
-import { getStudents } from "src/api/student";
-import { getAllContracts } from "src/api/studentContract";
+import { getDashboardSummary } from "src/api/franchisee_dashboard";
 import { userStore } from "src/stores/user";
 import { userStore } from "src/stores/user";
 import { permissionStore } from "src/stores/permission";
 import { permissionStore } from "src/stores/permission";
 
 
@@ -220,8 +219,14 @@ const permissions = permissionStore();
 const canAdd = computed(() =>
 const canAdd = computed(() =>
   permissions.getAccess("franchisee_dashboard", "add"),
   permissions.getAccess("franchisee_dashboard", "add"),
 );
 );
-const canEdit = computed(() =>
-  permissions.getAccess("franchisee_dashboard", "edit"),
+const canAddContracts = computed(() =>
+  permissions.getAccess("franchisee_contracts", "add"),
+);
+const canEditClasses = computed(() =>
+  permissions.getAccess("franchisee_classes", "edit"),
+);
+const canAddOrders = computed(() =>
+  permissions.getAccess("franchisee_orders", "add"),
 );
 );
 
 
 // Faturamento Serviço / Materiais — zerado (sem fluxo de dados ainda),
 // Faturamento Serviço / Materiais — zerado (sem fluxo de dados ainda),
@@ -326,19 +331,7 @@ const activeContracts = ref(0);
 const students = ref([]);
 const students = ref([]);
 
 
 // Aniversariantes do mês corrente (dia + nome), ordenados por dia.
 // Aniversariantes do mês corrente (dia + nome), ordenados por dia.
-const aniversariantes = computed(() => {
-  const month = new Date().getMonth() + 1;
-  return students.value
-    .filter((s) => {
-      if (!s.birth_date) return false;
-      return new Date(s.birth_date + "T00:00:00").getMonth() + 1 === month;
-    })
-    .map((s) => ({
-      day: new Date(s.birth_date + "T00:00:00").getDate(),
-      name: s.name,
-    }))
-    .sort((a, b) => a.day - b.day);
-});
+const aniversariantes = ref([]);
 
 
 // Matrículas por Período — alunos cadastrados (created_at) nos últimos 6 meses,
 // Matrículas por Período — alunos cadastrados (created_at) nos últimos 6 meses,
 // terminando no mês atual.
 // terminando no mês atual.
@@ -369,22 +362,17 @@ const matriculasChart = computed(() => {
   };
   };
 });
 });
 
 
-async function fetchAlunos() {
-  try {
-    students.value = await getStudents();
-    totalAlunos.value = students.value.length;
-  } catch {
-    // silencioso
-  }
-}
-
-async function fetchContracts() {
+async function fetchSummary() {
   try {
   try {
-    const contracts = await getAllContracts();
-    activeContracts.value = contracts.filter((c) => c.status === "active").length;
-    // Posiciona a agulha do medidor na quantidade de contratos ativos
-    // (escala de 0 a 10; valores maiores ficam no topo da escala).
-    gaugeData.value.datasets[0].needleValue = Math.min(activeContracts.value, 10);
+    const summary = await getDashboardSummary();
+    students.value = summary.enrollments;
+    totalAlunos.value = summary.students_count;
+    activeContracts.value = summary.contracts.active;
+    aniversariantes.value = summary.birthdays;
+    gaugeData.value.datasets[0].needleValue = Math.min(
+      activeContracts.value,
+      10,
+    );
   } catch {
   } catch {
     // silencioso
     // silencioso
   }
   }
@@ -464,8 +452,7 @@ watch(
   () => store.selectedUnit?.id,
   () => store.selectedUnit?.id,
   (newId, oldId) => {
   (newId, oldId) => {
     if (newId && newId !== oldId) {
     if (newId && newId !== oldId) {
-      fetchAlunos();
-      fetchContracts();
+      fetchSummary();
       fetchHolidays();
       fetchHolidays();
     }
     }
   },
   },
@@ -473,8 +460,7 @@ watch(
 
 
 onMounted(() => {
 onMounted(() => {
   fetchHolidays();
   fetchHolidays();
-  fetchAlunos();
-  fetchContracts();
+  fetchSummary();
 });
 });
 </script>
 </script>
 
 

+ 2 - 2
src/pages/students/components/AddEditContractDialog.vue

@@ -369,7 +369,7 @@ import {
   createStudentContract,
   createStudentContract,
   updateStudentContract,
   updateStudentContract,
 } from "src/api/studentContract";
 } from "src/api/studentContract";
-import { getContractMedias, deleteStudentMedia } from "src/api/student_media";
+import { getContractMedias, deleteContractMedia } from "src/api/student_media";
 
 
 const props = defineProps({
 const props = defineProps({
   student: {
   student: {
@@ -548,7 +548,7 @@ function handleDeleteMedia(media) {
     cancel: { color: "primary", outline: true, label: "Cancelar" },
     cancel: { color: "primary", outline: true, label: "Cancelar" },
   }).onOk(async () => {
   }).onOk(async () => {
     try {
     try {
-      await deleteStudentMedia(media.id);
+      await deleteContractMedia(media.id);
       medias.value = medias.value.filter((m) => m.id !== media.id);
       medias.value = medias.value.filter((m) => m.id !== media.id);
     } catch (e) {
     } catch (e) {
       console.error(e);
       console.error(e);

+ 12 - 5
src/pages/students/components/EditStudentDialog.vue

@@ -21,7 +21,7 @@
           <ResponsibleTab :student-id="props.student.id" />
           <ResponsibleTab :student-id="props.student.id" />
         </div>
         </div>
 
 
-        <div v-show="currentTab === 'contracts'">
+        <div v-if="canViewContracts" v-show="currentTab === 'contracts'">
           <ContractTab :student="props.student" />
           <ContractTab :student="props.student" />
         </div>
         </div>
 
 
@@ -55,12 +55,13 @@
 </template>
 </template>
 
 
 <script setup>
 <script setup>
-import { ref, watch, useTemplateRef, defineAsyncComponent } from "vue";
+import { computed, ref, watch, useTemplateRef, defineAsyncComponent } from "vue";
 import { useDialogPluginComponent } from "quasar";
 import { useDialogPluginComponent } from "quasar";
 import DefaultDialogHeader from "src/components/defaults/DefaultDialogHeader.vue";
 import DefaultDialogHeader from "src/components/defaults/DefaultDialogHeader.vue";
 import CustomTabComponent from "src/components/shared/CustomTabComponent.vue";
 import CustomTabComponent from "src/components/shared/CustomTabComponent.vue";
 import { useSubmitHandler } from "src/composables/useSubmitHandler";
 import { useSubmitHandler } from "src/composables/useSubmitHandler";
 import { updateStudent } from "src/api/student";
 import { updateStudent } from "src/api/student";
+import { permissionStore } from "src/stores/permission";
 
 
 const StudentDataTab = defineAsyncComponent(
 const StudentDataTab = defineAsyncComponent(
   () => import("src/pages/students/tabs/StudentDataTab.vue"),
   () => import("src/pages/students/tabs/StudentDataTab.vue"),
@@ -94,6 +95,10 @@ const studentDataTabRef = useTemplateRef("studentDataTabRef");
 const mediaTabRef = useTemplateRef("mediaTabRef");
 const mediaTabRef = useTemplateRef("mediaTabRef");
 
 
 const currentTab = ref("profile");
 const currentTab = ref("profile");
+const permissions = permissionStore();
+const canViewContracts = computed(() =>
+  permissions.getAccess("franchisee_contracts", "view"),
+);
 
 
 watch(currentTab, (tab) => {
 watch(currentTab, (tab) => {
   if (tab === "media") mediaTabRef.value?.refresh();
   if (tab === "media") mediaTabRef.value?.refresh();
@@ -103,13 +108,15 @@ const { loading: saving, execute } = useSubmitHandler({
   onSuccess: () => onDialogOK(true),
   onSuccess: () => onDialogOK(true),
 });
 });
 
 
-const tabs = [
+const tabs = computed(() => [
   { name: "profile", label: "Perfil do Aluno" },
   { name: "profile", label: "Perfil do Aluno" },
   { name: "responsible", label: "Responsáveis" },
   { name: "responsible", label: "Responsáveis" },
-  { name: "contracts", label: "Contratos" },
+  ...(canViewContracts.value
+    ? [{ name: "contracts", label: "Contratos" }]
+    : []),
   { name: "history", label: "Frequência" },
   { name: "history", label: "Frequência" },
   { name: "media", label: "Mídias" },
   { name: "media", label: "Mídias" },
-];
+]);
 
 
 async function handleSave() {
 async function handleSave() {
   const valid = await (studentDataTabRef.value?.validate() ?? true);
   const valid = await (studentDataTabRef.value?.validate() ?? true);

+ 2 - 2
src/pages/students/tabs/ContractTab.vue

@@ -118,8 +118,8 @@ const props = defineProps({
 
 
 const $q = useQuasar();
 const $q = useQuasar();
 const permissions = permissionStore();
 const permissions = permissionStore();
-const canAdd = computed(() => permissions.getAccess("franchisee_students", "add"));
-const canEdit = computed(() => permissions.getAccess("franchisee_students", "edit"));
+const canAdd = computed(() => permissions.getAccess("franchisee_contracts", "add"));
+const canEdit = computed(() => permissions.getAccess("franchisee_contracts", "edit"));
 const rows = ref([]);
 const rows = ref([]);
 const fileInputRef = ref(null);
 const fileInputRef = ref(null);
 const uploadingId = ref(null);
 const uploadingId = ref(null);

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

@@ -6,6 +6,7 @@ export default [
     meta: {
     meta: {
       title: "Contratos",
       title: "Contratos",
       requireAuth: true,
       requireAuth: true,
+      requiredPermission: "franchisee_contracts",
       breadcrumbs: [
       breadcrumbs: [
         {
         {
           name: "ContractsPage",
           name: "ContractsPage",

+ 2 - 1
src/stores/navigation.js

@@ -28,7 +28,8 @@ export const navigationStore = defineStore("navigation", () => {
       name: "ContractsPage",
       name: "ContractsPage",
       icon: "mdi-file-document-outline",
       icon: "mdi-file-document-outline",
       disable: false,
       disable: false,
-      permission: true,
+      permission: false,
+      permissionScope: "franchisee_contracts",
     },
     },
     {
     {
       type: "single",
       type: "single",