Autosave: 20260205-053735

This commit is contained in:
Flatlogic Bot 2026-02-05 05:37:35 +00:00
parent 2e2d50688f
commit 7917ea5f98
18 changed files with 366 additions and 128 deletions

View File

@ -1,21 +1,65 @@
<?php
$pageTitle = "Añadir Nueva Columna al Kanban";
require_once 'layout_header.php';
require_once 'db/config.php';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$nombre = trim($_POST['nombre']);
if (!empty($nombre)) {
$pdo = db();
// Find the current max order value
$stmt_max_order = $pdo->query('SELECT MAX(orden) AS max_orden FROM kanban_columns');
$max_orden = $stmt_max_order->fetchColumn();
$new_orden = $max_orden + 1;
$stmt = $pdo->prepare('INSERT INTO kanban_columns (nombre, orden) VALUES (?, ?)');
$stmt->execute([$nombre, $new_orden]);
}
// Asegurarse de que el usuario sea administrador
if ($_SESSION['user_role'] !== 'Administrador' && $_SESSION['user_role'] !== 'admin') {
echo "<div class='alert alert-danger'>Acceso denegado.</div>";
require_once 'layout_footer.php';
exit();
}
header('Location: kanban.php');
exit;
$message = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$nombre = trim($_POST['nombre'] ?? '');
if (!empty($nombre)) {
try {
$pdo = db();
// Find the current max order value
$stmt_max_order = $pdo->query('SELECT MAX(orden) AS max_orden FROM kanban_columns');
$max_orden = $stmt_max_order->fetchColumn();
$new_orden = ($max_orden === null) ? 0 : $max_orden + 1;
$stmt = $pdo->prepare('INSERT INTO kanban_columns (nombre, orden) VALUES (?, ?)');
if ($stmt->execute([$nombre, $new_orden])) {
// Usar una variable de sesión para mostrar el mensaje en la página de configuración
$_SESSION['success_message'] = "Columna '" . htmlspecialchars($nombre) . "' añadida correctamente.";
header('Location: configuracion.php');
exit;
} else {
$message = "<div class='alert alert-danger'>Error al añadir la columna.</div>";
}
} catch (PDOException $e) {
$message = "<div class='alert alert-danger'>Error de base de datos: " . $e->getMessage() . "</div>";
}
} else {
$message = "<div class='alert alert-warning'>El nombre de la columna no puede estar vacío.</div>";
}
}
?>
<div class="container mt-5">
<div class="card">
<div class="card-header">
<h2>Añadir Nueva Columna al Kanban</h2>
</div>
<div class="card-body">
<?php echo $message; ?>
<form method="POST" action="add_column.php">
<div class="mb-3">
<label for="nombre" class="form-label">Nombre de la Nueva Columna</label>
<input type="text" class="form-control" id="nombre" name="nombre" required>
</div>
<button type="submit" class="btn btn-primary">Guardar Columna</button>
<a href="configuracion.php" class="btn btn-secondary">Cancelar</a>
</form>
</div>
</div>
</div>
<?php require_once 'layout_footer.php'; ?>

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

View File

@ -12,63 +12,119 @@ if ($_SESSION['user_role'] !== 'Administrador' && $_SESSION['user_role'] !== 'ad
$conn = db();
// Obtener la configuración actual
$query = "SELECT * FROM configuracion WHERE id = 1";
$result = $conn->query($query);
$config = $result->fetch(PDO::FETCH_ASSOC);
// --- Gestión de Columnas ---
// Columnas disponibles
$available_columns = [
'new' => 'Nuevos',
'reagendado' => 'Reagendado',
'no_contactado' => 'No Contactado',
'contactado' => 'Contactado',
'agendado' => 'Agendado',
'en_ruta' => 'En Ruta',
'entregado' => 'Entregado',
'no_entregado' => 'No Entregado',
'reprogramado' => 'Reprogramado'
];
// Obtener todas las columnas del Kanban para la tabla de gestión
$query_manage_columns = "SELECT * FROM kanban_columns ORDER BY orden ASC";
$stmt_manage_columns = $conn->query($query_manage_columns);
$management_columns = $stmt_manage_columns->fetchAll(PDO::FETCH_ASSOC);
// --- Visibilidad de Columnas ---
// Obtener la configuración actual de visibilidad
$query_config = "SELECT * FROM configuracion WHERE id = 1";
$result_config = $conn->query($query_config);
$config = $result_config->fetch(PDO::FETCH_ASSOC);
$visible_columns = $config ? json_decode($config['kanban_columns'], true) : [];
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['save_visibility'])) {
$new_visible_columns = $_POST['columns'] ?? [];
$json_columns = json_encode($new_visible_columns);
$update_query = "UPDATE configuracion SET kanban_columns = :kanban_columns WHERE id = 1";
// Usar INSERT ... ON DUPLICATE KEY UPDATE para seguridad
$update_query = "INSERT INTO configuracion (id, kanban_columns) VALUES (1, :kanban_columns)
ON DUPLICATE KEY UPDATE kanban_columns = :kanban_columns";
$stmt = $conn->prepare($update_query);
$stmt->bindParam(':kanban_columns', $json_columns);
if ($stmt->execute()) {
echo "<div class='alert alert-success'>Configuración guardada correctamente.</div>";
$visible_columns = $new_visible_columns;
echo "<div class='alert alert-success'>Configuración de visibilidad guardada.</div>";
$visible_columns = $new_visible_columns; // Actualizar para mostrar el cambio inmediatamente
} else {
echo "<div class='alert alert-danger'>Error al guardar la configuración.</div>";
echo "<div class='alert alert-danger'>Error al guardar la configuración de visibilidad.</div>";
}
}
// Obtener todas las columnas de kanban_columns para el formulario de visibilidad
$all_db_columns_query = "SELECT nombre FROM kanban_columns ORDER BY orden ASC";
$stmt_all_db_columns = $conn->query($all_db_columns_query);
$available_columns_for_visibility = $stmt_all_db_columns->fetchAll(PDO::FETCH_COLUMN);
?>
<div class="container mt-5">
<h2>Configuración de Columnas del Kanban</h2>
<p>Selecciona las columnas que deseas que sean visibles en el tablero Kanban.</p>
<form method="POST">
<div class="row">
<?php foreach ($available_columns as $key => $name): ?>
<div class="col-md-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="columns[]" value="<?php echo $key; ?>" id="col_<?php echo $key; ?>" <?php echo in_array($key, $visible_columns) ? 'checked' : ''; ?>>
<label class="form-check-label" for="col_<?php echo $key; ?>">
<?php echo htmlspecialchars($name); ?>
</label>
</div>
</div>
<?php endforeach; ?>
<!-- Sección para Gestionar Columnas (Añadir, Editar, Eliminar) -->
<div class="card mb-5">
<div class="card-header">
<h3>Gestionar Columnas del Kanban</h3>
</div>
<div class="card-body">
<p>Aquí puedes añadir, editar y eliminar las columnas que se usarán en el tablero Kanban.</p>
<a href="add_column.php" class="btn btn-success mb-3">
<i class="fas fa-plus"></i> Añadir Nueva Columna
</a>
<table class="table table-bordered table-striped">
<thead class="table-dark">
<tr>
<th>Nombre de la Columna</th>
<th style="width: 150px;">Acciones</th>
</tr>
</thead>
<tbody>
<?php if (empty($management_columns)): ?>
<tr>
<td colspan="2" class="text-center">No hay columnas definidas.</td>
</tr>
<?php else: ?>
<?php foreach ($management_columns as $column): ?>
<tr>
<td><?php echo htmlspecialchars($column['nombre']); ?></td>
<td>
<a href="edit_column.php?id=<?php echo $column['id']; ?>" class="btn btn-primary btn-sm">
<i class="fas fa-edit"></i> Editar
</a>
<a href="delete_column.php?id=<?php echo $column['id']; ?>" class="btn btn-danger btn-sm" onclick="return confirm('¿Estás seguro de que quieres eliminar esta columna?');">
<i class="fas fa-trash"></i> Eliminar
</a>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
<button type="submit" class="btn btn-primary mt-3">Guardar Configuración</button>
</form>
<!-- Sección para Configurar Visibilidad de Columnas -->
<div class="card">
<div class="card-header">
<h3>Configurar Visibilidad de Columnas</h3>
</div>
<div class="card-body">
<p>Selecciona las columnas que deseas que sean visibles en el tablero Kanban.</p>
<form method="POST">
<input type="hidden" name="save_visibility" value="1">
<div class="row">
<?php foreach ($available_columns_for_visibility as $column_name): ?>
<div class="col-md-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="columns[]" value="<?php echo htmlspecialchars($column_name); ?>" id="col_<?php echo htmlspecialchars($column_name); ?>" <?php echo in_array($column_name, $visible_columns) ? 'checked' : ''; ?>>
<label class="form-check-label" for="col_<?php echo htmlspecialchars($column_name); ?>">
<?php echo htmlspecialchars($column_name); ?>
</label>
</div>
</div>
<?php endforeach; ?>
</div>
<button type="submit" class="btn btn-primary mt-3">Guardar Visibilidad</button>
</form>
</div>
</div>
</div>
<?php require_once 'layout_footer.php'; ?>
<?php require_once 'layout_footer.php'; ?>

View File

@ -1,21 +1,45 @@
<?php
session_start();
require_once 'db/config.php';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$id = $_POST['id'];
// Asegurarse de que el usuario sea administrador
if ($_SESSION['user_role'] !== 'Administrador' && $_SESSION['user_role'] !== 'admin') {
$_SESSION['error_message'] = "Acceso denegado.";
header('Location: configuracion.php');
exit();
}
if (!empty($id)) {
if (isset($_GET['id']) && is_numeric($_GET['id'])) {
$id = $_GET['id'];
try {
$pdo = db();
// First, update info_productos to set column_id to NULL for the cards in the deleted column
// Opcional: Obtener el nombre de la columna antes de eliminarla para el mensaje
$stmt_name = $pdo->prepare("SELECT nombre FROM kanban_columns WHERE id = ?");
$stmt_name->execute([$id]);
$column_name = $stmt_name->fetchColumn();
// Primero, actualiza info_productos para desasociar las tarjetas de la columna
$stmt_update = $pdo->prepare('UPDATE info_productos SET column_id = NULL WHERE column_id = ?');
$stmt_update->execute([$id]);
// Then, delete the column
// Luego, elimina la columna
$stmt_delete = $pdo->prepare('DELETE FROM kanban_columns WHERE id = ?');
$stmt_delete->execute([$id]);
if ($stmt_delete->execute([$id])) {
$_SESSION['success_message'] = "Columna '" . htmlspecialchars($column_name) . "' eliminada correctamente.";
} else {
$_SESSION['error_message'] = "Error al eliminar la columna.";
}
} catch (PDOException $e) {
$_SESSION['error_message'] = "Error de base de datos: " . $e->getMessage();
}
} else {
$_SESSION['error_message'] = "ID de columna no válido.";
}
header('Location: kanban.php');
header('Location: configuracion.php');
exit;
?>

View File

@ -1,18 +1,82 @@
<?php
session_start();
$pageTitle = "Editar Columna del Kanban";
require_once 'layout_header.php';
require_once 'db/config.php';
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['id'])) {
$id = $_POST['id'];
$nombre = $_POST['nombre'];
// Asegurarse de que el usuario sea administrador
if ($_SESSION['user_role'] !== 'Administrador' && $_SESSION['user_role'] !== 'admin') {
echo "<div class='alert alert-danger'>Acceso denegado.</div>";
require_once 'layout_footer.php';
exit();
}
$db = db();
$stmt = $db->prepare("UPDATE kanban_columns SET name = ? WHERE id = ?");
$stmt->bind_param('si', $nombre, $id);
$stmt->execute();
$message = '';
$column = null;
$_SESSION['success_message'] = "Columna actualizada exitosamente.";
header('Location: info_producto.php');
if (!isset($_GET['id']) || !is_numeric($_GET['id'])) {
header('Location: configuracion.php');
exit;
}
?>
$column_id = $_GET['id'];
try {
$pdo = db();
// Procesar el formulario cuando se envía
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$nombre = trim($_POST['nombre'] ?? '');
if (!empty($nombre)) {
$stmt = $pdo->prepare('UPDATE kanban_columns SET nombre = ? WHERE id = ?');
if ($stmt->execute([$nombre, $column_id])) {
$_SESSION['success_message'] = "Columna actualizada a '" . htmlspecialchars($nombre) . "'.";
header('Location: configuracion.php');
exit;
} else {
$message = "<div class='alert alert-danger'>Error al actualizar la columna.</div>";
}
} else {
$message = "<div class='alert alert-warning'>El nombre de la columna no puede estar vacío.</div>";
}
}
// Obtener los datos de la columna para mostrar en el formulario
$stmt = $pdo->prepare('SELECT * FROM kanban_columns WHERE id = ?');
$stmt->execute([$column_id]);
$column = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$column) {
echo "<div class='alert alert-danger'>La columna no existe.</div>";
require_once 'layout_footer.php';
exit;
}
} catch (PDOException $e) {
$message = "<div class='alert alert-danger'>Error de base de datos: " . $e->getMessage() . "</div>";
}
?>
<div class="container mt-5">
<div class="card">
<div class="card-header">
<h2>Editar Columna del Kanban</h2>
</div>
<div class="card-body">
<?php echo $message; ?>
<?php if ($column): ?>
<form method="POST" action="edit_column.php?id=<?php echo $column_id; ?>">
<div class="mb-3">
<label for="nombre" class="form-label">Nombre de la Columna</label>
<input type="text" class="form-control" id="nombre" name="nombre" value="<?php echo htmlspecialchars($column['nombre']); ?>" required>
</div>
<button type="submit" class="btn btn-primary">Actualizar Columna</button>
<a href="configuracion.php" class="btn btn-secondary">Cancelar</a>
</form>
<?php endif; ?>
</div>
</div>
</div>
<?php require_once 'layout_footer.php'; ?>

View File

@ -1,4 +1,5 @@
<?php
session_start();
require_once 'db/config.php';
@ -8,35 +9,44 @@ if (!isset($_SESSION['username'])) {
}
$username = $_SESSION['username'];
$role = $_SESSION['role'];
$role = isset($_SESSION['role']) ? $_SESSION['role'] : '';
// Conectar a la base de datos
$pdo = db();
// Obtener la configuración de las columnas del Kanban
$stmt = $pdo->query("SELECT kanban_columns FROM configuracion WHERE id = 1");
$config = $stmt->fetch(PDO::FETCH_ASSOC);
// 1. Obtener todas las columnas del Kanban
$stmt_cols = $pdo->query("SELECT id, nombre FROM kanban_columns ORDER BY orden, id");
$columns_to_display = $stmt_cols->fetchAll(PDO::FETCH_ASSOC);
$columns_to_display = [];
if ($config && !empty($config['kanban_columns'])) {
$column_ids = json_decode($config['kanban_columns'], true);
if (!empty($column_ids)) {
$placeholders = implode(',', array_fill(0, count($column_ids), '?'));
$stmt = $pdo->prepare("SELECT id, nombre FROM kanban_columns WHERE id IN ($placeholders)");
$stmt->execute($column_ids);
$columns_to_display = $stmt->fetchAll(PDO::FETCH_ASSOC);
// 2. Obtener todos los productos de información, uniéndolos con productos y columnas
$stmt_items = $pdo->query("
SELECT
ip.id,
ip.texto_informativo,
ip.imagen_url,
p.nombre as producto_nombre,
kc.nombre as estado_kanban
FROM
info_productos ip
LEFT JOIN
products p ON ip.producto_id = p.id
LEFT JOIN
kanban_columns kc ON ip.column_id = kc.id
ORDER BY
ip.orden, ip.id
");
$items = $stmt_items->fetchAll(PDO::FETCH_ASSOC);
// 3. Agrupar items por el nombre de la columna (estado_kanban)
$items_by_column = [];
if ($items) {
foreach ($items as $item) {
if (!empty($item['estado_kanban'])) {
$items_by_column[$item['estado_kanban']][] = $item;
}
}
}
if (empty($columns_to_display)) {
$stmt = $pdo->query("SELECT id, nombre FROM kanban_columns");
$columns_to_display = $stmt->fetchAll(PDO::FETCH_ASSOC);
}
// Obtener todos los productos de información
$stmt = $pdo->query("SELECT ip.*, p.nombre as producto_nombre FROM info_productos ip LEFT JOIN products p ON ip.product_id = p.id");
$items = $stmt->fetchAll(PDO::FETCH_ASSOC);
$pageTitle = "Kanban de Productos";
$pageDescription = "Tablero Kanban para visualizar la información de los productos.";
@ -58,26 +68,31 @@ include 'layout_header.php';
<div class="kanban-board-container">
<div class="kanban-board">
<?php foreach ($columns_to_display as $column): ?>
<div class="kanban-column" data-column-id="<?php echo $column['nombre']; ?>">
<div class="kanban-column" data-column-id="<?php echo $column['id']; ?>">
<div class="kanban-column-header">
<h5><?php echo htmlspecialchars($column['nombre']); ?></h5>
</div>
<div class="kanban-column-body">
<?php foreach ($items as $item): ?>
<?php if ($item['estado_kanban'] == $column['nombre']): ?>
<?php if (isset($items_by_column[$column['nombre']])): ?>
<?php foreach ($items_by_column[$column['nombre']] as $item): ?>
<div class="kanban-card" data-item-id="<?php echo $item['id']; ?>">
<div class="card-body">
<?php if (!empty($item['imagen'])): ?>
<img src="assets/uploads/info_images/<?php echo htmlspecialchars($item['imagen']); ?>" class="card-img-top mb-2" alt="Imagen del producto">
<?php if (!empty($item['imagen_url']) && file_exists($item['imagen_url'])): ?>
<img src="<?php echo htmlspecialchars($item['imagen_url']); ?>" class="card-img-top mb-2" alt="Imagen del producto">
<?php endif; ?>
<h6 class="card-title"><?php echo htmlspecialchars($item['titulo']); ?></h6>
<p class="card-text small"><?php echo htmlspecialchars($item['producto_nombre']); ?></p>
<button class="btn btn-sm btn-secondary" onclick="copiarTexto(this)">Copiar Texto</button>
<div class="texto-a-copiar" style="display:none;"><?php echo htmlspecialchars($item['texto']); ?></div>
<h6 class="card-title"><?php echo htmlspecialchars($item['producto_nombre'] ?? 'Producto sin nombre'); ?></h6>
<p class="card-text fs-sm text-muted"><?php echo nl2br(htmlspecialchars($item['texto_informativo'])); ?></p>
<button class="btn btn-sm btn-outline-secondary w-100 mt-2" onclick="copiarTexto(this)">
Copiar Texto
</button>
<div class="texto-a-copiar" style="display:none;"><?php echo htmlspecialchars($item['texto_informativo']); ?></div>
</div>
</div>
<?php endif; ?>
<?php endforeach; ?>
<?php endforeach; ?>
<?php endif; ?>
</div>
</div>
<?php endforeach; ?>
@ -101,45 +116,80 @@ include 'layout_header.php';
width: 300px;
flex-shrink: 0;
background-color: #f4f5f7;
border-radius: 3px;
border-radius: 8px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
.kanban-column-header {
padding: 10px 15px;
border-bottom: 1px solid #ddd;
padding: 12px 15px;
border-bottom: 1px solid #e0e0e0;
background-color: #fff;
border-top-left-radius: 8px;
border-top-right-radius: 8px;
}
.kanban-column-body {
padding: 10px;
min-height: 400px;
overflow-y: auto;
min-height: 200px; /* Altura mínima por si la columna está vacía */
max-height: 70vh; /* Altura máxima del 70% de la pantalla */
overflow-y: auto; /* Scroll vertical si el contenido excede la altura */
}
.kanban-card {
background-color: #fff;
border: 1px solid #ddd;
border-radius: 3px;
padding: 10px;
border-radius: 6px;
padding: 15px;
margin-bottom: 10px;
cursor: grab;
transition: box-shadow 0.2s ease;
}
.kanban-card:hover {
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}
.card-title {
font-weight: 600;
margin-bottom: 0; /* Ajustado para dar espacio al botón */
}
</style>
<script>
function copiarTexto(button) {
var cardBody = button.closest('.card-body');
var texto = cardBody.querySelector('.texto-a-copiar').innerText;
var tempInput = document.createElement('textarea');
tempInput.style.position = 'absolute';
tempInput.style.left = '-9999px';
tempInput.value = texto;
document.body.appendChild(tempInput);
tempInput.select();
document.execCommand('copy');
document.body.removeChild(tempInput);
button.innerText = 'Copiado!';
setTimeout(function() {
button.innerText = 'Copiar Texto';
}, 2000);
const cardBody = button.closest('.card-body');
const texto = cardBody.querySelector('.texto-a-copiar').innerText;
const originalText = button.innerHTML;
navigator.clipboard.writeText(texto).then(() => {
button.innerHTML = '<i class="fas fa-check"></i> Copiado!';
button.classList.remove('btn-outline-secondary');
button.classList.add('btn-success');
setTimeout(() => {
button.innerHTML = originalText;
button.classList.remove('btn-success');
button.classList.add('btn-outline-secondary');
}, 2000);
}).catch(err => {
console.error('Error al copiar texto: ', err);
const tempInput = document.createElement('textarea');
tempInput.style.position = 'absolute';
tempInput.style.left = '-9999px';
tempInput.value = texto;
document.body.appendChild(tempInput);
tempInput.select();
try {
document.execCommand('copy');
button.innerHTML = '<i class="fas fa-check"></i> Copiado!';
button.classList.remove('btn-outline-secondary');
button.classList.add('btn-success');
setTimeout(() => {
button.innerHTML = originalText;
button.classList.remove('btn-success');
button.classList.add('btn-outline-secondary');
}, 2000);
} catch (e) {
alert('No se pudo copiar el texto.');
}
document.body.removeChild(tempInput);
});
}
</script>