211 lines
14 KiB
PHP
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; ?>
|