Przeglądaj źródła

feat: adiciona dashboard page

ebagabee 2 tygodni temu
rodzic
commit
f62cef4439

+ 16 - 2
src/pages/dashboard/DashboardPage.vue

@@ -7,8 +7,8 @@
         <DashboardStatCard
           title="Total alunos (contratos ativos)"
           icon="mdi-account-multiple-outline"
-          value="0"
-          badge="0 ativos"
+          :value="String(totalAlunos)"
+          :badge="`${totalAlunos} ativos`"
         />
         <DashboardStatCard
           title="Receita Total"
@@ -199,6 +199,7 @@ import GroupedBarChart from "src/components/charts/normal/GroupedBarChart.vue";
 import AniversariantesCard from "src/components/charts/AniversariantesCard.vue";
 import FeriadosDialog from "./components/FeriadosDialog.vue";
 import { getHolidays } from "src/api/holiday";
+import { getStudents } from "src/api/student";
 
 ChartJS.register(ArcElement, Tooltip, Legend);
 
@@ -324,6 +325,18 @@ const aniversariantes = ref([
   { day: 34, name: "Sofia Martins" },
 ]);
 
+// Alunos
+const totalAlunos = ref(0);
+
+async function fetchAlunos() {
+  try {
+    const students = await getStudents();
+    totalAlunos.value = students.length;
+  } catch {
+    // silencioso
+  }
+}
+
 // Feriados
 const allHolidays = ref([]);
 const feriadosLoading = ref(false);
@@ -364,6 +377,7 @@ function openFeriadosDialog() {
 
 onMounted(() => {
   fetchHolidays();
+  fetchAlunos();
 });
 </script>
 

+ 9 - 9
src/pages/dashboard/components/FeriadosDialog.vue

@@ -227,21 +227,21 @@ const loadingHolidays = ref(false);
 const saving = ref(false);
 const deletingId = ref(null);
 
-const weekDays = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
+const weekDays = ["Dom", "Seg", "Ter", "Qua", "Qui", "Sex", "Sáb"];
 
 const monthOptions = [
   { label: "Jan", value: 1 },
-  { label: "Feb", value: 2 },
+  { label: "Fev", value: 2 },
   { label: "Mar", value: 3 },
-  { label: "Apr", value: 4 },
-  { label: "May", value: 5 },
+  { label: "Abr", value: 4 },
+  { label: "Mai", value: 5 },
   { label: "Jun", value: 6 },
   { label: "Jul", value: 7 },
-  { label: "Aug", value: 8 },
-  { label: "Sep", value: 9 },
-  { label: "Oct", value: 10 },
+  { label: "Ago", value: 8 },
+  { label: "Set", value: 9 },
+  { label: "Out", value: 10 },
   { label: "Nov", value: 11 },
-  { label: "Dec", value: 12 },
+  { label: "Dez", value: 12 },
 ];
 
 const typeOptions = [
@@ -394,7 +394,7 @@ onMounted(loadHolidays);
 
 <style scoped>
 .feriados-dialog {
-  width: 700px;
+  width: 900px;
   max-width: 95vw;
 }
 

+ 26 - 173
src/pages/students/components/EditStudentDialog.vue

@@ -4,15 +4,10 @@
       class="q-dialog-plugin overflow-hidden"
       style="width: 900px; max-width: 95vw"
     >
-      <DefaultDialogHeader title="Editar dados do Aluno" @close="onDialogCancel" />
+      <DefaultDialogHeader title="Dados do Aluno" @close="onDialogCancel" />
 
-      <!-- Tab navigation -->
-      <div class="q-px-md q-pt-sm q-pb-xs">
-        <CustomTabComponent v-model:active-tab="activeTab" :tabs="tabs" />
-      </div>
-
-      <!-- ─── TAB: PERFIL DO ALUNO ─── -->
-      <q-form v-if="activeTab === 'perfil'" ref="formRef" @submit="onSave">
+      <!-- ─── PERFIL DO ALUNO ─── -->
+      <div>
         <q-card-section class="q-pt-sm" style="max-height: 65vh; overflow-y: auto">
           <div class="text-subtitle2 q-mb-sm">Dados do Aluno</div>
 
@@ -21,43 +16,21 @@
               v-model="form.name"
               label="Nome do aluno"
               class="col-md-5 col-12"
-              :rules="[inputRules.required]"
+              readonly
             />
 
             <DefaultInputDatePicker
               v-model="form.birthdate"
               label="Data de Nascimento"
               class="col-md-5 col-12"
+              readonly
             />
 
             <div class="col-md-2 col-12 flex justify-center items-start">
-              <div style="position: relative; display: inline-block">
-                <q-avatar size="72px" color="grey-3">
-                  <img v-if="avatarPreview" :src="avatarPreview" />
-                  <q-icon
-                    v-else
-                    name="mdi-account"
-                    size="42px"
-                    color="grey-6"
-                  />
-                </q-avatar>
-                <q-btn
-                  round
-                  dense
-                  color="primary"
-                  icon="mdi-camera"
-                  size="xs"
-                  style="position: absolute; bottom: 0; right: 0"
-                  @click="triggerFileInput"
-                />
-                <input
-                  ref="fileInputRef"
-                  type="file"
-                  accept="image/*"
-                  style="display: none"
-                  @change="onAvatarChange"
-                />
-              </div>
+              <q-avatar size="72px" color="grey-3">
+                <img v-if="avatarPreview" :src="avatarPreview" />
+                <q-icon v-else name="mdi-account" size="42px" color="grey-6" />
+              </q-avatar>
             </div>
 
             <DefaultInput
@@ -65,7 +38,7 @@
               label="CPF / CNH"
               class="col-md-6 col-12"
               :mask="masks.Brasil.cpf"
-              :rules="[inputRules.cpf]"
+              readonly
             />
 
             <DefaultSelect
@@ -75,14 +48,14 @@
               emit-value
               map-options
               :options="genderOptions"
+              disable
             />
 
             <DefaultInput
               v-model="form.email"
               label="E-mail"
               class="col-md-6 col-12"
-              type="email"
-              :rules="[inputRules.email]"
+              readonly
             />
 
             <DefaultInput
@@ -90,6 +63,7 @@
               label="Celular com DDD"
               class="col-md-6 col-12"
               :mask="masks.Brasil.celular"
+              readonly
             />
 
             <DefaultInput
@@ -97,25 +71,28 @@
               label="CEP"
               class="col-md-3 col-12"
               :mask="masks.Brasil.cep"
-              :rules="[inputRules.cep]"
+              readonly
             />
 
             <DefaultInput
               v-model="form.address"
               label="Endereço"
               class="col-md-6 col-12"
+              readonly
             />
 
             <DefaultInput
               v-model="form.address_number"
               label="Número"
               class="col-md-3 col-12"
+              readonly
             />
 
             <DefaultInput
               v-model="form.neighborhood"
               label="Bairro"
               class="col-md-6 col-12"
+              readonly
             />
 
             <StateSelect
@@ -124,18 +101,21 @@
               label="Cidade / Estado"
               class="col-md-6 col-12"
               outlined
+              disable
             />
 
             <DefaultInput
               v-model="form.complement"
               label="Complemento"
               class="col-md-6 col-12"
+              readonly
             />
 
             <DefaultInput
               v-model="form.payer"
               label="Pagador"
               class="col-md-6 col-12"
+              readonly
             />
 
             <DefaultSelect
@@ -145,6 +125,7 @@
               emit-value
               map-options
               :options="howFoundOptions"
+              disable
             />
 
             <DefaultInput
@@ -153,90 +134,24 @@
               class="col-12"
               type="textarea"
               autogrow
+              readonly
             />
           </div>
         </q-card-section>
 
         <q-separator />
 
-        <q-card-actions align="right">
-          <q-btn outline color="primary" label="CANCELAR" no-caps @click="onDialogCancel" />
-          <q-btn
-            unelevated
-            color="primary"
-            label="SALVAR"
-            no-caps
-            type="submit"
-            :loading="loading"
-          />
-        </q-card-actions>
-      </q-form>
-
-      <!-- ─── TAB: RESPONSÁVEL ─── -->
-      <template v-else-if="activeTab === 'responsavel'">
-        <q-card-section style="min-height: 300px" class="flex flex-center column q-gutter-sm">
-          <q-icon name="mdi-account-supervisor-outline" size="64px" color="grey-4" />
-          <div class="text-subtitle1 text-grey-6">Responsável</div>
-          <div class="text-caption text-grey-5 text-center">
-            Funcionalidade em desenvolvimento.
-          </div>
-        </q-card-section>
-        <q-separator />
-        <q-card-actions align="right">
-          <q-btn outline color="primary" label="FECHAR" no-caps @click="onDialogCancel" />
-        </q-card-actions>
-      </template>
-
-      <!-- ─── TAB: CONTATOS ─── -->
-      <template v-else-if="activeTab === 'contatos'">
-        <q-card-section style="min-height: 300px" class="flex flex-center column q-gutter-sm">
-          <q-icon name="mdi-contacts-outline" size="64px" color="grey-4" />
-          <div class="text-subtitle1 text-grey-6">Contatos</div>
-          <div class="text-caption text-grey-5 text-center">
-            Funcionalidade em desenvolvimento.
-          </div>
-        </q-card-section>
-        <q-separator />
-        <q-card-actions align="right">
-          <q-btn outline color="primary" label="FECHAR" no-caps @click="onDialogCancel" />
-        </q-card-actions>
-      </template>
-
-      <!-- ─── TAB: HISTÓRICO ─── -->
-      <template v-else-if="activeTab === 'historico'">
-        <q-card-section style="min-height: 300px" class="flex flex-center column q-gutter-sm">
-          <q-icon name="mdi-history" size="64px" color="grey-4" />
-          <div class="text-subtitle1 text-grey-6">Histórico</div>
-          <div class="text-caption text-grey-5 text-center">
-            Funcionalidade em desenvolvimento.
-          </div>
-        </q-card-section>
-        <q-separator />
         <q-card-actions align="right">
           <q-btn outline color="primary" label="FECHAR" no-caps @click="onDialogCancel" />
         </q-card-actions>
-      </template>
-
-      <!-- ─── TAB: MÍDIAS ─── -->
-      <template v-else-if="activeTab === 'midias'">
-        <q-card-section style="min-height: 300px" class="flex flex-center column q-gutter-sm">
-          <q-icon name="mdi-image-multiple-outline" size="64px" color="grey-4" />
-          <div class="text-subtitle1 text-grey-6">Mídias</div>
-          <div class="text-caption text-grey-5 text-center">
-            Funcionalidade em desenvolvimento.
-          </div>
-        </q-card-section>
-        <q-separator />
-        <q-card-actions align="right">
-          <q-btn outline color="primary" label="FECHAR" no-caps @click="onDialogCancel" />
-        </q-card-actions>
-      </template>
+      </div>
     </q-card>
   </q-dialog>
 </template>
 
 <script setup>
 import { ref, useTemplateRef, onMounted } from "vue";
+
 import { useDialogPluginComponent } from "quasar";
 
 import DefaultDialogHeader from "src/components/defaults/DefaultDialogHeader.vue";
@@ -244,12 +159,8 @@ import DefaultInput from "src/components/defaults/DefaultInput.vue";
 import DefaultSelect from "src/components/defaults/DefaultSelect.vue";
 import DefaultInputDatePicker from "src/components/defaults/DefaultInputDatePicker.vue";
 import StateSelect from "src/components/selects/StateSelect.vue";
-import CustomTabComponent from "src/components/shared/CustomTabComponent.vue";
-import { useInputRules } from "src/composables/useInputRules";
-import { useSubmitHandler } from "src/composables/useSubmitHandler";
-import { updateStudent } from "src/api/student";
 import masks from "src/helpers/masks";
-import { formatDateDMYtoYMD, formatDateYMDtoDMY } from "src/helpers/utils";
+import { formatDateYMDtoDMY } from "src/helpers/utils";
 
 const props = defineProps({
   student: {
@@ -260,26 +171,12 @@ const props = defineProps({
 
 defineEmits([...useDialogPluginComponent.emits]);
 
-const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } =
+const { dialogRef, onDialogHide, onDialogCancel } =
   useDialogPluginComponent();
 
-const { inputRules } = useInputRules();
-
-const formRef = useTemplateRef("formRef");
-const fileInputRef = useTemplateRef("fileInputRef");
 const stateSelectRef = useTemplateRef("stateSelectRef");
 const avatarPreview = ref(null);
 
-const activeTab = ref("perfil");
-
-const tabs = [
-  { name: "perfil", label: "Perfil do Aluno" },
-  { name: "responsavel", label: "Responsável" },
-  { name: "contatos", label: "Contatos" },
-  { name: "historico", label: "Histórico" },
-  { name: "midias", label: "Mídias" },
-];
-
 const genderOptions = [
   { label: "Prefiro não informar", value: "no_preference" },
   { label: "Masculino", value: "male" },
@@ -314,50 +211,6 @@ const form = ref({
   notes: props.student.notes ?? null,
 });
 
-const { loading, execute } = useSubmitHandler({
-  formRef,
-  onSuccess: () => {
-    onDialogOK(true);
-  },
-});
-
-function buildPayload() {
-  return {
-    name: form.value.name,
-    birth_date: form.value.birthdate
-      ? formatDateDMYtoYMD(form.value.birthdate)
-      : null,
-    document_number: form.value.cpf,
-    gender: form.value.gender,
-    email: form.value.email || null,
-    phone: form.value.phone,
-    postal_code: form.value.cep,
-    street: form.value.address,
-    address_number: form.value.address_number,
-    neighborhood: form.value.neighborhood,
-    state_id: form.value.state?.value ?? null,
-    complement: form.value.complement,
-    payer_name: form.value.payer,
-    how_did_you_know_us: form.value.how_found,
-    notes: form.value.notes,
-  };
-}
-
-function triggerFileInput() {
-  fileInputRef.value?.click();
-}
-
-function onAvatarChange(event) {
-  const file = event.target.files[0];
-  if (file) {
-    avatarPreview.value = URL.createObjectURL(file);
-  }
-}
-
-async function onSave() {
-  await execute(() => updateStudent(buildPayload(), props.student.id));
-}
-
 onMounted(() => {
   if (props.student.state_id) {
     stateSelectRef.value?.selectStateById(props.student.state_id);