浏览代码

feat(packages): integra tab Unidades com unidades reais do sistema

- Cria src/api/package.js com CRUD de class-package
- Dialog busca unidades via getUnitsForSelect na montagem
- Em modo edição, carrega visibilidades salvas do pacote
- Estado de visibilidade por unidade é reativo (check/X por linha e toggle global)
- Salva unit_visibilities junto ao payload do pacote

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ebagabee 1 天之前
父节点
当前提交
602dcbb41f
共有 2 个文件被更改,包括 143 次插入63 次删除
  1. 26 0
      src/api/package.js
  2. 117 63
      src/pages/packages/components/AddEditPackageDialog.vue

+ 26 - 0
src/api/package.js

@@ -0,0 +1,26 @@
+import api from "src/api";
+
+export const getPackages = async () => {
+  const { data } = await api.get("/class-package");
+  return data.payload;
+};
+
+export const getPackage = async (id) => {
+  const { data } = await api.get(`/class-package/${id}`);
+  return data.payload;
+};
+
+export const createPackage = async (payload) => {
+  const { data } = await api.post("/class-package", payload);
+  return data.payload;
+};
+
+export const updatePackage = async (id, payload) => {
+  const { data } = await api.put(`/class-package/${id}`, payload);
+  return data.payload;
+};
+
+export const deletePackage = async (id) => {
+  const { data } = await api.delete(`/class-package/${id}`);
+  return data;
+};

+ 117 - 63
src/pages/packages/components/AddEditPackageDialog.vue

@@ -30,7 +30,7 @@
                 />
 
                 <DefaultInput
-                  v-model="form.quantity"
+                  v-model="form.quantity_classes"
                   label="Quantidade de Aulas"
                   class="col-12"
                   type="number"
@@ -38,19 +38,19 @@
                 />
 
                 <DefaultCurrencyInput
-                  v-model="form.enrollment_fee"
+                  v-model="form.contract_register_value"
                   label="R$ Matrícula"
                   class="col-4"
                 />
 
                 <DefaultCurrencyInput
-                  v-model="form.total_contract"
+                  v-model="form.contract_value"
                   label="R$ Total do Contrato"
                   class="col-4"
                 />
 
                 <DefaultInput
-                  v-model="form.discount_percentage"
+                  v-model="form.contrat_discount_value"
                   label="Desconto em %"
                   class="col-4"
                   type="number"
@@ -96,7 +96,7 @@
             <!-- Tab: Unidades -->
             <div v-show="currentTab === 'unidades'">
               <div class="column q-gutter-y-sm">
-                <!-- Visível para todas as Unidades / Grupos -->
+                <!-- Visível para todas as Unidades -->
                 <q-input
                   outlined
                   readonly
@@ -129,7 +129,7 @@
                   </template>
                 </q-input>
 
-                <!-- Buscar Unidade / Grupo + botão + -->
+                <!-- Buscar -->
                 <div class="row items-center q-gutter-x-sm">
                   <q-input
                     v-model="unitSearch"
@@ -158,50 +158,57 @@
                 <!-- Lista de Unidades -->
                 <div
                   class="rounded-borders q-pa-sm"
-                  style="background-color: #f5f5f5"
+                  style="background-color: #f5f5f5; max-height: 280px; overflow-y: auto"
                 >
-                  <div class="row items-center q-mb-xs q-px-sm">
-                    <q-icon
-                      name="mdi-account-multiple"
-                      color="secondary"
-                      size="sm"
-                      class="q-mr-xs"
-                    />
-                    <span class="text-body2">
-                      Unidades Ativas ({{ filteredUnits.length }})
-                    </span>
+                  <div v-if="loadingUnits" class="flex flex-center q-py-md">
+                    <q-spinner color="secondary" size="sm" />
                   </div>
 
-                  <q-separator class="q-mb-xs" />
+                  <template v-else>
+                    <div class="row items-center q-mb-xs q-px-sm">
+                      <q-icon
+                        name="mdi-account-multiple"
+                        color="secondary"
+                        size="sm"
+                        class="q-mr-xs"
+                      />
+                      <span class="text-body2">
+                        Unidades Ativas ({{ filteredUnits.length }})
+                      </span>
+                    </div>
 
-                  <div class="row q-px-sm q-py-xs">
-                    <span class="col text-caption text-grey-6">Unidade</span>
-                    <span class="col-auto text-caption text-grey-6">Status</span>
-                  </div>
+                    <q-separator class="q-mb-xs" />
 
-                  <template v-for="unit in filteredUnits" :key="unit.id">
-                    <q-separator />
-                    <div class="row items-center q-px-sm q-py-sm">
-                      <span class="col text-body2">{{ unit.name }}</span>
-                      <div class="col-auto row q-gutter-x-xs">
-                        <q-btn
-                          round
-                          unelevated
-                          color="positive"
-                          icon="mdi-check"
-                          size="xs"
-                          @click="unit.active = true"
-                        />
-                        <q-btn
-                          round
-                          outline
-                          color="negative"
-                          icon="mdi-close"
-                          size="xs"
-                          @click="unit.active = false"
-                        />
-                      </div>
+                    <div class="row q-px-sm q-py-xs">
+                      <span class="col text-caption text-grey-6">Unidade</span>
+                      <span class="col-auto text-caption text-grey-6">Status</span>
                     </div>
+
+                    <template v-for="unit in filteredUnits" :key="unit.id">
+                      <q-separator />
+                      <div class="row items-center q-px-sm q-py-sm">
+                        <span class="col text-body2">{{ unit.fantasy_name }}</span>
+                        <div class="col-auto row q-gutter-x-xs">
+                          <q-btn
+                            round
+                            unelevated
+                            :color="unit.visible ? 'positive' : 'grey-4'"
+                            icon="mdi-check"
+                            size="xs"
+                            @click="unit.visible = true"
+                          />
+                          <q-btn
+                            round
+                            :outline="unit.visible"
+                            :unelevated="!unit.visible"
+                            :color="unit.visible ? 'negative' : 'negative'"
+                            icon="mdi-close"
+                            size="xs"
+                            @click="unit.visible = false"
+                          />
+                        </div>
+                      </div>
+                    </template>
                   </template>
                 </div>
               </div>
@@ -229,7 +236,7 @@
 </template>
 
 <script setup>
-import { ref, computed } from "vue";
+import { ref, computed, onMounted } from "vue";
 import { useDialogPluginComponent } from "quasar";
 
 import CustomTabComponent from "src/components/shared/CustomTabComponent.vue";
@@ -237,9 +244,12 @@ import DefaultInput from "src/components/defaults/DefaultInput.vue";
 import DefaultSelect from "src/components/defaults/DefaultSelect.vue";
 import DefaultCurrencyInput from "src/components/defaults/DefaultCurrencyInput.vue";
 
+import { getUnitsForSelect } from "src/api/unit";
+import { getPackage, createPackage, updatePackage } from "src/api/package";
+
 defineEmits([...useDialogPluginComponent.emits]);
 
-defineProps({
+const props = defineProps({
   package: {
     type: Object,
     default: null,
@@ -251,6 +261,7 @@ const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } =
 
 const formRef = ref(null);
 const loading = ref(false);
+const loadingUnits = ref(false);
 const currentTab = ref("informacoes");
 
 const tabs = [
@@ -259,11 +270,12 @@ const tabs = [
 ];
 
 const form = ref({
-  name: null,
-  quantity: null,
-  enrollment_fee: null,
-  total_contract: null,
-  discount_percentage: null,
+  name: props.package?.name ?? null,
+  quantity_classes: props.package?.quantity_classes ?? null,
+  contract_register_value: props.package?.contract_register_value ?? null,
+  contract_value: props.package?.contract_value ?? null,
+  contrat_discount_value: props.package?.contrat_discount_value ?? null,
+  contract_material_value: props.package?.contract_material_value ?? null,
   materials: [{ type: null, value: null }],
 });
 
@@ -271,26 +283,68 @@ const addMaterial = () => {
   form.value.materials.push({ type: null, value: null });
 };
 
-// Unidades tab state
+// Unidades tab
 const unitSearch = ref("");
-
-const units = ref([
-  { id: 1, name: "Unidade 1", active: true },
-  { id: 2, name: "Unidade 2", active: true },
-  { id: 3, name: "Unidade 3", active: true },
-]);
+const units = ref([]);
 
 const filteredUnits = computed(() => {
   const q = unitSearch.value.toLowerCase();
   if (!q) return units.value;
-  return units.value.filter((u) => u.name.toLowerCase().includes(q));
+  return units.value.filter((u) =>
+    u.fantasy_name.toLowerCase().includes(q)
+  );
 });
 
-const setAllVisible = (active) => {
-  units.value.forEach((u) => (u.active = active));
+const setAllVisible = (visible) => {
+  units.value.forEach((u) => (u.visible = visible));
 };
 
-const onOKClick = () => {
-  onDialogOK(form.value);
+onMounted(async () => {
+  loadingUnits.value = true;
+  try {
+    const allUnits = await getUnitsForSelect();
+
+    let visibilityMap = {};
+    if (props.package?.id) {
+      const pkg = await getPackage(props.package.id);
+      (pkg.unit_visibilities ?? []).forEach((uv) => {
+        visibilityMap[uv.unit_id] = uv.visible;
+      });
+    }
+
+    units.value = allUnits.map((u) => ({
+      id: u.id,
+      fantasy_name: u.fantasy_name,
+      visible: visibilityMap[u.id] ?? true,
+    }));
+  } finally {
+    loadingUnits.value = false;
+  }
+});
+
+const onOKClick = async () => {
+  loading.value = true;
+  try {
+    const payload = {
+      name: form.value.name,
+      quantity_classes: form.value.quantity_classes,
+      contract_value: form.value.contract_value,
+      contract_material_value: form.value.contract_material_value,
+      contract_register_value: form.value.contract_register_value,
+      contrat_discount_value: form.value.contrat_discount_value,
+      unit_visibilities: units.value.map((u) => ({
+        unit_id: u.id,
+        visible: u.visible,
+      })),
+    };
+
+    const result = props.package?.id
+      ? await updatePackage(props.package.id, payload)
+      : await createPackage(payload);
+
+    onDialogOK(result);
+  } finally {
+    loading.value = false;
+  }
 };
 </script>