Przeglądaj źródła

feat(packages): update API routes to franchisee-specific endpoints and implement permission checks for package management

ebagabee 1 tydzień temu
rodzic
commit
38524c1151

+ 7 - 7
src/api/package.js

@@ -1,35 +1,35 @@
 import api from "src/api";
 import api from "src/api";
 
 
 export const getUnitPackages = async () => {
 export const getUnitPackages = async () => {
-  const { data } = await api.get("/class-package-unit/visible");
+  const { data } = await api.get("/franchisee/packages");
   return data.payload;
   return data.payload;
 };
 };
 
 
 export const getAllUnitPackages = async () => {
 export const getAllUnitPackages = async () => {
-  const { data } = await api.get("/class-package-unit");
+  const { data } = await api.get("/franchisee/packages");
   return data.payload;
   return data.payload;
 };
 };
 
 
 export const getUnitPackage = async (id) => {
 export const getUnitPackage = async (id) => {
-  const { data } = await api.get(`/class-package-unit/${id}`);
+  const { data } = await api.get(`/franchisee/packages/${id}`);
   return data.payload;
   return data.payload;
 };
 };
 
 
 export const createUnitPackage = async (payload) => {
 export const createUnitPackage = async (payload) => {
-  const { data } = await api.post("/class-package-unit", payload);
+  const { data } = await api.post("/franchisee/packages", payload);
   return data.payload;
   return data.payload;
 };
 };
 
 
 export const updateUnitPackage = async (id, payload) => {
 export const updateUnitPackage = async (id, payload) => {
-  const { data } = await api.put(`/class-package-unit/${id}`, payload);
+  const { data } = await api.put(`/franchisee/packages/${id}`, payload);
   return data.payload;
   return data.payload;
 };
 };
 
 
 export const toggleUnitPackageVisibility = async (id) => {
 export const toggleUnitPackageVisibility = async (id) => {
-  const { data } = await api.patch(`/class-package-unit/${id}/toggle-visibility`);
+  const { data } = await api.patch(`/franchisee/packages/${id}/visibility`);
   return data.payload;
   return data.payload;
 };
 };
 
 
 export const deleteUnitPackage = async (id) => {
 export const deleteUnitPackage = async (id) => {
-  await api.delete(`/class-package-unit/${id}`);
+  await api.delete(`/franchisee/packages/${id}`);
 };
 };

+ 1 - 1
src/api/product.js

@@ -1,6 +1,6 @@
 import api from "src/api";
 import api from "src/api";
 
 
 export const getProductsForSelect = async () => {
 export const getProductsForSelect = async () => {
-  const { data } = await api.get("/product/all/select");
+  const { data } = await api.get("/franchisee/packages/material-options");
   return data.payload;
   return data.payload;
 };
 };

+ 45 - 3
src/pages/packages/PackagesPage.vue

@@ -6,6 +6,7 @@
       <!-- Add button -->
       <!-- Add button -->
       <div class="row justify-end q-mb-md">
       <div class="row justify-end q-mb-md">
         <q-btn
         <q-btn
+          v-if="canAdd"
           color="primary"
           color="primary"
           icon="mdi-plus"
           icon="mdi-plus"
           unelevated
           unelevated
@@ -36,7 +37,12 @@
           :key="pkg.id"
           :key="pkg.id"
           class="col-12 col-sm-6 col-md-4"
           class="col-12 col-sm-6 col-md-4"
         >
         >
-          <PackageCard :pkg="pkg" @click="openEditDialog(pkg)" />
+          <PackageCard
+            :pkg="pkg"
+            :can-delete="canDelete"
+            @click="openPackage(pkg)"
+            @delete="confirmDelete(pkg)"
+          />
         </div>
         </div>
 
 
         <div
         <div
@@ -51,18 +57,30 @@
 </template>
 </template>
 
 
 <script setup>
 <script setup>
-import { ref, onMounted, watch } from "vue";
+import { computed, ref, onMounted, watch } from "vue";
 import { useQuasar } from "quasar";
 import { useQuasar } from "quasar";
 
 
 import DefaultHeaderPage from "src/components/layout/DefaultHeaderPage.vue";
 import DefaultHeaderPage from "src/components/layout/DefaultHeaderPage.vue";
 import PackageCard from "./components/PackageCard.vue";
 import PackageCard from "./components/PackageCard.vue";
 import AddEditPackageDialog from "./components/AddEditPackageDialog.vue";
 import AddEditPackageDialog from "./components/AddEditPackageDialog.vue";
+import PackageDetailDialog from "./components/PackageDetailDialog.vue";
 
 
-import { getUnitPackages } from "src/api/package";
+import { deleteUnitPackage, getUnitPackages } from "src/api/package";
 import { userStore } from "src/stores/user";
 import { userStore } from "src/stores/user";
+import { permissionStore } from "src/stores/permission";
 
 
 const $q = useQuasar();
 const $q = useQuasar();
 const store = userStore();
 const store = userStore();
+const permissions = permissionStore();
+const canAdd = computed(() =>
+  permissions.getAccess("franchisee_packages", "add"),
+);
+const canEdit = computed(() =>
+  permissions.getAccess("franchisee_packages", "edit"),
+);
+const canDelete = computed(() =>
+  permissions.getAccess("franchisee_packages", "delete"),
+);
 const loading = ref(false);
 const loading = ref(false);
 const packages = ref([]);
 const packages = ref([]);
 
 
@@ -90,6 +108,30 @@ const openEditDialog = (pkg) => {
   });
   });
 };
 };
 
 
+const openPackage = (pkg) => {
+  if (canEdit.value) {
+    openEditDialog(pkg);
+    return;
+  }
+
+  $q.dialog({
+    component: PackageDetailDialog,
+    componentProps: { pkg },
+  });
+};
+
+const confirmDelete = (pkg) => {
+  $q.dialog({
+    title: "Excluir pacote",
+    message: `Deseja excluir o pacote ${pkg.name}?`,
+    cancel: true,
+    persistent: true,
+  }).onOk(async () => {
+    await deleteUnitPackage(pkg.id);
+    await fetchPackages();
+  });
+};
+
 // Recarrega pacotes ao trocar de unidade
 // Recarrega pacotes ao trocar de unidade
 watch(
 watch(
   () => store.selectedUnit?.id,
   () => store.selectedUnit?.id,

+ 15 - 1
src/pages/packages/components/PackageCard.vue

@@ -2,6 +2,16 @@
   <q-card flat bordered class="package-card cursor-pointer" @click="$emit('click')">
   <q-card flat bordered class="package-card cursor-pointer" @click="$emit('click')">
     <q-card-section class="q-pa-md">
     <q-card-section class="q-pa-md">
       <div class="text-subtitle1 text-weight-bold">{{ pkg.name }}</div>
       <div class="text-subtitle1 text-weight-bold">{{ pkg.name }}</div>
+      <q-btn
+        v-if="canDelete"
+        flat
+        round
+        dense
+        icon="mdi-delete-outline"
+        color="negative"
+        class="absolute-top-right q-ma-sm"
+        @click.stop="$emit('delete')"
+      />
       <div class="text-body2 text-grey-6 q-mb-sm">{{ pkg.quantity_classes }} Aulas</div>
       <div class="text-body2 text-grey-6 q-mb-sm">{{ pkg.quantity_classes }} Aulas</div>
 
 
       <div class="column" style="gap: 2px">
       <div class="column" style="gap: 2px">
@@ -23,9 +33,13 @@ defineProps({
     type: Object,
     type: Object,
     required: true,
     required: true,
   },
   },
+  canDelete: {
+    type: Boolean,
+    default: false,
+  },
 });
 });
 
 
-defineEmits(["click"]);
+defineEmits(["click", "delete"]);
 </script>
 </script>
 
 
 <style scoped>
 <style scoped>

+ 13 - 4
src/pages/packages/components/PackageDetailDialog.vue

@@ -231,11 +231,12 @@
 </template>
 </template>
 
 
 <script setup>
 <script setup>
-import { ref } from "vue";
+import { computed, ref } from "vue";
 import { useDialogPluginComponent } from "quasar";
 import { useDialogPluginComponent } from "quasar";
 
 
 import CustomTabComponent from "src/components/shared/CustomTabComponent.vue";
 import CustomTabComponent from "src/components/shared/CustomTabComponent.vue";
 import { formatToBRLCurrency } from "src/helpers/utils";
 import { formatToBRLCurrency } from "src/helpers/utils";
+import { permissionStore } from "src/stores/permission";
 
 
 defineEmits([...useDialogPluginComponent.emits]);
 defineEmits([...useDialogPluginComponent.emits]);
 
 
@@ -249,9 +250,17 @@ defineProps({
 const { dialogRef, onDialogHide, onDialogCancel } = useDialogPluginComponent();
 const { dialogRef, onDialogHide, onDialogCancel } = useDialogPluginComponent();
 
 
 const currentTab = ref("dados");
 const currentTab = ref("dados");
+const permissions = permissionStore();
+const canViewClassAssignments = computed(
+  () =>
+    permissions.getAccess("franchisee_classes", "view") &&
+    permissions.getAccess("franchisee_users", "view"),
+);
 
 
-const tabs = [
+const tabs = computed(() => [
   { name: "dados", label: "Dados da Unidade" },
   { name: "dados", label: "Dados da Unidade" },
-  { name: "neurotrainer", label: "Neurotrainer/Aula" },
-];
+  ...(canViewClassAssignments.value
+    ? [{ name: "neurotrainer", label: "Neurotrainer/Aula" }]
+    : []),
+]);
 </script>
 </script>

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

@@ -36,7 +36,7 @@
         <q-td align="center">
         <q-td align="center">
           <q-item-section class="no-wrap" style="flex-direction: row; gap: 4px">
           <q-item-section class="no-wrap" style="flex-direction: row; gap: 4px">
             <q-btn
             <q-btn
-              v-if="canEdit"
+              v-if="canEditContract"
               outline
               outline
               icon="mdi-pencil-outline"
               icon="mdi-pencil-outline"
               style="width: 36px"
               style="width: 36px"
@@ -118,8 +118,15 @@ const props = defineProps({
 
 
 const $q = useQuasar();
 const $q = useQuasar();
 const permissions = permissionStore();
 const permissions = permissionStore();
-const canAdd = computed(() => permissions.getAccess("franchisee_contracts", "add"));
+const canViewPackages = computed(() =>
+  permissions.getAccess("franchisee_packages", "view"),
+);
+const canAdd = computed(() =>
+  permissions.getAccess("franchisee_contracts", "add") &&
+  canViewPackages.value,
+);
 const canEdit = computed(() => permissions.getAccess("franchisee_contracts", "edit"));
 const canEdit = computed(() => permissions.getAccess("franchisee_contracts", "edit"));
+const canEditContract = computed(() => canEdit.value && canViewPackages.value);
 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/package.route.js

@@ -6,6 +6,7 @@ export default [
     meta: {
     meta: {
       title: "Pacotes",
       title: "Pacotes",
       requireAuth: true,
       requireAuth: true,
+      requiredPermission: "franchisee_packages",
       breadcrumbs: [
       breadcrumbs: [
         {
         {
           name: "PackagesPage",
           name: "PackagesPage",

+ 2 - 1
src/stores/navigation.js

@@ -101,7 +101,8 @@ export const navigationStore = defineStore("navigation", () => {
       name: "PackagesPage",
       name: "PackagesPage",
       icon: "mdi-package-variant-closed",
       icon: "mdi-package-variant-closed",
       disable: false,
       disable: false,
-      permission: true,
+      permission: false,
+      permissionScope: "franchisee_packages",
     },
     },
     {
     {
       type: "single",
       type: "single",