699 lines
18 KiB
Vue
699 lines
18 KiB
Vue
<template>
|
|
<div class="holiday-management">
|
|
<!-- 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-btn variant="text" prepend-icon="mdi-arrow-left" class="back-button" to="/dashboard">
|
|
Voltar para Dashboard
|
|
</v-btn>
|
|
<v-btn color="primary" prepend-icon="mdi-plus" class="new-holiday-btn" @click="openCreateModal">
|
|
Novo Feriado
|
|
</v-btn>
|
|
<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>Feriados e Dias Especiais</h2>
|
|
<p class="section-description">Gerencie os feriados e dias com tratamento especial</p>
|
|
</div>
|
|
|
|
<!-- Card principal -->
|
|
<v-card class="main-card" elevation="1">
|
|
<div class="card-header">
|
|
<h3>Lista de Feriados</h3>
|
|
<p class="card-description">Gerencie os feriados e dias especiais do sistema</p>
|
|
</div>
|
|
|
|
<!-- Tabela de feriados -->
|
|
<v-data-table
|
|
:headers="headers.holidays"
|
|
:items="filteredholidays"
|
|
:search="filters.holiday"
|
|
:loading="loading.holidays"
|
|
:items-per-page="itemsPerPage"
|
|
:page.sync="page"
|
|
class="holiday-table"
|
|
>
|
|
<!-- Slot para campo tipo (adicional) -->
|
|
<template v-slot:item.type="{ item }">
|
|
<v-chip
|
|
class="holiday-type-chip"
|
|
:class="getTypeClass(item.type)"
|
|
text-color="white"
|
|
size="small"
|
|
label
|
|
>
|
|
{{ item.type || 'Nacional' }}
|
|
</v-chip>
|
|
</template>
|
|
|
|
<!-- Slot para recorrente -->
|
|
<template v-slot:item.recorrente="{ item }">
|
|
<span>{{ item.recorrente ? 'Sim' : 'Não' }}</span>
|
|
</template>
|
|
|
|
<!-- Slot para adicional_he -->
|
|
<template v-slot:item.adicional_he="{ item }">
|
|
{{ item.adicional_he }}%
|
|
</template>
|
|
|
|
<!-- Slot para ações -->
|
|
<template v-slot:item.actions="{ item }">
|
|
<div class="action-buttons" style="display: flex; gap: 8px;">
|
|
<v-btn
|
|
icon
|
|
variant="text"
|
|
color="black"
|
|
density="compact"
|
|
@click="openEditPage('holiday', item)"
|
|
>
|
|
<v-icon size="small">mdi-pencil-outline</v-icon>
|
|
</v-btn>
|
|
|
|
<v-btn
|
|
icon
|
|
variant="text"
|
|
color="error"
|
|
density="compact"
|
|
@click="confirmDelete('holiday', item)"
|
|
>
|
|
<v-icon size="small">mdi-delete-outline</v-icon>
|
|
</v-btn>
|
|
</div>
|
|
</template>
|
|
</v-data-table>
|
|
</v-card>
|
|
|
|
<!-- Modais existentes -->
|
|
<holidayModal
|
|
v-model="dialogs.holiday"
|
|
:isEditMode="isEditing"
|
|
:holiday="forms.holiday"
|
|
@save="submitForm"
|
|
@cancel="closeHolidayModal"
|
|
/>
|
|
|
|
<CreateHolidayModal
|
|
v-model="dialogs.createholiday"
|
|
@save="SubmitFormCreate"
|
|
@cancel="closeCreateModal"
|
|
/>
|
|
<!-- Diálogo de Confirmação de Exclusão -->
|
|
<v-dialog v-model="dialogs.delete" max-width="400px">
|
|
<v-card>
|
|
<v-card-title class="text-h5 bg-error text-white">
|
|
Confirmar Exclusão
|
|
</v-card-title>
|
|
|
|
<v-card-text class="pt-4">
|
|
<p v-if="itemToDelete && itemToDelete.name">
|
|
Tem certeza que deseja excluir <strong>{{ itemToDelete.name }}</strong>?
|
|
Esta ação não pode ser desfeita.
|
|
</p>
|
|
<p v-else>
|
|
Erro: Nenhum item selecionado para exclusão.
|
|
</p>
|
|
</v-card-text>
|
|
|
|
<v-card-actions>
|
|
<v-spacer></v-spacer>
|
|
<v-btn color="grey" @click="dialogs.delete = false">Cancelar</v-btn>
|
|
<v-btn
|
|
color="error"
|
|
:loading="loading.delete"
|
|
:disabled="!itemToDelete || !itemToDelete.id"
|
|
@click="deleteItem"
|
|
>
|
|
Excluir
|
|
</v-btn>
|
|
</v-card-actions>
|
|
</v-card>
|
|
</v-dialog>
|
|
|
|
<!-- Snackbar para Feedback -->
|
|
<v-snackbar
|
|
v-model="snackbar.show"
|
|
:color="snackbar.color"
|
|
:timeout="3000"
|
|
>
|
|
{{ snackbar.text }}
|
|
<template v-slot:actions>
|
|
<v-btn
|
|
color="white"
|
|
variant="text"
|
|
@click="snackbar.show = false"
|
|
>
|
|
Fechar
|
|
</v-btn>
|
|
</template>
|
|
</v-snackbar>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import { ref, computed, onMounted } from 'vue';
|
|
import { useRouter } from 'vue-router';
|
|
import holidayModal from '../components/modals/HolidayModal.vue';
|
|
import { useHolidayStore } from '../stores/holiday';
|
|
import CreateHolidayModal from '../components/modals/HolidayModalCreate.vue';
|
|
|
|
export default {
|
|
name: 'holidayManagement',
|
|
components: {
|
|
holidayModal,
|
|
CreateHolidayModal
|
|
},
|
|
setup() {
|
|
const router = useRouter();
|
|
const holidayStore = useHolidayStore();
|
|
|
|
// Estados
|
|
const isEditing = ref(false);
|
|
const itemToDelete = ref(null);
|
|
const page = ref(1);
|
|
const itemsPerPage = ref(10);
|
|
const selectedholiday = ref(null);
|
|
|
|
// Loading states
|
|
const loading = ref({
|
|
holidays: false,
|
|
submit: false,
|
|
delete: false,
|
|
export: false
|
|
});
|
|
|
|
// Diálogos
|
|
const dialogs = ref({
|
|
holiday: false,
|
|
createholiday: false,
|
|
delete: false
|
|
});
|
|
|
|
// Filtros
|
|
const filters = ref({
|
|
holiday: ''
|
|
});
|
|
|
|
// Dados - array vazio que será preenchido via API ou entrada do usuário
|
|
const holidays = ref([]);
|
|
|
|
// Formulários
|
|
const forms = ref({
|
|
holiday: {
|
|
id: null,
|
|
name: '',
|
|
date: '',
|
|
type: 'Nacional', // Adicionado campo tipo
|
|
estado: '',
|
|
municipio: '',
|
|
recorrente: true,
|
|
adicional_he: 0,
|
|
loading: false
|
|
}
|
|
});
|
|
|
|
// Headers para as tabelas (atualizados para o novo layout)
|
|
const headers = ref({
|
|
holidays: [
|
|
{ title: 'Nome', key: 'name', align: 'start' },
|
|
{ title: 'Data', key: 'date', align: 'start' },
|
|
{ title: 'Tipo', key: 'type', align: 'start' }, // Novo campo tipo
|
|
{ title: 'Recorrente', key: 'recorrente', align: 'start' },
|
|
{ title: 'Adicional H.E.', key: 'adicional_he', align: 'start' },
|
|
{ title: 'Ações', key: 'actions', sortable: false, align: 'end' }
|
|
]
|
|
});
|
|
|
|
// Snackbar
|
|
const snackbar = ref({
|
|
show: false,
|
|
text: '',
|
|
color: 'success'
|
|
});
|
|
|
|
// Método para determinar a cor do chip baseado no tipo
|
|
const getTypeClass = (type) => {
|
|
switch (type?.toLowerCase()) {
|
|
case 'nacional':
|
|
return 'chip-nacional';
|
|
case 'estadual':
|
|
return 'chip-estadual';
|
|
case 'ponto facultativo':
|
|
return 'chip-facultativo';
|
|
case 'municipal':
|
|
return 'chip-municipal';
|
|
default:
|
|
return 'chip-outro';
|
|
}
|
|
};
|
|
|
|
// Computed properties
|
|
const filteredholidays = computed(() => {
|
|
return holidays.value.filter(holiday => {
|
|
return (
|
|
holiday.name.toLowerCase().includes(filters.value.holiday.toLowerCase()) &&
|
|
!holiday.deleted
|
|
)
|
|
}).map(holiday => ({
|
|
...holiday,
|
|
date: formatDate(holiday.date),
|
|
created_at: holiday.created_at ? new Date(holiday.created_at).toLocaleString() : '-'
|
|
}));
|
|
});
|
|
|
|
// Função auxiliar para formatar data
|
|
function formatDate(dateString) {
|
|
if (!dateString) return '';
|
|
|
|
// Verificar se a data tem ano ou é apenas DD/MM
|
|
if (dateString.includes('/') && dateString.split('/').length === 2) {
|
|
return dateString; // Já está no formato DD/MM
|
|
}
|
|
|
|
const options = { day: '2-digit', month: '2-digit' };
|
|
try {
|
|
// Se tem ano completo, formatar como DD/MM/YYYY
|
|
if (dateString.includes('/') && dateString.split('/').length === 3) {
|
|
return dateString;
|
|
}
|
|
return new Date(dateString).toLocaleDateString('pt-BR', options);
|
|
} catch (e) {
|
|
return dateString; // Retorna a string original se falhar
|
|
}
|
|
}
|
|
|
|
// Métodos
|
|
const openCreateModal = () => {
|
|
dialogs.value.createholiday = true;
|
|
};
|
|
|
|
const closeHolidayModal = () => {
|
|
dialogs.value.holiday = false;
|
|
};
|
|
|
|
const closeCreateModal = () => {
|
|
dialogs.value.createholiday = false;
|
|
};
|
|
|
|
const openEditPage = (type, item) => {
|
|
console.log('Feriado que será editado:', item);
|
|
|
|
// Criar uma cópia do item para evitar problemas de mutação
|
|
const holidayData = { ...item };
|
|
|
|
// Garantir que todos os campos necessários estejam presentes
|
|
forms.value[type] = {
|
|
id: holidayData.id,
|
|
name: holidayData.name || '',
|
|
date: holidayData.date || '',
|
|
type: holidayData.type || 'Nacional', // Adicionado campo tipo
|
|
estado: holidayData.estado || '',
|
|
municipio: holidayData.municipio || '',
|
|
recorrente: holidayData.recorrente !== undefined ? holidayData.recorrente : true,
|
|
adicional_he: holidayData.adicional_he !== undefined ? holidayData.adicional_he : 0,
|
|
};
|
|
|
|
// Definir modo de edição e abrir o diálogo
|
|
isEditing.value = true;
|
|
dialogs.value[type] = true;
|
|
selectedholiday.value = null;
|
|
};
|
|
|
|
const confirmDelete = (type, item) => {
|
|
console.log("Item recebido para exclusão:", item);
|
|
|
|
if (!item || !item.id) {
|
|
console.error("Erro: Item inválido para exclusão.", item);
|
|
showNotification("Erro: Não foi possível selecionar o item para exclusão.", "error");
|
|
return;
|
|
}
|
|
|
|
// Armazena explicitamente o item completo
|
|
itemToDelete.value = {...item};
|
|
|
|
console.log("itemToDelete definido como:", itemToDelete.value);
|
|
dialogs.value.delete = true;
|
|
};
|
|
|
|
const deleteItem = async () => {
|
|
if (!itemToDelete.value || !itemToDelete.value.id) {
|
|
console.error("Erro: Item inválido para exclusão.", itemToDelete.value);
|
|
return;
|
|
}
|
|
|
|
loading.value.delete = true;
|
|
try {
|
|
const holidayId = itemToDelete.value.id;
|
|
console.log(`Excluindo Feriado com ID: ${holidayId}`);
|
|
|
|
// Faz a requisição para deletar
|
|
await holidayStore.deleteHoliday(holidayId);
|
|
|
|
// Remover o item diretamente da lista local
|
|
holidays.value = holidays.value.filter(holiday => holiday.id !== holidayId);
|
|
console.log("Lista atualizada após exclusão:", holidays.value);
|
|
|
|
showNotification(`Feriado excluído com sucesso!`);
|
|
dialogs.value.delete = false;
|
|
} catch (error) {
|
|
showNotification('Erro ao excluir o item', 'error');
|
|
console.error('Error deleting item:', error);
|
|
} finally {
|
|
loading.value.delete = false;
|
|
itemToDelete.value = null;
|
|
}
|
|
};
|
|
|
|
const showNotification = (text, color = 'success') => {
|
|
snackbar.value = {
|
|
show: true,
|
|
text,
|
|
color
|
|
};
|
|
};
|
|
|
|
const submitForm = async (holidayData) => {
|
|
loading.value.submit = true;
|
|
try {
|
|
if (isEditing.value) {
|
|
// Atualizar item existente
|
|
await holidayStore.updateHoliday(holidayData.id, holidayData);
|
|
const index = holidays.value.findIndex(holiday => holiday.id === holidayData.id);
|
|
if (index !== -1) {
|
|
holidays.value[index] = holidayData;
|
|
}
|
|
}
|
|
showNotification(`Feriado ${isEditing.value ? 'atualizado' : 'cadastrado'} com sucesso!`);
|
|
|
|
} catch (error) {
|
|
console.error('Error submitting form:', error);
|
|
} finally {
|
|
loading.value.submit = false;
|
|
dialogs.value.holiday = false;
|
|
}
|
|
};
|
|
const loadHolidays = async () => {
|
|
try {
|
|
loading.value.holidays = true;
|
|
const holidaysData = await holidayStore.fetchHolidays();
|
|
holidays.value = holidaysData.map(holiday => ({
|
|
...holiday,
|
|
type: holiday.type || 'Nacional'
|
|
}));
|
|
} catch (error) {
|
|
showNotification('Erro ao carregar dados da API', 'error');
|
|
console.error('Error fetching feriados:', error);
|
|
} finally {
|
|
loading.value.holidays = false;
|
|
}
|
|
};
|
|
|
|
|
|
const SubmitFormCreate = async (holidayData) => {
|
|
loading.value.submit = true;
|
|
try {
|
|
await holidayStore.createHoliday(holidayData);
|
|
await loadHolidays(); // 🔁 Recarrega a lista a partir da fonte oficial
|
|
showNotification('Feriado cadastrado com sucesso!');
|
|
} catch (error) {
|
|
console.error('Error submitting form:', error);
|
|
showNotification('Erro ao cadastrar feriado', 'error');
|
|
} finally {
|
|
loading.value.submit = false;
|
|
dialogs.value.createholiday = false;
|
|
}
|
|
};
|
|
|
|
// Lifecycle hooks
|
|
onMounted(async () => {
|
|
try {
|
|
loading.value.holidays = true;
|
|
const holidaysData = await holidayStore.fetchHolidays();
|
|
|
|
// Adicionar tipo para os dados existentes
|
|
holidays.value = holidaysData.map(holiday => ({
|
|
...holiday,
|
|
type: holiday.type || 'Nacional' // Adicionar campo tipo se não existir
|
|
}));
|
|
|
|
console.log("Dados recebidos da API:", holidays.value);
|
|
} catch (error) {
|
|
showNotification('Erro ao carregar dados da API', 'error');
|
|
console.error('Error fetching feriados:', error);
|
|
} finally {
|
|
loading.value.holidays = false;
|
|
}
|
|
});
|
|
onMounted(() => {
|
|
loadHolidays();
|
|
});
|
|
|
|
return {
|
|
// States
|
|
isEditing,
|
|
loading,
|
|
dialogs,
|
|
filters,
|
|
holidays,
|
|
forms,
|
|
headers,
|
|
snackbar,
|
|
page,
|
|
itemsPerPage,
|
|
itemToDelete,
|
|
selectedholiday,
|
|
|
|
// Computed
|
|
filteredholidays,
|
|
|
|
// Methods
|
|
getTypeClass,
|
|
confirmDelete,
|
|
submitForm,
|
|
SubmitFormCreate,
|
|
deleteItem,
|
|
openEditPage,
|
|
openCreateModal,
|
|
closeHolidayModal,
|
|
closeCreateModal,
|
|
showNotification
|
|
};
|
|
}
|
|
};
|
|
</script>
|
|
|
|
<style scoped>
|
|
.holiday-management {
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
padding: 20px;
|
|
font-family: Roboto, Arial, sans-serif;
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/* Estilo da seção de título */
|
|
.section-header {
|
|
margin-bottom: 24px;
|
|
}
|
|
|
|
.section-header h2 {
|
|
font-size: 24px;
|
|
font-weight: 600;
|
|
margin: 0 0 8px 0;
|
|
color: #1e293b;
|
|
}
|
|
|
|
.section-description {
|
|
color: #64748b;
|
|
font-size: 14px;
|
|
margin: 0;
|
|
}
|
|
|
|
/* Estilo do card principal */
|
|
.main-card {
|
|
border-radius: 12px;
|
|
overflow: hidden;
|
|
background-color: white;
|
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
|
|
}
|
|
|
|
.card-header {
|
|
padding: 24px;
|
|
border-bottom: 1px solid #eaecef;
|
|
}
|
|
|
|
.card-header h3 {
|
|
font-size: 18px;
|
|
font-weight: 600;
|
|
margin: 0 0 8px 0;
|
|
color: #1e293b;
|
|
}
|
|
|
|
.card-description {
|
|
color: #64748b;
|
|
font-size: 14px;
|
|
margin: 0;
|
|
}
|
|
|
|
/* Estilo da tabela */
|
|
.holiday-table {
|
|
padding: 0;
|
|
}
|
|
|
|
.holiday-table :deep(th) {
|
|
font-weight: 600;
|
|
color: #1e293b;
|
|
text-transform: none;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.holiday-table :deep(td) {
|
|
font-size: 14px;
|
|
color: #1e293b;
|
|
padding: 16px;
|
|
height: 64px;
|
|
}
|
|
|
|
/* Estilo dos chips de tipo */
|
|
.type-chip {
|
|
font-size: 12px;
|
|
font-weight: 500;
|
|
text-transform: none;
|
|
}
|
|
|
|
/* Estilo dos botões de ação */
|
|
.action-buttons {
|
|
display: flex;
|
|
gap: 8px;
|
|
justify-content: flex-end;
|
|
}
|
|
|
|
/* Estilos para os diferentes tipos de feriados */
|
|
.holiday-type-chip {
|
|
font-size: 13px;
|
|
font-weight: 500;
|
|
border-radius: 999px;
|
|
padding: 4px 12px;
|
|
text-transform: none;
|
|
}
|
|
|
|
/* Cores por tipo */
|
|
.chip-nacional {
|
|
background-color: #2563eb; /* azul */
|
|
color: white;
|
|
}
|
|
|
|
.chip-estadual {
|
|
background-color: #22c55e; /* verde */
|
|
color: white;
|
|
}
|
|
|
|
.chip-facultativo {
|
|
background-color: #f59e0b; /* laranja */
|
|
color: white;
|
|
}
|
|
|
|
.chip-municipal {
|
|
background-color: #0d9488; /* teal */
|
|
color: white;
|
|
}
|
|
|
|
.chip-outro {
|
|
background-color: #6b7280; /* cinza */
|
|
color: white;
|
|
}
|
|
|
|
</style> |