- Nueva tabla calendar_events (tipo, fecha, hora_inicio/fin, ubicación, descripción, creador) — migración 011 - API CRUD /calendario/api.php (list/create/update/delete) con AuditLog - Permisos: cualquiera autenticado crea; solo creador o Socio/Admin edita y elimina - UI calendario: chip "Eventos", botón "+ Nuevo evento" en toolbar y en vista Día, modal con tipo/fecha/horario/ubicación, opción "Todo el día" - Detalle de evento manual muestra creador, horario y ubicación; acciones Editar/Eliminar inline (sin navegación) si tiene permiso - Colores por tipo: reserva sala, cumpleaños, recordatorio, evento
169 lines
6.9 KiB
PHP
169 lines
6.9 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
require_once __DIR__ . '/../../src/Auth.php';
|
|
require_once __DIR__ . '/../../src/Db.php';
|
|
require_once __DIR__ . '/../../src/AuditLog.php';
|
|
|
|
Auth::requireAuth();
|
|
header('Content-Type: application/json; charset=utf-8');
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { http_response_code(204); exit; }
|
|
|
|
const TIPOS = ['reserva_sala','cumpleanos','recordatorio','evento'];
|
|
|
|
function ok($d = null): void { echo json_encode(['ok'=>true, 'data'=>$d, 'user'=>Auth::user()], JSON_UNESCAPED_UNICODE); exit; }
|
|
function err(string $m, int $c = 400): void { http_response_code($c); echo json_encode(['ok'=>false, 'error'=>$m], JSON_UNESCAPED_UNICODE); exit; }
|
|
function body(): array { return json_decode((string) file_get_contents('php://input'), true) ?? []; }
|
|
|
|
function clean_date(?string $v): ?string {
|
|
if (!$v) return null;
|
|
return preg_match('/^\d{4}-\d{2}-\d{2}$/', $v) ? $v : null;
|
|
}
|
|
function clean_time(?string $v): ?string {
|
|
if (!$v) return null;
|
|
return preg_match('/^\d{2}:\d{2}(:\d{2})?$/', $v) ? substr($v, 0, 5) : null;
|
|
}
|
|
|
|
$method = $_SERVER['REQUEST_METHOD'];
|
|
$action = $_GET['action'] ?? '';
|
|
$user = Auth::user();
|
|
$pdo = Db::pdo();
|
|
|
|
try {
|
|
if ($method === 'GET' && $action === 'list') {
|
|
$from = clean_date($_GET['from'] ?? null);
|
|
$to = clean_date($_GET['to'] ?? null);
|
|
$where = ['1=1'];
|
|
$params = [];
|
|
if ($from) { $where[] = 'fecha >= :from'; $params[':from'] = $from; }
|
|
if ($to) { $where[] = 'fecha <= :to'; $params[':to'] = $to; }
|
|
$sql = 'SELECT * FROM calendar_events WHERE ' . implode(' AND ', $where) . ' ORDER BY fecha ASC, hora_inicio ASC';
|
|
$stmt = $pdo->prepare($sql);
|
|
$stmt->execute($params);
|
|
$rows = $stmt->fetchAll();
|
|
foreach ($rows as &$r) { $r['id'] = (int)$r['id']; }
|
|
ok($rows);
|
|
}
|
|
|
|
if ($method === 'POST' && $action === 'create') {
|
|
$b = body();
|
|
$titulo = trim((string)($b['titulo'] ?? ''));
|
|
$descripcion = trim((string)($b['descripcion'] ?? ''));
|
|
$tipo = in_array($b['tipo'] ?? '', TIPOS, true) ? $b['tipo'] : 'evento';
|
|
$fecha = clean_date($b['fecha'] ?? null);
|
|
$horaInicio = clean_time($b['hora_inicio'] ?? null);
|
|
$horaFin = clean_time($b['hora_fin'] ?? null);
|
|
$ubicacion = trim((string)($b['ubicacion'] ?? ''));
|
|
|
|
if ($titulo === '') err('Título requerido');
|
|
if (!$fecha) err('Fecha requerida');
|
|
if ($horaFin && !$horaInicio) err('Si hay hora fin debe haber hora inicio');
|
|
if ($horaInicio && $horaFin && strcmp($horaFin, $horaInicio) < 0) {
|
|
err('Hora fin debe ser >= hora inicio');
|
|
}
|
|
|
|
$stmt = $pdo->prepare(
|
|
'INSERT INTO calendar_events
|
|
(titulo, descripcion, tipo, fecha, hora_inicio, hora_fin, ubicacion, creado_por, creado_por_nombre)
|
|
VALUES
|
|
(:titulo, :desc, :tipo, :fecha, :hi, :hf, :ub, :cp, :cpn)'
|
|
);
|
|
$stmt->execute([
|
|
':titulo' => $titulo,
|
|
':desc' => $descripcion ?: null,
|
|
':tipo' => $tipo,
|
|
':fecha' => $fecha,
|
|
':hi' => $horaInicio,
|
|
':hf' => $horaFin,
|
|
':ub' => $ubicacion ?: null,
|
|
':cp' => $user['email'],
|
|
':cpn' => $user['name'] ?: $user['email'],
|
|
]);
|
|
$id = (int)$pdo->lastInsertId();
|
|
|
|
AuditLog::log('calendario', 'create', 'evento', (string)$id, $titulo, ['tipo'=>$tipo, 'fecha'=>$fecha]);
|
|
|
|
$sel = $pdo->prepare('SELECT * FROM calendar_events WHERE id = :id');
|
|
$sel->execute([':id' => $id]);
|
|
$row = $sel->fetch();
|
|
$row['id'] = (int)$row['id'];
|
|
ok($row);
|
|
}
|
|
|
|
if ($method === 'PUT' && $action === 'update') {
|
|
$b = body();
|
|
$id = (int)($b['_id'] ?? 0);
|
|
if (!$id) err('id requerido');
|
|
|
|
$sel = $pdo->prepare('SELECT * FROM calendar_events WHERE id = :id');
|
|
$sel->execute([':id' => $id]);
|
|
$before = $sel->fetch();
|
|
if (!$before) err('Evento no encontrado', 404);
|
|
|
|
if (!Auth::isSocio() && $before['creado_por'] !== $user['email']) {
|
|
err('Solo el creador (o un Socio/Admin) puede editar este evento', 403);
|
|
}
|
|
|
|
$titulo = trim((string)($b['titulo'] ?? $before['titulo']));
|
|
$descripcion = isset($b['descripcion']) ? trim((string)$b['descripcion']) : ($before['descripcion'] ?? '');
|
|
$tipo = in_array($b['tipo'] ?? $before['tipo'], TIPOS, true) ? ($b['tipo'] ?? $before['tipo']) : $before['tipo'];
|
|
$fecha = clean_date($b['fecha'] ?? null) ?: $before['fecha'];
|
|
$horaInicio = array_key_exists('hora_inicio', $b) ? clean_time($b['hora_inicio']) : $before['hora_inicio'];
|
|
$horaFin = array_key_exists('hora_fin', $b) ? clean_time($b['hora_fin']) : $before['hora_fin'];
|
|
$ubicacion = isset($b['ubicacion']) ? trim((string)$b['ubicacion']) : ($before['ubicacion'] ?? '');
|
|
|
|
if ($titulo === '') err('Título requerido');
|
|
if ($horaFin && !$horaInicio) err('Si hay hora fin debe haber hora inicio');
|
|
if ($horaInicio && $horaFin && strcmp($horaFin, $horaInicio) < 0) {
|
|
err('Hora fin debe ser >= hora inicio');
|
|
}
|
|
|
|
$stmt = $pdo->prepare(
|
|
'UPDATE calendar_events SET
|
|
titulo = :titulo, descripcion = :desc, tipo = :tipo, fecha = :fecha,
|
|
hora_inicio = :hi, hora_fin = :hf, ubicacion = :ub
|
|
WHERE id = :id'
|
|
);
|
|
$stmt->execute([
|
|
':titulo' => $titulo, ':desc' => $descripcion ?: null, ':tipo' => $tipo,
|
|
':fecha' => $fecha, ':hi' => $horaInicio, ':hf' => $horaFin,
|
|
':ub' => $ubicacion ?: null, ':id' => $id,
|
|
]);
|
|
|
|
AuditLog::log('calendario', 'update', 'evento', (string)$id, $titulo);
|
|
|
|
$sel->execute([':id' => $id]);
|
|
$row = $sel->fetch();
|
|
$row['id'] = (int)$row['id'];
|
|
ok($row);
|
|
}
|
|
|
|
if ($method === 'DELETE' && $action === 'delete') {
|
|
$id = (int)($_GET['id'] ?? 0);
|
|
if (!$id) err('id requerido');
|
|
|
|
$sel = $pdo->prepare('SELECT id, titulo, creado_por FROM calendar_events WHERE id = :id');
|
|
$sel->execute([':id' => $id]);
|
|
$before = $sel->fetch();
|
|
if (!$before) err('Evento no encontrado', 404);
|
|
|
|
if (!Auth::isSocio() && $before['creado_por'] !== $user['email']) {
|
|
err('Solo el creador (o un Socio/Admin) puede eliminar este evento', 403);
|
|
}
|
|
|
|
$pdo->prepare('DELETE FROM calendar_events WHERE id = :id')->execute([':id' => $id]);
|
|
AuditLog::log('calendario', 'delete', 'evento', (string)$id, $before['titulo']);
|
|
ok(['deleted' => $id]);
|
|
}
|
|
|
|
err('Acción no reconocida', 404);
|
|
|
|
} catch (PDOException $e) {
|
|
error_log('calendario db error: ' . $e->getMessage());
|
|
err('Error de base de datos', 500);
|
|
} catch (Throwable $e) {
|
|
error_log('calendario error: ' . $e->getMessage());
|
|
err('Error interno', 500);
|
|
}
|