Feature: CRUD de categorías + sesiones persistentes
- categorias.html: lista con conteo de movimientos, drawer para agregar/editar - api/categorias.php: GET/POST/PUT/DELETE (DELETE bloqueado si tiene movimientos) - categorias.js: grid de 30 íconos Material Symbols + paleta de 12 colores, preview en vivo - Sidebar actualizado en todas las páginas con link a Categorías - Sesiones PHP guardadas en pagos/db/sessions/ (bind mount) → sobreviven reinicios Docker - Fix selector de categorías en add.html: sesiones persistentes resuelven el Cargando… Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
7cb6107cb4
commit
e98bfa0afb
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
require_once 'auth.php';
|
||||
header('Content-Type: application/json');
|
||||
require_once 'db.php';
|
||||
|
||||
$pdo = getDB();
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
|
||||
if ($method === 'GET') {
|
||||
$stmt = $pdo->query("
|
||||
SELECT c.*, COUNT(m.id) as total_movimientos
|
||||
FROM categorias c
|
||||
LEFT JOIN movimientos m ON m.id_categoria = c.id
|
||||
GROUP BY c.id ORDER BY c.nombre
|
||||
");
|
||||
echo json_encode(['status' => 'success', 'categorias' => $stmt->fetchAll(PDO::FETCH_ASSOC)]);
|
||||
|
||||
} elseif ($method === 'POST') {
|
||||
$d = json_decode(file_get_contents('php://input'), true);
|
||||
if (empty($d['nombre']) || empty($d['icono'])) {
|
||||
http_response_code(400);
|
||||
die(json_encode(['status' => 'error', 'message' => 'Nombre e ícono son obligatorios']));
|
||||
}
|
||||
$stmt = $pdo->prepare("INSERT INTO categorias (nombre, icono, color_hex) VALUES (?, ?, ?)");
|
||||
$stmt->execute([trim($d['nombre']), trim($d['icono']), $d['color_hex'] ?? '#005050']);
|
||||
echo json_encode(['status' => 'success', 'id' => $pdo->lastInsertId()]);
|
||||
|
||||
} elseif ($method === 'PUT') {
|
||||
$d = json_decode(file_get_contents('php://input'), true);
|
||||
if (empty($d['id']) || empty($d['nombre']) || empty($d['icono'])) {
|
||||
http_response_code(400);
|
||||
die(json_encode(['status' => 'error', 'message' => 'id, nombre e ícono son obligatorios']));
|
||||
}
|
||||
$stmt = $pdo->prepare("UPDATE categorias SET nombre=?, icono=?, color_hex=? WHERE id=?");
|
||||
$stmt->execute([trim($d['nombre']), trim($d['icono']), $d['color_hex'] ?? '#005050', (int)$d['id']]);
|
||||
echo json_encode(['status' => 'success']);
|
||||
|
||||
} elseif ($method === 'DELETE') {
|
||||
$d = json_decode(file_get_contents('php://input'), true);
|
||||
if (empty($d['id'])) {
|
||||
http_response_code(400);
|
||||
die(json_encode(['status' => 'error', 'message' => 'Falta el ID']));
|
||||
}
|
||||
$count = $pdo->prepare("SELECT COUNT(*) FROM movimientos WHERE id_categoria = ?");
|
||||
$count->execute([(int)$d['id']]);
|
||||
if ($count->fetchColumn() > 0) {
|
||||
http_response_code(409);
|
||||
die(json_encode(['status' => 'error', 'message' => 'No se puede eliminar: tiene movimientos asociados']));
|
||||
}
|
||||
$pdo->prepare("DELETE FROM categorias WHERE id = ?")->execute([(int)$d['id']]);
|
||||
echo json_encode(['status' => 'success']);
|
||||
|
||||
} else {
|
||||
http_response_code(405);
|
||||
echo json_encode(['status' => 'error', 'message' => 'Método no permitido']);
|
||||
}
|
||||
Reference in New Issue
Block a user