feat: vários ginásios (/3070/ e /<código postal>/), CRUD de ginásios, modalidades após a galeria
Estrutura multi-ginásio: as mesmas secções do site, conteúdo próprio por ginásio, e cada um numa pasta da URL com o código postal da zona. - migration_20260926_gyms: tabela gyms (pasta, nome, localidade, publicado, principal, secções visíveis, título/imagem do header, SEO, galeria, Place ID, página CrossFit) com o FitMe 3070 como ginásio 1 em /3070/; gym_id em team, modalidades (o horário segue a modalidade), work_hours, memberships, membership_leads e website_offer (tudo o existente → ginásio 1); events com gym_id NULL = todos; fitme3070_info passa a uma linha por ginásio (id = gym). - Site: /<pasta>/, /<pasta>/crossfit, /<pasta>/agendar|inscricao. Ginásio por publicar → 404 para o público, pré-visualização (com faixa) para a equipa. / e /home → 301 para o ginásio principal (a manter a query); com mais de um ginásio publicado, a raiz passa a uma página para escolher. Secções desligadas não aparecem nem no menu; menu, canonical, JSON-LD, sitemap, formulários, e-mails e reviews (Place ID e cache) são do ginásio. - Backoffice: Gestão → Ginásios (criar/editar/apagar, secções, e «começar com o conteúdo de» outro ginásio — cópias independentes) e o seletor «A editar» no topo; equipa, modalidades, horários, horário de funcionamento, mensalidades, pedidos, contactos e reviews trabalham sobre o ginásio escolhido, e um registo de outro ginásio não abre. - Modalidades passam para logo a seguir à galeria das instalações. - Galeria sem cantos arredondados (raio da marca). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
2f8f68c61a
commit
5e300fc341
33 files changed
+1146
-197
No files matched your search
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
// Shared gym form fields (create.php / edit.php). Expects $data (gym row or
|
||||
// gym_defaults()) and $galleries (id => name).
|
||||
$sections_on = [];
|
||||
foreach (array_keys(gym_sections()) as $k) if (gym_section_on($data, $k)) $sections_on[] = $k;
|
||||
?>
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Nome <span class="text-danger">*</span></label>
|
||||
<input class="form-control" name="name" required maxlength="100" placeholder="ex.: FitMe 3070" value="<?= htmlspecialchars($data['name'] ?? '') ?>">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Pasta da URL <span class="text-danger">*</span></label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">/</span>
|
||||
<input class="form-control font-monospace" name="slug" required maxlength="20" pattern="[a-z0-9][a-z0-9-]{1,19}" placeholder="3070" value="<?= htmlspecialchars($data['slug'] ?? '') ?>">
|
||||
<span class="input-group-text">/</span>
|
||||
</div>
|
||||
<div class="form-text">O código postal da zona.</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Ordem</label>
|
||||
<input type="number" class="form-control" name="item_order" value="<?= (int) ($data['item_order'] ?? 0) ?>">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Localidade</label>
|
||||
<input class="form-control" name="locality" maxlength="100" placeholder="ex.: Mira" value="<?= htmlspecialchars($data['locality'] ?? '') ?>">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Código postal</label>
|
||||
<input class="form-control" name="postal_code" maxlength="20" placeholder="ex.: 3070-xxx" value="<?= htmlspecialchars($data['postal_code'] ?? '') ?>">
|
||||
</div>
|
||||
|
||||
<div class="col-12"><hr class="my-1"><strong>Página</strong></div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Título do header</label>
|
||||
<input class="form-control" name="hero_title" maxlength="150" placeholder="Ginásio, Box, Aulas" value="<?= htmlspecialchars($data['hero_title'] ?? '') ?>">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Segunda linha do título</label>
|
||||
<input class="form-control" name="hero_subtitle" maxlength="150" placeholder="& Acompanhamento" value="<?= htmlspecialchars($data['hero_subtitle'] ?? '') ?>">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Imagem do header</label>
|
||||
<input class="form-control font-monospace" name="hero_image" maxlength="255" placeholder="site/assets/img/home.jpg" value="<?= htmlspecialchars($data['hero_image'] ?? '') ?>">
|
||||
<div class="form-text">Caminho de uma imagem do site. Vazio = a do ginásio principal.</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Galeria das instalações</label>
|
||||
<select class="form-select" name="gallery_id">
|
||||
<option value="">— nenhuma —</option>
|
||||
<?php foreach ($galleries as $gid => $gname): ?>
|
||||
<option value="<?= (int) $gid ?>" <?= (int) ($data['gallery_id'] ?? 0) === (int) $gid ? 'selected' : '' ?>><?= htmlspecialchars($gname) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<div class="form-text">As galerias gerem-se em Gestão → Galerias.</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Título para o Google (SEO)</label>
|
||||
<input class="form-control" name="seo_title" maxlength="160" placeholder="FitMe 3070 — Ginásio & CrossFit em Mira" value="<?= htmlspecialchars($data['seo_title'] ?? '') ?>">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Place ID Google (reviews)</label>
|
||||
<input class="form-control font-monospace" name="google_place_id" maxlength="120" placeholder="ChIJ…" value="<?= htmlspecialchars($data['google_place_id'] ?? '') ?>">
|
||||
<div class="form-text">A chave da API é comum (Gestão → Reviews Google).</div>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label">Descrição para o Google (SEO)</label>
|
||||
<textarea class="form-control" name="seo_description" rows="2" maxlength="300"><?= htmlspecialchars($data['seo_description'] ?? '') ?></textarea>
|
||||
</div>
|
||||
|
||||
<div class="col-12"><hr class="my-1"><strong>Secções visíveis</strong>
|
||||
<div class="form-text mt-0">O layout é o mesmo em todos os ginásios; aqui escolhes quais aparecem.</div></div>
|
||||
<?php foreach (gym_sections() as $key => $label): ?>
|
||||
<div class="col-sm-6 col-lg-4">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" name="sections[]" value="<?= $key ?>" id="sec_<?= $key ?>" <?= in_array($key, $sections_on, true) ? 'checked' : '' ?>>
|
||||
<label class="form-check-label" for="sec_<?= $key ?>"><?= htmlspecialchars($label) ?></label>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<div class="col-sm-6 col-lg-4">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" name="has_crossfit" value="1" id="has_crossfit" <?= !empty($data['has_crossfit']) ? 'checked' : '' ?>>
|
||||
<label class="form-check-label" for="has_crossfit">Página CrossFit</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12"><hr class="my-1"><strong>Estado</strong></div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" name="is_published" value="1" id="is_published" <?= !empty($data['is_published']) ? 'checked' : '' ?>>
|
||||
<label class="form-check-label" for="is_published">Publicado</label>
|
||||
</div>
|
||||
<div class="form-text">Por publicar, só quem tem sessão iniciada no backoffice vê a página (pré-visualização).</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" name="is_default" value="1" id="is_default" <?= !empty($data['is_default']) ? 'checked' : '' ?>>
|
||||
<label class="form-check-label" for="is_default">Ginásio principal</label>
|
||||
</div>
|
||||
<div class="form-text">É para onde vão os endereços antigos (/home). Só pode haver um.</div>
|
||||
</div>
|
||||
</div>
|
||||
Reference in new issue
Block a user