feat: Botón Guardar Todo en conceptos y mejoras Docker

Cambios realizados:
- concept_view.php: Agregado botón 'Guardar Todo' arriba y abajo de la tabla, eliminado botón individual por fila
- dashboard.php: Agregado endpoint save_all_concept_payments para guardar múltiples pagos
- docker-entrypoint.sh: Corregidos permisos de volúmenes para ZimaOS/CasaOS (cambia dueño a www-data)
- docker/Dockerfile: Corregida ruta del entrypoint
- build-and-push.sh: Script interactivo para crear imagen Docker con opción de caché/sin caché

Los cambios permiten guardar todos los pagos de conceptos de una sola vez y mejoran la compatibilidad con despliegues en ZimaOS.
This commit is contained in:
2026-02-13 23:09:45 -06:00
parent 8f2f04951f
commit 23b527d3f5
27 changed files with 1517 additions and 19 deletions

View File

@@ -70,6 +70,13 @@
<small>Esto actualizará todos los pagos a $0.00</small>
</div>
<?php endif; ?>
<?php if (Auth::isCapturist()): ?>
<div class="mb-3">
<button id="save-all-payments-btn-top" class="btn btn-success" onclick="saveAllConceptPayments()">
<i class="bi bi-check-all"></i> Guardar Todo
</button>
</div>
<?php endif; ?>
<div class="table-responsive">
<table class="table table-sm table-hover">
<thead>
@@ -78,9 +85,6 @@
<th>Propietario</th>
<th>Monto</th>
<th>Fecha de Pago</th>
<?php if (Auth::isCapturist()): ?>
<th>Acciones</th>
<?php endif; ?>
</tr>
</thead>
<tbody>
@@ -104,18 +108,18 @@
<?= $payment['payment_date'] ? date('d/m/Y', strtotime($payment['payment_date'])) : '-' ?>
<?php endif; ?>
</td>
<?php if (Auth::isCapturist()): ?>
<td>
<button class="btn btn-sm btn-primary" onclick="saveConceptPayment(this)">
<i class="bi bi-check"></i>
</button>
</td>
<?php endif; ?>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php if (Auth::isCapturist()): ?>
<div class="mt-3">
<button id="save-all-payments-btn-bottom" class="btn btn-success" onclick="saveAllConceptPayments()">
<i class="bi bi-check-all"></i> Guardar Todo
</button>
</div>
<?php endif; ?>
<?php endif; ?>
</div>
</div>
@@ -394,12 +398,99 @@ window.saveConceptPayment = function(btn) {
Swal.fire('Error', data.message, 'error');
}
})
.catch(function(error) {
.catch(function(error) {
console.error('Save payment error:', error);
Swal.fire('Error', 'Ocurrió un error: ' + error.message, 'error');
});
};
window.saveAllConceptPayments = function() {
const rows = document.querySelectorAll('tbody tr[data-payment-id]');
const payments = [];
let hasChanges = false;
rows.forEach(function(row) {
const cell = row.querySelector('.payment-cell');
const dateInput = row.querySelector('.payment-date-input');
const houseId = row.dataset.houseId;
const originalAmount = parseFloat(cell.dataset.amount) || 0;
const value = cell.textContent.trim();
const amount = parseFloat(value.replace(/[^0-9.-]+/g, '')) || 0;
const paymentDate = dateInput ? dateInput.value : null;
const originalDate = cell.dataset.paymentDate || '';
// Solo incluir si hay cambios
if (amount !== originalAmount || paymentDate !== originalDate) {
hasChanges = true;
payments.push({
house_id: houseId,
amount: amount,
payment_date: paymentDate
});
}
});
if (!hasChanges) {
Swal.fire('Info', 'No hay cambios para guardar', 'info');
return;
}
// Deshabilitar botones mientras se guarda
const btnTop = document.getElementById('save-all-payments-btn-top');
const btnBottom = document.getElementById('save-all-payments-btn-bottom');
if (btnTop) {
btnTop.disabled = true;
btnTop.innerHTML = '<span class="spinner-border spinner-border-sm"></span> Guardando...';
}
if (btnBottom) {
btnBottom.disabled = true;
btnBottom.innerHTML = '<span class="spinner-border spinner-border-sm"></span> Guardando...';
}
fetch('/dashboard.php?page=concept_view_actions&action=save_all_concept_payments', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
concept_id: '<?= $concept["id"] ?>',
payments: payments
})
})
.then(function(r) {
return r.json();
})
.then(function(data) {
if (data.success) {
Swal.fire('Éxito', data.message || 'Pagos guardados correctamente', 'success').then(function() {
location.reload();
});
} else {
Swal.fire('Error', data.message, 'error');
// Rehabilitar botones
if (btnTop) {
btnTop.disabled = false;
btnTop.innerHTML = '<i class="bi bi-check-all"></i> Guardar Todo';
}
if (btnBottom) {
btnBottom.disabled = false;
btnBottom.innerHTML = '<i class="bi bi-check-all"></i> Guardar Todo';
}
}
})
.catch(function(error) {
console.error('Save all payments error:', error);
Swal.fire('Error', 'Ocurrió un error: ' + error.message, 'error');
// Rehabilitar botones
if (btnTop) {
btnTop.disabled = false;
btnTop.innerHTML = '<i class="bi bi-check-all"></i> Guardar Todo';
}
if (btnBottom) {
btnBottom.disabled = false;
btnBottom.innerHTML = '<i class="bi bi-check-all"></i> Guardar Todo';
}
});
};
// Attach event listener to button
const initButton = document.getElementById('init-payments-btn');
if (initButton) {