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>
<v-navigation-drawer class="body" v-model="drawer" app temporary>
<v-list>
<v-list-item
v-for="(item, index) in menuItems"
:key="index"
:to="{ path: item.path }"
<v-navigation-drawer
v-model="drawer"
permanent
class="custom-drawer"
>
<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
active-class="active-item"
>
<v-icon>{{ item.icon }}</v-icon>
<v-list-item-content>{{ item.title }}</v-list-item-content>
<template #prepend>
<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>
</v-navigation-drawer>
</template>
<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'
@ -31,13 +44,13 @@ console.log("Permissões do usuário:", dashboardPermissions)
const menuItems = [
//{ title: 'Home', path: '/dashboard/home', icon: 'mdi mdi-home-circle', 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: '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: 'Escalas', path: '/dashboard/shifts', icon: 'mdi mdi-sitemap', 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' }
]
@ -48,44 +61,48 @@ const toggleDrawer = () => {
</script>
<style scoped>
.body {
background-color: #fafafab9;
.custom-drawer {
background-color: #fff;
border-right: 1px solid #eaecef;
padding-top: 24px;
width: 240px;
}
.v-navigation-drawer {
border-style: none;
.app-title {
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 {
color: rgba(60, 135, 245, 1);
transition: all 0.3s ease;
border-style: solid;
border-radius: 15px;
margin: 8px 0;
padding: 10px 16px;
font-weight: 500;
border-radius: 8px;
margin: 4px 12px;
padding-left: 8px;
transition: 0.2s;
}
.v-list-item:hover {
background-color: #f5f5f5;
color: rgba(76,201,240);
border-radius: 15px !important;
margin-left: 25px;
border-radius: 50px;
background: linear-gradient(145deg, #eeeded, #ffffff);
background-color: #f3f4f6;
}
.v-list-item--active {
background-color: #4cc9f0 !important;
color: white !important;
border-radius: 15px !important;
margin-left: 25px;
margin-right: 25px;
}
.v-list-item-content {
margin-left: 16px;
letter-spacing: 0.5px;
.active-item {
background-color: #e0f2fe !important;
color: #1e3a8a !important;
font-weight: 600;
}
</style>

View File

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

View File

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

View File

@ -1,289 +1,268 @@
<template class="body">
<template>
<v-container fluid>
<!-- Cabeçalho da página -->
<v-row>
<v-col cols="12" class="d-flex align-center">
<v-img
src="/logo3.png"
max-width="40"
class="mr-3"
:elevation="0"
:class="'clean-image'"
></v-img>
<div class="text">
<h3>Dashboard Olha ai</h3>
<p>Bem-vindo ao painel de controle</p>
<!-- Título -->
<!-- Header da página -->
<div class="header-container">
<div class="header-left">
<div class="icon-wrapper">
<v-icon color="primary" size="32">mdi-clock-outline</v-icon>
</div>
<div class="header-title">
<h1>Sistema de Ponto | Admin</h1>
</div>
</div>
<div class="header-right">
<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>
</v-col>
</v-row>
<!-- Cards de informações -->
<v-row>
<!-- Total de Usuários -->
<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>
<!-- Cards de Resumo -->
<v-row class="mb-4">
<v-col cols="12" md="3" v-for="card in cards" :key="card.label">
<v-card class="summary-card">
<v-card-text>
<v-row align="center">
<v-col class="sub">
<h2>{{ totalColaboradores }}</h2>
</v-col>
</v-row>
<div class="summary-label">{{ card.label }}</div>
<div class="summary-value">{{ card.value }}</div>
<div class="summary-sub">{{ card.sub }}</div>
</v-card-text>
</v-card>
</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>
<!-- 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-col cols="12" md="6">
<v-card class="cards">
<v-card-title>
<v-icon class="icon" left>mdi-chart-bar</v-icon>
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-col cols="12" md="4">
<v-card>
<v-card-title class="text-subtitle-1 font-weight-bold">Distribuição de Horas</v-card-title>
<v-card-text class="text-center">Gráfico de distribuição de horas</v-card-text>
</v-card>
</v-col>
<!-- Horas extras do mês -->
<v-col cols="12" md="6">
<v-card class="cards">
<v-card-title>
<v-icon class="icon" left>mdi-chart-bar</v-icon>
Horas extras do mês
<v-col cols="12" md="4">
<v-card>
<v-card-title class="text-subtitle-1 font-weight-bold">Marcações por Dia</v-card-title>
<v-card-text class="text-center">Gráfico de marcações diárias</v-card-text>
</v-card>
</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-subtitle>
Colaboradores - Terceiros, Freelancer
</v-card-subtitle>
<v-card-text>
<div :style="chartContainerStyle">
<apexchart type="line" height="350" :options="chartOptions" :series="series" />
<div class="holiday-entry" v-for="feriado in proximosFeriados" :key="feriado.nome">
<div>{{ feriado.nome }}</div>
<div>{{ feriado.data }}</div>
</div>
</v-card-text>
</v-card>
</v-col>
</v-row>
</v-row>
</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>
<script setup>
import { ref, onMounted } from 'vue';
import api from '../services/api';
import { useHolidayStore } from '../stores/holiday';
import { useUserStore } from '../stores/users';
import { useAgentStore } from '../stores/agentStore';
import VueApexCharts from 'vue3-apexcharts';
// Referências reativas para os valores
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 holidayStore = useHolidayStore();
const userStore = useUserStore();
const agentStore = useAgentStore();
// Função para carregar os dados
const loadData = async () => {
try {
// Atualizar total de usuários
const users = await userStore.catchUsers();
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,
const cards = ref([
{
label: 'Total de Colaboradores',
value: 0,
sub: 'Colaboradores cadastrados no sistema'
},
}
{
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>
<style scoped>
.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;
.dashboard-title {
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>

View File

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