324 lines
12 KiB
PHP
324 lines
12 KiB
PHP
<?php
|
|
require_once 'layout_header.php';
|
|
require_once 'db/config.php';
|
|
|
|
// --- Data Fetching ---
|
|
$month = isset($_GET['month']) ? $_GET['month'] : date('m');
|
|
$year = isset($_GET['year']) ? $_GET['year'] : date('Y');
|
|
$days_in_month = cal_days_in_month(CAL_GREGORIAN, $month, $year);
|
|
$provincias = ['LIMA', 'AREQUIPA', 'TRUJILLO', 'ICA', 'CUSCO', 'CAJAMARCA', 'CHICLAYO', 'PIURA'];
|
|
|
|
$pdo = db();
|
|
$stmt = $pdo->prepare("SELECT fecha, provincia, monto, estado FROM liquidaciones_provincia WHERE MONTH(fecha) = ? AND YEAR(fecha) = ?");
|
|
$stmt->execute([$month, $year]);
|
|
$data = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
$liquidaciones = [];
|
|
foreach ($data as $row) {
|
|
$liquidaciones[$row['fecha']][$row['provincia']] = [
|
|
'monto' => $row['monto'],
|
|
'estado' => $row['estado']
|
|
];
|
|
}
|
|
?>
|
|
|
|
<!-- --- STYLES --- -->
|
|
<style>
|
|
.table thead th {
|
|
background-color: #337ab7;
|
|
color: #ffffff;
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 10;
|
|
}
|
|
.table tfoot th {
|
|
position: sticky;
|
|
bottom: 0;
|
|
background-color: #f2f2f2;
|
|
z-index: 10;
|
|
font-weight: bold;
|
|
}
|
|
.editable-cell {
|
|
cursor: pointer;
|
|
position: relative;
|
|
}
|
|
.editable-cell:hover {
|
|
background-color: #f5f5f5;
|
|
}
|
|
.editable-cell input, .editable-cell select {
|
|
width: 100%;
|
|
box-sizing: border-box;
|
|
border: 1px solid #ccc;
|
|
border-radius: 4px;
|
|
}
|
|
.badge {
|
|
display: inline-block;
|
|
padding: 0.5em 0.75em;
|
|
font-size: 90%;
|
|
font-weight: 700;
|
|
line-height: 1;
|
|
text-align: center;
|
|
white-space: nowrap;
|
|
vertical-align: baseline;
|
|
border-radius: 0.25rem;
|
|
width: 100%;
|
|
box-sizing: border-box;
|
|
}
|
|
.badge.estado-pendiente {
|
|
background-color: #fff3cd;
|
|
color: #664d03;
|
|
}
|
|
.badge.estado-abonado {
|
|
background-color: #d4edda;
|
|
color: #0f5132;
|
|
}
|
|
</style>
|
|
|
|
<!-- --- HTML Structure --- -->
|
|
<div class="container-fluid mt-4">
|
|
<h2>Liquidaciones Provincia</h2>
|
|
<p>Haz <strong>un solo clic</strong> en una celda de "Monto" o "Estado" para editarla. Presiona "Enter" o haz clic fuera para guardar.</p>
|
|
|
|
<form method="GET" action="liquidaciones_provincia.php" class="form-inline mb-4">
|
|
<div class="form-group mr-2">
|
|
<label for="month" class="mr-2">Mes:</label>
|
|
<select name="month" id="month" class="form-control">
|
|
<?php for ($m = 1; $m <= 12; $m++): ?>
|
|
<option value="<?php echo str_pad($m, 2, '0', STR_PAD_LEFT); ?>" <?php echo $m == $month ? 'selected' : ''; ?>>
|
|
<?php echo DateTime::createFromFormat('!m', $m)->format('F'); ?>
|
|
</option>
|
|
<?php endfor; ?>
|
|
</select>
|
|
</div>
|
|
<div class="form-group mr-2">
|
|
<label for="year" class="mr-2">Año:</label>
|
|
<select name="year" id="year" class="form-control">
|
|
<?php for ($y = date('Y'); $y >= date('Y') - 5; $y--): ?>
|
|
<option value="<?php echo $y; ?>" <?php echo $y == $year ? 'selected' : ''; ?>><?php echo $y; ?></option>
|
|
<?php endfor; ?>
|
|
</select>
|
|
</div>
|
|
<button type="submit" class="btn btn-primary">Filtrar</button>
|
|
</form>
|
|
|
|
<div class="table-responsive" style="max-height: 75vh; overflow-y: auto; overflow-x: scroll;">
|
|
<table class="table table-bordered table-striped table-hover">
|
|
<thead>
|
|
<tr>
|
|
<th>Fecha</th>
|
|
<?php foreach ($provincias as $provincia): ?>
|
|
<th><?php echo htmlspecialchars($provincia); ?></th>
|
|
<th>ESTADO</th>
|
|
<?php endforeach; ?>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="liquidaciones-tbody">
|
|
<?php for ($day = 1; $day <= $days_in_month; $day++): ?>
|
|
<?php $date = date('Y-m-d', strtotime("$year-$month-$day")); ?>
|
|
<tr data-date="<?php echo $date; ?>">
|
|
<td><?php echo $date; ?></td>
|
|
<?php foreach ($provincias as $provincia): ?>
|
|
<?php
|
|
$monto = isset($liquidaciones[$date][$provincia]['monto']) ? number_format($liquidaciones[$date][$provincia]['monto'], 2, '.', '') : '0.00';
|
|
$estado = isset($liquidaciones[$date][$provincia]['estado']) ? htmlspecialchars($liquidaciones[$date][$provincia]['estado']) : '';
|
|
$estado_class = '';
|
|
if ($estado === 'PENDIENTE') {
|
|
$estado_class = 'estado-pendiente';
|
|
} elseif ($estado === 'ABONADO') {
|
|
$estado_class = 'estado-abonado';
|
|
}
|
|
?>
|
|
<td class="editable-cell" data-provincia="<?php echo htmlspecialchars($provincia); ?>" data-column="monto"><?php echo $monto; ?></td>
|
|
<td class="editable-cell" data-provincia="<?php echo htmlspecialchars($provincia); ?>" data-column="estado">
|
|
<span class="badge <?php echo $estado_class; ?>"><?php echo $estado; ?></span>
|
|
</td>
|
|
<?php endforeach; ?>
|
|
</tr>
|
|
<?php endfor; ?>
|
|
</tbody>
|
|
<tfoot>
|
|
<tr>
|
|
<th>TOTAL</th>
|
|
<?php foreach ($provincias as $provincia): ?>
|
|
<th data-total-column="<?php echo htmlspecialchars($provincia); ?>">0.00</th>
|
|
<th></th>
|
|
<?php endforeach; ?>
|
|
</tr>
|
|
</tfoot>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- --- JAVASCRIPT --- -->
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
const tableBody = document.getElementById('liquidaciones-tbody');
|
|
let activeCell = null;
|
|
|
|
function updateBadge(cell, estado) {
|
|
let badge = cell.querySelector('.badge');
|
|
if (!badge) {
|
|
badge = document.createElement('span');
|
|
badge.className = 'badge';
|
|
cell.innerHTML = '';
|
|
cell.appendChild(badge);
|
|
}
|
|
badge.classList.remove('estado-pendiente', 'estado-abonado');
|
|
if (estado === 'PENDIENTE') {
|
|
badge.classList.add('estado-pendiente');
|
|
} else if (estado === 'ABONADO') {
|
|
badge.classList.add('estado-abonado');
|
|
}
|
|
badge.textContent = estado;
|
|
}
|
|
|
|
function calculateTotals() {
|
|
const totals = {};
|
|
const provincias = <?php echo json_encode($provincias); ?>;
|
|
provincias.forEach(p => { totals[p] = 0; });
|
|
|
|
tableBody.querySelectorAll('tr').forEach(row => {
|
|
provincias.forEach(provincia => {
|
|
const cell = row.querySelector(`td[data-provincia="${provincia}"][data-column="monto"]`);
|
|
if (cell) {
|
|
totals[provincia] += parseFloat(cell.textContent.replace(/,/g, '')) || 0;
|
|
}
|
|
});
|
|
});
|
|
|
|
provincias.forEach(provincia => {
|
|
const totalCell = document.querySelector(`tfoot th[data-total-column="${provincia}"]`);
|
|
if (totalCell) {
|
|
totalCell.textContent = totals[provincia].toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
|
}
|
|
});
|
|
}
|
|
|
|
function saveCell(control) {
|
|
const cell = control.parentElement;
|
|
const originalValue = cell.getAttribute('data-original-value') || '';
|
|
const newValue = control.value.trim();
|
|
|
|
// Restore cell structure and optimistically update UI
|
|
cell.innerHTML = '';
|
|
if (cell.dataset.column === 'monto') {
|
|
cell.textContent = (parseFloat(newValue.replace(/,/g, '')) || 0).toFixed(2);
|
|
} else {
|
|
updateBadge(cell, newValue);
|
|
}
|
|
activeCell = null;
|
|
|
|
if (newValue === originalValue) {
|
|
if (cell.dataset.column === 'monto') calculateTotals();
|
|
return;
|
|
}
|
|
|
|
const dataToSend = {
|
|
fecha: cell.closest('tr').dataset.date,
|
|
provincia: cell.dataset.provincia,
|
|
columna: cell.dataset.column,
|
|
valor: newValue
|
|
};
|
|
|
|
fetch('save_liquidaciones_provincia.php', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(dataToSend)
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (!data.success) {
|
|
alert('Error al guardar: ' + (data.message || 'Error desconocido.'));
|
|
// Revert UI
|
|
if (cell.dataset.column === 'monto') {
|
|
cell.textContent = originalValue;
|
|
} else {
|
|
updateBadge(cell, originalValue);
|
|
}
|
|
}
|
|
})
|
|
.catch(err => {
|
|
alert('Error de conexión. No se pudo guardar el cambio.');
|
|
// Revert UI
|
|
if (cell.dataset.column === 'monto') {
|
|
cell.textContent = originalValue;
|
|
} else {
|
|
updateBadge(cell, originalValue);
|
|
}
|
|
})
|
|
.finally(() => {
|
|
if (cell.dataset.column === 'monto') calculateTotals();
|
|
});
|
|
}
|
|
|
|
function editCell(cell) {
|
|
if (cell === activeCell) return;
|
|
if (activeCell) {
|
|
const control = activeCell.querySelector('input, select');
|
|
if (control) saveCell(control);
|
|
}
|
|
|
|
activeCell = cell;
|
|
const originalValue = cell.dataset.column === 'estado'
|
|
? (cell.querySelector('.badge') ? cell.querySelector('.badge').textContent.trim() : '')
|
|
: cell.textContent.trim();
|
|
|
|
cell.setAttribute('data-original-value', originalValue);
|
|
cell.innerHTML = '';
|
|
|
|
let control;
|
|
const columnType = cell.dataset.column;
|
|
|
|
if (columnType === 'estado') {
|
|
control = document.createElement('select');
|
|
control.className = 'form-control';
|
|
['', 'PENDIENTE', 'ABONADO'].forEach(opt => {
|
|
const option = document.createElement('option');
|
|
option.value = opt;
|
|
option.textContent = opt || '-- Seleccionar --';
|
|
if (opt === originalValue) option.selected = true;
|
|
control.appendChild(option);
|
|
});
|
|
control.addEventListener('change', () => saveCell(control));
|
|
} else {
|
|
control = document.createElement('input');
|
|
control.type = 'text';
|
|
control.value = originalValue;
|
|
control.className = 'form-control';
|
|
}
|
|
|
|
cell.appendChild(control);
|
|
control.focus();
|
|
if (control.select) control.select();
|
|
|
|
control.addEventListener('blur', () => saveCell(control));
|
|
control.addEventListener('keydown', (e) => {
|
|
if (e.key === 'Enter') {
|
|
e.preventDefault();
|
|
saveCell(control);
|
|
} else if (e.key === 'Escape') {
|
|
cell.innerHTML = '';
|
|
if (columnType === 'estado') {
|
|
updateBadge(cell, originalValue);
|
|
} else {
|
|
cell.textContent = originalValue;
|
|
}
|
|
activeCell = null;
|
|
}
|
|
});
|
|
}
|
|
|
|
tableBody.addEventListener('click', function(e) {
|
|
const target = e.target.closest('.editable-cell');
|
|
if (target) {
|
|
editCell(target);
|
|
}
|
|
});
|
|
|
|
calculateTotals();
|
|
});
|
|
</script>
|
|
|
|
<?php
|
|
require_once 'layout_footer.php';
|
|
?>
|