Initial commit: Sistema de comisiones y gastos personales
This commit is contained in:
242
public/js/app.js
Executable file
242
public/js/app.js
Executable file
@@ -0,0 +1,242 @@
|
||||
/**
|
||||
* Nómina Pegaso - Global JavaScript Functions
|
||||
*/
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Initialize all components
|
||||
initTooltips();
|
||||
initAnimations();
|
||||
initFormValidation();
|
||||
initDeleteConfirmations();
|
||||
|
||||
// Auto-dismiss alerts after 5 seconds
|
||||
setTimeout(function() {
|
||||
const alerts = document.querySelectorAll('.alert:not(.alert-permanent)');
|
||||
alerts.forEach(function(alert) {
|
||||
const bsAlert = new bootstrap.Alert(alert);
|
||||
bsAlert.close();
|
||||
});
|
||||
}, 5000);
|
||||
});
|
||||
|
||||
/**
|
||||
* Initialize Bootstrap tooltips
|
||||
*/
|
||||
function initTooltips() {
|
||||
const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
|
||||
tooltipTriggerList.map(function(tooltipTriggerEl) {
|
||||
return new bootstrap.Tooltip(tooltipTriggerEl);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add subtle animations on page load
|
||||
*/
|
||||
function initAnimations() {
|
||||
// Fade in cards
|
||||
const cards = document.querySelectorAll('.card');
|
||||
cards.forEach(function(card, index) {
|
||||
card.style.opacity = '0';
|
||||
card.style.transform = 'translateY(20px)';
|
||||
card.style.transition = 'opacity 0.3s ease, transform 0.3s ease';
|
||||
|
||||
setTimeout(function() {
|
||||
card.style.opacity = '1';
|
||||
card.style.transform = 'translateY(0)';
|
||||
}, index * 100);
|
||||
});
|
||||
|
||||
// Animate stat numbers
|
||||
const statNumbers = document.querySelectorAll('.stat-number');
|
||||
statNumbers.forEach(function(el) {
|
||||
const target = parseFloat(el.getAttribute('data-target'));
|
||||
if (target) {
|
||||
animateValue(el, 0, target, 1000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Animate number counting
|
||||
*/
|
||||
function animateValue(el, start, end, duration) {
|
||||
let startTimestamp = null;
|
||||
const step = function(timestamp) {
|
||||
if (!startTimestamp) startTimestamp = timestamp;
|
||||
const progress = Math.min((timestamp - startTimestamp) / duration, 1);
|
||||
const value = progress * (end - start) + start;
|
||||
el.textContent = formatCurrency(value);
|
||||
if (progress < 1) {
|
||||
window.requestAnimationFrame(step);
|
||||
}
|
||||
};
|
||||
window.requestAnimationFrame(step);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format currency
|
||||
*/
|
||||
function formatCurrency(amount) {
|
||||
return '$' + parseFloat(amount).toLocaleString('es-ES', {
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize form validation feedback
|
||||
*/
|
||||
function initFormValidation() {
|
||||
const forms = document.querySelectorAll('.needs-validation');
|
||||
|
||||
forms.forEach(function(form) {
|
||||
form.addEventListener('submit', function(event) {
|
||||
if (!form.checkValidity()) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
form.classList.add('was-validated');
|
||||
}, false);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add confirmation to delete buttons
|
||||
*/
|
||||
function initDeleteConfirmations() {
|
||||
const deleteButtons = document.querySelectorAll('.btn-delete');
|
||||
|
||||
deleteButtons.forEach(function(btn) {
|
||||
btn.addEventListener('click', function(e) {
|
||||
if (!confirm('¿Estás seguro de que deseas eliminar este elemento?')) {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Format date for display
|
||||
*/
|
||||
window.formatDate = function(dateStr, format = 'short') {
|
||||
if (!dateStr) return '';
|
||||
|
||||
const date = new Date(dateStr);
|
||||
|
||||
if (format === 'short') {
|
||||
return date.toLocaleDateString('es-ES');
|
||||
}
|
||||
|
||||
return date.toLocaleDateString('es-ES', {
|
||||
weekday: 'long',
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Show toast notification
|
||||
*/
|
||||
window.showToast = function(message, type = 'info') {
|
||||
const toastContainer = document.querySelector('.toast-container');
|
||||
|
||||
if (!toastContainer) return;
|
||||
|
||||
const toast = document.createElement('div');
|
||||
toast.className = `toast show align-items-center text-white bg-${type} border-0`;
|
||||
toast.setAttribute('role', 'alert');
|
||||
toast.setAttribute('aria-live', 'assertive');
|
||||
toast.setAttribute('aria-atomic', 'true');
|
||||
|
||||
const icons = {
|
||||
success: 'bi-check-circle',
|
||||
error: 'bi-exclamation-circle',
|
||||
warning: 'bi-exclamation-triangle',
|
||||
info: 'bi-info-circle'
|
||||
};
|
||||
|
||||
toast.innerHTML = `
|
||||
<div class="d-flex">
|
||||
<div class="toast-body">
|
||||
<i class="${icons[type] || 'bi-info-circle'} me-2"></i>
|
||||
${message}
|
||||
</div>
|
||||
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast"></button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
toastContainer.appendChild(toast);
|
||||
|
||||
setTimeout(function() {
|
||||
toast.remove();
|
||||
}, 5000);
|
||||
};
|
||||
|
||||
/**
|
||||
* Mobile sidebar functionality
|
||||
*/
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const mobileNavBtn = document.getElementById('mobileNavBtn');
|
||||
const mobileNavBtn2 = document.getElementById('mobileNavBtn2');
|
||||
const sidebar = document.getElementById('sidebar');
|
||||
const sidebarOverlay = document.getElementById('sidebarOverlay');
|
||||
|
||||
function toggleSidebar() {
|
||||
if (sidebar && sidebarOverlay) {
|
||||
sidebar.classList.toggle('show');
|
||||
sidebarOverlay.classList.toggle('show');
|
||||
}
|
||||
}
|
||||
|
||||
if (mobileNavBtn) {
|
||||
mobileNavBtn.addEventListener('click', toggleSidebar);
|
||||
}
|
||||
|
||||
if (mobileNavBtn2) {
|
||||
mobileNavBtn2.addEventListener('click', toggleSidebar);
|
||||
}
|
||||
|
||||
if (sidebarOverlay) {
|
||||
sidebarOverlay.addEventListener('click', toggleSidebar);
|
||||
}
|
||||
|
||||
// Close sidebar on escape key
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape' && sidebar && sidebar.classList.contains('show')) {
|
||||
toggleSidebar();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Helper to parse numeric input
|
||||
*/
|
||||
window.parseNumber = function(value, decimals = 2) {
|
||||
const parsed = parseFloat(value);
|
||||
return isNaN(parsed) ? 0 : parseFloat(parsed.toFixed(decimals));
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculate percentage
|
||||
*/
|
||||
window.calculatePercentage = function(value, total) {
|
||||
if (total === 0) return 0;
|
||||
return ((value / total) * 100).toFixed(2);
|
||||
};
|
||||
|
||||
/**
|
||||
* Debounce function for search inputs
|
||||
*/
|
||||
window.debounce = function(func, wait) {
|
||||
let timeout;
|
||||
return function executedFunction(...args) {
|
||||
const later = () => {
|
||||
clearTimeout(timeout);
|
||||
func(...args);
|
||||
};
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(later, wait);
|
||||
};
|
||||
};
|
||||
138
public/js/calendar.js
Executable file
138
public/js/calendar.js
Executable file
@@ -0,0 +1,138 @@
|
||||
/**
|
||||
* Calendar JavaScript - FullCalendar Configuration
|
||||
* Nómina Pegaso
|
||||
*/
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Calendar initialization is now handled in the blade template
|
||||
// This file provides additional utility functions
|
||||
|
||||
/**
|
||||
* Format currency values
|
||||
*/
|
||||
window.formatCurrency = function(amount) {
|
||||
return '$' + parseFloat(amount).toLocaleString('es-ES', {
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Format date for display
|
||||
*/
|
||||
window.formatDate = function(dateStr, format = 'long') {
|
||||
const date = new Date(dateStr + 'T00:00:00');
|
||||
|
||||
if (format === 'short') {
|
||||
return date.toLocaleDateString('es-ES', {
|
||||
day: '2-digit',
|
||||
month: '2-digit',
|
||||
year: 'numeric'
|
||||
});
|
||||
}
|
||||
|
||||
return date.toLocaleDateString('es-ES', {
|
||||
weekday: 'long',
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Show notification toast
|
||||
*/
|
||||
window.showToast = function(message, type = 'info') {
|
||||
const toast = document.createElement('div');
|
||||
toast.className = `toast show align-items-center text-white bg-${type} border-0`;
|
||||
toast.setAttribute('role', 'alert');
|
||||
toast.setAttribute('aria-live', 'assertive');
|
||||
toast.setAttribute('aria-atomic', 'true');
|
||||
|
||||
toast.innerHTML = `
|
||||
<div class="d-flex">
|
||||
<div class="toast-body">${message}</div>
|
||||
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast"></button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const container = document.querySelector('.toast-container') || createToastContainer();
|
||||
container.appendChild(toast);
|
||||
|
||||
setTimeout(() => {
|
||||
toast.remove();
|
||||
}, 5000);
|
||||
};
|
||||
|
||||
function createToastContainer() {
|
||||
const container = document.createElement('div');
|
||||
container.className = 'toast-container';
|
||||
document.body.appendChild(container);
|
||||
return container;
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirm action with modal
|
||||
*/
|
||||
window.confirmAction = function(message, onConfirm) {
|
||||
if (confirm(message)) {
|
||||
onConfirm();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Validate date is within current month
|
||||
*/
|
||||
window.validateDateInMonth = function(dateInput, monthId) {
|
||||
// This can be extended to validate against specific month
|
||||
const selectedDate = new Date(dateInput.value);
|
||||
const now = new Date();
|
||||
|
||||
return selectedDate <= now;
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculate difference between two values
|
||||
*/
|
||||
window.calculateDifference = function(value1, value2) {
|
||||
const diff = parseFloat(value1) - parseFloat(value2);
|
||||
const sign = diff >= 0 ? '+' : '';
|
||||
return sign + formatCurrency(diff);
|
||||
};
|
||||
|
||||
/**
|
||||
* Update summary cards dynamically
|
||||
*/
|
||||
window.updateSummaryCard = function(cardId, value) {
|
||||
const card = document.getElementById(cardId);
|
||||
if (card) {
|
||||
card.textContent = formatCurrency(value);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Mobile sidebar toggle
|
||||
*/
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const mobileNavBtn = document.getElementById('mobileNavBtn');
|
||||
const mobileNavBtn2 = document.getElementById('mobileNavBtn2');
|
||||
const sidebar = document.getElementById('sidebar');
|
||||
const sidebarOverlay = document.getElementById('sidebarOverlay');
|
||||
|
||||
function toggleSidebar() {
|
||||
sidebar.classList.toggle('show');
|
||||
sidebarOverlay.classList.toggle('show');
|
||||
}
|
||||
|
||||
if (mobileNavBtn) mobileNavBtn.addEventListener('click', toggleSidebar);
|
||||
if (mobileNavBtn2) mobileNavBtn2.addEventListener('click', toggleSidebar);
|
||||
if (sidebarOverlay) sidebarOverlay.addEventListener('click', toggleSidebar);
|
||||
|
||||
// Close sidebar on escape key
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape' && sidebar.classList.contains('show')) {
|
||||
toggleSidebar();
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user