Przeglądaj źródła

feat(dashboard): implement API call for franchisor dashboard data and update metrics display

ebagabee 9 godzin temu
rodzic
commit
2afc7aeb08

+ 8 - 0
src/api/dashboard.js

@@ -0,0 +1,8 @@
+import api from "src/api";
+
+export const getFranchisorDashboard = async (unitIds = []) => {
+  const { data } = await api.get("/franchisor-dashboard/overview", {
+    params: unitIds.length ? { unit_ids: unitIds } : {},
+  });
+  return data.payload;
+};

+ 44 - 74
src/pages/dashboard/DashboardPage.vue

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

+ 19 - 7
src/pages/financial/TreasuryPage.vue

@@ -11,7 +11,7 @@
           :title="bank.label"
           :icon="bank.icon"
           :financial-value="bank.value"
-          :percentage="0"
+          :percentage="bank.percentage"
           clickable
           :editable="!!bank.account"
           :selected="selectedBank.key === bank.key"
@@ -54,12 +54,23 @@ const accounts = ref([]);
 const launches = ref([]);
 const loading = ref(false);
 
-const totalCard = computed(() => ({
-  key: "total",
-  label: "Saldo Total",
-  icon: "mdi-bank",
-  value: accounts.value.reduce((sum, account) => sum + (account.balance ?? 0), 0),
-}));
+// Variação % do saldo vs. o fim do mês anterior.
+const monthPercentage = (current, previous) => {
+  if (previous === 0) return current > 0 ? 100 : 0;
+  return Math.round(((current - previous) / Math.abs(previous)) * 1000) / 10;
+};
+
+const totalCard = computed(() => {
+  const value = accounts.value.reduce((sum, account) => sum + (account.balance ?? 0), 0);
+  const previous = accounts.value.reduce((sum, account) => sum + (account.balance_previous ?? 0), 0);
+  return {
+    key: "total",
+    label: "Saldo Total",
+    icon: "mdi-bank",
+    value,
+    percentage: monthPercentage(value, previous),
+  };
+});
 
 const selectedBank = ref({ key: "total", label: "Saldo Total" });
 
@@ -70,6 +81,7 @@ const cards = computed(() => [
     label: account.name,
     icon: "mdi-bank-outline",
     value: account.balance ?? 0,
+    percentage: monthPercentage(account.balance ?? 0, account.balance_previous ?? 0),
     account,
   })),
 ]);