Autosave: 20260203-072219
This commit is contained in:
parent
9a3241c3ec
commit
e2dbd8b86f
@ -9,6 +9,13 @@ $start_date = "$year-$month-01";
|
|||||||
$days_in_month = cal_days_in_month(CAL_GREGORIAN, $month, $year);
|
$days_in_month = cal_days_in_month(CAL_GREGORIAN, $month, $year);
|
||||||
$end_date = "$year-$month-$days_in_month";
|
$end_date = "$year-$month-$days_in_month";
|
||||||
|
|
||||||
|
$columns = [
|
||||||
|
'bcp_yape', 'b_nacion', 'interbank', 'bbva', 'otros_ingresos',
|
||||||
|
'tu1', 'tu2', 'tu3', 'fl1', 'fl2', 'fl3',
|
||||||
|
'rc_envio', 'rc_contraent'
|
||||||
|
];
|
||||||
|
|
||||||
|
// Fetch data from DB
|
||||||
$flujo_data = [];
|
$flujo_data = [];
|
||||||
try {
|
try {
|
||||||
$pdo = db();
|
$pdo = db();
|
||||||
@ -22,22 +29,51 @@ try {
|
|||||||
// Handle error
|
// Handle error
|
||||||
}
|
}
|
||||||
|
|
||||||
$columns = [
|
// Prepare data for the entire month
|
||||||
'bcp_yape', 'b_nacion', 'interbank', 'bbva', 'otros_ingresos',
|
$all_days_data = [];
|
||||||
'tu1', 'tu2', 'tu3', 'fl1', 'fl2', 'fl3',
|
for ($day = 1; $day <= $days_in_month; $day++) {
|
||||||
'rc_envio', 'rc_contraent'
|
$date = date('Y-m-d', strtotime("$year-$month-$day"));
|
||||||
];
|
$all_days_data[$date] = isset($flujo_data[$date]) ? $flujo_data[$date] : array_fill_keys($columns, '0.00');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate totals
|
||||||
|
$totals = array_fill_keys($columns, 0);
|
||||||
|
$totals['total_ingresos'] = 0;
|
||||||
|
$totals['total_inversion'] = 0;
|
||||||
|
$totals['recaudo_final'] = 0;
|
||||||
|
|
||||||
|
foreach ($all_days_data as $day_data) {
|
||||||
|
$ingresos_dia = (float)$day_data['bcp_yape'] + (float)$day_data['b_nacion'] + (float)$day_data['interbank'] + (float)$day_data['bbva'] + (float)$day_data['otros_ingresos'] + (float)$day_data['rc_envio'] + (float)$day_data['rc_contraent'];
|
||||||
|
$inversion_dia = (float)$day_data['tu1'] + (float)$day_data['tu2'] + (float)$day_data['tu3'] + (float)$day_data['fl1'] + (float)$day_data['fl2'] + (float)$day_data['fl3'];
|
||||||
|
|
||||||
|
foreach ($columns as $col) {
|
||||||
|
$totals[$col] += (float)$day_data[$col];
|
||||||
|
}
|
||||||
|
$totals['total_ingresos'] += $ingresos_dia;
|
||||||
|
$totals['total_inversion'] += $inversion_dia;
|
||||||
|
$totals['recaudo_final'] += ($ingresos_dia - $inversion_dia);
|
||||||
|
}
|
||||||
|
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.table thead th {
|
.table thead th {
|
||||||
background-color: #e9ecef; /* Un gris claro y profesional */
|
background-color: #e9ecef;
|
||||||
color: #495057;
|
color: #495057;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
.table tfoot th {
|
||||||
|
position: sticky;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: #e9ecef;
|
||||||
|
z-index: 10;
|
||||||
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
.table td[contenteditable="true"]:focus {
|
.table td[contenteditable="true"]:focus {
|
||||||
background-color: #fff3cd; /* Un amarillo suave para resaltar la celda activa */
|
background-color: #fff3cd;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
@ -67,7 +103,7 @@ $columns = [
|
|||||||
<button type="submit" class="btn btn-primary">Filtrar</button>
|
<button type="submit" class="btn btn-primary">Filtrar</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div class="table-responsive">
|
<div class="table-responsive" style="max-height: 70vh; overflow-y: auto;">
|
||||||
<table class="table table-bordered table-striped table-hover" style="width: 100%;">
|
<table class="table table-bordered table-striped table-hover" style="width: 100%;">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@ -95,21 +131,29 @@ $columns = [
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<?php for ($day = 1; $day <= $days_in_month; $day++):
|
<?php foreach ($all_days_data as $date => $day_data): ?>
|
||||||
$date = date('Y-m-d', strtotime("$year-$month-$day"));
|
|
||||||
$day_data = isset($flujo_data[$date]) ? $flujo_data[$date] : array_fill_keys($columns, '0.00');
|
|
||||||
?>
|
|
||||||
<tr data-date="<?php echo $date; ?>">
|
<tr data-date="<?php echo $date; ?>">
|
||||||
<td><?php echo $date; ?></td>
|
<td><?php echo $date; ?></td>
|
||||||
<?php foreach ($columns as $col): ?>
|
<?php foreach ($columns as $col): ?>
|
||||||
<td contenteditable="true" data-column="<?php echo $col; ?>"><?php echo htmlspecialchars($day_data[$col]); ?></td>
|
<td contenteditable="true" data-column="<?php echo $col; ?>"><?php echo htmlspecialchars(number_format((float)$day_data[$col], 2, '.', '')); ?></td>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
<td class="total-ingresos">0.00</td>
|
<td class="total-ingresos">0.00</td>
|
||||||
<td class="total-inversion">0.00</td>
|
<td class="total-inversion">0.00</td>
|
||||||
<td class="recaudo-final">0.00</td>
|
<td class="recaudo-final">0.00</td>
|
||||||
</tr>
|
</tr>
|
||||||
<?php endfor; ?>
|
<?php endforeach; ?>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
<tfoot>
|
||||||
|
<tr>
|
||||||
|
<th>TOTAL</th>
|
||||||
|
<?php foreach ($columns as $col): ?>
|
||||||
|
<th data-total-column="<?php echo $col; ?>"><?php echo number_format($totals[$col], 2); ?></th>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<th data-total-column="total-ingresos"><?php echo number_format($totals['total_ingresos'], 2); ?></th>
|
||||||
|
<th data-total-column="total-inversion"><?php echo number_format($totals['total_inversion'], 2); ?></th>
|
||||||
|
<th data-total-column="recaudo-final"><?php echo number_format($totals['recaudo_final'], 2); ?></th>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -118,17 +162,14 @@ $columns = [
|
|||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
const table = document.querySelector('.table');
|
const table = document.querySelector('.table');
|
||||||
|
|
||||||
// Function to update totals for a specific row
|
|
||||||
function updateTotals(row) {
|
function updateTotals(row) {
|
||||||
const getVal = (selector) => {
|
const getVal = (selector) => {
|
||||||
const cell = row.querySelector(`[data-column="${selector}"]`);
|
const cell = row.querySelector(`[data-column="${selector}"]`);
|
||||||
return parseFloat(cell.textContent) || 0;
|
return parseFloat(cell.textContent.replace(/,/g, '')) || 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ingresos = getVal('bcp_yape') + getVal('b_nacion') + getVal('interbank') + getVal('bbva') + getVal('otros_ingresos') + getVal('rc_envio') + getVal('rc_contraent');
|
const ingresos = getVal('bcp_yape') + getVal('b_nacion') + getVal('interbank') + getVal('bbva') + getVal('otros_ingresos') + getVal('rc_envio') + getVal('rc_contraent');
|
||||||
|
|
||||||
const totalInversion = getVal('tu1') + getVal('tu2') + getVal('tu3') + getVal('fl1') + getVal('fl2') + getVal('fl3');
|
const totalInversion = getVal('tu1') + getVal('tu2') + getVal('tu3') + getVal('fl1') + getVal('fl2') + getVal('fl3');
|
||||||
|
|
||||||
const recaudoFinal = ingresos - totalInversion;
|
const recaudoFinal = ingresos - totalInversion;
|
||||||
|
|
||||||
row.querySelector('.total-ingresos').textContent = ingresos.toFixed(2);
|
row.querySelector('.total-ingresos').textContent = ingresos.toFixed(2);
|
||||||
@ -136,10 +177,34 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
row.querySelector('.recaudo-final').textContent = recaudoFinal.toFixed(2);
|
row.querySelector('.recaudo-final').textContent = recaudoFinal.toFixed(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initial calculation for all rows
|
function updateGrandTotals() {
|
||||||
table.querySelectorAll('tbody tr').forEach(updateTotals);
|
const grandTotals = {
|
||||||
|
<?php foreach ($columns as $col) echo "'$col': 0,"; ?>
|
||||||
|
'total-ingresos': 0,
|
||||||
|
'total-inversion': 0,
|
||||||
|
'recaudo-final': 0
|
||||||
|
};
|
||||||
|
|
||||||
|
table.querySelectorAll('tbody tr').forEach(row => {
|
||||||
|
<?php foreach ($columns as $col): ?>
|
||||||
|
grandTotals['<?php echo $col; ?>'] += parseFloat(row.querySelector(`[data-column="<?php echo $col; ?>"]`).textContent.replace(/,/g, '')) || 0;
|
||||||
|
<?php endforeach; ?>
|
||||||
|
grandTotals['total-ingresos'] += parseFloat(row.querySelector('.total-ingresos').textContent.replace(/,/g, '')) || 0;
|
||||||
|
grandTotals['total-inversion'] += parseFloat(row.querySelector('.total-inversion').textContent.replace(/,/g, '')) || 0;
|
||||||
|
grandTotals['recaudo-final'] += parseFloat(row.querySelector('.recaudo-final').textContent.replace(/,/g, '')) || 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const key in grandTotals) {
|
||||||
|
const th = table.querySelector(`tfoot [data-total-column="${key}"]`);
|
||||||
|
if (th) {
|
||||||
|
th.textContent = grandTotals[key].toFixed(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table.querySelectorAll('tbody tr').forEach(updateTotals);
|
||||||
|
updateGrandTotals();
|
||||||
|
|
||||||
// Event listener for cell focus to select all content
|
|
||||||
table.addEventListener('focus', function(e) {
|
table.addEventListener('focus', function(e) {
|
||||||
if (e.target.hasAttribute('contenteditable')) {
|
if (e.target.hasAttribute('contenteditable')) {
|
||||||
const range = document.createRange();
|
const range = document.createRange();
|
||||||
@ -148,50 +213,43 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
sel.removeAllRanges();
|
sel.removeAllRanges();
|
||||||
sel.addRange(range);
|
sel.addRange(range);
|
||||||
}
|
}
|
||||||
}, true); // Use capturing for focus
|
}, true);
|
||||||
|
|
||||||
// Event listener for cell edits
|
|
||||||
table.addEventListener('blur', function(e) {
|
table.addEventListener('blur', function(e) {
|
||||||
if (e.target.hasAttribute('contenteditable')) {
|
if (e.target.hasAttribute('contenteditable')) {
|
||||||
const cell = e.target;
|
const cell = e.target;
|
||||||
const row = cell.closest('tr');
|
const row = cell.closest('tr');
|
||||||
const date = row.dataset.date;
|
const date = row.dataset.date;
|
||||||
const column = cell.dataset.column;
|
const column = cell.dataset.column;
|
||||||
let value = parseFloat(cell.textContent);
|
let value = parseFloat(cell.textContent.replace(/,/g, ''));
|
||||||
|
|
||||||
if (isNaN(value)) {
|
if (isNaN(value)) {
|
||||||
value = 0;
|
value = 0;
|
||||||
cell.textContent = '0.00';
|
|
||||||
}
|
}
|
||||||
cell.textContent = value.toFixed(2);
|
cell.textContent = value.toFixed(2);
|
||||||
|
|
||||||
// Save data to server
|
|
||||||
fetch('save_flujo_caja.php', {
|
fetch('save_flujo_caja.php', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: { 'Content-Type': 'application/json' },
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ fecha: date, columna: column, valor: value })
|
body: JSON.stringify({ fecha: date, columna: column, valor: value })
|
||||||
})
|
})
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
if (!data.success) {
|
if (data.success) {
|
||||||
|
updateTotals(row);
|
||||||
|
updateGrandTotals(); // Update grand totals after successful save
|
||||||
|
} else {
|
||||||
console.error('Error saving data:', data.message);
|
console.error('Error saving data:', data.message);
|
||||||
// Optional: Add user feedback for save failure
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => console.error('Fetch error:', error));
|
.catch(error => console.error('Fetch error:', error));
|
||||||
|
|
||||||
// Update totals for the edited row
|
|
||||||
updateTotals(row);
|
|
||||||
}
|
}
|
||||||
}, true); // Use capturing to ensure blur event is handled properly
|
}, true);
|
||||||
|
|
||||||
// Event listener for Enter key to save and not expand
|
|
||||||
table.addEventListener('keydown', function(e) {
|
table.addEventListener('keydown', function(e) {
|
||||||
if (e.key === 'Enter' && e.target.hasAttribute('contenteditable')) {
|
if (e.key === 'Enter' && e.target.hasAttribute('contenteditable')) {
|
||||||
e.preventDefault(); // Prevent new line
|
e.preventDefault();
|
||||||
e.target.blur(); // Trigger blur to save
|
e.target.blur();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -76,7 +76,7 @@ $navItems = [
|
|||||||
],
|
],
|
||||||
'inventario_group' => [
|
'inventario_group' => [
|
||||||
'icon' => 'fa-warehouse',
|
'icon' => 'fa-warehouse',
|
||||||
'text' => 'Inventario',
|
'text' => 'Inventario General',
|
||||||
'roles' => ['Administrador', 'admin', 'Control Logistico'],
|
'roles' => ['Administrador', 'admin', 'Control Logistico'],
|
||||||
'submenu' => [
|
'submenu' => [
|
||||||
'panel_inventario' => [
|
'panel_inventario' => [
|
||||||
|
|||||||
@ -9,51 +9,160 @@ require_once 'db/config.php';
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
$pdo = db();
|
$pdo = db();
|
||||||
$stmt = $pdo->query("SELECT * FROM products ORDER BY order_position ASC");
|
|
||||||
$products = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
// 1. Datos para las tarjetas de resumen
|
||||||
|
$total_productos = $pdo->query("SELECT COUNT(*) FROM products")->fetchColumn();
|
||||||
|
$total_sedes = $pdo->query("SELECT COUNT(*) FROM sedes")->fetchColumn();
|
||||||
|
$total_stock = $pdo->query("SELECT SUM(quantity) FROM stock_sedes")->fetchColumn();
|
||||||
|
|
||||||
|
// 2. Datos para la tabla de inventario
|
||||||
|
$sedes_stmt = $pdo->query("SELECT id, nombre FROM sedes ORDER BY nombre");
|
||||||
|
$sedes = $sedes_stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
$products_stmt = $pdo->query("SELECT id, nombre FROM products ORDER BY nombre");
|
||||||
|
$products = $products_stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
$stock_stmt = $pdo->query("SELECT product_id, sede_id, quantity FROM stock_sedes");
|
||||||
|
$stock_data = $stock_stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// Procesar datos para la tabla
|
||||||
|
$inventario = [];
|
||||||
|
foreach ($products as $product) {
|
||||||
|
$inventario[$product['id']] = [
|
||||||
|
'nombre' => $product['nombre'],
|
||||||
|
'total' => 0,
|
||||||
|
'sedes' => array_fill_keys(array_column($sedes, 'id'), 0)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($stock_data as $stock_item) {
|
||||||
|
if (isset($inventario[$stock_item['product_id']])) {
|
||||||
|
$inventario[$stock_item['product_id']]['sedes'][$stock_item['sede_id']] = $stock_item['quantity'];
|
||||||
|
$inventario[$stock_item['product_id']]['total'] += $stock_item['quantity'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Datos para el gráfico circular (Distribución por Sede)
|
||||||
|
$stock_por_sede_stmt = $pdo->query("
|
||||||
|
SELECT s.nombre, SUM(ss.quantity) as total_stock
|
||||||
|
FROM sedes s
|
||||||
|
JOIN stock_sedes ss ON s.id = ss.sede_id
|
||||||
|
GROUP BY s.nombre
|
||||||
|
HAVING total_stock > 0
|
||||||
|
ORDER BY s.nombre
|
||||||
|
");
|
||||||
|
$stock_por_sede = $stock_por_sede_stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
$sede_chart_labels = json_encode(array_column($stock_por_sede, 'nombre'));
|
||||||
|
$sede_chart_data = json_encode(array_column($stock_por_sede, 'total_stock'));
|
||||||
|
|
||||||
|
// 4. Datos para el gráfico de barras (Stock por Producto)
|
||||||
|
$stock_por_producto_stmt = $pdo->query("
|
||||||
|
SELECT p.nombre, SUM(ss.quantity) as total_stock
|
||||||
|
FROM products p
|
||||||
|
JOIN stock_sedes ss ON p.id = ss.product_id
|
||||||
|
GROUP BY p.nombre
|
||||||
|
HAVING total_stock > 0
|
||||||
|
ORDER BY total_stock DESC
|
||||||
|
LIMIT 15
|
||||||
|
");
|
||||||
|
$stock_por_producto = $stock_por_producto_stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
$producto_chart_labels = json_encode(array_column($stock_por_producto, 'nombre'));
|
||||||
|
$producto_chart_data = json_encode(array_column($stock_por_producto, 'total_stock'));
|
||||||
|
|
||||||
|
|
||||||
} catch (PDOException $e) {
|
} catch (PDOException $e) {
|
||||||
echo "<div class='alert alert-danger'>Error al conectar con la base de datos: " . $e->getMessage() . "</div>";
|
echo "<div class='alert alert-danger'>Error al conectar o consultar la base de datos: " . $e->getMessage() . "</div>";
|
||||||
// Consider logging the error and showing a more user-friendly message
|
require_once 'layout_footer.php';
|
||||||
// For now, we stop execution if the database connection fails.
|
|
||||||
die();
|
die();
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<div class="container-fluid mt-4">
|
<div class="container-fluid mt-4">
|
||||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
<h1 class="mb-4">Dashboard de Inventario</h1>
|
||||||
<h1 class="m-0">Inventario General</h1>
|
|
||||||
<a href="edit_product.php" class="btn btn-primary">Añadir Producto</a>
|
<!-- Tarjetas de Resumen -->
|
||||||
|
<div class="row mb-4">
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="card text-white bg-primary mb-3">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">Total de Productos</h5>
|
||||||
|
<p class="card-text fs-4"><?php echo $total_productos; ?></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="card text-white bg-success mb-3">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">Sedes Activas</h5>
|
||||||
|
<p class="card-text fs-4"><?php echo $total_sedes; ?></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="card text-white bg-info mb-3">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">Unidades Totales en Stock</h5>
|
||||||
|
<p class="card-text fs-4"><?php echo $total_stock ?: 0; ?></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Sección de Gráficos -->
|
||||||
|
<div class="row mb-4">
|
||||||
|
<div class="col-lg-6 mb-4">
|
||||||
|
<div class="card h-100">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5 class="m-0">Distribución por Sede</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body d-flex justify-content-center align-items-center">
|
||||||
|
<canvas id="distribucionSedeChart" style="max-height: 300px;"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-6 mb-4">
|
||||||
|
<div class="card h-100">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5 class="m-0">Top 15 Productos con Más Stock</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<canvas id="stockProductoChart"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Tabla de Inventario Detallado -->
|
||||||
<div class="card">
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5 class="m-0">Inventario por Sede</h5>
|
||||||
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-striped table-hover">
|
<table class="table table-bordered table-hover">
|
||||||
<thead class="thead-dark">
|
<thead class="thead-light">
|
||||||
<tr>
|
<tr>
|
||||||
<th>ID</th>
|
<th>Producto</th>
|
||||||
<th>Nombre</th>
|
<th class="text-center">Stock Total</th>
|
||||||
<th>SKU</th>
|
<?php foreach ($sedes as $sede): ?>
|
||||||
<th>Precio</th>
|
<th class="text-center"><?php echo htmlspecialchars($sede['nombre']); ?></th>
|
||||||
<th class="text-center">Acciones</th>
|
<?php endforeach; ?>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<?php if (empty($products)): ?>
|
<?php if (empty($inventario)): ?>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="5" class="text-center">No hay productos en el inventario. <a href="edit_product.php">Agrega el primero</a>.</td>
|
<td colspan="<?php echo count($sedes) + 2; ?>" class="text-center">No hay productos para mostrar.</td>
|
||||||
</tr>
|
</tr>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<?php foreach ($products as $product): ?>
|
<?php foreach ($inventario as $datos_producto): ?>
|
||||||
<tr>
|
<tr>
|
||||||
<td><?php echo htmlspecialchars($product['id']); ?></td>
|
<td><?php echo htmlspecialchars($datos_producto['nombre']); ?></td>
|
||||||
<td><?php echo htmlspecialchars($product['nombre']); ?></td>
|
<td class="text-center fw-bold"><?php echo $datos_producto['total']; ?></td>
|
||||||
<td><?php echo htmlspecialchars($product['sku']); ?></td>
|
<?php foreach ($datos_producto['sedes'] as $cantidad): ?>
|
||||||
<td>S/ <?php echo htmlspecialchars(number_format($product['precio'], 2)); ?></td>
|
<td class="text-center"><?php echo $cantidad; ?></td>
|
||||||
<td class="text-center">
|
<?php endforeach; ?>
|
||||||
<a href="edit_product.php?id=<?php echo $product['id']; ?>" class="btn btn-sm btn-warning">Editar</a>
|
|
||||||
<a href="delete_product.php?id=<?php echo $product['id']; ?>" class="btn btn-sm btn-danger" onclick="return confirm('¿Estás seguro de que quieres eliminar este producto?');">Eliminar</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
@ -64,4 +173,88 @@ try {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Incluir Chart.js -->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
// --- Gráfico 1: Distribución por Sede (Circular) ---
|
||||||
|
const sedeLabels = <?php echo $sede_chart_labels; ?>;
|
||||||
|
const sedeData = <?php echo $sede_chart_data; ?>;
|
||||||
|
|
||||||
|
if (sedeLabels.length > 0) {
|
||||||
|
const defaultColors = ['#FF6384', '#36A2EB', '#FFCE56', '#4BC0C0', '#9966FF', '#FF9F40'];
|
||||||
|
const alidrvColor = '#28a745'; // Verde
|
||||||
|
|
||||||
|
const backgroundColors = sedeLabels.map((label, index) => {
|
||||||
|
if (label.toLowerCase() === 'alidrv') {
|
||||||
|
return alidrvColor;
|
||||||
|
}
|
||||||
|
return defaultColors[index % defaultColors.length];
|
||||||
|
});
|
||||||
|
|
||||||
|
const ctxSede = document.getElementById('distribucionSedeChart').getContext('2d');
|
||||||
|
new Chart(ctxSede, {
|
||||||
|
type: 'pie',
|
||||||
|
data: {
|
||||||
|
labels: sedeLabels,
|
||||||
|
datasets: [{
|
||||||
|
label: 'Stock',
|
||||||
|
data: sedeData,
|
||||||
|
backgroundColor: backgroundColors,
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
position: 'bottom',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Gráfico 2: Stock por Producto (Barras) ---
|
||||||
|
const productoLabels = <?php echo $producto_chart_labels; ?>;
|
||||||
|
const productoData = <?php echo $producto_chart_data; ?>;
|
||||||
|
|
||||||
|
if (productoLabels.length > 0) {
|
||||||
|
const ctxProducto = document.getElementById('stockProductoChart').getContext('2d');
|
||||||
|
new Chart(ctxProducto, {
|
||||||
|
type: 'bar',
|
||||||
|
data: {
|
||||||
|
labels: productoLabels,
|
||||||
|
datasets: [{
|
||||||
|
label: 'Cantidad en Stock',
|
||||||
|
data: productoData,
|
||||||
|
backgroundColor: 'rgba(54, 162, 235, 0.6)',
|
||||||
|
borderColor: 'rgba(54, 162, 235, 1)',
|
||||||
|
borderWidth: 1
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
indexAxis: 'y', // Hace que el gráfico sea horizontal
|
||||||
|
responsive: true,
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
display: false
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'Stock por Producto'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
x: {
|
||||||
|
beginAtZero: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
<?php require_once 'layout_footer.php'; ?>
|
<?php require_once 'layout_footer.php'; ?>
|
||||||
Loading…
x
Reference in New Issue
Block a user