Ver código fonte

feat: add btn para upload do contrato assinado

Gustavo Mantovani 2 dias atrás
pai
commit
54ec328027

+ 176 - 40
src/pages/contracts/ContractsPage.vue

@@ -1,5 +1,13 @@
 <template>
   <div>
+    <input
+      ref="signedFileInputRef"
+      accept="image/jpeg,image/png,.pdf"
+      style="display: none"
+      type="file"
+      @change="onSignedFileSelected"
+    />
+
     <DefaultHeaderPage title="Contratos" />
 
     <div class="q-px-sm">
@@ -100,65 +108,127 @@
               </q-btn>
 
               <q-btn
-                v-if="row.file_url || canEdit"
-                :icon="
-                  row.file_url
-                    ? 'mdi-file-eye-outline'
-                    : 'mdi-file-plus-outline'
-                "
-                :loading="generatingId === row.id"
-                outline
-                style="width: 36px"
-                @click.prevent.stop="handleContractFile(row)"
-              >
-                <q-tooltip>
-                  {{
-                    row.file_url
-                      ? "Visualizar arquivo do contrato"
-                      : "Gerar arquivo do contrato"
-                  }}
-                </q-tooltip>
-              </q-btn>
-
-              <q-btn
-                v-if="canEdit"
+                v-if="canEdit || row.file_url || row.signed_file_url"
                 icon="mdi-dots-vertical"
                 outline
                 style="width: 36px"
                 @click.prevent.stop
               >
                 <q-menu>
-                  <q-list style="min-width: 170px">
-                    <template
-                      v-if="
-                        row.status === 'frozen' || row.status === 'cancelled'
-                      "
-                    >
+                  <q-list style="min-width: 280px">
                       <q-item
+                        v-if="row.file_url || canEdit"
                         v-close-popup
+                        class="q-pa-md"
                         clickable
-                        @click="handleReactivate(row)"
+                        @click="handleContractFile(row)"
                       >
-                        <q-item-section> Reativar Contrato </q-item-section>
+                        <q-item-section avatar>
+                          <q-spinner
+                            v-if="generatingId === row.id"
+                            color="primary"
+                            size="20px"
+                          />
+
+                          <q-icon
+                            v-else
+                            :name="
+                              row.file_url
+                                ? 'mdi-file-eye-outline'
+                                : 'mdi-file-plus-outline'
+                            "
+                          />
+                        </q-item-section>
+
+                        <q-item-section>
+                          {{
+                            row.file_url
+                              ? "Visualizar arquivo do contrato"
+                              : "Gerar arquivo do contrato"
+                          }}
+                        </q-item-section>
                       </q-item>
-                    </template>
 
-                    <template v-else>
                       <q-item
+                        v-if="row.signed_file_url || canEdit"
                         v-close-popup
+                        class="q-pa-md"
                         clickable
-                        @click="handleFreeze(row)"
+                        @click="handleSignedContractFile(row)"
                       >
-                        <q-item-section> Trancar Contrato </q-item-section>
+                        <q-item-section avatar>
+                          <q-spinner
+                            v-if="uploadingSignedId === row.id"
+                            color="primary"
+                            size="20px"
+                          />
+
+                          <q-icon
+                            v-else
+                            :name="
+                              row.signed_file_url
+                                ? 'mdi-file-check-outline'
+                                : 'mdi-file-upload-outline'
+                            "
+                          />
+                        </q-item-section>
+
+                        <q-item-section>
+                          {{
+                            row.signed_file_url
+                              ? "Visualizar contrato assinado"
+                              : "Enviar contrato assinado"
+                          }}
+                        </q-item-section>
                       </q-item>
 
-                      <q-item
-                        v-close-popup
-                        clickable
-                        @click="handleCancel(row)"
+                      <template
+                        v-if="
+                          canEdit &&
+                          (row.status === 'frozen' ||
+                            row.status === 'cancelled')
+                        "
                       >
-                        <q-item-section> Cancelar Contrato </q-item-section>
-                      </q-item>
+                        <q-item
+                          v-close-popup
+                          class="q-pa-md"
+                          clickable
+                          @click="handleReactivate(row)"
+                        >
+                          <q-item-section avatar>
+                            <q-icon name="mdi-refresh" />
+                          </q-item-section>
+
+                          <q-item-section> Reativar Contrato </q-item-section>
+                        </q-item>
+                      </template>
+
+                      <template v-else-if="canEdit">
+                        <q-item
+                          v-close-popup
+                          class="q-pa-md"
+                          clickable
+                          @click="handleFreeze(row)"
+                        >
+                          <q-item-section avatar>
+                            <q-icon name="mdi-file-cancel-outline" />
+                          </q-item-section>
+
+                          <q-item-section> Trancar Contrato </q-item-section>
+                        </q-item>
+
+                        <q-item
+                          v-close-popup
+                          class="q-pa-md"
+                          clickable
+                          @click="handleCancel(row)"
+                        >
+                          <q-item-section avatar>
+                            <q-icon name="mdi-file-cancel-outline" />
+                          </q-item-section>
+
+                          <q-item-section> Cancelar Contrato </q-item-section>
+                        </q-item>
                     </template>
                   </q-list>
                 </q-menu>
@@ -173,6 +243,7 @@
 
 <script setup>
 import {
+  attachSignedContractFile,
   cancelContract,
   generateStudentContractFile,
   getAllContracts,
@@ -202,6 +273,8 @@ const permissions = permissionStore();
 
 const isLoading = ref(false);
 const generatingId = ref(null);
+const signedFileInputRef = ref(null);
+const uploadingSignedId = ref(null);
 const rows = ref([]);
 
 const canEdit = computed(() =>
@@ -247,6 +320,8 @@ const columns = [
   },
 ];
 
+let pendingSignedContractId = null;
+
 const confirmAction = (title, message, apiFunction, contract) => {
   $q.dialog({
     component: ContractActionConfirmDialog,
@@ -323,6 +398,8 @@ const handleHistory = (contract) => {
   });
 };
 
+//
+
 const handleContractFile = async (contract) => {
   if (contract.file_url) {
     window.open(contract.file_url, "_blank", "noopener,noreferrer");
@@ -367,6 +444,65 @@ const handleContractFile = async (contract) => {
   }
 };
 
+//
+
+const handleSignedContractFile = (contract) => {
+  if (contract.signed_file_url) {
+    window.open(contract.signed_file_url, "_blank", "noopener,noreferrer");
+
+    return;
+  }
+
+  pendingSignedContractId = contract.id;
+
+  signedFileInputRef.value.value = "";
+  signedFileInputRef.value.click();
+};
+
+const onSignedFileSelected = async (event) => {
+  const file = event.target.files?.[0];
+
+  const contractId = pendingSignedContractId;
+
+  if (!file || !contractId) return;
+
+  uploadingSignedId.value = contractId;
+
+  try {
+    const formData = new FormData();
+
+    formData.append("file", file);
+
+    const updated = await attachSignedContractFile(contractId, formData);
+
+    const index = rows.value.findIndex((row) => row.id === contractId);
+
+    if (index !== -1) {
+      rows.value[index] = {
+        ...rows.value[index],
+        ...updated,
+      };
+    }
+
+    $q.notify({
+      message: "Contrato assinado enviado com sucesso.",
+      type: "positive",
+    });
+  } catch (error) {
+    console.error(error);
+
+    $q.notify({
+      message: "Erro ao enviar o contrato assinado.",
+      type: "negative",
+    });
+  } finally {
+    pendingSignedContractId = null;
+    uploadingSignedId.value = null;
+  }
+};
+
+//
+
 const handleReactivate = (contract) => {
   confirmAction(
     "Reativar Contrato",

+ 118 - 18
src/pages/students/components/ContractHistoryDialog.vue

@@ -4,6 +4,14 @@
       class="q-dialog-plugin overflow-hidden"
       style="width: 100%; max-width: 1100px"
     >
+      <input
+        ref="signedFileInputRef"
+        accept="image/jpeg,image/png,.pdf"
+        style="display: none"
+        type="file"
+        @change="onSignedFileSelected"
+      />
+
       <DefaultDialogHeader
         title="Histórico do Contrato"
         @close="onDialogCancel"
@@ -92,6 +100,28 @@
                   }}
                 </q-tooltip>
               </q-btn>
+
+              <q-btn
+                v-if="row.signed_file_url || canEdit"
+                class="q-ml-xs"
+                outline
+                style="width: 36px"
+                :icon="
+                  row.signed_file_url
+                    ? 'mdi-file-check-outline'
+                    : 'mdi-file-upload-outline'
+                "
+                :loading="uploadingSignedId === row.id"
+                @click="handleSignedContractFile(row)"
+              >
+                <q-tooltip>
+                  {{
+                    row.signed_file_url
+                      ? "Visualizar contrato assinado desta versão"
+                      : "Enviar contrato assinado desta versão"
+                  }}
+                </q-tooltip>
+              </q-btn>
             </q-td>
           </template>
         </q-table>
@@ -111,12 +141,14 @@
 </template>
 
 <script setup>
-import { computed, onMounted, ref } from "vue";
-import { getUnitPackagesForSelect } from "src/api/package";
 import {
+  attachSignedContractFile,
   generateStudentContractFile,
   getStudentContractById,
 } from "src/api/studentContract";
+
+import { computed, onMounted, ref } from "vue";
+import { getUnitPackagesForSelect } from "src/api/package";
 import { permissionStore } from "src/stores/permission";
 import { useDialogPluginComponent, useQuasar } from "quasar";
 
@@ -144,8 +176,12 @@ const permissions = permissionStore();
 const contract = ref(null);
 const loading = ref(false);
 const generatingId = ref(null);
+const signedFileInputRef = ref(null);
+const uploadingSignedId = ref(null);
 const packages = ref([]);
 
+let pendingSignedContractId = null;
+
 const canEdit = computed(() =>
   permissions.getAccess("franchisee_contracts", "edit"),
 );
@@ -229,6 +265,8 @@ const historyRows = computed(() =>
     .sort((first, second) => Number(second.version) - Number(first.version)),
 );
 
+//
+
 const formatCurrency = (value) =>
   new Intl.NumberFormat("pt-BR", {
     currency: "BRL",
@@ -246,22 +284,6 @@ const formatDateTime = (value) => {
   return `${day}/${month}/${year}${time ? ` ${time.slice(0, 5)}` : ""}`;
 };
 
-const statusColor = (status) => {
-  if (status === "active") return "positive";
-  if (status === "frozen") return "info";
-  if (status === "cancelled") return "negative";
-
-  return "warning";
-};
-
-const statusLabel = (status) => {
-  if (status === "active") return "Ativo";
-  if (status === "frozen") return "Trancado";
-  if (status === "cancelled") return "Cancelado";
-
-  return "Inativo";
-};
-
 const handleContractFile = async (row) => {
   if (row.file_url) {
     window.open(row.file_url, "_blank", "noopener,noreferrer");
@@ -306,6 +328,84 @@ const handleContractFile = async (row) => {
   }
 };
 
+//
+
+const handleSignedContractFile = (row) => {
+  if (row.signed_file_url) {
+    window.open(row.signed_file_url, "_blank", "noopener,noreferrer");
+
+    return;
+  }
+
+  pendingSignedContractId = row.id;
+
+  signedFileInputRef.value.value = "";
+  signedFileInputRef.value.click();
+};
+
+const onSignedFileSelected = async (event) => {
+  const file = event.target.files?.[0];
+
+  const contractId = pendingSignedContractId;
+
+  if (!file || !contractId) return;
+
+  uploadingSignedId.value = contractId;
+
+  try {
+    const formData = new FormData();
+
+    formData.append("file", file);
+
+    const updated = await attachSignedContractFile(contractId, formData);
+
+    const version = contract.value?.version_history?.find(
+      (item) => item.id === contractId,
+    );
+
+    if (version) {
+      version.signed_file_url = updated.signed_file_url;
+      version.signed_file_type = updated.signed_file_type;
+    }
+
+    $q.notify({
+      message: "Contrato assinado enviado com sucesso.",
+      type: "positive",
+    });
+  } catch (error) {
+    console.error(error);
+
+    $q.notify({
+      message: "Erro ao enviar o contrato assinado.",
+      type: "negative",
+    });
+  } finally {
+    pendingSignedContractId = null;
+
+    uploadingSignedId.value = null;
+  }
+};
+
+//
+
+const statusColor = (status) => {
+  if (status === "active") return "positive";
+  if (status === "frozen") return "info";
+  if (status === "cancelled") return "negative";
+
+  return "warning";
+};
+
+const statusLabel = (status) => {
+  if (status === "active") return "Ativo";
+  if (status === "frozen") return "Trancado";
+  if (status === "cancelled") return "Cancelado";
+
+  return "Inativo";
+};
+
+//
+
 onMounted(async () => {
   loading.value = true;
 

+ 25 - 6
src/pages/students/tabs/ContractTab.vue

@@ -74,18 +74,22 @@
             </q-btn>
 
             <q-btn
-              v-if="canEdit"
-              icon="mdi-file-sign"
+              v-if="row.signed_file_url || canEdit"
+              :icon="
+                row.signed_file_url
+                  ? 'mdi-file-check-outline'
+                  : 'mdi-file-upload-outline'
+              "
+              :loading="uploadingId === row.id"
               outline
               style="width: 36px"
-              :loading="uploadingId === row.id"
-              @click.prevent.stop="triggerFileInput(row)"
+              @click.prevent.stop="handleSignedContractFile(row)"
             >
               <q-tooltip>
                 {{
                   row.signed_file_url
-                    ? "Substituir contrato assinado"
-                    : "Anexar contrato assinado"
+                    ? "Visualizar contrato assinado"
+                    : "Enviar contrato assinado"
                 }}
               </q-tooltip>
             </q-btn>
@@ -319,6 +323,16 @@ const handleContractFile = async (contract) => {
   }
 };
 
+const handleSignedContractFile = (contract) => {
+  if (contract.signed_file_url) {
+    window.open(contract.signed_file_url, "_blank", "noopener,noreferrer");
+
+    return;
+  }
+
+  triggerFileInput(contract);
+};
+
 const handleReactivate = (contract) => {
   confirmAction(
     "Reativar Contrato",
@@ -354,6 +368,11 @@ const onFileSelected = async (event) => {
         ...updated,
       };
     }
+
+    $q.notify({
+      message: "Contrato assinado enviado com sucesso.",
+      type: "positive",
+    });
   } catch (error) {
     console.error(error);