Initial commit: Sistema de comisiones y gastos personales
This commit is contained in:
584
resources/views/calendar/index.blade.php
Executable file
584
resources/views/calendar/index.blade.php
Executable file
@@ -0,0 +1,584 @@
|
||||
@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(date('Y'), date('Y') - 5))
|
||||
@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
|
||||
Reference in New Issue
Block a user