|
@@ -45,6 +45,7 @@
|
|
|
<template v-if="selectedPeriod === 'custom'">
|
|
<template v-if="selectedPeriod === 'custom'">
|
|
|
<DefaultInputDatePicker
|
|
<DefaultInputDatePicker
|
|
|
v-model="startDate"
|
|
v-model="startDate"
|
|
|
|
|
+ v-model:untreated-date="startDateIso"
|
|
|
dense
|
|
dense
|
|
|
label="Data Inicial"
|
|
label="Data Inicial"
|
|
|
color="secondary"
|
|
color="secondary"
|
|
@@ -53,6 +54,7 @@
|
|
|
|
|
|
|
|
<DefaultInputDatePicker
|
|
<DefaultInputDatePicker
|
|
|
v-model="endDate"
|
|
v-model="endDate"
|
|
|
|
|
+ v-model:untreated-date="endDateIso"
|
|
|
dense
|
|
dense
|
|
|
label="Data Final"
|
|
label="Data Final"
|
|
|
color="secondary"
|
|
color="secondary"
|
|
@@ -141,6 +143,27 @@
|
|
|
/>
|
|
/>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
|
|
+ <div class="stat-cards-row">
|
|
|
|
|
+ <DashboardStatCard
|
|
|
|
|
+ title="Total de Unidades"
|
|
|
|
|
+ icon="mdi-store-outline"
|
|
|
|
|
+ :value="unitsSummary.total"
|
|
|
|
|
+ subtitle="Unidades cadastradas"
|
|
|
|
|
+ />
|
|
|
|
|
+ <DashboardStatCard
|
|
|
|
|
+ title="Faturamento das Unidades"
|
|
|
|
|
+ icon="mdi-cash-multiple"
|
|
|
|
|
+ :value="formatToBRLCurrency(unitsSummary.revenue)"
|
|
|
|
|
+ :subtitle="revenuePeriodLabel"
|
|
|
|
|
+ />
|
|
|
|
|
+ <DashboardStatCard
|
|
|
|
|
+ title="Contratos das Unidades"
|
|
|
|
|
+ icon="mdi-file-document-check-outline"
|
|
|
|
|
+ :value="`${unitsSummary.active_contracts} vigentes`"
|
|
|
|
|
+ :subtitle="`${unitsSummary.without_active_contract} sem contrato vigente`"
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
<div class="charts-row">
|
|
<div class="charts-row">
|
|
|
<DashboardChartCard title="Faturamento Serviço / Materiais">
|
|
<DashboardChartCard title="Faturamento Serviço / Materiais">
|
|
|
<GroupedBarChart
|
|
<GroupedBarChart
|
|
@@ -217,15 +240,23 @@ const openTickets = ref(0);
|
|
|
const pendingTasks = ref(0);
|
|
const pendingTasks = ref(0);
|
|
|
const productStock = ref(0);
|
|
const productStock = ref(0);
|
|
|
const generalRevenue = ref({ value: 0, pending_count: 0 });
|
|
const generalRevenue = ref({ value: 0, pending_count: 0 });
|
|
|
|
|
+const unitsSummary = ref({
|
|
|
|
|
+ total: 0,
|
|
|
|
|
+ revenue: 0,
|
|
|
|
|
+ active_contracts: 0,
|
|
|
|
|
+ without_active_contract: 0,
|
|
|
|
|
+});
|
|
|
const birthdays = ref([]);
|
|
const birthdays = ref([]);
|
|
|
|
|
|
|
|
// ─── Filter state ────────────────────────────────────────────
|
|
// ─── Filter state ────────────────────────────────────────────
|
|
|
const showFilter = ref(false);
|
|
const showFilter = ref(false);
|
|
|
const selectedUnit = ref(null); // { label, value } | null
|
|
const selectedUnit = ref(null); // { label, value } | null
|
|
|
const selectedGroup = ref(null); // { label, value, unit_ids } | null
|
|
const selectedGroup = ref(null); // { label, value, unit_ids } | null
|
|
|
-const selectedPeriod = ref("custom");
|
|
|
|
|
|
|
+const selectedPeriod = ref("month");
|
|
|
const startDate = ref("");
|
|
const startDate = ref("");
|
|
|
const endDate = ref("");
|
|
const endDate = ref("");
|
|
|
|
|
+const startDateIso = ref("");
|
|
|
|
|
+const endDateIso = ref("");
|
|
|
|
|
|
|
|
const periodOptions = [
|
|
const periodOptions = [
|
|
|
{ label: "Hoje", value: "today" },
|
|
{ label: "Hoje", value: "today" },
|
|
@@ -235,6 +266,45 @@ const periodOptions = [
|
|
|
{ label: "Personalizado", value: "custom" },
|
|
{ label: "Personalizado", value: "custom" },
|
|
|
];
|
|
];
|
|
|
|
|
|
|
|
|
|
+const toIsoDate = (date) => {
|
|
|
|
|
+ const year = date.getFullYear();
|
|
|
|
|
+ const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
|
|
|
+ const day = String(date.getDate()).padStart(2, "0");
|
|
|
|
|
+ return `${year}-${month}-${day}`;
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const revenuePeriod = computed(() => {
|
|
|
|
|
+ const now = new Date();
|
|
|
|
|
+ let start;
|
|
|
|
|
+ let end = now;
|
|
|
|
|
+
|
|
|
|
|
+ if (selectedPeriod.value === "today") {
|
|
|
|
|
+ start = now;
|
|
|
|
|
+ } else if (selectedPeriod.value === "week") {
|
|
|
|
|
+ start = new Date(now);
|
|
|
|
|
+ const day = start.getDay() || 7;
|
|
|
|
|
+ start.setDate(start.getDate() - day + 1);
|
|
|
|
|
+ } else if (selectedPeriod.value === "year") {
|
|
|
|
|
+ start = new Date(now.getFullYear(), 0, 1);
|
|
|
|
|
+ } else if (selectedPeriod.value === "custom") {
|
|
|
|
|
+ if (!startDateIso.value || !endDateIso.value) return {};
|
|
|
|
|
+ return { startDate: startDateIso.value, endDate: endDateIso.value };
|
|
|
|
|
+ } else {
|
|
|
|
|
+ start = new Date(now.getFullYear(), now.getMonth(), 1);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return { startDate: toIsoDate(start), endDate: toIsoDate(end) };
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+const revenuePeriodLabel = computed(() => {
|
|
|
|
|
+ if (!revenuePeriod.value.startDate) return "Mês atual";
|
|
|
|
|
+ const format = (value) => value.split("-").reverse().join("/");
|
|
|
|
|
+ if (revenuePeriod.value.startDate === revenuePeriod.value.endDate) {
|
|
|
|
|
+ return `Faturamento em ${format(revenuePeriod.value.startDate)}`;
|
|
|
|
|
+ }
|
|
|
|
|
+ return `De ${format(revenuePeriod.value.startDate)} a ${format(revenuePeriod.value.endDate)}`;
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* Array of unit IDs to use as filter.
|
|
* Array of unit IDs to use as filter.
|
|
|
* - Group selected → all unit IDs in that group
|
|
* - Group selected → all unit IDs in that group
|
|
@@ -310,7 +380,7 @@ const fetchMetrics = async () => {
|
|
|
const [studentSummary, contractSummary, dashboard] = await Promise.all([
|
|
const [studentSummary, contractSummary, dashboard] = await Promise.all([
|
|
|
getStudentSummaryFranchisor(ids),
|
|
getStudentSummaryFranchisor(ids),
|
|
|
getFranchisorContractSummary(ids),
|
|
getFranchisorContractSummary(ids),
|
|
|
- getFranchisorDashboard(ids),
|
|
|
|
|
|
|
+ getFranchisorDashboard(ids, revenuePeriod.value),
|
|
|
]);
|
|
]);
|
|
|
|
|
|
|
|
totalStudents.value = studentSummary.total;
|
|
totalStudents.value = studentSummary.total;
|
|
@@ -322,6 +392,7 @@ const fetchMetrics = async () => {
|
|
|
pendingTasks.value = dashboard.pending_tasks ?? 0;
|
|
pendingTasks.value = dashboard.pending_tasks ?? 0;
|
|
|
productStock.value = dashboard.product_stock ?? 0;
|
|
productStock.value = dashboard.product_stock ?? 0;
|
|
|
generalRevenue.value = dashboard.general_revenue ?? { value: 0, pending_count: 0 };
|
|
generalRevenue.value = dashboard.general_revenue ?? { value: 0, pending_count: 0 };
|
|
|
|
|
+ unitsSummary.value = dashboard.units_summary ?? unitsSummary.value;
|
|
|
birthdays.value = dashboard.birthdays ?? [];
|
|
birthdays.value = dashboard.birthdays ?? [];
|
|
|
|
|
|
|
|
matriculasChart.value = {
|
|
matriculasChart.value = {
|
|
@@ -337,7 +408,8 @@ const fetchMetrics = async () => {
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
// Re-fetch whenever unit/group filter changes
|
|
// Re-fetch whenever unit/group filter changes
|
|
|
-watch(activeUnitIds, async () => {
|
|
|
|
|
|
|
+watch([activeUnitIds, selectedPeriod, startDateIso, endDateIso], async () => {
|
|
|
|
|
+ if (selectedPeriod.value === "custom" && (!startDateIso.value || !endDateIso.value)) return;
|
|
|
isLoading.value = true;
|
|
isLoading.value = true;
|
|
|
try {
|
|
try {
|
|
|
await fetchMetrics();
|
|
await fetchMetrics();
|