One Hat Cyber Team
Your IP :
104.23.243.58
Server IP :
104.21.51.23
Server :
Linux 128-201-239-36.cprapid.com 3.10.0-1160.41.1.el7.x86_64 #1 SMP Tue Aug 31 14:52:47 UTC 2021 x86_64
Server Software :
Apache
PHP Version :
7.4.33
Buat File
|
Buat Folder
Eksekusi
Dir :
~
/
home
/
juscatamarca
/
www
/
asuetos-feriados
/
public
/
Edit File:
index.php
<?php include __DIR__ . '/partials/header.php'; require_once __DIR__ . '/../app/auth.php'; $me = current_user(); // null si no hay sesión require_once __DIR__ . '/../app/calendar.php'; // ===== Navegación base (fallback si no hay filtros) ===== $year = max(1970, (int)($_GET['y'] ?? (int)date('Y'))); $month = min(12, max(1, (int)($_GET['m'] ?? (int)date('n')))); // ===== Filtros de búsqueda ===== $q = trim($_GET['q'] ?? ''); $type = $_GET['type'] ?? ''; $from = $_GET['from'] ?? ''; $to = $_GET['to'] ?? ''; // ===== Si hay filtros, forzar año/mes de la grilla al del filtro ===== if ($from && preg_match('/^(\d{4})-(\d{2})-\d{2}$/', $from, $m)) { $year = (int)$m[1]; $month = (int)$m[2]; } elseif ($to && preg_match('/^(\d{4})-(\d{2})-\d{2}$/', $to, $m)) { $year = (int)$m[1]; $month = (int)$m[2]; } // ===== Fechas del mes visible ===== $firstDay = new DateTime(sprintf('%04d-%02d-01', $year, $month)); $startOfMonth = $firstDay->format('Y-m-d'); $endOfMonth = (clone $firstDay)->modify('last day of this month')->format('Y-m-d'); $titleYear = $year; // ===== Query no recurrentes por rango real ===== $events = []; try { $params = [':to' => $endOfMonth, ':from' => $startOfMonth]; $sql = "SELECT * FROM holidays WHERE is_recurring = 0 AND start_date <= :to AND (end_date IS NULL OR end_date >= :from)"; if ($q !== '') { $sql .= " AND (title LIKE :q OR description LIKE :q OR scope LIKE :q)"; $params[':q'] = "%$q%"; } if ($type === 'FERIADO' || $type === 'ASUETO') { $sql .= " AND type = :type"; $params[':type'] = $type; } if ($from !== '' && preg_match('/^\d{4}-\d{2}-\d{2}$/', $from)) { $sql .= " AND (end_date IS NULL OR end_date >= :fromFilter)"; $params[':fromFilter'] = $from; } if ($to !== '' && preg_match('/^\d{4}-\d{2}-\d{2}$/', $to)) { $sql .= " AND start_date <= :toFilter"; $params[':toFilter'] = $to; } $stmt = $pdo->prepare($sql . ' ORDER BY start_date ASC, end_date ASC, title ASC'); $stmt->execute($params); $events = $stmt->fetchAll() ?: []; } catch (Throwable $e) { $events = []; } // ===== Indexar eventos por día ===== $eventsByDate = []; foreach ($events as $ev) { $start = new DateTime($ev['start_date']); $end = !empty($ev['end_date']) ? new DateTime($ev['end_date']) : new DateTime($ev['start_date']); $s = (new DateTime($startOfMonth)) < $start ? clone $start : new DateTime($startOfMonth); $e = (new DateTime($endOfMonth)) > $end ? clone $end : new DateTime($endOfMonth); for ($d = clone $s; $d <= $e; $d->modify('+1 day')) { $eventsByDate[$d->format('Y-m-d')][] = $ev; } } // ===== Recurrentes ===== try { $recParams = []; $sqlRec = "SELECT * FROM holidays WHERE is_recurring = 1"; if ($q !== '') { $sqlRec .= " AND (title LIKE :rq OR description LIKE :rq OR scope LIKE :rq)"; $recParams[':rq'] = "%$q%"; } if ($type === 'FERIADO' || $type === 'ASUETO') { $sqlRec .= " AND type = :rtype"; $recParams[':rtype'] = $type; } $stmtRec = $pdo->prepare($sqlRec . ' ORDER BY start_date ASC, end_date ASC, title ASC'); $stmtRec->execute($recParams); $recurring = $stmtRec->fetchAll() ?: []; $monthDays = []; $cursor = new DateTime($startOfMonth); $endIter = new DateTime($endOfMonth); for ($d = $cursor; $d <= $endIter; $d->modify('+1 day')) { $monthDays[] = clone $d; } foreach ($recurring as $ev) { $startMD = substr($ev['start_date'], 5); $endMD = !empty($ev['end_date']) ? substr($ev['end_date'], 5) : $startMD; foreach ($monthDays as $day) { $md = $day->format('m-d'); $inRange = ($startMD <= $endMD) ? ($md >= $startMD && $md <= $endMD) : ($md >= $startMD || $md <= $endMD); if ($inRange) { $key = $day->format('Y-m-d'); $evProjected = $ev; $evProjected['start_date'] = $day->format('Y-m-d'); $eventsByDate[$key][] = $evProjected; } } } } catch (Throwable $e) { } // ===== Grilla y navegación ===== $weeks = build_month_grid($year, $month); $prev = (new DateTime(sprintf('%04d-%02d-01', $year, $month)))->modify('-1 month'); $next = (new DateTime(sprintf('%04d-%02d-01', $year, $month)))->modify('+1 month'); $meses = [1 => 'Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre']; ?> <!doctype html> <html lang="es"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Calendario — Asuetos & Feriados</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"> <style> .day-cell { min-height: 120px; } .day-out { background: #f8f9fa; } .event-dot { display: inline-block; width: 8px; height: 8px; border-radius: 999px; margin-right: 6px; vertical-align: middle; } .event-badge { font-size: .85rem; } .day-num { font-weight: 600; } .sticky-top-custom { position: sticky; top: 0; z-index: 1020; } /* === Ajustes responsive sin cambiar el diseño en desktop === */ /* Buscador ancho completo en pantallas chicas */ #searchTitle { width: 100%; } @media (min-width: 768px) { #searchTitle { width: 350px; } } /* Form de filtros apilable en mobile */ .filters-wrap { gap: .5rem; } @media (max-width: 991.98px) { .filters-wrap { flex-wrap: wrap; } .btn-outline-secondary { width: 100% !important; } } /* Celdas un poco más compactas en móvil */ @media (max-width: 576px) { .day-cell { min-height: 90px; } .event-badge { font-size: .8rem; } } /* Modal: mantener tu estilo, pero corregir recorte en móviles */ .modal-dialog { top: 120px !important; } @media (max-width: 576px) { .modal-dialog { top: 60px !important; margin: 0 .5rem; } } .navbar-toggler-icon { margin-bottom: 20px !important; } .navbar-toggler-icon:active { border: none !important; } </style> </head> <body class="bg-light"> <nav class="navbar navbar-expand-lg navbar-light bg-white border-bottom sticky-top-custom"> <div class="container-fluid"> <a class="navbar-brand" href="<?= base_url() ?>/index.php">Asuetos y Feriados</a> <!-- Toggler --> <button class="navbar-toggler border-0" type="button" data-bs-toggle="collapse" data-bs-target="#topNav" aria-controls="topNav" aria-expanded="false" aria-label="Mostrar navegación"> <span class="navbar-toggler-icon"></span> </button> <div id="topNav" class="collapse navbar-collapse"> <!-- Wrapper que se apila en mobile --> <div class="d-flex flex-column flex-lg-row ms-auto align-items-stretch align-items-lg-center gap-2 filters-wrap w-100"> <!-- Form: 100% ancho en mobile --> <form class="d-flex flex-column flex-sm-row align-items-stretch gap-2 w-100" role="search" method="get" action="<?= base_url() ?>/index.php"> <input type="hidden" name="y" value="<?= (int)$year ?>"> <input type="hidden" name="m" value="<?= (int)$month ?>"> <div class="position-relative flex-grow-1"> <input id="searchTitle" class="form-control" type="search" name="q" placeholder="Buscar título / descripción / alcance" autocomplete="off" value="<?= htmlspecialchars($q) ?>"> <div id="suggestBox" class="dropdown-menu w-100" style="max-height:260px;overflow:auto;"></div> </div> <select name="type" class="form-select"> <option value="">Todos</option> <option value="FERIADO" <?= $type === 'FERIADO' ? 'selected' : '' ?>>Feriados</option> <option value="ASUETO" <?= $type === 'ASUETO' ? 'selected' : '' ?>>Asuetos</option> </select> <input type="date" name="from" class="form-control" value="<?= htmlspecialchars($from) ?>"> <!-- <input type="date" name="to" class="form-control" value="<?= htmlspecialchars($to) ?>"> --> <button class="btn btn-outline-primary">Buscar</button> </form> <!-- Separador visible solo en desktop --> <div class="vr d-none d-lg-block"></div> <!-- Usuario / Auth: 100% ancho en mobile para bajar debajo del form --> <div class="d-flex justify-content-lg-end w-100"> <?php if ($me): ?> <div class="dropdown w-100 w-lg-auto"> <button class="btn btn-outline-secondary dropdown-toggle w-100 w-lg-auto" data-bs-toggle="dropdown" aria-expanded="false"> <?= htmlspecialchars($me['name']) ?> </button> <ul class="dropdown-menu dropdown-menu-end"> <li> <a class="dropdown-item" href="<?= base_url() ?>/logout.php">Salir</a> </li> </ul> </div> <?php else: ?> <a class="btn btn-primary w-100 w-lg-auto" href="<?= base_url() ?>/login.php">Ingresar</a> <?php endif; ?> </div> </div> </div> </div> </nav> <div class="container my-4"> <div class="d-flex flex-wrap justify-content-between align-items-center mb-3 gap-2"> <h3 class="mb-0">Calendario de Asuetos y Feriados</h3> <?php if ($me && $me['role'] === 'admin'): ?> <a href="<?= base_url() ?>/holidays_new.php" class="btn btn-success">➕ Agregar evento</a> <?php endif; ?> </div> <div class="d-flex flex-column flex-md-row justify-content-between align-items-center mb-3 gap-2"> <a class="btn btn-outline-secondary w-md-auto" href="<?= base_url() ?>/index.php?y=<?= (int)$prev->format('Y') ?>&m=<?= (int)$prev->format('n') ?>">« Mes anterior</a> <h2 class="h4 mb-0 text-center flex-grow-1"><?= $meses[$month] . ' de ' . $titleYear ?></h2> <a class="btn btn-outline-secondary w-md-auto" href="<?= base_url() ?>/index.php?y=<?= (int)$next->format('Y') ?>&m=<?= (int)$next->format('n') ?>">Mes siguiente »</a> </div> <?php // ... arriba de la grilla: $visibleBlock = sprintf('%04d-%02d', $year, $month); // mes que quiero mostrar (YYYY-MM) ?> <?php // Abrimos bloques por mes dinámicamente (YYYY-MM) $currentBlock = null; ?> <?php foreach ($weeks as $week): ?> <?php $week = array_values($week); if (isset($week[0]['date']) && $week[0]['date'] instanceof DateTime) { $firstDow = (int)$week[0]['date']->format('N'); // 1=Lun ... 7=Dom if ($firstDow === 7) { $week = array_merge(array_slice($week, 1), [$week[0]]); } } ?> <?php foreach ($week as $cell): $d = $cell['date']; $key = $d->format('Y-m-d'); ?> <?php $blockKey = $d->format('Y-m'); // mes del casillero if ($blockKey !== $visibleBlock) { // ⬅️ FILTRO: solo el mes visible continue; } $mesIdx = (int)$d->format('n'); $anio = $d->format('Y'); $mesYAnio = $meses[$mesIdx] . ' ' . $anio; ?> <?php if ($currentBlock !== $blockKey): ?> <?php if ($currentBlock !== null): ?> </div> <!-- /row del bloque anterior --> <?php endif; ?> <div class="row row-cols-2 row-cols-sm-3 row-cols-md-4 row-cols-lg-7 g-2 mb-2"> <?php $currentBlock = $blockKey; ?> <?php endif; ?> <div class="col"> <div class="border rounded p-2 day-cell <?= $cell['in_month'] ? '' : 'day-out' ?>"> <div class="d-flex justify-content-between align-items-baseline"> <span class="day-num"><?= (int)$d->format('j') ?></span> <span class="text-muted d-lg-none" style="font-size:.75rem;"> <?= ['Lun', 'Mar', 'Mié', 'Jue', 'Vie', 'Sáb', 'Dom'][$d->format('N') - 1] ?> </span> <small class="text-muted ms-2 d-none d-sm-inline" title="<?= htmlspecialchars($d->format('Y-m-d')) ?>"> <?= htmlspecialchars($mesYAnio) ?> </small> </div> <div class="mt-2"> <?php if (!empty($eventsByDate[$key])): ?> <?php foreach ($eventsByDate[$key] as $ev): ?> <div class="event-badge mb-1"> <button type="button" class="btn btn-link p-0 text-start event-open" data-bs-toggle="modal" data-bs-target="#eventModal" data-id="<?= (int)$ev['id'] ?>" data-title="<?= htmlspecialchars($ev['title']) ?>" data-type="<?= htmlspecialchars($ev['type']) ?>" data-scope="<?= htmlspecialchars($ev['scope'] ?? '') ?>" data-desc="<?= htmlspecialchars($ev['description'] ?? '') ?>" data-start="<?= htmlspecialchars($ev['start_date']) ?>" data-end="<?= htmlspecialchars($ev['end_date'] ?? '') ?>" data-color="<?= htmlspecialchars($ev['color'] ?? '#0d6efd') ?>"> <span class="event-dot" style="background:<?= htmlspecialchars($ev['color'] ?? '#0d6efd') ?>"></span> <span class="badge bg-light text-dark border"><?= htmlspecialchars($ev['type']) ?></span> <?= htmlspecialchars($ev['title']) ?> </button> </div> <?php if ($me && $me['role'] === 'admin'): ?> <div class="mb-2"> <a href="<?= base_url() ?>/holidays_edit.php?id=<?= $ev['id'] ?>" class="btn btn-sm btn-outline-primary">Editar</a> <a href="<?= base_url() ?>/holidays_delete.php?id=<?= $ev['id'] ?>" class="btn btn-sm btn-outline-danger">Eliminar</a> </div> <?php endif; ?> <?php endforeach; ?> <?php else: ?> <span class="text-muted" style="font-size:.85rem;">—</span> <?php endif; ?> </div> </div> </div> <?php endforeach; ?> <?php endforeach; ?> <?php if ($currentBlock !== null): ?> </div> <!-- /row del último bloque --> <?php endif; ?> <div class="alert alert-info mt-4"><strong>Tip:</strong> Podés filtrar por texto, tipo y rango de fechas arriba.</div> </div> <!-- Modal detalle de evento --> <div class="modal fade" id="eventModal" tabindex="-1" aria-hidden="true"> <div class="modal-dialog modal-dialog-scrollable"> <div class="modal-content"> <div class="modal-header"> <h5 id="evTitle" class="modal-title">Detalle</h5> <button type="button" class="btn-close" data-bs-dismiss="modal"></button> </div> <div class="modal-body"> <div class="mb-2"> <span id="evType" class="badge bg-light text-dark border">Tipo</span> <span id="evScope" class="badge bg-secondary">Ámbito</span> </div> <div class="mb-2"> <span class="badge" id="evColor" style="background:#0d6efd;"> </span> <span id="evRange" class="text-muted ms-2"></span> </div> <p id="evDesc" class="mb-0 text-break"></p> </div> <div class="modal-footer"> <?php if ($me && $me['role'] === 'admin'): ?> <a id="evEdit" href="#" class="btn btn-outline-primary">Editar</a> <a id="evDelete" href="#" class="btn btn-outline-danger">Eliminar</a> <?php endif; ?> <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cerrar</button> </div> </div> </div> </div> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script> <script> (function() { // Autocomplete const input = document.getElementById('searchTitle'); const box = document.getElementById('suggestBox'); const form = input ? input.closest('form') : null; let timer = null; function clearSuggest() { box.innerHTML = ''; box.classList.remove('show'); } function renderSuggest(items) { if (!items.length) { clearSuggest(); return; } box.innerHTML = items.map(it => ` <button type="button" class="dropdown-item suggest-item" data-title="${it.title}"> <strong>${it.title}</strong> <small class="text-muted ms-2">${it.type}${it.range?' · '+it.range:''}</small> </button>`).join(''); box.classList.add('show'); } async function fetchSuggest(q) { try { const resp = await fetch('<?= base_url() ?>/api_suggest.php?q=' + encodeURIComponent(q)); if (!resp.ok) { clearSuggest(); return; } const data = await resp.json(); renderSuggest(Array.isArray(data) ? data : []); } catch (e) { clearSuggest(); } } if (input && box) { input.addEventListener('input', () => { const q = input.value.trim(); if (timer) clearTimeout(timer); if (q.length < 2) { clearSuggest(); return; } timer = setTimeout(() => fetchSuggest(q), 200); }); document.addEventListener('click', (e) => { if (box.contains(e.target)) { const btn = e.target.closest('.suggest-item'); if (btn) { input.value = btn.dataset.title || ''; clearSuggest(); if (form) form.submit(); } } else if (!input.contains(e.target)) { clearSuggest(); } }); } // Modal populate document.addEventListener('click', (e) => { const btn = e.target.closest('.event-open'); if (!btn) return; const title = btn.dataset.title || ''; const type = btn.dataset.type || ''; const scope = btn.dataset.scope || ''; const desc = btn.dataset.desc || ''; const start = btn.dataset.start || ''; const end = btn.dataset.end || ''; const color = btn.dataset.color || '#0d6efd'; const id = btn.dataset.id || ''; document.getElementById('evTitle').textContent = title; document.getElementById('evType').textContent = type || '—'; const evScope = document.getElementById('evScope'); evScope.textContent = scope || '—'; evScope.classList.toggle('d-none', !scope); document.getElementById('evDesc').textContent = desc || '—'; document.getElementById('evRange').textContent = start ? (end ? `${start} → ${end}` : start) : '—'; document.getElementById('evColor').style.background = color; <?php if ($me && $me['role'] === 'admin'): ?> const evEdit = document.getElementById('evEdit'); const evDelete = document.getElementById('evDelete'); if (evEdit) evEdit.href = '<?= base_url() ?>/holidays_edit.php?id=' + encodeURIComponent(id); if (evDelete) evDelete.href = '<?= base_url() ?>/holidays_delete.php?id=' + encodeURIComponent(id); <?php endif; ?> }); })(); </script> </body> </html> <?php include __DIR__ . '/partials/footer.php'; ?>
Simpan