|
|
@@ -1,86 +1,82 @@
|
|
|
<template>
|
|
|
<div>
|
|
|
- <DefaultHeaderPage />
|
|
|
-
|
|
|
- <div class="q-pa-md">
|
|
|
- <!-- Row 1: Stats -->
|
|
|
- <div class="row q-col-gutter-md q-mb-md">
|
|
|
- <div class="col-12 col-sm-6 col-lg-3">
|
|
|
- <q-card flat bordered>
|
|
|
- <q-card-section class="row justify-between items-start q-pb-none">
|
|
|
- <span class="text-caption text-grey-6">Total alunos (contratos ativos)</span>
|
|
|
- <q-icon name="mdi-account-multiple" color="grey-4" size="sm" />
|
|
|
- </q-card-section>
|
|
|
- <q-card-section class="q-pt-sm">
|
|
|
- <div class="text-h5 text-bold">0</div>
|
|
|
- <q-badge color="orange" class="q-mt-sm q-pa-xs" style="font-size: 11px">
|
|
|
- 0 ativos
|
|
|
- </q-badge>
|
|
|
- </q-card-section>
|
|
|
- </q-card>
|
|
|
- </div>
|
|
|
-
|
|
|
- <div class="col-12 col-sm-6 col-lg-3">
|
|
|
- <q-card flat bordered>
|
|
|
- <q-card-section class="row justify-between items-start q-pb-none">
|
|
|
- <span class="text-caption text-grey-6">Receita Total</span>
|
|
|
- <q-icon name="mdi-currency-usd" color="grey-4" size="sm" />
|
|
|
- </q-card-section>
|
|
|
- <q-card-section class="q-pt-sm">
|
|
|
- <div class="text-h5 text-bold">R$ 0,00</div>
|
|
|
- <div class="text-caption text-grey-5 q-mt-sm">0 pagamentos pendentes</div>
|
|
|
- </q-card-section>
|
|
|
- </q-card>
|
|
|
- </div>
|
|
|
+ <DefaultHeaderPage class="q-pa-sm">
|
|
|
+ <template #after>
|
|
|
+ <div class="flex items-center no-wrap" style="gap: 12px">
|
|
|
+ <template v-if="$q.screen.gt.sm">
|
|
|
+ <div
|
|
|
+ class="column"
|
|
|
+ style="line-height: 1.2; white-space: nowrap; flex-shrink: 0"
|
|
|
+ >
|
|
|
+ <span class="text-caption text-grey-6 text-primary text-center"
|
|
|
+ >Ultimo acesso</span
|
|
|
+ >
|
|
|
+ <span class="text-caption text-primary text-center"
|
|
|
+ >16/02/2026, 14:16</span
|
|
|
+ >
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
|
|
|
- <div class="col-12 col-sm-6 col-lg-3">
|
|
|
- <q-card flat bordered>
|
|
|
- <q-card-section class="row justify-between items-start q-pb-none">
|
|
|
- <span class="text-caption text-grey-6">Ticket Médio</span>
|
|
|
- <q-icon name="mdi-receipt-outline" color="grey-4" size="sm" />
|
|
|
- </q-card-section>
|
|
|
- <q-card-section class="q-pt-sm">
|
|
|
- <div class="text-h5 text-bold">R$ 12,00</div>
|
|
|
- <div class="text-caption text-grey-5 q-mt-sm">Estável</div>
|
|
|
- </q-card-section>
|
|
|
- </q-card>
|
|
|
+ <div
|
|
|
+ class="flex items-center no-wrap"
|
|
|
+ style="gap: 2px; flex-shrink: 0"
|
|
|
+ >
|
|
|
+ <q-btn flat round dense icon="mdi-bell-badge" color="secondary" />
|
|
|
+ <q-btn flat round dense icon="mdi-account" color="secondary" />
|
|
|
+ <q-btn flat round dense icon="mdi-cog-outline" color="secondary" />
|
|
|
+ </div>
|
|
|
</div>
|
|
|
+ </template>
|
|
|
+ </DefaultHeaderPage>
|
|
|
|
|
|
- <div class="col-12 col-sm-6 col-lg-3">
|
|
|
- <q-card flat bordered>
|
|
|
- <q-card-section class="row justify-between items-start q-pb-none">
|
|
|
- <span class="text-caption text-grey-6">Aniversariantes</span>
|
|
|
- <q-icon name="mdi-emoticon-outline" color="grey-4" size="sm" />
|
|
|
- </q-card-section>
|
|
|
- <q-card-section class="q-pt-sm">
|
|
|
- <div class="text-h5 text-bold">0</div>
|
|
|
- <div class="text-caption text-grey-5 q-mt-sm">Fortaleça seus relacionamentos</div>
|
|
|
- </q-card-section>
|
|
|
- </q-card>
|
|
|
- </div>
|
|
|
+ <div class="q-pa-sm">
|
|
|
+ <div class="stat-cards-row q-mb-md">
|
|
|
+ <DashboardStatCard
|
|
|
+ title="Total alunos (contratos ativos)"
|
|
|
+ icon="mdi-account-multiple-outline"
|
|
|
+ value="0"
|
|
|
+ badge="0 ativos"
|
|
|
+ />
|
|
|
+ <DashboardStatCard
|
|
|
+ title="Receita Total"
|
|
|
+ icon="mdi-currency-usd"
|
|
|
+ value="R$ 0,00"
|
|
|
+ subtitle="0 pagamentos pendentes"
|
|
|
+ />
|
|
|
+ <DashboardStatCard
|
|
|
+ title="Ticket Médio"
|
|
|
+ icon="mdi-calendar-blank"
|
|
|
+ value="R$ 12,00"
|
|
|
+ subtitle="Estável"
|
|
|
+ />
|
|
|
+ <DashboardStatCard
|
|
|
+ title="Aniversariantes"
|
|
|
+ icon="mdi-emoticon-happy-outline"
|
|
|
+ value="0"
|
|
|
+ subtitle="Fortaleça seus relacionamentos"
|
|
|
+ />
|
|
|
</div>
|
|
|
|
|
|
- <!-- Row 2: Charts -->
|
|
|
- <div class="row q-col-gutter-md q-mb-md">
|
|
|
- <!-- Faturamento Serviço / Materiais -->
|
|
|
+ <div class="row q-col-gutter-md q-mb-md items-stretch">
|
|
|
<div class="col-12 col-md-5">
|
|
|
- <q-card flat bordered style="height: 280px">
|
|
|
- <q-card-section class="row justify-between items-center q-pb-xs">
|
|
|
- <span class="text-subtitle2 text-weight-medium">Faturamento Serviço / Materiais</span>
|
|
|
- <q-icon name="mdi-book-open-outline" color="grey-5" />
|
|
|
- </q-card-section>
|
|
|
- <q-separator />
|
|
|
- <q-card-section style="height: calc(100% - 57px)" class="q-pt-sm q-px-sm">
|
|
|
- <Bar :data="faturamentoData" :options="faturamentoOptions" />
|
|
|
- </q-card-section>
|
|
|
- </q-card>
|
|
|
+ <DashboardChartCard title="Faturamento Serviço / Materiais" style="height: 100%">
|
|
|
+ <GroupedBarChart
|
|
|
+ :labels="faturamentoChart.labels"
|
|
|
+ :datasets="faturamentoChart.datasets"
|
|
|
+ label-y="R$"
|
|
|
+ :tick-formatter="formatCurrencyTick"
|
|
|
+ :tooltip-formatter="formatCurrencyTooltip"
|
|
|
+ class="full-width full-height"
|
|
|
+ />
|
|
|
+ </DashboardChartCard>
|
|
|
</div>
|
|
|
|
|
|
- <!-- Contratos Ativos -->
|
|
|
<div class="col-12 col-md-4">
|
|
|
- <q-card flat bordered style="height: 280px">
|
|
|
+ <q-card flat bordered class="full-height">
|
|
|
<q-card-section class="row justify-between items-center q-pb-xs">
|
|
|
- <span class="text-subtitle2 text-weight-medium">Contratos Ativos</span>
|
|
|
+ <span class="text-subtitle2 text-weight-medium"
|
|
|
+ >Contratos Ativos</span
|
|
|
+ >
|
|
|
<q-icon name="mdi-trending-up" color="grey-5" />
|
|
|
</q-card-section>
|
|
|
<q-separator />
|
|
|
@@ -103,11 +99,12 @@
|
|
|
</q-card>
|
|
|
</div>
|
|
|
|
|
|
- <!-- Atalhos rápidos -->
|
|
|
<div class="col-12 col-md-3">
|
|
|
- <q-card flat bordered style="height: 280px">
|
|
|
+ <q-card flat bordered class="full-height">
|
|
|
<q-card-section class="row justify-between items-center q-pb-xs">
|
|
|
- <span class="text-subtitle2 text-weight-medium">Atalhos rápidos</span>
|
|
|
+ <span class="text-subtitle2 text-weight-medium"
|
|
|
+ >Atalhos rápidos</span
|
|
|
+ >
|
|
|
<q-icon name="mdi-apps" color="grey-5" />
|
|
|
</q-card-section>
|
|
|
<q-separator />
|
|
|
@@ -139,66 +136,32 @@
|
|
|
</div>
|
|
|
|
|
|
<!-- Row 3: Bottom -->
|
|
|
- <div class="row q-col-gutter-md">
|
|
|
- <!-- Matrículas por Período -->
|
|
|
- <div class="col-12 col-md-4">
|
|
|
- <q-card flat bordered style="height: 320px">
|
|
|
- <q-card-section class="row justify-between items-center q-pb-xs">
|
|
|
- <span class="text-subtitle2 text-weight-medium">Matrículas por Período</span>
|
|
|
- <q-icon name="mdi-book-open-outline" color="grey-5" />
|
|
|
- </q-card-section>
|
|
|
- <q-separator />
|
|
|
- <q-card-section style="height: calc(100% - 57px)" class="q-pt-sm q-px-sm">
|
|
|
- <Bar
|
|
|
- :data="matriculasData"
|
|
|
- :options="matriculasOptions"
|
|
|
- :plugins="[ChartDataLabels]"
|
|
|
- />
|
|
|
- </q-card-section>
|
|
|
- </q-card>
|
|
|
+ <div class="row q-col-gutter-md items-stretch">
|
|
|
+ <div class="col-12 col-md-5">
|
|
|
+ <DashboardChartCard title="Matrículas por Período" style="height: 100%">
|
|
|
+ <GroupedBarChart
|
|
|
+ :labels="matriculasChart.labels"
|
|
|
+ :datasets="matriculasChart.datasets"
|
|
|
+ :bar-radius="50"
|
|
|
+ :show-datalabels="true"
|
|
|
+ :max-bar-thickness="44"
|
|
|
+ :category-percentage="0.6"
|
|
|
+ :bar-percentage="0.85"
|
|
|
+ class="full-width full-height"
|
|
|
+ />
|
|
|
+ </DashboardChartCard>
|
|
|
</div>
|
|
|
|
|
|
- <!-- Aniversariantes do Mês -->
|
|
|
<div class="col-12 col-md-4">
|
|
|
- <q-card flat bordered style="height: 320px">
|
|
|
- <q-card-section class="row justify-between items-center q-pb-xs">
|
|
|
- <span class="text-subtitle2 text-weight-medium">Aniversariantes do Mês</span>
|
|
|
- <q-icon name="mdi-gift-outline" color="grey-5" />
|
|
|
- </q-card-section>
|
|
|
- <q-separator />
|
|
|
- <div class="row justify-between items-center q-px-md q-py-xs">
|
|
|
- <span class="text-caption text-grey-6">Nome</span>
|
|
|
- <span class="text-caption text-grey-6">Ações</span>
|
|
|
- </div>
|
|
|
- <q-separator />
|
|
|
- <div style="height: calc(100% - 105px); overflow-y: auto">
|
|
|
- <q-list separator>
|
|
|
- <q-item v-for="(pessoa, i) in aniversariantes" :key="i" dense class="q-py-sm">
|
|
|
- <q-item-section avatar>
|
|
|
- <q-avatar :color="pessoa.color" text-color="white" size="34px" class="text-caption text-bold">
|
|
|
- {{ pessoa.nome[0] }}
|
|
|
- </q-avatar>
|
|
|
- </q-item-section>
|
|
|
- <q-item-section>
|
|
|
- <q-item-label>{{ pessoa.nome }}</q-item-label>
|
|
|
- </q-item-section>
|
|
|
- <q-item-section side>
|
|
|
- <div class="row">
|
|
|
- <q-btn flat dense round icon="mdi-whatsapp" color="green" size="sm" />
|
|
|
- <q-btn flat dense round icon="mdi-email-outline" color="grey-6" size="sm" />
|
|
|
- </div>
|
|
|
- </q-item-section>
|
|
|
- </q-item>
|
|
|
- </q-list>
|
|
|
- </div>
|
|
|
- </q-card>
|
|
|
+ <AniversariantesCard :people="aniversariantes" style="height: 100%" />
|
|
|
</div>
|
|
|
|
|
|
- <!-- Feriados do mês -->
|
|
|
- <div class="col-12 col-md-4">
|
|
|
- <q-card flat bordered style="height: 320px">
|
|
|
+ <div class="col-12 col-md-3">
|
|
|
+ <q-card flat bordered class="full-height">
|
|
|
<q-card-section class="row justify-between items-center q-pb-xs">
|
|
|
- <span class="text-subtitle2 text-weight-medium">Feriados do mês</span>
|
|
|
+ <span class="text-subtitle2 text-weight-medium"
|
|
|
+ >Feriados do mês</span
|
|
|
+ >
|
|
|
<q-icon name="mdi-calendar-outline" color="grey-5" />
|
|
|
</q-card-section>
|
|
|
<q-separator />
|
|
|
@@ -224,7 +187,9 @@
|
|
|
>
|
|
|
{{ feriado.dia }}
|
|
|
</q-badge>
|
|
|
- <div class="text-caption q-mt-xs text-center">{{ feriado.nome }}</div>
|
|
|
+ <div class="text-caption q-mt-xs text-center">
|
|
|
+ {{ feriado.nome }}
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</q-card-section>
|
|
|
@@ -237,72 +202,60 @@
|
|
|
|
|
|
<script setup>
|
|
|
import { ref } from "vue";
|
|
|
-import { Bar, Doughnut } from "vue-chartjs";
|
|
|
-import {
|
|
|
- Chart as ChartJS,
|
|
|
- Title,
|
|
|
- Tooltip,
|
|
|
- Legend,
|
|
|
- BarElement,
|
|
|
- CategoryScale,
|
|
|
- LinearScale,
|
|
|
- ArcElement,
|
|
|
-} from "chart.js";
|
|
|
-import ChartDataLabels from "chartjs-plugin-datalabels";
|
|
|
+import { Doughnut } from "vue-chartjs";
|
|
|
+import { Chart as ChartJS, ArcElement, Tooltip, Legend } from "chart.js";
|
|
|
import DefaultHeaderPage from "src/components/layout/DefaultHeaderPage.vue";
|
|
|
+import DashboardStatCard from "src/components/charts/DashboardStatCard.vue";
|
|
|
+import DashboardChartCard from "src/components/charts/DashboardChartCard.vue";
|
|
|
+import GroupedBarChart from "src/components/charts/normal/GroupedBarChart.vue";
|
|
|
+import AniversariantesCard from "src/components/charts/AniversariantesCard.vue";
|
|
|
|
|
|
-ChartJS.register(
|
|
|
- Title,
|
|
|
- Tooltip,
|
|
|
- Legend,
|
|
|
- BarElement,
|
|
|
- CategoryScale,
|
|
|
- LinearScale,
|
|
|
- ArcElement,
|
|
|
-);
|
|
|
+ChartJS.register(ArcElement, Tooltip, Legend);
|
|
|
|
|
|
-// ── Faturamento Serviço / Materiais ───────────────────────────────────────────
|
|
|
-const faturamentoData = ref({
|
|
|
- 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"],
|
|
|
+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",
|
|
|
+ ],
|
|
|
datasets: [
|
|
|
{
|
|
|
- label: "Serviços",
|
|
|
+ label: "Serviço",
|
|
|
data: [120, 185, 95, 210, 155, 200, 170, 130, 195, 160, 145, 180],
|
|
|
- backgroundColor: "rgba(99, 102, 241, 0.75)",
|
|
|
- borderColor: "rgba(99, 102, 241, 1)",
|
|
|
- borderWidth: 1,
|
|
|
+ color: "#a274f1",
|
|
|
},
|
|
|
{
|
|
|
label: "Materiais",
|
|
|
data: [75, 115, 60, 140, 95, 125, 105, 85, 135, 100, 90, 115],
|
|
|
- backgroundColor: "rgba(236, 72, 153, 0.75)",
|
|
|
- borderColor: "rgba(236, 72, 153, 1)",
|
|
|
- borderWidth: 1,
|
|
|
+ color: "#ff9999",
|
|
|
},
|
|
|
],
|
|
|
-});
|
|
|
+};
|
|
|
|
|
|
-const faturamentoOptions = ref({
|
|
|
- responsive: true,
|
|
|
- maintainAspectRatio: false,
|
|
|
- plugins: {
|
|
|
- legend: { display: true, position: "top", labels: { font: { size: 11 } } },
|
|
|
- tooltip: { mode: "index", intersect: false },
|
|
|
- datalabels: { display: false },
|
|
|
- },
|
|
|
- scales: {
|
|
|
- x: { ticks: { font: { size: 9 } }, grid: { display: false } },
|
|
|
- y: { ticks: { font: { size: 10 } }, suggestedMin: 0 },
|
|
|
- },
|
|
|
-});
|
|
|
+const formatCurrencyTick = (value) => {
|
|
|
+ if (value >= 1000) return `R$ ${(value / 1000).toFixed(0)}k`;
|
|
|
+ return `R$ ${value}`;
|
|
|
+};
|
|
|
+
|
|
|
+const formatCurrencyTooltip = (context) => {
|
|
|
+ const value = context.parsed.y;
|
|
|
+ return ` ${context.dataset.label}: R$ ${value.toLocaleString("pt-BR", { minimumFractionDigits: 2 })}`;
|
|
|
+};
|
|
|
|
|
|
-// ── Contratos Ativos (gauge) ──────────────────────────────────────────────────
|
|
|
const gaugeData = ref({
|
|
|
datasets: [
|
|
|
{
|
|
|
backgroundColor: [
|
|
|
- "#00a550", "#4dbb7e", "#9ad2ad", "#cce156",
|
|
|
- "#fff100", "#ffbe00", "#ff8c00", "#FC3D23", "#D01616", "#8A0000",
|
|
|
+ "#00a550",
|
|
|
+ "#4dbb7e",
|
|
|
+ "#9ad2ad",
|
|
|
+ "#cce156",
|
|
|
+ "#fff100",
|
|
|
+ "#ffbe00",
|
|
|
+ "#ff8c00",
|
|
|
+ "#FC3D23",
|
|
|
+ "#D01616",
|
|
|
+ "#8A0000",
|
|
|
],
|
|
|
data: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
|
|
needleValue: 7,
|
|
|
@@ -314,13 +267,17 @@ const gaugeData = ref({
|
|
|
const gaugeOptions = ref({
|
|
|
rotation: 270,
|
|
|
circumference: 180,
|
|
|
- cutout: "55%",
|
|
|
+ cutout: "50%",
|
|
|
responsive: true,
|
|
|
maintainAspectRatio: false,
|
|
|
plugins: {
|
|
|
tooltip: { enabled: false },
|
|
|
legend: { display: false },
|
|
|
- datalabels: { display: false },
|
|
|
+ datalabels: {
|
|
|
+ color: "black",
|
|
|
+ font: { size: 14, weight: "bold" },
|
|
|
+ formatter: (_value, ctx) => ctx.dataIndex,
|
|
|
+ },
|
|
|
},
|
|
|
});
|
|
|
|
|
|
@@ -355,51 +312,26 @@ const gaugeNeedlePlugin = {
|
|
|
},
|
|
|
};
|
|
|
|
|
|
-// ── Matrículas por Período ────────────────────────────────────────────────────
|
|
|
-const matriculasData = ref({
|
|
|
+const matriculasChart = {
|
|
|
labels: ["JAN", "FEV", "MAR", "ABR", "MAI", "JUN"],
|
|
|
datasets: [
|
|
|
{
|
|
|
label: "Matrículas",
|
|
|
data: [120, 200, 150, 80, 70, 110],
|
|
|
- backgroundColor: ["#2196F3", "#F44336", "#9C27B0", "#FFC107", "#212121", "#009688"],
|
|
|
- borderColor: ["#2196F3", "#F44336", "#9C27B0", "#FFC107", "#212121", "#009688"],
|
|
|
- borderWidth: 1,
|
|
|
- borderRadius: 2,
|
|
|
+ color: ["#3B82F6", "#EF4444", "#A855F7", "#374151", "#EAB308", "#06B6D4"],
|
|
|
},
|
|
|
],
|
|
|
-});
|
|
|
-
|
|
|
-const matriculasOptions = ref({
|
|
|
- responsive: true,
|
|
|
- maintainAspectRatio: false,
|
|
|
- plugins: {
|
|
|
- legend: { display: false },
|
|
|
- datalabels: {
|
|
|
- anchor: "end",
|
|
|
- align: "top",
|
|
|
- color: "#666",
|
|
|
- font: { size: 11, weight: "bold" },
|
|
|
- formatter: (v) => v,
|
|
|
- },
|
|
|
- },
|
|
|
- scales: {
|
|
|
- x: { ticks: { font: { size: 11 } }, grid: { display: false } },
|
|
|
- y: { display: false, suggestedMin: 0, suggestedMax: 230 },
|
|
|
- },
|
|
|
-});
|
|
|
+};
|
|
|
|
|
|
-// ── Aniversariantes do Mês ────────────────────────────────────────────────────
|
|
|
const aniversariantes = ref([
|
|
|
- { nome: "Heloisa Faria", color: "orange" },
|
|
|
- { nome: "Juliana Costa", color: "green" },
|
|
|
- { nome: "Juliana Costa", color: "green" },
|
|
|
- { nome: "Fernando Almeida", color: "blue" },
|
|
|
- { nome: "Lucas Pereira", color: "purple" },
|
|
|
- { nome: "Sofia Martins", color: "teal" },
|
|
|
+ { 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" },
|
|
|
]);
|
|
|
|
|
|
-// ── Feriados do mês ───────────────────────────────────────────────────────────
|
|
|
const feriados = ref([
|
|
|
{ dia: 17, nome: "Carnaval", color: "amber" },
|
|
|
{ dia: 17, nome: "Carnaval", color: "amber" },
|
|
|
@@ -408,6 +340,26 @@ const feriados = ref([
|
|
|
</script>
|
|
|
|
|
|
<style scoped>
|
|
|
+.stat-cards-row {
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: nowrap;
|
|
|
+ gap: 16px;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-cards-row > * {
|
|
|
+ flex: 1 1 0;
|
|
|
+ min-width: 0;
|
|
|
+}
|
|
|
+
|
|
|
+@media (max-width: 599px) {
|
|
|
+ .stat-cards-row {
|
|
|
+ flex-wrap: wrap;
|
|
|
+ }
|
|
|
+ .stat-cards-row > * {
|
|
|
+ flex: 1 1 calc(50% - 8px);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
.gauge-label {
|
|
|
position: absolute;
|
|
|
bottom: 28%;
|