|
|
@@ -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>
|
|
|
|