|
|
@@ -105,8 +105,8 @@
|
|
|
<DashboardStatCard
|
|
|
title="Receita Geral"
|
|
|
icon="mdi-currency-usd"
|
|
|
- value="R$ 0,00"
|
|
|
- subtitle="0 pagamentos pendentes"
|
|
|
+ :value="formatToBRLCurrency(generalRevenue.value)"
|
|
|
+ :subtitle="`${generalRevenue.pending_count} pagamentos pendentes`"
|
|
|
/>
|
|
|
</div>
|
|
|
|
|
|
@@ -121,20 +121,20 @@
|
|
|
<DashboardStatCard
|
|
|
title="Estoque Geral de Produtos"
|
|
|
icon="mdi-currency-usd"
|
|
|
- value="0"
|
|
|
+ :value="productStock"
|
|
|
clickable
|
|
|
@click="openProductStockDialog"
|
|
|
/>
|
|
|
<DashboardStatCard
|
|
|
title="Tarefas Pendentes"
|
|
|
icon="mdi-draw"
|
|
|
- value="0"
|
|
|
+ :value="pendingTasks"
|
|
|
subtitle="Não deixe para amanhã"
|
|
|
/>
|
|
|
<DashboardStatCard
|
|
|
title="Tickets Abertos"
|
|
|
icon="mdi-calendar-outline"
|
|
|
- value="0"
|
|
|
+ :value="openTickets"
|
|
|
subtitle="Estável"
|
|
|
clickable
|
|
|
@click="openTicketsDialog"
|
|
|
@@ -199,6 +199,8 @@ import ProductStockDialog from "src/pages/dashboard/components/ProductStockDialo
|
|
|
import OpenTicketsDialog from "src/pages/dashboard/components/OpenTicketsDialog.vue";
|
|
|
import { getStudentSummaryFranchisor } from "src/api/student";
|
|
|
import { getFranchisorContractSummary } from "src/api/student_contract";
|
|
|
+import { getFranchisorDashboard } from "src/api/dashboard";
|
|
|
+import { formatToBRLCurrency } from "src/helpers/utils";
|
|
|
|
|
|
useI18n(); // i18n instance kept for future use
|
|
|
const $q = useQuasar();
|
|
|
@@ -211,6 +213,11 @@ const totalStudents = ref(0);
|
|
|
const activeContracts = ref(0);
|
|
|
const frozenContracts = ref(0);
|
|
|
const cancelledContracts = ref(0);
|
|
|
+const openTickets = ref(0);
|
|
|
+const pendingTasks = ref(0);
|
|
|
+const productStock = ref(0);
|
|
|
+const generalRevenue = ref({ value: 0, pending_count: 0 });
|
|
|
+const birthdays = ref([]);
|
|
|
|
|
|
// ─── Filter state ────────────────────────────────────────────
|
|
|
const showFilter = ref(false);
|
|
|
@@ -290,64 +297,61 @@ const openTicketsDialog = () => {
|
|
|
$q.dialog({ component: OpenTicketsDialog });
|
|
|
};
|
|
|
|
|
|
+// Matrículas por Período — preenchido pela API (últimos 6 meses).
|
|
|
+const MATRICULA_COLORS = ["#3B82F6", "#EF4444", "#A855F7", "#374151", "#EAB308", "#06B6D4"];
|
|
|
+const matriculasChart = ref({
|
|
|
+ labels: [],
|
|
|
+ datasets: [{ label: "Matrículas", data: [], color: MATRICULA_COLORS }],
|
|
|
+});
|
|
|
+
|
|
|
// ─── Data fetching ───────────────────────────────────────────
|
|
|
const fetchMetrics = async () => {
|
|
|
const ids = activeUnitIds.value;
|
|
|
- const [studentSummary, contractSummary] = await Promise.all([
|
|
|
+ const [studentSummary, contractSummary, dashboard] = await Promise.all([
|
|
|
getStudentSummaryFranchisor(ids),
|
|
|
getFranchisorContractSummary(ids),
|
|
|
+ getFranchisorDashboard(ids),
|
|
|
]);
|
|
|
|
|
|
totalStudents.value = studentSummary.total;
|
|
|
activeContracts.value = contractSummary.active ?? 0;
|
|
|
frozenContracts.value = contractSummary.frozen;
|
|
|
cancelledContracts.value = contractSummary.cancelled;
|
|
|
+
|
|
|
+ openTickets.value = dashboard.open_tickets ?? 0;
|
|
|
+ pendingTasks.value = dashboard.pending_tasks ?? 0;
|
|
|
+ productStock.value = dashboard.product_stock ?? 0;
|
|
|
+ generalRevenue.value = dashboard.general_revenue ?? { value: 0, pending_count: 0 };
|
|
|
+ birthdays.value = dashboard.birthdays ?? [];
|
|
|
+
|
|
|
+ matriculasChart.value = {
|
|
|
+ labels: dashboard.enrollments?.labels ?? [],
|
|
|
+ datasets: [
|
|
|
+ {
|
|
|
+ label: "Matrículas",
|
|
|
+ data: dashboard.enrollments?.data ?? [],
|
|
|
+ color: MATRICULA_COLORS,
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ };
|
|
|
};
|
|
|
|
|
|
// Re-fetch whenever unit/group filter changes
|
|
|
watch(activeUnitIds, async () => {
|
|
|
isLoading.value = true;
|
|
|
try {
|
|
|
- await Promise.all([fetchMetrics(), updateDashboardData()]);
|
|
|
+ await fetchMetrics();
|
|
|
} finally {
|
|
|
isLoading.value = false;
|
|
|
}
|
|
|
});
|
|
|
|
|
|
-// ─── Mock data (charts still mocked) ─────────────────────────
|
|
|
-const birthdays = [
|
|
|
- { 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" },
|
|
|
-];
|
|
|
-
|
|
|
-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"],
|
|
|
- },
|
|
|
- ],
|
|
|
-};
|
|
|
-
|
|
|
+// Faturamento Serviço / Materiais — zerado (sem fluxo de dados ainda).
|
|
|
const faturamentoChart = {
|
|
|
- labels: ["17/02","18/02","19/02","20/02","21/02","22/02","23/02","24/02","25/02","26/02"],
|
|
|
+ labels: [],
|
|
|
datasets: [
|
|
|
- {
|
|
|
- label: "Serviço",
|
|
|
- data: [18500,21000,16400,22300,19800,17200,15800,24100,20500,27600],
|
|
|
- color: "#a274f1",
|
|
|
- },
|
|
|
- {
|
|
|
- label: "Materiais",
|
|
|
- data: [9200,10500,8100,11400,9800,8400,8700,12200,10100,13100],
|
|
|
- color: "#ff9999",
|
|
|
- },
|
|
|
+ { label: "Serviço", data: [], color: "#a274f1" },
|
|
|
+ { label: "Materiais", data: [], color: "#ff9999" },
|
|
|
],
|
|
|
};
|
|
|
|
|
|
@@ -361,45 +365,11 @@ const formatCurrencyTooltip = (context) => {
|
|
|
return ` ${context.dataset.label}: R$ ${value.toLocaleString("pt-BR", { minimumFractionDigits: 2 })}`;
|
|
|
};
|
|
|
|
|
|
-// Legacy mock refs (kept so chart bindings don't break)
|
|
|
-const ordersChart = ref({});
|
|
|
-const participantsChart = ref({});
|
|
|
-const paymentsChart = ref({});
|
|
|
-const ticketsSoldChart = ref({});
|
|
|
-
|
|
|
-const generateMockData = () => {
|
|
|
- const createMiniChartData = (currentTotal, percentage) => ({
|
|
|
- current_total: currentTotal,
|
|
|
- percentage_change: percentage,
|
|
|
- trend_data: Array.from({ length: 10 }, () => Math.floor(Math.random() * 100)),
|
|
|
- });
|
|
|
-
|
|
|
- return {
|
|
|
- payments: createMiniChartData((Math.random() * 20000 + 5000).toFixed(2), (Math.random() * 20 - 5).toFixed(2)),
|
|
|
- orders: createMiniChartData(Math.floor(Math.random() * 500 + 50), (Math.random() * 15 - 5).toFixed(2)),
|
|
|
- tickets_sold: createMiniChartData(Math.floor(Math.random() * 1500 + 200), (Math.random() * 25 - 5).toFixed(2)),
|
|
|
- participants: createMiniChartData(Math.floor(Math.random() * 1000 + 100), (Math.random() * 10 - 5).toFixed(2)),
|
|
|
- };
|
|
|
-};
|
|
|
-
|
|
|
-const updateDashboardData = async () => {
|
|
|
- return new Promise((resolve) => {
|
|
|
- setTimeout(() => {
|
|
|
- const mockData = generateMockData();
|
|
|
- ordersChart.value = mockData.orders;
|
|
|
- participantsChart.value = mockData.participants;
|
|
|
- paymentsChart.value = mockData.payments;
|
|
|
- ticketsSoldChart.value = mockData.tickets_sold;
|
|
|
- resolve();
|
|
|
- }, 300);
|
|
|
- });
|
|
|
-};
|
|
|
-
|
|
|
// ─── Init ─────────────────────────────────────────────────────
|
|
|
onMounted(async () => {
|
|
|
isLoading.value = true;
|
|
|
try {
|
|
|
- await Promise.all([fetchMetrics(), updateDashboardData()]);
|
|
|
+ await fetchMetrics();
|
|
|
} finally {
|
|
|
isLoading.value = false;
|
|
|
}
|