Files
fitme3070.pt/includes/header.php
T

211 lines
14 KiB
PHP

<?php
// Shared admin shell — open. Requires $user (from require_login/admin/staff/sysadmin)
// and optionally $page_title for the <title> tag.
if (!defined('ADMIN_SHELL_OPENED')) define('ADMIN_SHELL_OPENED', true);
$page_title = $page_title ?? 'Painel';
$user = $user ?? current_user() ?? ['name' => 'Visitante', 'role' => 'client'];
$app_name = defined('APP_NAME') ? APP_NAME : 'FitMe 3070';
?><!doctype html>
<html lang="pt-PT">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title><?= htmlspecialchars($page_title) ?> — <?= htmlspecialchars($app_name) ?></title>
<meta name="color-scheme" content="light dark">
<meta name="theme-color" content="#eef0f4" media="(prefers-color-scheme: light)">
<meta name="theme-color" content="#0a0a0a" media="(prefers-color-scheme: dark)">
<script>
// Tema antes do CSS, para não piscar o tema errado. Escolha guardada no
// navegador (claro / escuro / auto); auto segue o sistema operativo.
(function () {
var pref = 'auto';
try { pref = localStorage.getItem('bo-theme') || 'auto'; } catch (e) {}
var dark = pref === 'dark' || (pref === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches);
document.documentElement.setAttribute('data-bs-theme', dark ? 'dark' : 'light');
document.documentElement.setAttribute('data-theme-pref', pref);
})();
</script>
<script>
// Estado da barra lateral antes da primeira pintura (senão cada página salta
// de 264 para 80px): modo mini no <html> e grupos fechados em data-bo-groups.
try{var d=document.documentElement;if(localStorage.getItem('bo-sidebar-mini')==='1')d.classList.add('bo-mini');
var g=JSON.parse(localStorage.getItem('bo-nav-groups')||'{}');d.setAttribute('data-bo-groups',Object.keys(g).filter(function(k){return g[k]}).join(' '));}catch(e){}
</script>
<link href="<?= base_url('assets/vendor/bootstrap5/css/bootstrap.min.css') ?>" rel="stylesheet">
<link href="<?= htmlspecialchars(_asset_versioned('assets/css/bs-brand.css'), ENT_QUOTES) ?>" rel="stylesheet">
<link href="<?= base_url('assets/vendor/bootstrap-icons/bootstrap-icons.css') ?>" rel="stylesheet">
<link href="<?= base_url('assets/vendor/summernote/summernote-bs5.min.css') ?>" rel="stylesheet">
<link href="<?= htmlspecialchars(_asset_versioned('assets/css/backoffice.css'), ENT_QUOTES) ?>" rel="stylesheet">
<?php include __DIR__ . '/env_ribbon.php'; // moldura de aviso fora de produção ?>
</head>
<body>
<?php require_once __DIR__ . '/nav_helpers.php'; $dash = bo_dashboard_path(); ?>
<aside class="sidebar" id="sidebar" aria-label="Menu principal">
<div class="bo-sidebar__head">
<button type="button" class="bo-sidebar__toggle" data-sidebar-toggle
aria-label="Reduzir menu" aria-expanded="true" aria-controls="sidebar">
<i class="bi bi-list" aria-hidden="true"></i>
</button>
<a class="bo-sidebar__brand" href="<?= base_url($dash) ?>"><i class="bi bi-lightning-charge me-2" aria-hidden="true"></i><?= htmlspecialchars($app_name) ?></a>
<?php // Único sítio que abre o site público (nova aba). ?>
<a class="bo-sidebar__site" href="<?= base_url('/') ?>" target="_blank" rel="noopener"
title="Ver site público" aria-label="Ver site público"><i class="bi bi-box-arrow-up-right" aria-hidden="true"></i></a>
</div>
<div class="dropdown bo-sidebar__user">
<button class="bo-user-menu dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false"
title="<?= htmlspecialchars($user['name']) ?>">
<span class="bo-user-menu__avatar" aria-hidden="true"><?= htmlspecialchars(bo_initials((string) $user['name'])) ?></span>
<span class="bo-user-menu__text">
<span class="bo-user-menu__name"><?= htmlspecialchars($user['name']) ?></span>
<span class="badge rounded-pill badge-role-<?= htmlspecialchars($user['role']) ?>"><?= htmlspecialchars(role_label($user['role'])) ?></span>
</span>
</button>
<ul class="dropdown-menu">
<li class="px-3 pt-2 pb-1"><div class="fw-semibold small lh-sm"><?= htmlspecialchars($user['name']) ?></div></li>
<li><hr class="dropdown-divider my-1"></li>
<li><a class="dropdown-item" href="<?= base_url('views/user/profile.php') ?>"><i class="bi bi-person me-2" aria-hidden="true"></i>Perfil</a></li>
<li><a class="dropdown-item text-danger" href="<?= base_url('views/auth/logout.php') ?>"><i class="bi bi-box-arrow-right me-2" aria-hidden="true"></i>Terminar sessão</a></li>
</ul>
</div>
<nav class="bo-sidebar__nav" aria-label="Navegação">
<?= bo_nav_link($dash, 'bi-speedometer2', 'Painel', ['mod' => 'primary']) ?>
<?php if (is_admin()): ?>
<?php bo_nav_group_start('gestao', 'Gestão', 'bi-briefcase'); ?>
<?php bo_nav_group_start('site', 'Site', 'bi-window', true); ?>
<?= bo_nav_link('views/admin/gyms/index.php', 'bi-buildings', 'Ginásios', ['mod' => 'sub2', 'match' => 'views/admin/gyms/']) ?>
<?= bo_nav_link('views/admin/modalidades/index.php', 'bi-grid-3x3-gap', 'Modalidades', ['mod' => 'sub2', 'match' => 'views/admin/modalidades/']) ?>
<?= bo_nav_link('views/admin/galerias/index.php', 'bi-images', 'Galerias', ['mod' => 'sub2', 'match' => 'views/admin/galerias/']) ?>
<?= bo_nav_link('views/admin/events/index.php', 'bi-megaphone', 'Eventos', ['mod' => 'sub2', 'match' => 'views/admin/events/']) ?>
<?= bo_nav_link('views/admin/info/edit.php', 'bi-telephone', 'Contactos', ['mod' => 'sub2', 'match' => 'views/admin/info/']) ?>
<?= bo_nav_link('views/admin/reviews/index.php', 'bi-star', 'Reviews Google', ['mod' => 'sub2', 'match' => 'views/admin/reviews/']) ?>
<?php bo_nav_group_end(); ?>
<?php bo_nav_group_start('horarios', 'Horários', 'bi-clock-history', true); ?>
<?= bo_nav_link('views/admin/work_hours/index.php', 'bi-clock', 'Horário de Funcionamento', ['mod' => 'sub2', 'match' => 'views/admin/work_hours/']) ?>
<?= bo_nav_link('views/admin/horario/index.php', 'bi-calendar-week', 'Horário de Aulas', ['mod' => 'sub2', 'match' => 'views/admin/horario/']) ?>
<?php bo_nav_group_end(); ?>
<?php bo_nav_group_start('comercial', 'Comercial', 'bi-cash-coin', true); ?>
<?= bo_nav_link('views/admin/memberships/index.php', 'bi-credit-card-2-front', 'Mensalidades', ['mod' => 'sub2', 'match' => 'views/admin/memberships/']) ?>
<?php $__tn = function_exists('db_trial_leads_count_new') ? db_trial_leads_count_new($pdo, admin_gym_id()) : 0; ?>
<?= bo_nav_link('views/admin/trials/index.php', 'bi-person-check', 'Aulas experimentais', ['mod' => 'sub2', 'match' => 'views/admin/trials/', 'badge' => $__tn]) ?>
<?= bo_nav_link('views/admin/team/index.php', 'bi-person-workspace', 'Equipa', ['mod' => 'sub2', 'match' => 'views/admin/team/']) ?>
<?php bo_nav_group_end(); ?>
<?php bo_nav_group_start('legal', 'Legal', 'bi-shield-check', true); ?>
<?= bo_nav_link('views/admin/rgpd/index.php', 'bi-file-earmark-lock', 'Pedidos RGPD', ['mod' => 'sub2', 'match' => 'views/admin/rgpd/']) ?>
<?= bo_nav_link('views/admin/trash/index.php', 'bi-trash3', 'Lixeira', ['mod' => 'sub2', 'match' => 'views/admin/trash/']) ?>
<?php bo_nav_group_end(); ?>
<?php bo_nav_group_end(); ?>
<?php bo_nav_group_start('utilizadores', 'Utilizadores', 'bi-people'); ?>
<?= bo_nav_link('views/user/index.php', 'bi-person-vcard', 'Utilizadores', ['mod' => 'sub', 'match' => ['views/user/index.php', 'views/user/edit.php']]) ?>
<?php bo_nav_group_end(); ?>
<?php endif; ?>
<?php if (is_sysadmin()): ?>
<?php bo_nav_group_start('sistema', 'Sistema', 'bi-gear'); ?>
<?= bo_nav_link('views/sysadmin/system_settings.php', 'bi-sliders', 'Definições', ['mod' => 'sub']) ?>
<?= bo_nav_link('views/sysadmin/messages.php', 'bi-envelope-paper', 'Mensagens', ['mod' => 'sub']) ?>
<?= bo_nav_link('views/sysadmin/comms.php', 'bi-send', 'Comunicações', ['mod' => 'sub']) ?>
<?php // Backups/Auditoria/IP bans/Geo/Origem das visitas/Atualizações/Hub: um só
// link para o índice partilhado do platform-tools (como a fotografia/design/
// dance fazem), em vez de um por ferramenta — uma ferramenta nova no
// platform-tools passa a aparecer sozinha, sem precisar de link novo aqui. ?>
<?= bo_nav_link('platform.php?tool=index', 'bi-cpu', 'Sistema', ['mod' => 'sub',
'active' => (($_GET['tool'] ?? 'index') === 'index' && str_contains($_SERVER['REQUEST_URI'] ?? '', 'platform.php'))]) ?>
<?php bo_nav_group_end(); ?>
<?php endif; ?>
</nav>
</aside>
<div class="sidebar-backdrop" id="sidebarBackdrop" aria-hidden="true"></div>
<main>
<div class="topbar">
<?php // Só em telemóvel: abre a gaveta (a barra está fora do ecrã). ?>
<button type="button" class="menu-toggle" data-sidebar-toggle aria-label="Abrir menu" aria-expanded="false" aria-controls="sidebar">
<i class="bi bi-list" aria-hidden="true"></i>
</button>
<?php
// Which gym the content pages edit (functions_gyms.php). Switching goes back
// to the section's list — an open record may not exist in the other gym.
if (is_admin() && function_exists('db_gyms_list') && ($hdr_gyms = db_gyms_list($pdo))):
$hdr_cur = admin_gym_id();
$hdr_dir = trim(str_replace(chr(92), '/', dirname($_SERVER['SCRIPT_NAME'] ?? '')), '/');
$hdr_back = (str_starts_with($hdr_dir, 'views/') && is_file(dirname(__DIR__) . '/' . $hdr_dir . '/index.php'))
? $hdr_dir . '/index.php' : 'views/admin/dashboard.php';
?>
<form method="POST" action="<?= base_url('views/admin/gyms/switch.php') ?>" class="gym-switch d-flex align-items-center gap-2 ms-2">
<?= csrf_field() ?>
<input type="hidden" name="back" value="<?= htmlspecialchars($hdr_back) ?>">
<label for="gymSwitch" class="small text-muted text-nowrap mb-0"><i class="bi bi-buildings"></i> A editar</label>
<select id="gymSwitch" name="gym_id" class="form-select form-select-sm fw-semibold" style="max-width:200px" onchange="this.form.submit()"<?= count($hdr_gyms) < 2 ? ' disabled title="Só há um ginásio"' : '' ?>>
<?php foreach ($hdr_gyms as $hg): ?>
<option value="<?= (int) $hg['id'] ?>" <?= (int) $hg['id'] === $hdr_cur ? 'selected' : '' ?>><?= htmlspecialchars($hg['name']) ?><?= $hg['is_published'] ? '' : ' (por publicar)' ?></option>
<?php endforeach; ?>
</select>
<noscript><button class="btn btn-sm btn-outline-secondary">Mudar</button></noscript>
</form>
<?php endif; ?>
<div class="bo-topbar__tools">
<div class="dropdown">
<button class="btn btn-sm btn-outline-secondary theme-toggle" type="button" data-bs-toggle="dropdown"
aria-expanded="false" aria-label="Tema: automático" id="themeToggle">
<i class="bi bi-circle-half" aria-hidden="true"></i>
</button>
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="themeToggle">
<li><button type="button" class="dropdown-item" data-theme-set="light"><i class="bi bi-sun me-2" aria-hidden="true"></i>Claro</button></li>
<li><button type="button" class="dropdown-item" data-theme-set="dark"><i class="bi bi-moon-stars me-2" aria-hidden="true"></i>Escuro</button></li>
<li><button type="button" class="dropdown-item" data-theme-set="auto"><i class="bi bi-circle-half me-2" aria-hidden="true"></i>Automático</button></li>
</ul>
</div>
</div>
</div>
<?php
// Breadcrumbs: a page sets $breadcrumbs = [['label'=>'Equipa','url'=>base_url('views/admin/team/index.php')], ['label'=>'Editar']]
// (last item has no 'url' — it's the current page) for a full trail; with
// nothing set, this falls back to Painel > $page_title, which is right for
// every top-level listing page and wrong for nothing.
$breadcrumbs = $breadcrumbs ?? (
$page_title !== 'Painel'
? [['label' => 'Painel', 'url' => base_url('views/admin/dashboard.php')], ['label' => $page_title]]
: []
);
if ($breadcrumbs): ?>
<nav aria-label="breadcrumb" class="px-3 px-md-4 pt-3">
<ol class="breadcrumb mb-0 small">
<?php foreach ($breadcrumbs as $__i => $__bc): ?>
<?php if (!empty($__bc['url']) && $__i < count($breadcrumbs) - 1): ?>
<li class="breadcrumb-item"><a href="<?= htmlspecialchars($__bc['url']) ?>"><?= htmlspecialchars($__bc['label']) ?></a></li>
<?php else: ?>
<li class="breadcrumb-item active" aria-current="page"><?= htmlspecialchars($__bc['label']) ?></li>
<?php endif; ?>
<?php endforeach; ?>
</ol>
</nav>
<?php endif; ?>
<div class="p-3 p-md-4">
<?php
// Show flash messages if any
$flash = session()->getFlashdata('success');
if ($flash): ?>
<div class="alert alert-success alert-dismissible fade show" role="alert">
<?= htmlspecialchars($flash) ?>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<?php endif; ?>
<?php $flash = session()->getFlashdata('error');
if ($flash): ?>
<div class="alert alert-danger alert-dismissible fade show" role="alert">
<?= htmlspecialchars($flash) ?>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<?php endif; ?>
<?php $flash = session()->getFlashdata('warning');
if ($flash): ?>
<div class="alert alert-warning alert-dismissible fade show" role="alert">
<?= htmlspecialchars($flash) ?>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<?php endif; ?>