- Sidebar do backoffice: Backups/Auditoria/IP bans/Geo/Origem das visitas/ Atualizações deixam de ser um link cada (enumerados à mão) e passam a um só link "Sistema" para o índice partilhado do platform-tools (platform.php?tool=index), como a fotografia/design/dance já fazem — uma ferramenta nova no platform-tools passa a aparecer sozinha, sem link novo a escrever aqui. Definições, Mensagens e Comunicações continuam com link próprio (são páginas do fitme, não do router). - Horário de Aulas (home e CrossFit, mesmo componente partilhado): a grelha fica fechada por omissão atrás de um botão "Ver Horário das aulas" (Bootstrap collapse); clicar em "Horário" na navbar (ou abrir a página já com #horario no URL) abre-a de imediato em vez de só rolar até uma secção fechada. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
177 lines
9.1 KiB
PHP
177 lines
9.1 KiB
PHP
<?php
|
|
// Weekly schedule — time-aligned CSS grid with a single-day picker. Shared by
|
|
// the CrossFit page (the home page renders its own copy inline). $data holds the
|
|
// horario rows (site_horario), $schedule_h2 the heading. Mirrors the home grid
|
|
// so the same CSS (.schedule…) and the day-filter JS apply unchanged.
|
|
$week_day = ['Seg'=>'segunda','Ter'=>'terça','Qua'=>'quarta','Qui'=>'quinta','Sex'=>'sexta','Sab'=>'sábado'];
|
|
$rows = $data ?? [];
|
|
|
|
$week_values = array_values($week_day);
|
|
$today_map = [1=>'segunda', 2=>'terça', 3=>'quarta', 4=>'quinta', 5=>'sexta', 6=>'sábado'];
|
|
$default_day = $today_map[(int) date('N')] ?? null;
|
|
if (!in_array($default_day, $week_values, true)) { $default_day = $week_values[0]; }
|
|
|
|
// Distinct start times → grid rows; days → columns. Split Manhã (<12:00) / Tarde.
|
|
$slot_times = [];
|
|
foreach ($rows as $d) { $slot_times[$d['hora_inicio']] = true; }
|
|
ksort($slot_times);
|
|
$slot_times = array_keys($slot_times);
|
|
|
|
$morning = $afternoon = [];
|
|
foreach ($slot_times as $t) { if ((int) substr($t, 0, 2) < 12) { $morning[] = $t; } else { $afternoon[] = $t; } }
|
|
$row_of = []; $sections = []; $gr = 2; // row 1 = day headers
|
|
if ($morning) { $sections[] = ['Manhã', $gr++]; foreach ($morning as $t) { $row_of[$t] = $gr++; } }
|
|
if ($afternoon) { $sections[] = ['Tarde', $gr++]; foreach ($afternoon as $t) { $row_of[$t] = $gr++; } }
|
|
$total_rows = $gr - 1;
|
|
|
|
$grid = [];
|
|
foreach ($rows as $d) { $grid[$d['dia_semana']][$d['hora_inicio']][] = $d; }
|
|
?>
|
|
<div class="schedule fade-in">
|
|
<div class="container">
|
|
<?php if (!empty($schedule_h2)): ?>
|
|
<div class="row justify-content-md-center mb-4">
|
|
<div class="col-lg-8">
|
|
<div class="section-title text-center deco">
|
|
<h2><?= htmlspecialchars($schedule_h2) ?></h2>
|
|
</div>
|
|
<div class="text-center">
|
|
<button type="button" class="site-btn" data-bs-toggle="collapse" data-bs-target="#horarioCollapseCf" aria-expanded="false" aria-controls="horarioCollapseCf">
|
|
Ver Horário das aulas
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<?php endif; ?>
|
|
<div class="collapse" id="horarioCollapseCf">
|
|
<div class="text-center mb-4">
|
|
<button type="button" class="schedule-dl-btn" id="scheduleDownloadCf">
|
|
<i class="fas fa-download" aria-hidden="true"></i> Guardar horário
|
|
</button>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col">
|
|
<div class="schedule_container">
|
|
<div class="schedule_panel" style="--sched-days:<?= count($week_day) ?>;--sched-rows:<?= $total_rows ?>;">
|
|
<?php $col = 0; foreach ($week_day as $key => $value): $col++; ?>
|
|
<div class="schedule_stripe<?= $col % 2 === 0 ? ' is-alt' : '' ?>" aria-hidden="true" style="grid-column:<?= $col ?>;grid-row:1 / -1;"></div>
|
|
<?php endforeach; ?>
|
|
<?php $col = 0; foreach ($week_day as $key => $value): $col++; ?>
|
|
<div class="schedule_dayhead<?= $value === $default_day ? '' : ' sched-hide' ?>" data-day="<?= htmlspecialchars($value) ?>" style="grid-column:<?= $col ?>;grid-row:1;">
|
|
<button type="button" class="sched-nav sched-prev" aria-label="Dia anterior"><i class="fas fa-chevron-left" aria-hidden="true"></i></button>
|
|
<div class="rounded_frame"><?= $key ?></div>
|
|
<button type="button" class="sched-nav sched-next" aria-label="Dia seguinte"><i class="fas fa-chevron-right" aria-hidden="true"></i></button>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
<?php foreach ($sections as $sec): ?>
|
|
<div class="schedule_section_label" data-section="<?= $sec[0] === 'Manhã' ? 'manha' : 'tarde' ?>" style="grid-row:<?= $sec[1] ?>;"><span><?= htmlspecialchars($sec[0]) ?></span></div>
|
|
<?php endforeach; ?>
|
|
<?php $col = 0; foreach ($week_day as $key => $value): $col++; ?>
|
|
<?php foreach ($slot_times as $time): ?>
|
|
<?php if (!empty($grid[$value][$time])): ?>
|
|
<div class="schedule_cell<?= $value === $default_day ? '' : ' sched-hide' ?>" data-day="<?= htmlspecialchars($value) ?>" data-section="<?= ((int) substr($time, 0, 2) < 12) ? 'manha' : 'tarde' ?>" style="grid-column:<?= $col ?>;grid-row:<?= $row_of[$time] ?>;">
|
|
<?php foreach ($grid[$value][$time] as $d): ?>
|
|
<div class="schedule_box" style="background-color:<?= htmlspecialchars((string) $d['cor']) ?>">
|
|
<img src="<?= htmlspecialchars(site_img_url($d['icon'], 'uploads/modalidades/icons')) ?>" alt="<?= htmlspecialchars((string) $d['nome']) ?>" <?= site_img_dims_attr($d['icon'], 'uploads/modalidades/icons') ?> decoding="async">
|
|
<a><b><?= htmlspecialchars((string) $d['nome']) ?></b></a>
|
|
<div class="schedule_info">
|
|
<?= htmlspecialchars((string) $d['hora_inicio']) ?>
|
|
<b><?= htmlspecialchars((string) $d['local']) ?></b>
|
|
</div>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
<?php endforeach; ?>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<script>
|
|
/* Quick "save the weekly schedule as JPG" — replicated from the home page,
|
|
scoped to this partial's .schedule (the CrossFit page has no #horario wrapper).
|
|
Renders the live schedule in a 1920px virtual window and restyles the CLONE
|
|
(onclone) with .schedule-export so the image is always the full desktop week,
|
|
stamped with the generation date. */
|
|
(function () {
|
|
var btn = document.getElementById('scheduleDownloadCf');
|
|
var schedule = btn ? btn.closest('.schedule') : null;
|
|
if (!btn || !schedule) return;
|
|
var H2C_SRC = '<?= base_url('site/assets/js/print/html2canvas.min.js') ?>?v=1.4.1';
|
|
var BOARD_W = 1920;
|
|
|
|
function loadH2C() {
|
|
if (window.html2canvas) return Promise.resolve();
|
|
return new Promise(function (resolve, reject) {
|
|
var s = document.createElement('script');
|
|
s.src = H2C_SRC;
|
|
s.onload = resolve;
|
|
s.onerror = function () { reject(new Error('Falha a carregar html2canvas')); };
|
|
document.head.appendChild(s);
|
|
});
|
|
}
|
|
|
|
btn.addEventListener('click', function () {
|
|
var label = btn.innerHTML;
|
|
btn.disabled = true;
|
|
btn.innerHTML = 'A gerar imagem…';
|
|
|
|
var fontsReady = (document.fonts && document.fonts.ready) ? document.fonts.ready : Promise.resolve();
|
|
|
|
loadH2C().then(function () { return fontsReady; }).then(function () {
|
|
var bg = (getComputedStyle(document.body).getPropertyValue('--fitme-surface') || '#F7F8FA').trim();
|
|
return html2canvas(schedule, {
|
|
backgroundColor: bg,
|
|
scale: 1,
|
|
useCORS: true,
|
|
logging: false,
|
|
windowWidth: BOARD_W,
|
|
windowHeight: 1080,
|
|
scrollX: 0,
|
|
scrollY: 0,
|
|
onclone: function (doc) {
|
|
var board = doc.querySelector('.schedule');
|
|
if (!board) return;
|
|
board.classList.add('schedule-export');
|
|
board.classList.remove('fade-in');
|
|
Array.prototype.forEach.call(board.querySelectorAll('.fade-in, .revealed'), function (el) {
|
|
el.classList.remove('fade-in');
|
|
el.classList.remove('revealed');
|
|
});
|
|
var b = board.querySelector('.schedule-dl-btn'); if (b) { var w = b.closest('div'); (w || b).parentNode.removeChild(w || b); }
|
|
Array.prototype.forEach.call(board.querySelectorAll('.sched-hide'), function (c) { c.classList.remove('sched-hide'); });
|
|
|
|
var stamp = doc.createElement('p');
|
|
stamp.className = 'schedule-stamp';
|
|
stamp.textContent = 'Gerado a ' + new Date().toLocaleDateString('pt-PT') + ' · sujeito a alterações';
|
|
(board.querySelector('.container') || board).appendChild(stamp);
|
|
}
|
|
});
|
|
}).then(function (canvas) {
|
|
return new Promise(function (resolve, reject) {
|
|
canvas.toBlob(function (blob) {
|
|
if (!blob) { reject(new Error('Falha ao gerar a imagem')); return; }
|
|
var url = URL.createObjectURL(blob);
|
|
var a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = 'horario-crossfit-' + new Date().toISOString().slice(0, 10) + '.jpg';
|
|
document.body.appendChild(a); a.click(); document.body.removeChild(a);
|
|
setTimeout(function () { URL.revokeObjectURL(url); }, 1500);
|
|
resolve();
|
|
}, 'image/jpeg', 0.95);
|
|
});
|
|
}).catch(function (err) {
|
|
console.error(err);
|
|
alert('Não foi possível gerar a imagem. Tenta novamente.');
|
|
}).then(function () {
|
|
btn.disabled = false;
|
|
btn.innerHTML = label;
|
|
});
|
|
});
|
|
})();
|
|
</script>
|