'Segunda','terça'=>'Terça','quarta'=>'Quarta','quinta'=>'Quinta','sexta'=>'Sexta','sábado'=>'Sábado','domingo'=>'Domingo']; $salas = ['sala1'=>'Sala 1','sala2'=>'Sala 2','crossfit'=>'CrossFit',''=>'Outro']; // ─── AJAX: same-page JSON endpoints ─────────────────────────────────────────── // move — drag-and-drop reschedule (day + start/end; modalidade/sala unchanged) // save — modal create (id=0) or full edit of a class // delete — remove a class // All validate CSRF via the X-CSRF-Token header. if ($_SERVER['REQUEST_METHOD'] === 'POST' && in_array($_POST['action'] ?? '', ['move', 'save', 'delete'], true)) { header('Content-Type: application/json; charset=utf-8'); $supplied = $_SERVER['HTTP_X_CSRF_TOKEN'] ?? ($_POST['_csrf'] ?? ''); if (!is_string($supplied) || !hash_equals($_SESSION['csrf_token'] ?? '', $supplied)) { http_response_code(403); echo json_encode(['ok' => false, 'error' => 'Sessão expirada']); exit; } $fail = static function (int $code, string $msg): void { http_response_code($code); echo json_encode(['ok' => false, 'error' => $msg]); exit; }; $hhmm = '/^([01]\d|2[0-3]):[0-5]\d$/'; try { switch ($_POST['action']) { case 'move': $id = (int) ($_POST['id'] ?? 0); $dia = (string) ($_POST['dia'] ?? ''); $hi = (string) ($_POST['hora_inicio'] ?? ''); $hf = (string) ($_POST['hora_fim'] ?? ''); if ($id < 1 || !array_key_exists($dia, $dias) || !preg_match($hhmm, $hi) || !preg_match($hhmm, $hf)) { $fail(422, 'Dados inválidos'); } db_horario_move($pdo, $id, $dia, $hi . ':00', $hf . ':00'); echo json_encode(['ok' => true]); break; case 'delete': $id = (int) ($_POST['id'] ?? 0); if ($id < 1) $fail(422, 'Dados inválidos'); db_horario_delete($pdo, $id); echo json_encode(['ok' => true]); break; case 'save': $id = (int) ($_POST['id'] ?? 0); // 0 = criar nova aula $mod = (int) ($_POST['id_modalidade'] ?? 0); $dia = (string) ($_POST['dia'] ?? ''); $hi = (string) ($_POST['hora_inicio'] ?? ''); $hf = (string) ($_POST['hora_fim'] ?? ''); $local = trim((string) ($_POST['local'] ?? '')); if ($id < 0 || !array_key_exists($dia, $dias)) $fail(422, 'Dados inválidos'); if (!preg_match($hhmm, $hi) || !preg_match($hhmm, $hf)) $fail(422, 'Horas inválidas'); if ($hf <= $hi) $fail(422, 'A hora de fim deve ser depois do início'); if (mb_strlen($local) > 50) $fail(422, 'Sala inválida'); if ($mod < 1 || !db_modalidades_get($pdo, $mod)) $fail(422, 'Modalidade inválida'); $payload = [ 'id_modalidade' => $mod, 'dia_semana' => $dia, 'hora_inicio' => $hi . ':00', 'hora_fim' => $hf . ':00', 'local' => $local, ]; if ($id === 0) $id = db_horario_create($pdo, $payload); else db_horario_update($pdo, $id, $payload); echo json_encode(['ok' => true, 'id' => $id]); break; } } catch (Throwable $e) { http_response_code(500); echo json_encode(['ok' => false, 'error' => 'Erro ao guardar']); } exit; } // ─── Build the calendar model ───────────────────────────────────────────────── $items = db_horario_list($pdo); $modalidades = db_modalidades_options($pdo); $to_min = static function ($t): ?int { if (!is_string($t) || !preg_match('/^(\d{1,2}):(\d{2})/', $t, $m)) return null; return ((int) $m[1]) * 60 + (int) $m[2]; }; $events = []; $minStart = 8 * 60; // default window 08:00–21:00, widened to fit the data $maxEnd = 21 * 60; foreach ($items as $it) { $s = $to_min($it['hora_inicio_fmt'] ?? $it['hora_inicio'] ?? null); if ($s === null) continue; $e = $to_min($it['hora_fim_fmt'] ?? $it['hora_fim'] ?? null); if ($e === null || $e <= $s) $e = $s + 60; $minStart = min($minStart, $s); $maxEnd = max($maxEnd, $e); $events[] = [ 'id' => (int) $it['id'], 'dia' => (string) $it['dia_semana'], 'start' => $s, 'end' => $e, 'mod' => (int) $it['id_modalidade'], 'nome' => (string) ($it['modalidade_nome'] ?? ''), 'cor' => ($it['modalidade_cor'] ?? '') ?: '#6c757d', 'sala' => (string) ($it['local'] ?? ''), ]; } // Sala options for the filter + modal: the known set plus any legacy values in use. $locais = $salas; foreach ($events as $ev) { if ($ev['sala'] !== '' && !isset($locais[$ev['sala']])) $locais[$ev['sala']] = $ev['sala']; } $rangeStart = max(0, intdiv($minStart, 60) * 60); // floor to the hour $rangeEnd = (int) min(24 * 60, ceil($maxEnd / 60) * 60); // ceil to the hour if ($rangeEnd <= $rangeStart) $rangeEnd = $rangeStart + 60; $hourpx = 52; $startHour = intdiv($rangeStart, 60); $endHour = intdiv($rangeEnd, 60); $bodyH = (int) round((($rangeEnd - $rangeStart) / 60) * $hourpx); $byDay = array_fill_keys(array_keys($dias), []); foreach ($events as $ev) { $byDay[$ev['dia']][] = $ev; } $mods_js = []; foreach ($modalidades as $m) { $mods_js[(string) $m['m_id']] = ['nome' => $m['nome'], 'cor' => ($m['cor'] ?? '') ?: '#6c757d']; } $page_title = 'Horário — Calendário'; include __DIR__ . '/../../../includes/header.php'; ?>

Horário de Aulas

Arraste uma aula para reagendar, ou uma modalidade do painel para criar. Clique numa aula para editar.

Ainda não há aulas. Arraste uma modalidade do painel para o calendário para criar a primeira.

Modalidades

Arraste para uma hora do calendário

$label): ?>
–