Files
nomina_ventas/resources/views/calendar/index.blade.php

584 lines
22 KiB
PHP
Executable File

@extends('layouts.app')
@section('title', 'Calendario')
@section('content')
<div class="row">
<div class="col-12">
<h2 class="mb-4">
<i class="bi bi-calendar3 text-primary"></i> Calendario de Ventas y Gastos
</h2>
</div>
</div>
<!-- Selector de mes -->
<div class="row mb-4">
<div class="col-md-6">
<div class="card">
<div class="card-body">
<form method="GET" class="d-flex gap-2 flex-wrap">
<select name="year" class="form-select" style="width: auto;" onchange="this.form.submit()">
@php($years = range(2035, 2026))
@foreach($years as $y)
<option value="{{ $y }}" {{ $year == $y ? 'selected' : '' }}>{{ $y }}</option>
@endforeach
</select>
<select name="month" class="form-select" style="width: auto;" onchange="this.form.submit()">
<option value="">Todos los meses</option>
@foreach(['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'] as $m)
<option value="{{ $m }}" {{ request('month') == $m ? 'selected' : '' }}>{{ $m }}</option>
@endforeach
</select>
</form>
</div>
</div>
</div>
<div class="col-md-6 text-end">
@if($currentMonth)
<span class="badge bg-primary fs-6">
<i class="bi bi-calendar"></i> {{ $currentMonth->name }} {{ $currentMonth->year }}
</span>
<span class="badge bg-{{ $currentMonth->status === 'open' ? 'success' : ($currentMonth->status === 'closed' ? 'warning' : 'info') }}">
{{ ucfirst($currentMonth->status) }}
</span>
@endif
</div>
</div>
@if($currentMonth)
<!-- Resumen del mes -->
<div class="row mb-4">
<div class="col-md-4">
<div class="card stat-card primary">
<div class="card-body">
<h6 class="text-muted">Ventas del Usuario</h6>
<h4 class="text-primary">${{ number_format($currentMonth->dailySales()->sum('user_sales'), 2) }}</h4>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card stat-card success">
<div class="card-body">
<h6 class="text-muted">Ventas del Sistema</h6>
<h4 class="text-success">${{ number_format($currentMonth->dailySales()->sum('system_sales'), 2) }}</h4>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card stat-card warning">
<div class="card-body">
<h6 class="text-muted">Total Gastos</h6>
<h4 class="text-warning">${{ number_format($currentMonth->expenses()->sum('amount'), 2) }}</h4>
</div>
</div>
</div>
</div>
<!-- Calendario FullCalendar -->
<div class="card mb-4">
<div class="card-body">
<div id="calendar"></div>
</div>
</div>
<!-- Leyenda -->
<div class="row mt-3">
<div class="col-12">
<div class="d-flex gap-3 flex-wrap">
<span class="badge bg-light text-dark border p-2">
<i class="bi bi-square text-secondary"></i> Sin datos
</span>
<span class="badge bg-success-subtle text-success border p-2">
<i class="bi bi-square text-success"></i> Con ventas
</span>
<span class="badge bg-danger-subtle text-danger border p-2">
<i class="bi bi-square text-danger"></i> Con gastos
</span>
<span class="badge bg-warning-subtle text-warning border p-2">
<i class="bi bi-square text-warning"></i> Con ambos
</span>
</div>
</div>
</div>
@else
<div class="alert alert-info">
<h5><i class="bi bi-info-circle"></i> No hay un mes seleccionado</h5>
<p>Selecciona un mes del dropdown o crea uno nuevo.</p>
<a href="{{ route('months.index') }}" class="btn btn-primary">Ver Meses</a>
</div>
@endif
<!-- Modal para capturar ventas -->
<div class="modal fade" id="dayModal" tabindex="-1" aria-labelledby="dayModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header bg-primary text-white">
<h5 class="modal-title" id="dayModalLabel">
<i class="bi bi-calendar-plus"></i> Capturar Día
</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form method="POST" id="dayForm">
@csrf
<div class="modal-body">
<input type="hidden" name="month_id" id="formMonthId" value="{{ $currentMonth->id ?? '' }}">
<input type="hidden" name="date" id="modalDate">
<div class="alert alert-info">
<i class="bi bi-info-circle"></i> Fecha: <strong id="modalDateDisplay"></strong>
</div>
<!-- Ventas -->
<h6 class="border-bottom pb-2 mb-3">
<i class="bi bi-currency-dollar text-success"></i> Ventas
</h6>
<div class="mb-3">
<label for="user_sales" class="form-label">Ventas del Usuario</label>
<div class="input-group">
<span class="input-group-text">$</span>
<input type="number" class="form-control" id="user_sales" name="user_sales"
value="0" step="0.01" min="0">
</div>
</div>
<div class="mb-3">
<label for="system_sales" class="form-label">Ventas del Sistema</label>
<div class="input-group">
<span class="input-group-text">$</span>
<input type="number" class="form-control" id="system_sales" name="system_sales"
value="0" step="0.01" min="0">
</div>
</div>
<!-- Gastos -->
<h6 class="border-bottom pb-2 mb-3 mt-4">
<i class="bi bi-receipt text-danger"></i> Gastos
</h6>
<div class="mb-3">
<label for="expense_description" class="form-label">Descripción del Gasto</label>
<input type="text" class="form-control" id="expense_description" name="expense_description"
placeholder="Opcional">
</div>
<div class="mb-3">
<label for="expense_amount" class="form-label">Monto del Gasto</label>
<div class="input-group">
<span class="input-group-text">$</span>
<input type="number" class="form-control" id="expense_amount" name="expense_amount"
value="0" step="0.01" min="0">
</div>
</div>
<div class="mb-3">
<label for="expense_type" class="form-label">Quincena del Gasto</label>
<select class="form-select" id="expense_type" name="expense_type">
<option value="q1">1ra Quincena (1-15)</option>
<option value="q2">2da Quincena (16-31)</option>
<option value="mensual">Gasto Mensual (se divide en 2)</option>
</select>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
<button type="submit" class="btn btn-primary">
<i class="bi bi-save"></i> Guardar
</button>
</div>
</form>
</div>
</div>
</div>
<!-- Modal para ver detalles del día -->
<div class="modal fade" id="dayDetailModal" tabindex="-1" aria-labelledby="dayDetailModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header bg-info text-white">
<h5 class="modal-title" id="dayDetailModalLabel">
<i class="bi bi-eye"></i> Detalles del Día
</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p class="mb-3"><strong>Fecha:</strong> <span id="detailDate"></span></p>
<div class="row mb-3">
<div class="col-6">
<div class="card bg-light">
<div class="card-body text-center">
<h6 class="text-muted">Ventas Usuario</h6>
<h5 class="text-success" id="detailUserSales">$0.00</h5>
</div>
</div>
</div>
<div class="col-6">
<div class="card bg-light">
<div class="card-body text-center">
<h6 class="text-muted">Ventas Sistema</h6>
<h5 class="text-info" id="detailSystemSales">$0.00</h5>
</div>
</div>
</div>
</div>
<div class="card bg-light">
<div class="card-body">
<h6 class="text-muted">Gastos del Día</h6>
<div id="detailExpenses">
<p class="text-muted mb-0">No hay gastos registrados</p>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cerrar</button>
<button type="button" class="btn btn-primary" id="editDayBtn">
<i class="bi bi-pencil"></i> Editar
</button>
</div>
</div>
</div>
</div>
@endsection
@push('scripts')
<script>
document.addEventListener('DOMContentLoaded', function() {
// Data for calendar events
const dailySales = @json($dailySales ?? []);
const expenses = @json($expenses ?? []);
const year = {{ $year }};
const currentMonthId = {{ $currentMonth->id ?? 0 }};
console.log('Month ID:', currentMonthId);
console.log('DailySales raw:', dailySales);
console.log('DailySales type:', typeof dailySales);
console.log('Expenses:', expenses);
// Map data to calendar events
const events = [];
// Add sales events - iterate over object values
Object.keys(dailySales).forEach(date => {
const saleData = dailySales[date];
if (saleData && saleData.user_sales > 0) {
events.push({
title: 'V: $' + parseFloat(saleData.user_sales).toLocaleString(),
start: date,
className: 'bg-success',
extendedProps: {
type: 'sale',
user_sales: saleData.user_sales,
system_sales: saleData.system_sales
}
});
}
});
// Add expense events
Object.keys(expenses).forEach(date => {
const expData = expenses[date];
if (expData && expData.amount > 0) {
events.push({
title: 'G: $' + parseFloat(expData.amount).toLocaleString(),
start: date,
className: 'bg-danger',
extendedProps: {
type: 'expense',
amount: expData.amount,
description: expData.description
}
});
}
});
console.log('Events:', events);
const calendarEl = document.getElementById('calendar');
// FullCalendar initialization
const calendar = new FullCalendar.Calendar(calendarEl, {
initialView: 'dayGridMonth',
initialDate: year + '-04-01',
locale: 'es',
headerToolbar: {
left: 'prev,next today',
center: 'title',
right: 'dayGridMonth,listMonth'
},
buttonText: {
today: 'Hoy',
month: 'Mes',
list: 'Lista'
},
dayMaxEvents: true,
eventDisplay: 'block',
events: events,
dateClick: function(info) {
// Verify month exists
const monthId = {{ $currentMonth->id ?? 0 }};
if (!monthId || monthId === 0) {
console.log('No hay mes seleccionado');
return;
}
openDayModal(info.dateStr);
},
eventClick: function(info) {
showDayDetails(info.event.startStr);
},
eventDidMount: function(info) {
// Add custom styling based on data
const date = info.event.startStr;
if (dailySales[date] && expenses[date]) {
info.el.style.background = 'linear-gradient(135deg, #f39c12 0%, #e74c3c 100%)';
} else if (dailySales[date]) {
info.el.style.background = 'linear-gradient(135deg, #27ae60 0%, #2ecc71 100%)';
} else if (expenses[date]) {
info.el.style.background = 'linear-gradient(135deg, #e74c3c 0%, #c0392b 100%)';
}
}
});
calendar.render();
// Initialize selected date variable
window.selectedDate = null;
// Functions
window.openDayModal = function(dateStr) {
const modalEl = document.getElementById('dayModal');
if (!modalEl) return;
const modal = new bootstrap.Modal(modalEl);
const modalDateEl = document.getElementById('modalDate');
const modalDateDisplayEl = document.getElementById('modalDateDisplay');
if (modalDateEl && modalDateDisplayEl) {
modalDateEl.value = dateStr;
modalDateDisplayEl.textContent = formatDate(dateStr);
}
// Check if there's existing data
const userSalesEl = document.getElementById('user_sales');
const systemSalesEl = document.getElementById('system_sales');
const expenseDescEl = document.getElementById('expense_description');
const expenseAmountEl = document.getElementById('expense_amount');
if (userSalesEl && systemSalesEl) {
// Buscar coincidencia parcial
let saleData = dailySales[dateStr];
if (!saleData) {
// Buscar en claves
for (const key in dailySales) {
if (key.includes(dateStr) || dateStr.includes(key)) {
saleData = dailySales[key];
break;
}
}
}
if (saleData) {
userSalesEl.value = saleData.user_sales;
systemSalesEl.value = saleData.system_sales;
} else {
userSalesEl.value = 0;
systemSalesEl.value = 0;
}
}
if (expenseDescEl && expenseAmountEl) {
let expData = expenses[dateStr];
if (!expData) {
for (const key in expenses) {
if (key.includes(dateStr) || dateStr.includes(key)) {
expData = expenses[key];
break;
}
}
}
if (expData) {
expenseDescEl.value = expData.description || '';
expenseAmountEl.value = expData.amount;
} else {
expenseDescEl.value = '';
expenseAmountEl.value = 0;
}
}
modal.show();
};
window.showDayDetails = function(dateStr) {
const modal = new bootstrap.Modal(document.getElementById('dayDetailModal'));
document.getElementById('detailDate').textContent = formatDate(dateStr);
// Buscar en todas las claves
let foundKey = null;
for (const key in dailySales) {
if (key.includes(dateStr) || dateStr.includes(key)) {
foundKey = key;
break;
}
}
if (foundKey) {
// Found
document.getElementById('detailUserSales').textContent = '$' + dailySales[foundKey].user_sales.toLocaleString();
document.getElementById('detailSystemSales').textContent = '$' + dailySales[foundKey].system_sales.toLocaleString();
} else {
document.getElementById('detailUserSales').textContent = '$0.00';
document.getElementById('detailSystemSales').textContent = '$0.00';
}
if (expenses[dateStr]) {
document.getElementById('detailExpenses').innerHTML = `
<p class="mb-1"><strong>${expenses[dateStr].description || 'Gasto'}</strong></p>
<h5 class="text-danger mb-0">$${expenses[dateStr].amount.toLocaleString()}</h5>
`;
} else {
document.getElementById('detailExpenses').innerHTML = '<p class="text-muted mb-0">No hay gastos registrados</p>';
}
// Set up edit button
document.getElementById('editDayBtn').onclick = function() {
window.selectedDate = dateStr;
modal.hide();
setTimeout(() => openDayModal(dateStr), 300);
};
modal.show();
};
function formatDate(dateStr) {
if (!dateStr) return 'Sin fecha';
const parts = dateStr.split('-');
if (parts.length === 3) {
const year = parseInt(parts[0]);
const month = parseInt(parts[1]) - 1;
const day = parseInt(parts[2]);
const date = new Date(year, month, day);
return date.toLocaleDateString('es-ES', { year: 'numeric', month: 'long', day: 'numeric' });
}
return dateStr;
}
// Form submission handler
document.getElementById('dayForm').addEventListener('submit', function(e) {
e.preventDefault();
// Get values directly from DOM elements
const monthIdEl = document.getElementById('formMonthId');
const dateEl = document.getElementById('modalDate');
const userSalesEl = document.getElementById('user_sales');
const systemSalesEl = document.getElementById('system_sales');
const expenseAmountEl = document.getElementById('expense_amount');
const expenseDescEl = document.getElementById('expense_description');
const expenseTypeEl = document.getElementById('expense_type');
let monthId = monthIdEl ? monthIdEl.value : '';
let date = dateEl ? dateEl.value : '';
// Use window.selectedDate as fallback
if (!date && window.selectedDate) date = window.selectedDate;
// Try fallback
if (!monthId) monthId = '{{ $currentMonth->id ?? 0 }}';
const userSales = userSalesEl ? (parseFloat(userSalesEl.value) || 0) : 0;
const systemSales = systemSalesEl ? (parseFloat(systemSalesEl.value) || 0) : 0;
const expenseAmount = expenseAmountEl ? (parseFloat(expenseAmountEl.value) || 0) : 0;
const expenseDesc = expenseDescEl ? expenseDescEl.value : '';
const expenseType = expenseTypeEl ? expenseTypeEl.value : 'q1';
// Save data
if (!monthId || !date) {
return;
}
const data = {
month_id: monthId,
date: date,
user_sales: userSales,
system_sales: systemSales,
expense_description: expenseDesc,
expense_amount: expenseAmount,
expense_type: expenseType
};
// Send data via fetch
fetch('{{ route("calendar.day.store") }}', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': '{{ csrf_token() }}'
},
body: JSON.stringify(data)
})
.then(response => response.json())
.then(data => {
if (data.success) {
window.location.reload();
} else {
console.error('Error saving:', data.message);
}
})
.catch(error => {
console.error('Error:', error);
});
});
});
</script>
@endpush
@push('styles')
<style>
.fc {
font-size: 0.9rem;
}
.fc .fc-toolbar-title {
font-size: 1.2rem;
}
.fc .fc-daygrid-day-number {
padding: 8px;
}
.fc-event {
padding: 2px 4px;
font-size: 0.75rem;
border-radius: 4px;
margin: 2px 0;
}
.fc .fc-col-header-cell-cushion {
padding: 8px;
font-weight: 600;
}
.fc-daygrid-day.has-data {
background: linear-gradient(135deg, #e8f5e9 0%, #c8e6c9 100%);
}
.fc-daygrid-day.has-expense {
background: linear-gradient(135deg, #ffebee 0%, #ffcdd2 100%);
}
.fc-daygrid-day.has-both {
background: linear-gradient(135deg, #fff3e0 0%, #ffe0b2 100%);
}
.day-cell-clickable {
cursor: pointer;
transition: background 0.2s;
}
.day-cell-clickable:hover {
background: #e3f2fd !important;
}
</style>
@endpush