Mapa SMCC — guia do projeto

Catálogo digital dos espaços do Santa Mônica Clube de Campo: lista de locais com busca, filtros e ficha de cada espaço. Um painel administrativo permite à secretaria manter tudo sem um desenvolvedor.

47
locais ativos, incluindo 33 churrasqueiras
7
categorias + o filtro "Todos"
0
dependências de front-end (sem npm, sem build)

1. O que é

Um site público de uma única página (/) e um painel administrativo (/admin).

Para o associado

Busca por nome, filtro por categoria, ficha com foto, horário, telefone, "Como chegar" (Google Maps) e compartilhamento.

Para a secretaria

Cadastra e edita locais, categorias, horários, fotos e a planta do clube pelo painel, sem tocar em código.

Para integrações

Uma API JSON pública em /api/mapa, pensada para totem, app futuro ou outro sistema.

Stack

CamadaTecnologiaObservação
FrameworkLaravel 13 (PHP 8.3)composer.json
Painel adminFilament 5CRUD, upload, repeater e reordenação
Front-end públicoBlade + JavaScript puroSem Vite no caminho do site: public/js/app.js e public/css/app.css são servidos como estão
BancoSQLite (dev) / MySQL (produção atual)Trocar é mudar o .env
CacheTabela cache do bancoCACHE_STORE=database
ImagensGD (nativa do PHP)Converte para WebP com php artisan mapa:otimizar-imagens

2. Onde está cada coisa

mapa-smcc/
├── app/
│   ├── Console/Commands/OtimizarImagens.php   comando mapa:otimizar-imagens
│   ├── Filament/
│   │   ├── Resources/Locals/                  CRUD de locais (formulário e tabela)
│   │   ├── Resources/Categorias/              CRUD de categorias
│   │   ├── Resources/MapVersions/             plantas do clube + âncora geográfica
│   │   └── Widgets/                           ResumoClube, LocaisMaisVistos (dashboard)
│   ├── Http/Controllers/
│   │   ├── MapaController.php                 página /, ficha /locais/{slug}
│   │   └── MetricaController.php              contador de visualizações
│   ├── Models/                                Local, Categoria, LocalHorario, LocalView, MapVersion, User
│   ├── Observers/                             LocalObserver (calcula lat/lng), MapVersionObserver (1 planta ativa), ...
│   ├── Services/
│   │   ├── MapaService.php                    monta o payload (cache de 1 h)
│   │   └── OtimizadorImagem.php               conversão para WebP
│   └── Support/GoogleMapsLocal.php            formata/valida as coordenadas do admin
├── config/mapa.php                            contato, logo, origem padrão, reservas  ← ajustes do clube
├── database/
│   ├── migrations/                            estrutura das tabelas
│   ├── seeders/ClubeSeeder.php                dados iniciais (locais, categorias, planta)
│   └── database.sqlite                        banco local (SQLite)
├── docs/
│   ├── api-locais.md                          documentação da API pública
│   └── guia-do-projeto.html                   este guia
├── public/                                    o que o navegador acessa diretamente
│   ├── css/app.css                            design do site (tokens de cor no topo, :root)
│   ├── js/app.js                              busca, filtro, ficha, compartilhar, beacon
│   ├── storage/ → storage/app/public          link simbólico criado por storage:link
│   └── index.php                              entrada da aplicação
├── resources/views/
│   ├── components/layouts/app.blade.php       layout base (fontes, CSS, JS)
│   ├── locais/index.blade.php                 página principal
│   ├── locais/partials/                       header, card, ficha, footer
│   └── filament/mapa-picker.blade.php         seletor visual da planta (ver seção 11)
├── routes/web.php                             todas as rotas do site
├── storage/app/public/                        fotos, planta, logo (o que vai para o ar)
├── tests/Feature/                             AdminTest, MapaTest, MetricaTest
├── .env                                       configuração do ambiente (não versionar)
└── .env.example                               modelo do .env

Tabela de respostas rápidas

Quero…Onde mexer
Editar um local (texto, telefone, horário, foto)Painel /admin/locals
Mudar o telefone central, endereço ou horário do rodapéconfig/mapa.php, chave contato
Trocar a logoconfig/mapa.php, chave logo
Mudar cor ou fonte do sitepublic/css/app.css, variáveis em :root
Mudar o texto ou a lógica da busca e da fichapublic/js/app.js
Mudar o layout da páginaresources/views/locais/
Criar nova rotaroutes/web.php
Ativar reserva onlineconfig/mapa.php, reservas.url
Ver quantas visualizações um local teveDashboard do painel (/admin)

3. Como funciona

Uma visita à página principal percorre este caminho:

Por que não tem AJAX na busca?

Os 47 locais chegam junto com a página, dentro de um <script type="application/json" id="mapa-payload">. Filtrar é só alternar hidden nos cards. Isso mantém o site rápido no 4G do clube e indexável pelo Google.

Ficha do local

A ficha é um <dialog> nativo. Ao abrir, a URL muda para /locais/{slug} (sem recarregar), e a ficha só conta como visualização se ficar aberta por 2 segundos. O envio é um sendBeacon para POST /locais/{slug}/view, limitado a 60 chamadas por minuto por IP.

Cache e atualização

O payload do banco fica em cache por 1 hora. Os observers de Local, Categoria, LocalHorario e MapVersion apagam essa chave ao salvar, então a secretaria vê a mudança na hora. Já o que vem de config/mapa.php é mesclado a cada requisição, de fora do cache.

Imagens

Cada foto vira duas versões WebP: 1200 px para a ficha e 400 px para o card. A planta vira WebP de 2400 px. O comando precisa ser rodado sempre que uma foto for trocada pelo painel.

4. Configurar

Requisitos

Neste computador o PHP já está no PATH

php -v responde 8.3.28 e composer -V responde 2.10.3. O README antigo menciona o caminho do WampServer (C:/wamp64/bin/php/php8.3.28/php.exe); use ele apenas se o php do PATH não funcionar.

Passo a passo (ambiente novo)

  1. Copiar o ambiente: copy .env.example .env (no Linux/macOS: cp).
  2. Gerar a chave: php artisan key:generate.
  3. Ajustar o .env (tabela abaixo).
  4. Instalar dependências PHP: composer install.
  5. Criar o banco e os dados: php artisan migrate --seed. O seeder é idempotente: pode rodar de novo sem duplicar nada.
  6. Criar o link da pasta pública: php artisan storage:link.
  7. Otimizar as imagens: php artisan mapa:otimizar-imagens.
  8. Criar um usuário do painel: veja a seção 8.

Variáveis do .env que importam

ChavePara que serveLocal (dev)Produção (atual)
APP_ENVAmbientelocalproduction
APP_DEBUGMostra erros detalhados. Nunca true em produçãotruefalse
APP_URLEndereço público. Gera as URLs das imagens e o prefixo das rotashttp://localhost:8000https://servicos2.santamonica.rec.br/mapa-online-smcc
ASSET_URLBase dos arquivos estáticos (produção sob subpasta)vaziomesmo valor do APP_URL
DB_CONNECTIONsqlite ou mysqlsqlitemysql
DB_*Host, porta, nome, usuário e senha do MySQLnão usadopreencher
CACHE_STOREOnde fica o cache do payloaddatabasedatabase
SESSION_PATHCaminho do cookie de sessão (importante em subpasta)//mapa-online-smcc
MAIL_MAILERE-mail (o site não envia e-mails hoje)loglog
O .env atual é o de produção

Ele tem APP_ENV=production, APP_DEBUG=false, MySQL e a URL do servidor. Por isso, rodar php artisan serve com este arquivo tenta conectar no MySQL e gera links apontando para o servidor, não para localhost:8000. Para desenvolver localmente, use um .env separado com os valores da coluna "Local (dev)".

Ajustes do clube: config/mapa.php

ChaveO que faz
origem_padraoPosição da portaria na planta (x: 31.5, y: 48.0). Hoje é enviada no payload, mas nenhuma tela a usa.
logoURL da logo, servida pelo site do clube (hotlink).
contato.telefone, telefone_linkTelefone central, exibido no rodapé e nas reservas. telefone_link é o formato do tel:.
contato.endereco, rodape, rodape2Endereço e horários do rodapé.
contato.google_mapsDestino geral do "Como chegar" (place do clube). Usado quando o local não tem link próprio.
reservas.urlnull = botão liga para a secretaria. Preencher com um link torna o botão um link de reserva.
reservas.avisoTexto exibido junto ao botão Reservar.
icone_todosÍcone do filtro "Todos os locais".

Como config/ não dispara os observers, mudanças aqui aparecem na hora. Se o servidor usar php artisan config:cache, rode php artisan config:clear depois de editar.

5. Rodar

Desenvolvimento local

php artisan serve            # http://localhost:8000
php artisan test            # roda os testes

Se mudar a porta, mude o APP_URL junto. Senão as fotos quebram.

Em produção

O site responde em https://servicos2.santamonica.rec.br/mapa-online-smcc/. O script test_deploy_check.sh (na raiz) faz uma checagem rápida: página principal, redirecionamento de /mapa-online-smcc e o título no HTML.

Comandos do dia a dia

ComandoQuando usar
php artisan migrate --seedPrimeira vez ou depois de nova migração. Idempotente.
php artisan storage:linkUma vez por ambiente (cria public/storage).
php artisan mapa:otimizar-imagensDepois de trocar foto ou planta pelo painel.
php artisan mapa:otimizar-imagens --forceRegerar todos os WebP, mesmo os já existentes.
php artisan cache:clearForçar a releitura do payload (normalmente os observers já fazem isso).
php artisan route:listVer todas as rotas.
php artisan tinkerMexer no banco direto pelo terminal.

6. Os dois números: x/y e latitude/longitude

Um local tem duas coordenadas de natureza diferente. Entender a diferença é a chave para o resto do sistema:

ParUnidadeOnde é guardadoQuem preenchePara que serve
x, y porcentagem da imagem da planta (0 a 100)
x: esquerda → direita · y: topo → base
colunas locais.x e locais.y (decimal 5,2, podem ser nulas) a intenção era o clique na planta; hoje não há campo no formulário (ver seção 11) fonte da verdade da posição. Gera a latitude/longitude abaixo
latitude, longitude graus decimais (WGS84) colunas locais.latitude e locais.longitude calculadas automaticamente pelo LocalObserver a partir de x/y expostas na API (/api/mapa) para integrações

A conversão (x/y → lat/lng)

Implementada em MapVersion::coordenada(). A planta ativa define o centro e a extensão:

latitude  = base_lat + (50 - y) / 100 × span_lat
longitude = base_lng + (x - 50) / 100 × span_lng

Com a planta atual (base_lat -22.741900, base_lng -47.312600, span_lat 0.0072, span_lng 0.0087): o centro da planta (x=50, y=50) cai exatamente em -22.741900, -47.312600. O y cresce para baixo na tela, por isso o sinal é invertido na latitude.

Experimente

Digite um x e um y e veja o ponto na planta e a latitude/longitude correspondente. Use os mesmos valores do banco.

planta · 100 × 100

Ponto laranja: portaria (origem padrão). Ponto azul: o local digitado.

latitude—
longitude—

Exemplo: a Secretaria usa x 44.4 e y 50.2.

As duas coordenadas do Google Maps

O campo "Coordenadas" no formulário do local é outra coisa. Ele guarda o link google_maps_url, usado no botão "Como chegar" da ficha. Não é o mesmo par que x/y e não atualiza a latitude/longitude automaticamente. O formato esperado é latitude, longitude (com vírgula), e o sistema monta o link de rota a pé a partir dele.

Os dois conjuntos não batem entre si

A âncora da planta (-22.74, -47.31, região de Campinas/SP) não coincide com o endereço real do clube (-25.38, -49.15, Paraná, DDD 41). Por isso, a latitude/longitude derivada de x/y está deslocada. O README registra isso como pendência. Enquanto não for corrigido, use o campo "Coordenadas" para o "Como chegar" e não confie na lat/lng derivada.

7. Como ajustar

Adicionar um local

  1. No painel, em Locais, clique em Criar.
  2. Digite o nome. O slug (usado na URL) é preenchido sozinho. Ele não muda depois de criado, porque links compartilhados dependem dele.
  3. Escolha a categoria e preencha resumo, destaque e descrição.
  4. Em Horários, adicione as faixas (ex.: "Terça a sexta" / "10h às 20h").
  5. Envie a foto e rode php artisan mapa:otimizar-imagens.

Desativar sem apagar

Desligue o toggle ativo. O local some da lista, mas o histórico de visualizações é preservado. A página /locais/{slug} dele passa a responder 404.

Mudar a ordem da lista

Campo ordem (menor aparece primeiro) ou arraste na tabela do painel.

Trocar a planta do clube

  1. Em Plantas do clube, envie a nova imagem.
  2. Marque ativa. As outras são desativadas automaticamente (só pode haver uma).
  3. Ajuste a âncora (latitude/longitude do centro e extensões em graus) se necessário. Mudar isso move o destino de "Como chegar" de todos os locais que dependem de x/y.
  4. Rode php artisan mapa:otimizar-imagens.

Mudar cores e fontes

Tokens de cor no topo de public/css/app.css, dentro de :root (--brand: #0F2091, --ink, etc.). A fonte é Plus Jakarta Sans, carregada do Google Fonts no layout resources/views/components/layouts/app.blade.php.

8. Painel administrativo

Acesso em /admin. Só entra quem tem admin = true na tabela users. Sem isso, o login é aceito mas a conta recebe 403.

Criar um usuário

php artisan tinker --execute="App\Models\User::create(['name'=>'Secretaria','email'=>'secretaria@exemplo.com','password'=>bcrypt('uma-senha-forte'),'admin'=>true]);"

Seções do painel

SeçãoO que tem
DashboardTotal de locais publicados, categorias ativas, visualizações em 7 e 30 dias e ranking dos 10 mais consultados.
LocaisCRUD completo, toggles "ativo" e "reservável" direto na tabela, endereço editável na linha, filtro por categoria, repeater de horários.
CategoriasCRUD com preview do ícone SVG e reordenação.
Plantas do clubeUpload da planta, marcação de ativa e âncora geográfica.

9. API e rotas

MétodoCaminhoRetorna
GET/Página principal (HTML)
GET/locaisRedireciona (301) para /
GET/locais/{slug}Mesma página, com a ficha aberta e meta tags para compartilhamento. 404 se inativo
POST/locais/{slug}/view204. Conta uma visualização do dia. Limite de 60/min por IP
GET/api/mapaJSON com locais, categorias, contato, reservas, origem padrão e logo
GET/adminPainel Filament (login)

A documentação completa do JSON, com exemplos e campos, está em docs/api-locais.md. Não há endpoint de escrita: edição só pelo painel.

10. Testes e manutenção

php artisan test

Testes em tests/Feature/: AdminTest (acesso ao painel), MapaTest (página, API, coordenadas, cache, idempotência do seeder) e MetricaTest (contador de visualizações).

Rotina sugerida

11. Atenção: divergências encontradas

Ao montar este guia, comparei o que o README descreve com o código. Estes pontos merecem ação:

1. O seletor visual da planta não está ligado ao formulário

O README diz que, no admin, "clique na planta e o marcador vai para lá". O arquivo resources/views/filament/mapa-picker.blade.php existe, mas LocalForm não o referencia e não há campos x/y no formulário. Resultado: a posição só pode ser definida pelo tinker ou pelo banco. A tabela mostra "não marcada" para os locais sem posição.

2. "Como chegar" não muda ao mover o marcador

O README afirma que arrastar o marcador corrige o link do "Como chegar". Na prática, o LocalObserver só atualiza latitude/longitude. O botão usa google_maps_url (campo "Coordenadas") ou, se vazio, o link geral do clube. São dados separados.

3. Distância não existe no código

O README descreve distância a pé a partir da portaria, geolocalização do visitante e ordenação por distância. Nada disso está em app.js ou nas views. A origem padrão chega no payload, mas não é usada. Ou a funcionalidade foi removida, ou ainda não foi feita. Vale confirmar antes de divulgar.

4. Senha temporária documentada no README

O README traz a senha temporária do usuário provisionado. Como o repositório pode ser compartilhado, troque a senha e remova o trecho do README.

5. Configuração de ambiente

O .env está com valores de produção (ver seção 4). Os comandos do README para rodar localmente pressupõem outro .env. Manter credenciais de produção num arquivo que roda na máquina de desenvolvimento aumenta o risco de um comando apagar ou alterar dados do servidor.

6. Dados fictícios

Telefones por local (DDD 19, de outra região), as 33 churrasqueiras sem endereço, posição nem foto própria, e a âncora geográfica da planta. Detalhes na seção "Dados que ainda são do protótipo" do README.

12. Pendências antes do go-live

Lista do README, para acompanhar: