Ver código fonte

feat: adiciona funcionalidade de midias dentro de Contratos

ebagabee 1 mês atrás
pai
commit
b08f64ac6e

+ 9 - 6
src/api/student_media.js

@@ -1,14 +1,17 @@
 import api from "src/api";
 
 export const getStudentMedias = async (studentId) => {
-  const { data } = await api.get("/media", {
-    params: { origem: "student", origem_id: studentId },
-  });
+  const { data } = await api.get("/student-media", { params: { student_id: studentId } });
+  return data.payload;
+};
+
+export const getContractMedias = async (contractId) => {
+  const { data } = await api.get("/student-media", { params: { student_contract_id: contractId } });
   return data.payload;
 };
 
 export const createStudentMedia = async (formData) => {
-  const { data } = await api.post("/media", formData, {
+  const { data } = await api.post("/student-media", formData, {
     headers: { "Content-Type": "multipart/form-data" },
   });
   return data.payload;
@@ -16,13 +19,13 @@ export const createStudentMedia = async (formData) => {
 
 export const updateStudentMedia = async (id, formData) => {
   formData.append("_method", "PUT");
-  const { data } = await api.post(`/media/${id}`, formData, {
+  const { data } = await api.post(`/student-media/${id}`, formData, {
     headers: { "Content-Type": "multipart/form-data" },
   });
   return data.payload;
 };
 
 export const deleteStudentMedia = async (id) => {
-  const { data } = await api.delete(`/media/${id}`);
+  const { data } = await api.delete(`/student-media/${id}`);
   return data;
 };

+ 1 - 2
src/pages/students/components/AddStudentMediaDialog.vue

@@ -66,8 +66,7 @@ async function onSubmit() {
   await execute(() => {
     const formData = new FormData();
     formData.append("name", form.value.name);
-    formData.append("origem", "student");
-    formData.append("origem_id", studentId);
+    formData.append("student_id", studentId);
     formData.append("file", selectedFile.value);
     return createStudentMedia(formData);
   });

+ 335 - 235
src/pages/students/components/CreateContractDialog.vue

@@ -9,248 +9,298 @@
         @close="onDialogCancel"
       />
 
+      <template v-if="props.contract">
+        <q-tabs
+          v-model="activeTab"
+          dense
+          align="left"
+          class="q-px-md text-grey-7"
+          active-color="primary"
+          indicator-color="primary"
+        >
+          <q-tab name="dados" label="Dados do Contrato" />
+          <q-tab name="midias" label="Mídias do Contrato" />
+        </q-tabs>
+        <q-separator />
+      </template>
+
       <q-card-section class="q-pt-sm" style="height: 65vh; overflow-y: auto">
-        <div class="text-subtitle1 q-mb-md">Dados do Aluno</div>
-
-        <div class="row q-col-gutter-sm">
-          <div class="col-12">
-            <DefaultInput
-              :model-value="props.student.name"
-              label="Aluno"
-              disable
-            />
+        <div v-show="activeTab === 'dados'">
+          <div class="text-subtitle1 q-mb-md">Dados do Aluno</div>
+
+          <div class="row q-col-gutter-sm">
+            <div class="col-12">
+              <DefaultInput
+                :model-value="props.student.name"
+                label="Aluno"
+                disable
+              />
+            </div>
+
+            <div class="col-6">
+              <DefaultInput
+                :model-value="props.student.document_number"
+                label="CPF"
+                disable
+              />
+            </div>
+
+            <div class="col-6">
+              <DefaultInput
+                :model-value="formattedBirthDate"
+                label="Data de Nascimento"
+                disable
+              />
+            </div>
           </div>
 
-          <div class="col-6">
-            <DefaultInput
-              :model-value="props.student.document_number"
-              label="CPF"
-              disable
-            />
+          <div class="text-subtitle1 q-mt-lg q-mb-md">Dados do Contrato</div>
+
+          <div class="row q-col-gutter-sm">
+            <div class="col-4">
+              <DefaultInput v-model="form.protocol" label="Protocolo" />
+            </div>
+
+            <div class="col-4">
+              <DefaultInputDatePicker
+                v-model="form.signature_date"
+                label="Data Assinatura"
+              />
+            </div>
+
+            <div class="col-4">
+              <DefaultInputDatePicker
+                v-model="form.end_date"
+                label="Data Encerramento"
+              />
+            </div>
+
+            <div class="col-5">
+              <DefaultSelect
+                v-model="form.package_id"
+                label="Pacote de Aulas"
+                :options="packages"
+                option-value="id"
+                option-label="name"
+                emit-value
+                map-options
+              />
+            </div>
+
+            <div class="col-7">
+              <DefaultInput
+                v-model="form.class_quantity"
+                label="Qtd. Aulas"
+                type="number"
+                disable
+              />
+            </div>
+
+            <div class="col-4">
+              <DefaultSelect
+                v-model="form.weekday"
+                label="Dia da Semana"
+                :options="weekdays"
+                option-value="value"
+                option-label="label"
+                emit-value
+                map-options
+              />
+            </div>
+
+            <div class="col-4">
+              <DefaultInput
+                v-model="form.start_time"
+                label="Hora de Início"
+                mask="##:##"
+              >
+                <template #append>
+                  <q-icon name="mdi-clock-outline" />
+                </template>
+              </DefaultInput>
+            </div>
+
+            <div class="col-4">
+              <DefaultInput
+                v-model="form.end_time"
+                label="Hora de Término"
+                mask="##:##"
+              >
+                <template #append>
+                  <q-icon name="mdi-clock-outline" />
+                </template>
+              </DefaultInput>
+            </div>
+
+            <div class="col-4">
+              <DefaultSelect
+                v-model="form.second_weekday"
+                label="2° Dia da Semana"
+                :options="weekdays"
+                option-value="value"
+                option-label="label"
+                emit-value
+                map-options
+              />
+            </div>
+
+            <div class="col-4">
+              <DefaultInput
+                v-model="form.second_start_time"
+                label="Hora de Início"
+                mask="##:##"
+              >
+                <template #append>
+                  <q-icon name="mdi-clock-outline" />
+                </template>
+              </DefaultInput>
+            </div>
+
+            <div class="col-4">
+              <DefaultInput
+                v-model="form.second_end_time"
+                label="Hora de Término"
+                mask="##:##"
+              >
+                <template #append>
+                  <q-icon name="mdi-clock-outline" />
+                </template>
+              </DefaultInput>
+            </div>
           </div>
 
-          <div class="col-6">
-            <DefaultInput
-              :model-value="formattedBirthDate"
-              label="Data de Nascimento"
-              disable
-            />
+          <div class="text-subtitle1 q-mt-lg q-mb-md">Dados Financeiros</div>
+
+          <div class="row q-col-gutter-sm">
+            <div class="col-4">
+              <DefaultInput
+                v-model="form.due_day"
+                label="Dia de Vencimento"
+                type="number"
+              />
+            </div>
+
+            <div class="col-4">
+              <DefaultCurrencyInput
+                v-model="form.enrollment_fee"
+                label="Taxa de Matrícula"
+                disable
+              />
+            </div>
+
+            <div class="col-4">
+              <DefaultInput
+                v-model="form.total_classes"
+                label="Total de Aulas"
+                type="number"
+                disable
+              />
+            </div>
+
+            <div class="col-3">
+              <DefaultCurrencyInput v-model="form.down_payment" label="Entrada" />
+            </div>
+
+            <div class="col-3">
+              <DefaultSelect
+                v-model="form.installments"
+                label="Parcelas"
+                :options="installmentOptions"
+                option-value="value"
+                option-label="label"
+                emit-value
+                map-options
+              />
+            </div>
+
+            <div class="col-6">
+              <DefaultInput
+                v-model="form.early_payment_discount"
+                label="Desconto até o vencimento (%)"
+                type="number"
+              />
+            </div>
+
+            <div class="col-3">
+              <DefaultCurrencyInput
+                v-model="form.material_value"
+                label="Valor dos Materiais"
+              />
+            </div>
+
+            <div class="col-3">
+              <DefaultSelect
+                v-model="form.material_installments"
+                label="Parcelas"
+                :options="installmentOptions"
+                option-value="value"
+                option-label="label"
+                emit-value
+                map-options
+              />
+            </div>
+
+            <div class="col-6">
+              <DefaultInput
+                v-model="form.interest_rate"
+                label="Juros (%) a.m"
+                type="number"
+              />
+            </div>
+
+            <div class="col-6">
+              <DefaultSelect
+                v-model="form.payment_method"
+                label="Forma de Pagamento"
+                :options="paymentMethods"
+                option-value="value"
+                option-label="label"
+                emit-value
+                map-options
+              />
+            </div>
+
+            <div class="col-6">
+              <DefaultInput
+                v-model="form.late_fee"
+                label="Multa (%)"
+                type="number"
+              />
+            </div>
           </div>
         </div>
 
-        <div class="text-subtitle1 q-mt-lg q-mb-md">Dados do Contrato</div>
-
-        <div class="row q-col-gutter-sm">
-          <div class="col-4">
-            <DefaultInput v-model="form.protocol" label="Protocolo" />
-          </div>
-
-          <div class="col-4">
-            <DefaultInputDatePicker
-              v-model="form.signature_date"
-              label="Data Assinatura"
-            />
-          </div>
-
-          <div class="col-4">
-            <DefaultInputDatePicker
-              v-model="form.end_date"
-              label="Data Encerramento"
-            />
-          </div>
-
-          <div class="col-5">
-            <DefaultSelect
-              v-model="form.package_id"
-              label="Pacote de Aulas"
-              :options="packages"
-              option-value="id"
-              option-label="name"
-              emit-value
-              map-options
-            />
-          </div>
-
-          <div class="col-7">
-            <DefaultInput
-              v-model="form.class_quantity"
-              label="Qtd. Aulas"
-              type="number"
-              disable
-            />
-          </div>
-
-          <div class="col-4">
-            <DefaultSelect
-              v-model="form.weekday"
-              label="Dia da Semana"
-              :options="weekdays"
-              option-value="value"
-              option-label="label"
-              emit-value
-              map-options
-            />
-          </div>
-
-          <div class="col-4">
-            <DefaultInput
-              v-model="form.start_time"
-              label="Hora de Início"
-              mask="##:##"
-            >
-              <template #append>
-                <q-icon name="mdi-clock-outline" />
-              </template>
-            </DefaultInput>
-          </div>
-
-          <div class="col-4">
-            <DefaultInput
-              v-model="form.end_time"
-              label="Hora de Término"
-              mask="##:##"
-            >
-              <template #append>
-                <q-icon name="mdi-clock-outline" />
-              </template>
-            </DefaultInput>
-          </div>
-
-          <div class="col-4">
-            <DefaultSelect
-              v-model="form.second_weekday"
-              label="2° Dia da Semana"
-              :options="weekdays"
-              option-value="value"
-              option-label="label"
-              emit-value
-              map-options
-            />
-          </div>
-
-          <div class="col-4">
-            <DefaultInput
-              v-model="form.second_start_time"
-              label="Hora de Início"
-              mask="##:##"
-            >
-              <template #append>
-                <q-icon name="mdi-clock-outline" />
-              </template>
-            </DefaultInput>
-          </div>
-
-          <div class="col-4">
-            <DefaultInput
-              v-model="form.second_end_time"
-              label="Hora de Término"
-              mask="##:##"
-            >
-              <template #append>
-                <q-icon name="mdi-clock-outline" />
-              </template>
-            </DefaultInput>
-          </div>
-        </div>
-
-        <div class="text-subtitle1 q-mt-lg q-mb-md">Dados Financeiros</div>
-
-        <div class="row q-col-gutter-sm">
-          <div class="col-4">
-            <DefaultInput
-              v-model="form.due_day"
-              label="Dia de Vencimento"
-              type="number"
-            />
-          </div>
-
-          <div class="col-4">
-            <DefaultCurrencyInput
-              v-model="form.enrollment_fee"
-              label="Taxa de Matrícula"
-              disable
-            />
-          </div>
-
-          <div class="col-4">
-            <DefaultInput
-              v-model="form.total_classes"
-              label="Total de Aulas"
-              type="number"
-              disable
-            />
-          </div>
-
-          <div class="col-3">
-            <DefaultCurrencyInput v-model="form.down_payment" label="Entrada" />
-          </div>
-
-          <div class="col-3">
-            <DefaultSelect
-              v-model="form.installments"
-              label="Parcelas"
-              :options="installmentOptions"
-              option-value="value"
-              option-label="label"
-              emit-value
-              map-options
-            />
-          </div>
-
-          <div class="col-6">
-            <DefaultInput
-              v-model="form.early_payment_discount"
-              label="Desconto até o vencimento (%)"
-              type="number"
-            />
-          </div>
-
-          <div class="col-3">
-            <DefaultCurrencyInput
-              v-model="form.material_value"
-              label="Valor dos Materiais"
-            />
-          </div>
-
-          <div class="col-3">
-            <DefaultSelect
-              v-model="form.material_installments"
-              label="Parcelas"
-              :options="installmentOptions"
-              option-value="value"
-              option-label="label"
-              emit-value
-              map-options
-            />
-          </div>
-
-          <div class="col-6">
-            <DefaultInput
-              v-model="form.interest_rate"
-              label="Juros (%) a.m"
-              type="number"
-            />
-          </div>
-
-          <div class="col-6">
-            <DefaultSelect
-              v-model="form.payment_method"
-              label="Forma de Pagamento"
-              :options="paymentMethods"
-              option-value="value"
-              option-label="label"
-              emit-value
-              map-options
-            />
-          </div>
-
-          <div class="col-6">
-            <DefaultInput
-              v-model="form.late_fee"
-              label="Multa (%)"
-              type="number"
-            />
-          </div>
+        <div v-if="props.contract" v-show="activeTab === 'midias'">
+          <DefaultTable
+            v-model:rows="files"
+            title="Mídias"
+            :columns="mediaColumns"
+            descricao="mídias"
+            :feminino="true"
+            no-api-call
+            :show-search-field="false"
+            :loading="loadingFiles"
+          >
+            <template #body-cell-actions="{ row }">
+              <q-td align="center">
+                <q-item-section class="no-wrap" style="flex-direction: row; gap: 4px">
+                  <q-btn
+                    outline
+                    icon="mdi-eye-outline"
+                    style="width: 36px"
+                    @click.prevent.stop="openFile(row.file_url)"
+                  />
+                  <q-btn
+                    outline
+                    icon="mdi-trash-can-outline"
+                    style="width: 36px"
+                    color="negative"
+                    @click.prevent.stop="handleDeleteMedia(row)"
+                  />
+                </q-item-section>
+              </q-td>
+            </template>
+          </DefaultTable>
         </div>
       </q-card-section>
 
@@ -264,6 +314,7 @@
           @click="onDialogCancel"
         />
         <q-btn
+          v-if="activeTab === 'dados'"
           color="primary"
           label="SALVAR"
           :loading="saving"
@@ -276,12 +327,13 @@
 
 <script setup>
 import { computed, ref, watch, onMounted } from "vue";
-import { useDialogPluginComponent } from "quasar";
+import { useDialogPluginComponent, useQuasar } from "quasar";
 import DefaultDialogHeader from "src/components/defaults/DefaultDialogHeader.vue";
 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 DefaultInputDatePicker from "src/components/defaults/DefaultInputDatePicker.vue";
+import DefaultTable from "src/components/defaults/DefaultTable.vue";
 import { useSubmitHandler } from "src/composables/useSubmitHandler";
 import { useFormUpdateTracker } from "src/composables/useFormUpdateTracker";
 import { formatDateYMDtoDMY, formatDateDMYtoYMD } from "src/helpers/utils";
@@ -290,6 +342,7 @@ import {
   createStudentContract,
   updateStudentContract,
 } from "src/api/studentContract";
+import { getContractMedias, deleteStudentMedia } from "src/api/student_media";
 
 const props = defineProps({
   student: {
@@ -307,6 +360,12 @@ defineEmits([...useDialogPluginComponent.emits]);
 const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } =
   useDialogPluginComponent();
 
+const $q = useQuasar();
+
+const activeTab = ref("dados");
+const files = ref([]);
+const loadingFiles = ref(false);
+
 const trimTime = (t) => (t ? t.slice(0, 5) : null);
 
 const { form } = useFormUpdateTracker({
@@ -355,10 +414,30 @@ const weekdays = [
   { value: 0, label: "Domingo" },
 ];
 
+const mediaColumns = [
+  { name: "created_at", label: "Data de Anexo", field: "created_at", align: "left" },
+  { name: "actions", label: "Ações", field: null, align: "center" },
+];
+
 const packages = ref([]);
 
+async function fetchFiles() {
+  if (!props.contract) return;
+  loadingFiles.value = true;
+  try {
+    files.value = await getContractMedias(props.contract.id);
+  } finally {
+    loadingFiles.value = false;
+  }
+}
+
 onMounted(async () => {
   packages.value = await getUnitPackages();
+  await fetchFiles();
+});
+
+watch(activeTab, (tab) => {
+  if (tab === "midias") fetchFiles();
 });
 
 watch(
@@ -409,6 +488,27 @@ function buildPayload() {
   };
 }
 
+function openFile(url) {
+  window.open(url, "_blank");
+}
+
+function handleDeleteMedia(media) {
+  $q.dialog({
+    title: "Excluir mídia",
+    message: "Deseja excluir esta mídia permanentemente?",
+    ok: { color: "negative", label: "Excluir" },
+    cancel: { color: "primary", outline: true, label: "Cancelar" },
+  }).onOk(async () => {
+    try {
+      await deleteStudentMedia(media.id);
+      files.value = files.value.filter((f) => f.id !== media.id);
+    } catch (e) {
+      console.error(e);
+      $q.notify({ type: "negative", message: "Erro ao excluir mídia." });
+    }
+  });
+}
+
 async function handleSave() {
   const payload = buildPayload();
   await execute(() =>

+ 7 - 2
src/pages/students/components/EditStudentDialog.vue

@@ -30,7 +30,7 @@
         </div>
 
         <div v-show="currentTab === 'media'">
-          <MediaTab :student-id="props.student.id" />
+          <MediaTab ref="mediaTabRef" :student-id="props.student.id" />
         </div>
       </q-card-section>
 
@@ -55,7 +55,7 @@
 </template>
 
 <script setup>
-import { ref, useTemplateRef, defineAsyncComponent } from "vue";
+import { ref, watch, useTemplateRef, defineAsyncComponent } from "vue";
 import { useDialogPluginComponent } from "quasar";
 import DefaultDialogHeader from "src/components/defaults/DefaultDialogHeader.vue";
 import CustomTabComponent from "src/components/shared/CustomTabComponent.vue";
@@ -91,9 +91,14 @@ const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } =
   useDialogPluginComponent();
 
 const studentDataTabRef = useTemplateRef("studentDataTabRef");
+const mediaTabRef = useTemplateRef("mediaTabRef");
 
 const currentTab = ref("profile");
 
+watch(currentTab, (tab) => {
+  if (tab === "media") mediaTabRef.value?.refresh();
+});
+
 const { loading: saving, execute } = useSubmitHandler({
   onSuccess: () => onDialogOK(true),
 });

+ 1 - 2
src/pages/students/components/EditStudentMediaDialog.vue

@@ -66,8 +66,7 @@ async function onSubmit() {
   await execute(() => {
     const formData = new FormData();
     formData.append("name", form.value.name);
-    formData.append("origem", media.origem);
-    formData.append("origem_id", media.origem_id);
+    formData.append("student_id", media.student_id);
     if (selectedFile.value) {
       formData.append("file", selectedFile.value);
     }

+ 17 - 0
src/pages/students/tabs/MediaTab.vue

@@ -13,6 +13,19 @@
         <q-td>{{ rowIndex + 1 }}</q-td>
       </template>
 
+      <template #body-cell-type="{ row }">
+        <q-td>
+          <q-badge
+            :color="row.type === 'contract' ? 'info' : 'primary'"
+            :label="row.type === 'contract' ? 'Contrato' : 'Mídia'"
+          />
+        </q-td>
+      </template>
+
+      <template #body-cell-name="{ row }">
+        <q-td>{{ row.name ?? '—' }}</q-td>
+      </template>
+
       <template #body-cell-actions="{ row }">
         <q-btn
           flat
@@ -24,6 +37,7 @@
           @click.stop="openFile(row.file_url)"
         />
         <q-btn
+          v-if="row.type !== 'contract'"
           flat
           round
           dense
@@ -61,6 +75,7 @@ const rows = ref([]);
 const columns = [
   { name: "item", label: "Item", field: "id", align: "left" },
   { name: "date", label: "Data", field: "created_at", align: "left" },
+  { name: "type", label: "Tipo", field: "type", align: "left" },
   { name: "name", label: "Nome", field: "name", align: "left" },
   { name: "actions", label: "Ações", field: null, align: "right" },
 ];
@@ -113,4 +128,6 @@ function confirmDelete(row) {
 }
 
 onMounted(fetchMedias);
+
+defineExpose({ refresh: fetchMedias });
 </script>