ContractTab.vue 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. <template>
  2. <div>
  3. <input
  4. ref="fileInputRef"
  5. type="file"
  6. accept="image/*,video/*,.pdf"
  7. style="display: none"
  8. @change="onFileSelected"
  9. />
  10. <DefaultTable
  11. v-model:rows="rows"
  12. title="Contratos"
  13. :columns
  14. descricao="contratos"
  15. :feminino="false"
  16. no-api-call
  17. add-item
  18. :show-search-field="false"
  19. @on-add-item="handleAdd"
  20. >
  21. <template #body-cell-period="{ row }">
  22. <q-td>{{ row.signature_date }} — {{ row.end_date }}</q-td>
  23. </template>
  24. <template #body-cell-status="{ row }">
  25. <q-td align="center">
  26. <q-badge
  27. :color="statusColor(row.status)"
  28. :label="statusLabel(row.status)"
  29. />
  30. </q-td>
  31. </template>
  32. <template #body-cell-actions="{ row }">
  33. <q-td align="center">
  34. <q-item-section class="no-wrap" style="flex-direction: row; gap: 4px">
  35. <q-btn
  36. outline
  37. icon="mdi-pencil-outline"
  38. style="width: 36px"
  39. @click.prevent.stop="handleEdit(row)"
  40. />
  41. <q-btn
  42. outline
  43. icon="mdi-paperclip"
  44. style="width: 36px"
  45. :loading="uploadingId === row.id"
  46. @click.prevent.stop="triggerFileInput(row)"
  47. />
  48. <q-btn
  49. outline
  50. icon="mdi-dots-vertical"
  51. style="width: 36px"
  52. @click.prevent.stop
  53. >
  54. <q-menu>
  55. <q-list style="min-width: 170px">
  56. <q-item
  57. v-close-popup
  58. clickable
  59. :disable="!row.file_url"
  60. @click="openFile(row.file_url)"
  61. >
  62. <q-item-section>Visualizar arquivo</q-item-section>
  63. </q-item>
  64. <template v-if="row.status === 'frozen' || row.status === 'cancelled'">
  65. <q-item
  66. v-close-popup
  67. clickable
  68. @click="handleReactivate(row)"
  69. >
  70. <q-item-section>Reativar Contrato</q-item-section>
  71. </q-item>
  72. </template>
  73. <template v-else>
  74. <q-item
  75. v-close-popup
  76. clickable
  77. @click="handleFreeze(row)"
  78. >
  79. <q-item-section>Congelar Contrato</q-item-section>
  80. </q-item>
  81. <q-item
  82. v-close-popup
  83. clickable
  84. @click="handleCancel(row)"
  85. >
  86. <q-item-section>Cancelar Contrato</q-item-section>
  87. </q-item>
  88. </template>
  89. </q-list>
  90. </q-menu>
  91. </q-btn>
  92. </q-item-section>
  93. </q-td>
  94. </template>
  95. </DefaultTable>
  96. </div>
  97. </template>
  98. <script setup>
  99. import { ref, onMounted } from "vue";
  100. import { useQuasar } from "quasar";
  101. import DefaultTable from "src/components/defaults/DefaultTable.vue";
  102. import AddEditContractDialog from "src/pages/students/components/AddEditContractDialog.vue";
  103. import ContractActionConfirmDialog from "src/pages/students/components/ContractActionConfirmDialog.vue";
  104. import {
  105. getStudentContracts,
  106. attachContractFile,
  107. freezeContract,
  108. cancelContract,
  109. reactivateContract,
  110. } from "src/api/studentContract";
  111. const props = defineProps({
  112. student: {
  113. type: Object,
  114. required: true,
  115. },
  116. });
  117. const $q = useQuasar();
  118. const rows = ref([]);
  119. const fileInputRef = ref(null);
  120. const uploadingId = ref(null);
  121. let pendingContractId = null;
  122. async function loadContracts() {
  123. rows.value = await getStudentContracts(props.student.id);
  124. }
  125. onMounted(loadContracts);
  126. function openDialog(contract = null) {
  127. $q.dialog({
  128. component: AddEditContractDialog,
  129. componentProps: { student: props.student, contract },
  130. }).onOk(loadContracts);
  131. }
  132. function handleAdd() {
  133. openDialog();
  134. }
  135. function handleEdit(contract) {
  136. openDialog(contract);
  137. }
  138. function triggerFileInput(row) {
  139. pendingContractId = row.id;
  140. fileInputRef.value.value = "";
  141. fileInputRef.value.click();
  142. }
  143. async function onFileSelected(event) {
  144. const file = event.target.files?.[0];
  145. if (!file || !pendingContractId) return;
  146. uploadingId.value = pendingContractId;
  147. try {
  148. const formData = new FormData();
  149. formData.append("file", file);
  150. const updated = await attachContractFile(pendingContractId, formData);
  151. const idx = rows.value.findIndex((r) => r.id === pendingContractId);
  152. if (idx !== -1) rows.value[idx] = updated;
  153. } catch (e) {
  154. console.error(e);
  155. $q.notify({ type: "negative", message: "Erro ao anexar arquivo." });
  156. } finally {
  157. uploadingId.value = null;
  158. pendingContractId = null;
  159. }
  160. }
  161. function openFile(url) {
  162. window.open(url, "_blank");
  163. }
  164. function confirmAction(title, message, apiFn, contract) {
  165. $q.dialog({
  166. component: ContractActionConfirmDialog,
  167. componentProps: { title, message },
  168. }).onOk(async () => {
  169. try {
  170. const updated = await apiFn(contract.id);
  171. const idx = rows.value.findIndex((r) => r.id === contract.id);
  172. if (idx !== -1) rows.value[idx] = updated;
  173. } catch (e) {
  174. console.error(e);
  175. $q.notify({ type: "negative", message: "Erro ao atualizar status do contrato." });
  176. }
  177. });
  178. }
  179. function handleFreeze(contract) {
  180. confirmAction(
  181. "Congelar Contrato",
  182. "Você tem certeza que deseja CONGELAR este contrato? Isso irá gerar multas e cancelamento da cobrança recorrente.",
  183. freezeContract,
  184. contract,
  185. );
  186. }
  187. function handleCancel(contract) {
  188. confirmAction(
  189. "Cancelar Contrato",
  190. "Você tem certeza que deseja CANCELAR este contrato? Isso irá gerar multas e cancelamento da cobrança recorrente.",
  191. cancelContract,
  192. contract,
  193. );
  194. }
  195. function handleReactivate(contract) {
  196. confirmAction(
  197. "Reativar Contrato",
  198. "Você tem certeza que deseja REATIVAR este contrato?",
  199. reactivateContract,
  200. contract,
  201. );
  202. }
  203. function statusColor(status) {
  204. if (status === "active") return "positive";
  205. if (status === "frozen") return "info";
  206. if (status === "cancelled") return "negative";
  207. return "warning";
  208. }
  209. function statusLabel(status) {
  210. if (status === "active") return "Ativo";
  211. if (status === "frozen") return "Congelado";
  212. if (status === "cancelled") return "Cancelado";
  213. return "Inativo";
  214. }
  215. const columns = ref([
  216. { name: "contract", label: "Contrato", field: "protocol", align: "left" },
  217. {
  218. name: "period",
  219. label: "Data Assinatura - Encerramento",
  220. field: null,
  221. align: "left",
  222. },
  223. { name: "status", label: "Status", field: "status", align: "center" },
  224. { name: "actions", label: "Ações", field: null, align: "center" },
  225. ]);
  226. </script>