Просмотр исходного кода

feat: enhance AniversariantesCard to display message when no birthdays are present; update DashboardPage to reflect real-time data for anniversaries and active contracts

ebagabee 1 месяц назад
Родитель
Сommit
8974b29d91
2 измененных файлов с 106 добавлено и 60 удалено
  1. 9 18
      src/components/charts/AniversariantesCard.vue
  2. 97 42
      src/pages/dashboard/DashboardPage.vue

+ 9 - 18
src/components/charts/AniversariantesCard.vue

@@ -12,15 +12,20 @@
     <q-separator />
 
     <div class="list-scroll">
-      <q-list dense>
+      <div v-if="people.length === 0" class="text-caption text-grey-5 text-center q-mt-md">
+        Nenhum aniversariante neste mês.
+      </div>
+      <q-list v-else dense>
         <template v-for="(person, index) in people" :key="index">
           <q-item class="q-px-none person-item">
-            <q-item-section avatar style="min-width: 36px">
-              <div class="day-badge">{{ person.day }}</div>
-            </q-item-section>
             <q-item-section>
               <q-item-label class="text-body2">{{ person.name }}</q-item-label>
             </q-item-section>
+            <q-item-section side>
+              <q-item-label class="text-body2 text-weight-medium text-grey-7">
+                Dia {{ person.day }}
+              </q-item-label>
+            </q-item-section>
           </q-item>
           <q-separator v-if="index < people.length - 1" />
         </template>
@@ -56,18 +61,4 @@ defineProps({
   padding-top: 6px;
   padding-bottom: 6px;
 }
-
-.day-badge {
-  width: 30px;
-  height: 30px;
-  border-radius: 50%;
-  background-color: #f97316;
-  color: #fff;
-  font-size: 12px;
-  font-weight: 600;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  flex-shrink: 0;
-}
 </style>

+ 97 - 42
src/pages/dashboard/DashboardPage.vue

@@ -10,22 +10,24 @@
           :value="String(totalAlunos)"
           :badge="`${totalAlunos} ativos`"
         />
+        <!-- TODO: usar dados reais de receita (zerado por enquanto). -->
         <DashboardStatCard
           title="Receita Total"
           icon="mdi-currency-usd"
           value="R$ 0,00"
           subtitle="0 pagamentos pendentes"
         />
+        <!-- TODO: usar dados reais de ticket médio (zerado por enquanto). -->
         <DashboardStatCard
           title="Ticket Médio"
           icon="mdi-calendar-blank"
-          value="R$ 12,00"
+          value="R$ 0,00"
           subtitle="Estável"
         />
         <DashboardStatCard
           title="Aniversariantes"
           icon="mdi-emoticon-happy-outline"
-          value="0"
+          :value="String(aniversariantes.length)"
           subtitle="Fortaleça seus relacionamentos"
         />
       </div>
@@ -65,8 +67,8 @@
                 />
               </div>
               <div class="gauge-label">
-                <div class="text-h5 text-bold">70</div>
-                <div class="text-caption text-grey-6">Grade</div>
+                <div class="text-h5 text-bold">{{ activeContracts }}</div>
+                <div class="text-caption text-grey-6">Ativos</div>
               </div>
             </q-card-section>
           </q-card>
@@ -88,6 +90,7 @@
                 label="Criar contrato"
                 no-caps
                 class="full-width"
+                @click="onCriarContrato"
               />
               <q-btn
                 unelevated
@@ -95,6 +98,7 @@
                 label="Registrar presença"
                 no-caps
                 class="full-width"
+                @click="onRegistrarPresenca"
               />
               <q-btn
                 unelevated
@@ -102,6 +106,7 @@
                 label="Novo pedido"
                 no-caps
                 class="full-width"
+                @click="onNovoPedido"
               />
             </q-card-section>
           </q-card>
@@ -199,6 +204,7 @@ import FeriadosDialog from "./components/FeriadosDialog.vue";
 import FeriadosEditDialog from "./components/FeriadosEditDialog.vue";
 import { getHolidays } from "src/api/holiday";
 import { getStudents } from "src/api/student";
+import { getAllContracts } from "src/api/studentContract";
 import { userStore } from "src/stores/user";
 
 ChartJS.register(ArcElement, Tooltip, Legend);
@@ -206,23 +212,14 @@ ChartJS.register(ArcElement, Tooltip, Legend);
 const $q = useQuasar();
 const store = userStore();
 
+// Faturamento Serviço / Materiais — zerado (sem fluxo de dados ainda),
+// igual ao Franqueador.
+// TODO: alimentar com dados reais de faturamento de serviço/materiais.
 const faturamentoChart = {
-  labels: [
-    "17/02", "18/02", "19/02", "20/02", "21/02",
-    "22/02", "23/02", "24/02", "25/02", "26/02",
-    "27/02", "28/02",
-  ],
+  labels: [],
   datasets: [
-    {
-      label: "Serviço",
-      data: [120, 185, 95, 210, 155, 200, 170, 130, 195, 160, 145, 180],
-      color: "#a274f1",
-    },
-    {
-      label: "Materiais",
-      data: [75, 115, 60, 140, 95, 125, 105, 85, 135, 100, 90, 115],
-      color: "#ff9999",
-    },
+    { label: "Serviço", data: [], color: "#a274f1" },
+    { label: "Materiais", data: [], color: "#ff9999" },
   ],
 };
 
@@ -252,7 +249,7 @@ const gaugeData = ref({
         "#8A0000",
       ],
       data: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
-      needleValue: 7,
+      needleValue: 0,
       borderColor: "transparent",
     },
   ],
@@ -306,38 +303,94 @@ const gaugeNeedlePlugin = {
   },
 };
 
-const matriculasChart = {
-  labels: ["JAN", "FEV", "MAR", "ABR", "MAI", "JUN"],
-  datasets: [
-    {
-      label: "Matrículas",
-      data: [120, 200, 150, 80, 70, 110],
-      color: ["#3B82F6", "#EF4444", "#A855F7", "#374151", "#EAB308", "#06B6D4"],
-    },
-  ],
-};
+const MONTHS_PT = ["JAN", "FEV", "MAR", "ABR", "MAI", "JUN", "JUL", "AGO", "SET", "OUT", "NOV", "DEZ"];
+const MATRICULA_COLORS = ["#3B82F6", "#EF4444", "#A855F7", "#374151", "#EAB308", "#06B6D4"];
 
-const aniversariantes = ref([
-  { day: 10, name: "Heloisa Faria" },
-  { day: 11, name: "Juliana Costa" },
-  { day: 16, name: "Juliana Costa" },
-  { day: 23, name: "Fernando Almeida" },
-  { day: 29, name: "Lucas Pereira" },
-  { day: 34, name: "Sofia Martins" },
-]);
-
-// Alunos
+// Alunos / Contratos
 const totalAlunos = ref(0);
+const activeContracts = ref(0);
+
+// Lista de alunos crua, usada para derivar aniversariantes e matrículas.
+const students = ref([]);
+
+// Aniversariantes do mês corrente (dia + nome), ordenados por dia.
+const aniversariantes = computed(() => {
+  const month = new Date().getMonth() + 1;
+  return students.value
+    .filter((s) => {
+      if (!s.birth_date) return false;
+      return new Date(s.birth_date + "T00:00:00").getMonth() + 1 === month;
+    })
+    .map((s) => ({
+      day: new Date(s.birth_date + "T00:00:00").getDate(),
+      name: s.name,
+    }))
+    .sort((a, b) => a.day - b.day);
+});
+
+// Matrículas por Período — alunos cadastrados (created_at) nos últimos 6 meses,
+// terminando no mês atual.
+const matriculasChart = computed(() => {
+  const now = new Date();
+  const start = new Date(now.getFullYear(), now.getMonth() - 5, 1);
+
+  const counts = {};
+  for (const s of students.value) {
+    if (!s.created_at) continue;
+    const d = new Date(s.created_at.replace(" ", "T"));
+    if (d < start) continue;
+    const key = `${d.getFullYear()}-${d.getMonth()}`;
+    counts[key] = (counts[key] ?? 0) + 1;
+  }
+
+  const labels = [];
+  const data = [];
+  for (let i = 0; i < 6; i++) {
+    const month = new Date(start.getFullYear(), start.getMonth() + i, 1);
+    labels.push(MONTHS_PT[month.getMonth()]);
+    data.push(counts[`${month.getFullYear()}-${month.getMonth()}`] ?? 0);
+  }
+
+  return {
+    labels,
+    datasets: [{ label: "Matrículas", data, color: MATRICULA_COLORS }],
+  };
+});
 
 async function fetchAlunos() {
   try {
-    const students = await getStudents();
-    totalAlunos.value = students.length;
+    students.value = await getStudents();
+    totalAlunos.value = students.value.length;
+  } catch {
+    // silencioso
+  }
+}
+
+async function fetchContracts() {
+  try {
+    const contracts = await getAllContracts();
+    activeContracts.value = contracts.filter((c) => c.status === "active").length;
+    // Posiciona a agulha do medidor na quantidade de contratos ativos
+    // (escala de 0 a 10; valores maiores ficam no topo da escala).
+    gaugeData.value.datasets[0].needleValue = Math.min(activeContracts.value, 10);
   } catch {
     // silencioso
   }
 }
 
+// ─── Atalhos rápidos (ainda não implementados) ───────────────
+function onCriarContrato() {
+  console.log("Atalho rápido: Criar contrato");
+}
+
+function onRegistrarPresenca() {
+  console.log("Atalho rápido: Registrar presença");
+}
+
+function onNovoPedido() {
+  console.log("Atalho rápido: Novo pedido");
+}
+
 // Feriados
 const allHolidays = ref([]);
 const feriadosLoading = ref(false);
@@ -400,6 +453,7 @@ watch(
   (newId, oldId) => {
     if (newId && newId !== oldId) {
       fetchAlunos();
+      fetchContracts();
       fetchHolidays();
     }
   },
@@ -408,6 +462,7 @@ watch(
 onMounted(() => {
   fetchHolidays();
   fetchAlunos();
+  fetchContracts();
 });
 </script>