Эх сурвалжийг харах

feat(contract): update contract dialog to support student selection and improve layout

ebagabee 1 долоо хоног өмнө
parent
commit
6a81009b42

+ 6 - 6
src/pages/dashboard/DashboardPage.vue

@@ -85,7 +85,7 @@
             <q-separator />
             <q-separator />
             <q-card-section class="q-pt-md column q-gutter-sm">
             <q-card-section class="q-pt-md column q-gutter-sm">
               <q-btn
               <q-btn
-                v-if="canAddContracts"
+                v-if="canAdd"
                 unelevated
                 unelevated
                 color="primary"
                 color="primary"
                 label="Criar contrato"
                 label="Criar contrato"
@@ -206,6 +206,7 @@ import GroupedBarChart from "src/components/charts/normal/GroupedBarChart.vue";
 import AniversariantesCard from "src/components/charts/AniversariantesCard.vue";
 import AniversariantesCard from "src/components/charts/AniversariantesCard.vue";
 import FeriadosDialog from "./components/FeriadosDialog.vue";
 import FeriadosDialog from "./components/FeriadosDialog.vue";
 import FeriadosEditDialog from "./components/FeriadosEditDialog.vue";
 import FeriadosEditDialog from "./components/FeriadosEditDialog.vue";
+import AddEditContractDialog from "src/pages/students/components/AddEditContractDialog.vue";
 import { getHolidays } from "src/api/holiday";
 import { getHolidays } from "src/api/holiday";
 import { getDashboardSummary } from "src/api/franchisee_dashboard";
 import { getDashboardSummary } from "src/api/franchisee_dashboard";
 import { userStore } from "src/stores/user";
 import { userStore } from "src/stores/user";
@@ -219,9 +220,6 @@ const permissions = permissionStore();
 const canAdd = computed(() =>
 const canAdd = computed(() =>
   permissions.getAccess("franchisee_dashboard", "add"),
   permissions.getAccess("franchisee_dashboard", "add"),
 );
 );
-const canAddContracts = computed(() =>
-  permissions.getAccess("franchisee_contracts", "add"),
-);
 const canEditClasses = computed(() =>
 const canEditClasses = computed(() =>
   permissions.getAccess("franchisee_classes", "edit"),
   permissions.getAccess("franchisee_classes", "edit"),
 );
 );
@@ -378,9 +376,11 @@ async function fetchSummary() {
   }
   }
 }
 }
 
 
-// ─── Atalhos rápidos (ainda não implementados) ───────────────
 function onCriarContrato() {
 function onCriarContrato() {
-  console.log("Atalho rápido: Criar contrato");
+  $q.dialog({
+    component: AddEditContractDialog,
+    componentProps: { selectStudent: true },
+  }).onOk(fetchSummary);
 }
 }
 
 
 function onRegistrarPresenca() {
 function onRegistrarPresenca() {

+ 78 - 13
src/pages/students/components/AddEditContractDialog.vue

@@ -1,8 +1,8 @@
 <template>
 <template>
   <q-dialog ref="dialogRef" @hide="onDialogHide">
   <q-dialog ref="dialogRef" @hide="onDialogHide">
     <q-card
     <q-card
-      class="q-dialog-plugin overflow-hidden"
-      style="width: 100%; max-width: 1350px"
+      class="q-dialog-plugin overflow-hidden column no-wrap"
+      :style="dialogCardStyle"
     >
     >
       <DefaultDialogHeader
       <DefaultDialogHeader
         :title="props.contract ? 'Editar Contrato' : 'Novo Contrato'"
         :title="props.contract ? 'Editar Contrato' : 'Novo Contrato'"
@@ -24,28 +24,51 @@
         <q-separator />
         <q-separator />
       </template>
       </template>
 
 
-      <q-card-section class="q-pt-sm" style="height: 65vh; overflow-y: auto">
+      <q-card-section class="q-pt-sm col" style="overflow-y: auto">
         <div v-show="activeTab === 'dados'">
         <div v-show="activeTab === 'dados'">
         <div class="text-subtitle1 q-mb-md">Dados do Aluno</div>
         <div class="text-subtitle1 q-mb-md">Dados do Aluno</div>
 
 
         <div class="row q-col-gutter-sm">
         <div class="row q-col-gutter-sm">
-          <div class="col-12">
+          <div v-if="props.selectStudent" class="col-12">
+            <DefaultSelect
+              v-model="selectedStudent"
+              label="Aluno"
+              :options="filteredStudents"
+              option-label="name"
+              use-input
+              fill-input
+              hide-selected
+              input-debounce="0"
+              dropdown-icon="mdi-magnify"
+              @filter="filterStudents"
+            />
+          </div>
+
+          <div v-if="!props.selectStudent" class="col-12">
             <DefaultInput
             <DefaultInput
-              :model-value="props.student.name"
+              :model-value="currentStudent?.name"
               label="Aluno"
               label="Aluno"
               disable
               disable
             />
             />
           </div>
           </div>
 
 
-          <div class="col-6">
+          <div v-if="props.selectStudent" class="col-12 col-md-4">
             <DefaultInput
             <DefaultInput
-              :model-value="props.student.document_number"
-              label="CPF"
+              :model-value="currentStudent?.id"
+              label="ID do Aluno"
               disable
               disable
             />
             />
           </div>
           </div>
 
 
-          <div class="col-6">
+          <div :class="props.selectStudent ? 'col-12 col-md-4' : 'col-6'">
+            <DefaultInput
+              :model-value="currentStudent?.document_number"
+              label="CPF / CNH"
+              disable
+            />
+          </div>
+
+          <div :class="props.selectStudent ? 'col-12 col-md-4' : 'col-6'">
             <DefaultInput
             <DefaultInput
               :model-value="formattedBirthDate"
               :model-value="formattedBirthDate"
               label="Data de Nascimento"
               label="Data de Nascimento"
@@ -372,16 +395,21 @@ import {
   updateStudentContract,
   updateStudentContract,
 } from "src/api/studentContract";
 } from "src/api/studentContract";
 import { getContractMedias, deleteContractMedia } from "src/api/student_media";
 import { getContractMedias, deleteContractMedia } from "src/api/student_media";
+import { getStudentsForSelect } from "src/api/student";
 
 
 const props = defineProps({
 const props = defineProps({
   student: {
   student: {
     type: Object,
     type: Object,
-    required: true,
+    default: null,
   },
   },
   contract: {
   contract: {
     type: Object,
     type: Object,
     default: null,
     default: null,
   },
   },
+  selectStudent: {
+    type: Boolean,
+    default: false,
+  },
 });
 });
 
 
 defineEmits([...useDialogPluginComponent.emits]);
 defineEmits([...useDialogPluginComponent.emits]);
@@ -397,6 +425,27 @@ const canViewFinancial = computed(() =>
 const activeTab = ref("dados");
 const activeTab = ref("dados");
 const medias = ref([]);
 const medias = ref([]);
 const loadingMedias = ref(false);
 const loadingMedias = ref(false);
+const selectedStudent = ref(props.student);
+const students = ref([]);
+const filteredStudents = ref([]);
+const currentStudent = computed(() => selectedStudent.value ?? props.student);
+const dialogCardStyle = computed(() => ({
+  width: "100%",
+  maxWidth: props.selectStudent ? "1400px" : "1350px",
+  height: props.selectStudent ? "95vh" : "auto",
+  maxHeight: props.selectStudent ? "95vh" : "none",
+}));
+
+function filterStudents(value, update) {
+  update(() => {
+    const search = value.trim().toLocaleLowerCase("pt-BR");
+    filteredStudents.value = search
+      ? students.value.filter((student) =>
+          student.name.toLocaleLowerCase("pt-BR").includes(search),
+        )
+      : students.value;
+  });
+}
 
 
 const trimTime = (t) => (t ? t.slice(0, 5) : null);
 const trimTime = (t) => (t ? t.slice(0, 5) : null);
 
 
@@ -603,7 +652,13 @@ watch(activeTab, (tab) => {
 });
 });
 
 
 onMounted(async () => {
 onMounted(async () => {
-  packages.value = await getUnitPackagesForSelect();
+  const requests = [getUnitPackagesForSelect()];
+  if (props.selectStudent) requests.push(getStudentsForSelect());
+
+  const [unitPackages, unitStudents = []] = await Promise.all(requests);
+  packages.value = unitPackages;
+  students.value = unitStudents;
+  filteredStudents.value = unitStudents;
 
 
   // Para novo contrato, pré-preenche desconto/juros/multa com os defaults da unidade
   // Para novo contrato, pré-preenche desconto/juros/multa com os defaults da unidade
   if (!props.contract && canViewFinancial.value) {
   if (!props.contract && canViewFinancial.value) {
@@ -690,7 +745,9 @@ watch(
 );
 );
 
 
 const formattedBirthDate = computed(() =>
 const formattedBirthDate = computed(() =>
-  props.student.birth_date ? formatDateYMDtoDMY(props.student.birth_date) : "",
+  currentStudent.value?.birth_date
+    ? formatDateYMDtoDMY(currentStudent.value.birth_date)
+    : "",
 );
 );
 
 
 const { loading: saving, execute } = useSubmitHandler({
 const { loading: saving, execute } = useSubmitHandler({
@@ -699,7 +756,7 @@ const { loading: saving, execute } = useSubmitHandler({
 
 
 function buildPayload() {
 function buildPayload() {
   return {
   return {
-    student_id: props.student.id,
+    student_id: currentStudent.value?.id ?? null,
     protocol: form.protocol,
     protocol: form.protocol,
     signature_date: form.signature_date
     signature_date: form.signature_date
       ? formatDateDMYtoYMD(form.signature_date)
       ? formatDateDMYtoYMD(form.signature_date)
@@ -732,6 +789,14 @@ function buildPayload() {
 }
 }
 
 
 async function handleSave() {
 async function handleSave() {
+  if (!currentStudent.value) {
+    $q.notify({
+      type: "warning",
+      message: "Selecione um aluno para criar o contrato.",
+    });
+    return;
+  }
+
   const payload = buildPayload();
   const payload = buildPayload();
   await execute(() =>
   await execute(() =>
     props.contract
     props.contract