ultima versao

This commit is contained in:
ka-lucas 2025-04-30 17:39:04 -03:00
parent 326276d4a4
commit 9c4c66a4f6
5 changed files with 297 additions and 298 deletions

View File

@ -1,19 +1,32 @@
<template> <template>
<v-navigation-drawer class="body" v-model="drawer" app temporary> <v-navigation-drawer
<v-list> v-model="drawer"
<v-list-item permanent
v-for="(item, index) in menuItems" class="custom-drawer"
:key="index" >
:to="{ path: item.path }" <v-list nav dense>
<v-list-item-title class="app-title">Admin</v-list-item-title>
<v-list-subheader class="app-sub">Sistema de Ponto</v-list-subheader>
<v-divider class="my-2" />
<v-list-item
v-for="(item, index) in menuItems"
:key="index"
:to="item.path"
exact exact
active-class="active-item"
> >
<v-icon>{{ item.icon }}</v-icon> <template #prepend>
<v-list-item-content>{{ item.title }}</v-list-item-content> <v-icon class="mr-3" size="20">{{ item.icon }}</v-icon>
</template>
<v-list-item-title class="menu-text">{{ item.title }}</v-list-item-title>
</v-list-item> </v-list-item>
</v-list> </v-list>
</v-navigation-drawer> </v-navigation-drawer>
</template> </template>
<script setup> <script setup>
import { ref } from 'vue' import { ref } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
@ -31,13 +44,13 @@ console.log("Permissões do usuário:", dashboardPermissions)
const menuItems = [ const menuItems = [
//{ title: 'Home', path: '/dashboard/home', icon: 'mdi mdi-home-circle', requiredPermission: 'read' }, //{ title: 'Home', path: '/dashboard/home', icon: 'mdi mdi-home-circle', requiredPermission: 'read' },
{ title: 'Dashboard', path: '/dashboard', icon: 'mdi mdi-view-dashboard', requiredPermission: 'read' }, { title: 'Dashboard', path: '/dashboard', icon: 'mdi mdi-view-dashboard', requiredPermission: 'read' },
{ title: 'Admin', path: '/dashboard/profile', icon: 'mdi-account', requiredPermission: 'read' }, //{ title: 'Admin', path: '/dashboard/profile', icon: 'mdi-account', requiredPermission: 'read' },
{ title: 'Colaboradores', path: '/dashboard/colaboradores', icon: 'mdi-account-multiple-outline', requiredPermission: 'write' }, { title: 'Colaboradores', path: '/dashboard/colaboradores', icon: 'mdi-account-multiple-outline', requiredPermission: 'write' },
{ title: 'Câmera', path: '/dashboard/agentlist', icon: 'mdi mdi-cctv', requiredPermission: 'write' }, //{ title: 'Câmera', path: '/dashboard/agentlist', icon: 'mdi mdi-cctv', requiredPermission: 'write' },
//{ title: 'Train', path: '/dashboard/train', icon: 'mdi mdi-thought-bubble', requiredPermission: 'write' }, //{ title: 'Train', path: '/dashboard/train', icon: 'mdi mdi-thought-bubble', requiredPermission: 'write' },
{ title: 'Escalas', path: '/dashboard/shifts', icon: 'mdi mdi-sitemap', requiredPermission: 'write' }, { title: 'Escalas', path: '/dashboard/shifts', icon: 'mdi mdi-sitemap', requiredPermission: 'write' },
{ title: 'Feriados', path: '/dashboard/feriados', icon: 'mdi mdi-calendar-blank', requiredPermission: 'write' }, { title: 'Feriados', path: '/dashboard/feriados', icon: 'mdi mdi-calendar-blank', requiredPermission: 'write' },
{ title: 'Empresas', path: '/dashboard/empresas', icon: 'mdi mdi-domain', requiredPermission: 'write' }, //{ title: 'Empresas', path: '/dashboard/empresas', icon: 'mdi mdi-domain', requiredPermission: 'write' },
{ title: 'Configurações', path: '/dashboard/settings', icon: 'mdi-cog', requiredPermission: 'write' } { title: 'Configurações', path: '/dashboard/settings', icon: 'mdi-cog', requiredPermission: 'write' }
] ]
@ -48,44 +61,48 @@ const toggleDrawer = () => {
</script> </script>
<style scoped> <style scoped>
.body { .custom-drawer {
background-color: #fafafab9; background-color: #fff;
border-right: 1px solid #eaecef;
padding-top: 24px;
width: 240px;
} }
.v-navigation-drawer { .app-title {
border-style: none; font-weight: 700;
font-size: 18px;
padding-left: 10px;
color: #1e293b;
}
.app-sub {
font-size: 13px;
padding-left: 10px;
margin-bottom: 8px;
color: #64748b;
}
.menu-text {
font-size: 14px;
color: #1e293b;
} }
.v-list-item { .v-list-item {
color: rgba(60, 135, 245, 1); border-radius: 8px;
transition: all 0.3s ease; margin: 4px 12px;
border-style: solid; padding-left: 8px;
border-radius: 15px; transition: 0.2s;
margin: 8px 0;
padding: 10px 16px;
font-weight: 500;
} }
.v-list-item:hover { .v-list-item:hover {
background-color: #f5f5f5; background-color: #f3f4f6;
color: rgba(76,201,240);
border-radius: 15px !important;
margin-left: 25px;
border-radius: 50px;
background: linear-gradient(145deg, #eeeded, #ffffff);
} }
.v-list-item--active { .active-item {
background-color: #4cc9f0 !important; background-color: #e0f2fe !important;
color: white !important; color: #1e3a8a !important;
border-radius: 15px !important; font-weight: 600;
margin-left: 25px;
margin-right: 25px;
}
.v-list-item-content {
margin-left: 16px;
letter-spacing: 0.5px;
} }
</style> </style>

View File

@ -1,9 +1,6 @@
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import api from '../services/api'; import api from '../services/api';
import router from '../routes/router'; import router from '../routes/router';
import { useAuthStore } from './auth';
import { ca } from 'date-fns/locale';
export const useServiceInstanceStore = defineStore('servic_instance', { export const useServiceInstanceStore = defineStore('servic_instance', {
state: () => ({ state: () => ({
service_instance: [], service_instance: [],

View File

@ -1,8 +1,7 @@
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import api from '../services/api'; // A instância personalizada do Axios import api from '../services/api'; // A instância personalizada do Axios
import router from '../routes/router'; import router from '../routes/router';
import { useAuthStore } from './auth'; // Importa o AuthStore import { useAuthStore } from './auth';
import { ca } from 'date-fns/locale';
export const useSpaceStore = defineStore('spaces', { export const useSpaceStore = defineStore('spaces', {
state: () => ({ state: () => ({

View File

@ -1,289 +1,268 @@
<template class="body"> <template>
<v-container fluid> <v-container fluid>
<!-- Cabeçalho da página --> <!-- Título -->
<v-row> <!-- Header da página -->
<v-col cols="12" class="d-flex align-center"> <div class="header-container">
<v-img <div class="header-left">
src="/logo3.png" <div class="icon-wrapper">
max-width="40" <v-icon color="primary" size="32">mdi-clock-outline</v-icon>
class="mr-3" </div>
:elevation="0" <div class="header-title">
:class="'clean-image'" <h1>Sistema de Ponto | Admin</h1>
></v-img> </div>
<div class="text"> </div>
<h3>Dashboard Olha ai</h3> <div class="header-right">
<p>Bem-vindo ao painel de controle</p>
<v-badge dot color="error" class="notification-badge">
<v-icon>mdi-bell</v-icon>
</v-badge>
<div class="admin-profile">
<v-avatar class="mr-2" color="primary" size="40">
<v-img src="/api/placeholder/40/40" alt="Administrador"></v-img>
</v-avatar>
<div class="admin-info">
<span class="admin-name">Administrador</span>
<span class="admin-role">Gestor</span>
</div>
<v-icon>mdi-chevron-down</v-icon>
</div>
</div>
</div>
<!-- Título e descrição da seção -->
<div class="section-header">
<h2>Dashboard</h2>
</div> </div>
</v-col>
</v-row>
<!-- Cards de Resumo -->
<!-- Cards de informações --> <v-row class="mb-4">
<v-row> <v-col cols="12" md="3" v-for="card in cards" :key="card.label">
<!-- Total de Usuários --> <v-card class="summary-card">
<v-col cols="12" md="3">
<v-card class="cards">
<v-card-title>
<v-icon class="icon" left>mdi-account-group</v-icon>
Colaboradores
</v-card-title>
<v-card-text> <v-card-text>
<v-row align="center"> <div class="summary-label">{{ card.label }}</div>
<v-col class="sub"> <div class="summary-value">{{ card.value }}</div>
<h2>{{ totalColaboradores }}</h2> <div class="summary-sub">{{ card.sub }}</div>
</v-col>
</v-row>
</v-card-text> </v-card-text>
</v-card> </v-card>
</v-col> </v-col>
<!-- Trabalhando -->
<v-col cols="12" md="3">
<v-card class="cards">
<v-card-title>
<v-icon class="icon" left>mdi mdi-account-hard-hat</v-icon>
Trabalhando
</v-card-title>
<v-card-text>
<v-row align="center">
<v-col class="sub">
<h2>{{ totalAgentes }}</h2>
</v-col>
</v-row>
</v-card-text>
</v-card>
</v-col>
<!-- atrasados -->
<v-col cols="12" md="3">
<v-card class="cards">
<v-card-title class="card-text justify-center mx-auto">
<v-icon class="icon" left>mdi mdi-clock</v-icon>
Atrasados
</v-card-title>
<v-card-text>
<v-row align="center">
<v-col class="sub">
<h2>{{ temperaturaAgente }}</h2>
</v-col>
</v-row>
</v-card-text>
</v-card>
</v-col>
<!-- Total de Faltas -->
<v-col cols="12" md="3">
<v-card class="cards">
<v-card-title>
<v-icon class="icon" left>mdi mdi-account-multiple-remove-outline</v-icon>
Faltas e Ausências
</v-card-title>
<v-card-text>
<v-row align="center">
<v-col class="sub">
<h2>{{ totalFalhas }}</h2>
</v-col>
</v-row>
</v-card-text>
<v-btn block class="button">Ver Logs...</v-btn>
</v-card>
</v-col>
</v-row> </v-row>
<!-- Gráfico de Colaboradores por categoria --> <!-- Filtros Visão Geral / Solicitações -->
<v-row class="mb-4">
<v-col cols="12">
<v-btn-toggle v-model="visaoSelecionada" divided>
<v-btn value="geral" prepend-icon="mdi-chart-line">Visão Geral</v-btn>
<v-btn value="pendentes" prepend-icon="mdi-information">Solicitações Pendentes</v-btn>
</v-btn-toggle>
</v-col>
</v-row>
<!-- Gráficos + Feriados -->
<v-row> <v-row>
<v-col cols="12" md="6"> <v-col cols="12" md="4">
<v-card class="cards"> <v-card>
<v-card-title> <v-card-title class="text-subtitle-1 font-weight-bold">Distribuição de Horas</v-card-title>
<v-icon class="icon" left>mdi-chart-bar</v-icon> <v-card-text class="text-center">Gráfico de distribuição de horas</v-card-text>
Gráfico de Colaboradores por categoria
</v-card-title>
<v-card-subtitle>
Trabalhando, Atrasados, Ausentes
</v-card-subtitle>
<v-card-text>
<div>
<apexchart type="bar" height="350" :options="chartOptions" :series="series" />
</div>
</v-card-text>
</v-card> </v-card>
</v-col> </v-col>
<!-- Horas extras do mês --> <v-col cols="12" md="4">
<v-col cols="12" md="6"> <v-card>
<v-card class="cards"> <v-card-title class="text-subtitle-1 font-weight-bold">Marcações por Dia</v-card-title>
<v-card-title> <v-card-text class="text-center">Gráfico de marcações diárias</v-card-text>
<v-icon class="icon" left>mdi-chart-bar</v-icon> </v-card>
Horas extras do mês </v-col>
<v-col cols="12" md="4">
<v-card>
<v-card-title class="text-subtitle-1 font-weight-bold">
<v-icon size="18" class="mr-1">mdi-calendar</v-icon>
Próximos Feriados
</v-card-title> </v-card-title>
<v-card-subtitle>
Colaboradores - Terceiros, Freelancer
</v-card-subtitle>
<v-card-text> <v-card-text>
<div :style="chartContainerStyle"> <div class="holiday-entry" v-for="feriado in proximosFeriados" :key="feriado.nome">
<apexchart type="line" height="350" :options="chartOptions" :series="series" /> <div>{{ feriado.nome }}</div>
<div>{{ feriado.data }}</div>
</div> </div>
</v-card-text> </v-card-text>
</v-card> </v-card>
</v-col> </v-col>
</v-row> </v-row>
</v-container> </v-container>
<!-- Card de logs
<v-col cols="12" md="12">
<v-card class="cards">
<v-card-item class="text">
<v-card-title>
<v-icon class="icon" left>mdi mdi-math-log</v-icon>
Log dos Agentes
</v-card-title>
</v-card-item>
<v-btn block class="button">Ver Logs...</v-btn>
</v-card>
</v-col>
-->
</template> </template>
<script setup> <script setup>
import { ref, onMounted } from 'vue'; import { ref, onMounted } from 'vue';
import api from '../services/api';
import { useHolidayStore } from '../stores/holiday';
import { useUserStore } from '../stores/users'; import { useUserStore } from '../stores/users';
import { useAgentStore } from '../stores/agentStore';
import VueApexCharts from 'vue3-apexcharts';
// Referências reativas para os valores const holidayStore = useHolidayStore();
const totalColaboradores = ref(0);
const totalAgentes = ref(0);
const totalFalhas = ref(0);
const temperaturaAgente = ref(0);
// Dados de vendas por categoria de produto
const series = ref([]);
// Configurações do gráfico
const chartOptions = ref({
chart: {
type: 'bar',
},
plotOptions: {
bar: {
horizontal: false,
columnWidth: '50%',
endingShape: 'rounded',
borderRadius: 20,
},
},
dataLabels: {
enabled: false,
},
xaxis: {
categories: [], // Categorias de produtos
},
yaxis: {
title: {
text: 'Número de Colaboradores',
},
},
fill: {
type: 'gradient',
gradient: {
shade: 'dark',
type: 'vertical',
gradientToColors: ['#00eeef'],
stops: [0, 50, 100],
},
opacity: 1,
},
tooltip: {
y: {
formatter: function (val) {
return `${val} `;
},
},
},
});
// Stores
const userStore = useUserStore(); const userStore = useUserStore();
const agentStore = useAgentStore();
// Função para carregar os dados const cards = ref([
const loadData = async () => { {
try { label: 'Total de Colaboradores',
// Atualizar total de usuários value: 0,
const users = await userStore.catchUsers(); sub: 'Colaboradores cadastrados no sistema'
totalColaboradores.value = users.length;
// Atualizar total de agentes
const agents = agentStore.agents.filter(agent => !agent.isDeleted);
totalAgentes.value = agents.length;
// Atualizar temperatura dos agentes (exemplo: média de temperaturas)
temperaturaAgente.value = agents.reduce((sum, agent) => sum + (agent.temperature || 0), 0) / agents.length;
// Atualizar total de falhas (exemplo: agentes com status de falha)
totalFalhas.value = agents.filter(agent => agent.status === 'falha').length;
// Atualizar dados do gráfico
series.value = [
{
name: 'Colaboradores',
data: [15, 10, 20], // Substituir pelos dados reais
},
];
chartOptions.value.xaxis.categories = ['Trabalhando', 'Atrasados', 'Ausentes']; // Substituir pelas categorias reais
} catch (error) {
console.error('Erro ao carregar os dados:', error);
}
};
// Carregar os dados ao montar o componente
onMounted(loadData);
</script>
<script>
export default {
components: {
apexchart: VueApexCharts,
}, },
} {
label: 'Marcações Hoje',
value: '-',
sub: 'Registros de ponto realizados hoje'
},
{
label: 'Ajustes Pendentes',
value: '-',
sub: 'Solicitações aguardando aprovação'
},
{
label: 'Horas Extras',
value: '-',
sub: 'Total de horas extras no mês'
}
]);
const visaoSelecionada = ref('geral');
const proximosFeriados = ref([]);
onMounted(async () => {
// Busca o total de colaboradores
try {
const response = await userStore.catchUsers(); // Retorna diretamente um array
cards.value[0].value = response.length; // Calcula quantidade de usuários
} catch (error) {
console.error('Erro ao buscar colaboradores:', error);
}
// Busca feriados
try {
const response = await holidayStore.getHolidays(); // Aguarda a Promise e recebe um array
console.log('Feriados:', response);
const feriados = response
.filter(f => new Date(f.date) > new Date())
.sort((a, b) => new Date(a.date) - new Date(b.date))
.slice(0, 3)
.map(f => ({
nome: f.name,
data: new Date(f.date).toLocaleDateString('pt-BR')
}));
proximosFeriados.value = feriados;
} catch (error) {
console.error('Erro ao buscar feriados:', error);
}
});
</script> </script>
<style scoped> <style scoped>
.dashboard-title {
.fill-height {
min-height: 95vh;
}
.cards {
text-align: center;
color: rgba(0,0,0);
border-radius: 50px;
background: linear-gradient(145deg, #eeeded, #ffffff);
box-shadow: 20px 20px 60px #d9d9d9,
-20px -20px 60px #ffffff;
}
.button {
background-color: rgba(76, 202, 240, 0.336);
color: #000000;
}
.text {
font-size: 18px;
color: rgba(0,0,0);
}
.icon {
color: rgba(0,0,0);
font-size: 55px;
}
.sub {
color: #23408e;
font-size: 20px; font-size: 20px;
font-weight: 600;
color: #1e293b;
} }
.dashboard-subtitle {
font-size: 14px;
color: #64748b;
}
.summary-card {
background-color: #fff;
border-radius: 12px;
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
padding: 16px;
}
.summary-label {
font-size: 14px;
color: #64748b;
}
.summary-value {
font-size: 24px;
font-weight: bold;
color: #1e293b;
}
.summary-sub {
font-size: 13px;
color: #94a3b8;
}
.holiday-entry {
display: flex;
justify-content: space-between;
padding: 8px 0;
border-bottom: 1px solid #f1f1f1;
}
.holiday-entry:last-child {
border-bottom: none;
}
/* Estilo do cabeçalho */
.header-container {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding: 0 0 20px 0;
border-bottom: 1px solid #eaecef;
}
.header-left {
display: flex;
align-items: center;
}
.icon-wrapper {
background-color: #e3f2fd;
border-radius: 8px;
padding: 12px;
margin-right: 12px;
}
.header-title h1 {
font-size: 18px;
font-weight: 600;
margin: 0;
color: #1e293b;
}
.header-right {
display: flex;
align-items: center;
gap: 16px;
}
.back-button {
color: #1976d2;
}
.new-holiday-btn {
background-color: #1976d2;
color: white;
}
.admin-profile {
display: flex;
align-items: center;
margin-left: 16px;
cursor: pointer;
}
.admin-info {
display: flex;
flex-direction: column;
margin: 0 8px;
}
.admin-name {
font-size: 14px;
font-weight: 500;
color: #1e293b;
}
.admin-role {
font-size: 12px;
color: #64748b;
}
.notification-badge {
margin: 0 8px;
}
</style> </style>

View File

@ -1,5 +1,6 @@
import { defineConfig } from 'vite'; import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue'; import vue from '@vitejs/plugin-vue';
import path from 'path';
export default defineConfig({ export default defineConfig({
plugins: [vue()], plugins: [vue()],
@ -15,6 +16,12 @@ export default defineConfig({
target: 'http://127.0.0.1:5001', target: 'http://127.0.0.1:5001',
changeOrigin: true, changeOrigin: true,
rewrite: (path) => path.replace(/^\/new-api/, '') rewrite: (path) => path.replace(/^\/new-api/, '')
},
fs: {
allow: [
// Caminho absoluto para o node_modules no seu caso
path.resolve(__dirname, 'node_modules')
]
} }
} }
} }