Procházet zdrojové kódy

feat: adiciona funcionalidade de congelar e cancelar contrato

ebagabee před 1 měsícem
rodič
revize
2fc1fb8fc3

+ 5 - 0
src/api/studentContract.js

@@ -22,6 +22,11 @@ export const attachContractFile = async (id, formData) => {
   return data.payload;
 };
 
+export const updateContractStatus = async (id, status) => {
+  const { data } = await api.patch(`/student-contract/${id}/status`, { status });
+  return data.payload;
+};
+
 export const deleteStudentContract = async (id) => {
   await api.delete(`/student-contract/${id}`);
 };

+ 45 - 0
src/pages/students/components/ContractActionConfirmDialog.vue

@@ -0,0 +1,45 @@
+<template>
+  <q-dialog ref="dialogRef" @hide="onDialogHide">
+    <q-card class="q-dialog-plugin" style="width: 100%; max-width: 400px">
+      <q-card-section class="row items-center q-pb-none q-pt-md">
+        <q-icon
+          name="mdi-alert-circle"
+          color="negative"
+          size="28px"
+          class="q-mr-sm"
+        />
+        <span class="text-h6">{{ props.title }}</span>
+        <q-space />
+        <q-btn icon="close" flat round dense @click="onDialogCancel" />
+      </q-card-section>
+
+      <q-card-section>
+        <p class="q-my-sm">{{ props.message }}</p>
+      </q-card-section>
+
+      <q-card-actions align="right">
+        <q-btn
+          unelevated
+          color="teal"
+          text-color="white"
+          label="CONFIRMAR"
+          @click="onDialogOK"
+        />
+      </q-card-actions>
+    </q-card>
+  </q-dialog>
+</template>
+
+<script setup>
+import { useDialogPluginComponent } from "quasar";
+
+const props = defineProps({
+  title: { type: String, required: true },
+  message: { type: String, required: true },
+});
+
+defineEmits([...useDialogPluginComponent.emits]);
+
+const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } =
+  useDialogPluginComponent();
+</script>

+ 38 - 0
src/pages/students/components/ContractActionsDialog.vue

@@ -0,0 +1,38 @@
+<template>
+  <q-dialog ref="dialogRef" @hide="onDialogHide">
+    <q-card class="q-dialog-plugin" style="width: 100%; max-width: 400px">
+      <DefaultDialogHeader title="Ações do Contrato" @close="onDialogCancel" />
+
+      <q-card-section class="q-pt-sm column q-gutter-sm">
+        <q-btn
+          unelevated
+          color="teal-3"
+          text-color="white"
+          label="Congelar"
+          size="md"
+          class="full-width"
+          @click="onDialogOK('frozen')"
+        />
+        <q-btn
+          unelevated
+          color="deep-orange"
+          text-color="white"
+          label="Cancelar"
+          size="md"
+          class="full-width"
+          @click="onDialogOK('cancelled')"
+        />
+      </q-card-section>
+    </q-card>
+  </q-dialog>
+</template>
+
+<script setup>
+import { useDialogPluginComponent } from "quasar";
+import DefaultDialogHeader from "src/components/defaults/DefaultDialogHeader.vue";
+
+defineEmits([...useDialogPluginComponent.emits]);
+
+const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } =
+  useDialogPluginComponent();
+</script>

+ 73 - 4
src/pages/students/tabs/ContractTab.vue

@@ -26,8 +26,8 @@
       <template #body-cell-status="{ row }">
         <q-td align="center">
           <q-badge
-            :color="row.status === 'active' ? 'positive' : 'warning'"
-            :label="row.status === 'active' ? 'Ativo' : 'Inativo'"
+            :color="statusColor(row.status)"
+            :label="statusLabel(row.status)"
           />
         </q-td>
       </template>
@@ -64,6 +64,13 @@
                   >
                     <q-item-section>Visualizar arquivo</q-item-section>
                   </q-item>
+                  <q-item
+                    v-close-popup
+                    clickable
+                    @click="handleContractActions(row)"
+                  >
+                    <q-item-section>Ações do Contrato</q-item-section>
+                  </q-item>
                 </q-list>
               </q-menu>
             </q-btn>
@@ -79,7 +86,13 @@ import { ref, onMounted } from "vue";
 import { useQuasar } from "quasar";
 import DefaultTable from "src/components/defaults/DefaultTable.vue";
 import CreateContractDialog from "src/pages/students/components/CreateContractDialog.vue";
-import { getStudentContracts, attachContractFile } from "src/api/studentContract";
+import ContractActionsDialog from "src/pages/students/components/ContractActionsDialog.vue";
+import ContractActionConfirmDialog from "src/pages/students/components/ContractActionConfirmDialog.vue";
+import {
+  getStudentContracts,
+  attachContractFile,
+  updateContractStatus,
+} from "src/api/studentContract";
 
 const props = defineProps({
   student: {
@@ -145,9 +158,65 @@ function openFile(url) {
   window.open(url, "_blank");
 }
 
+const actionLabels = {
+  frozen: {
+    title: "Congelar Contrato",
+    message:
+      "Você tem certeza que deseja CONGELAR este contrato? isso irá gerar multas e cancelamento da cobrança recorrente.",
+  },
+  cancelled: {
+    title: "Cancelar Contrato",
+    message:
+      "Você tem certeza que deseja CANCELAR este contrato? isso irá gerar multas e cancelamento da cobrança recorrente.",
+  },
+};
+
+function handleContractActions(contract) {
+  $q.dialog({
+    component: ContractActionsDialog,
+  }).onOk((status) => {
+    const { title, message } = actionLabels[status];
+    $q.dialog({
+      component: ContractActionConfirmDialog,
+      componentProps: { title, message },
+    }).onOk(async () => {
+      try {
+        const updated = await updateContractStatus(contract.id, status);
+        const idx = rows.value.findIndex((r) => r.id === contract.id);
+        if (idx !== -1) rows.value[idx] = updated;
+      } catch (e) {
+        console.error(e);
+        $q.notify({
+          type: "negative",
+          message: "Erro ao atualizar status do contrato.",
+        });
+      }
+    });
+  });
+}
+
+function statusColor(status) {
+  if (status === "active") return "positive";
+  if (status === "frozen") return "info";
+  if (status === "cancelled") return "negative";
+  return "warning";
+}
+
+function statusLabel(status) {
+  if (status === "active") return "Ativo";
+  if (status === "frozen") return "Congelado";
+  if (status === "cancelled") return "Cancelado";
+  return "Inativo";
+}
+
 const columns = ref([
   { name: "contract", label: "Contrato", field: "protocol", align: "left" },
-  { name: "period", label: "Data Assinatura - Encerramento", field: null, align: "left" },
+  {
+    name: "period",
+    label: "Data Assinatura - Encerramento",
+    field: null,
+    align: "left",
+  },
   { name: "status", label: "Status", field: "status", align: "center" },
   { name: "actions", label: "Ações", field: null, align: "center" },
 ]);

+ 2 - 6
src/pages/students/tabs/StudentDataTab.vue

@@ -81,6 +81,7 @@
         label="Cidade"
         class="col-4"
         :state="selectedState"
+        :initial-id="props.student.city_id"
       />
 
       <StateSelect
@@ -88,6 +89,7 @@
         v-model="selectedState"
         label="Estado"
         class="col-4"
+        :initial-id="props.student.state_id"
       />
 
       <DefaultInput
@@ -202,12 +204,6 @@ onMounted(() => {
   if (props.student.photo_url) {
     avatarRef.value?.setImageUrl(props.student.photo_url);
   }
-  if (props.student.state_id) {
-    stateSelectRef.value?.selectStateById(props.student.state_id);
-  }
-  if (props.student.city_id) {
-    citySelectRef.value?.selectCityById(props.student.city_id);
-  }
 });
 
 function onAvatarChange(file) {