Documentação
Guia completo para desenvolvedores da plataforma Ameciclo
Visão Geral
O projeto Ameciclo é uma plataforma web full-stack desenvolvida com Remix, TypeScript e Reactque fornece dados abertos sobre mobilidade ativa na região metropolitana do Recife.
A plataforma integra múltiplas fontes de dados (APIs externas, CMS Strapi, dados estáticos) e oferece: visualizações interativas com mapas (Mapbox), gráficos (Highcharts), calendários (FullCalendar), observatórios especializados (Ideciclo, Sinistros Fatais, Vias Inseguras, SAMU, CicloDados), sistema de contagens de ciclistas, documentos públicos, projetos da organização e ferramentas de acessibilidade.
Monitoramento em Tempo Real
A plataforma conta com uma página dedicada ao monitoramento do status de todos os serviços e páginas em tempo real.
Ver Status dos ServiçosArquitetura
SSR com Remix, 27 rotas, 182+ componentes, deploy Vercel
Stack Principal
Remix 2.16, React 18, TypeScript 5, Vite 5, Tailwind CSS 3
Recursos
Mapas, gráficos, calendários, busca, acessibilidade WCAG
Instalação
Pré-requisitos
- Node.js ≥ 20.0.0
- npm ou yarn
- Git
Passos para instalação
1. Clone o repositório:
git clone https://github.com/Ameciclo/ameciclo.git2. Instale as dependências:
npm install3. Inicie o servidor de desenvolvimento:
npm run dev4. Acesse a aplicação:
http://localhost:5173Estrutura do Projeto
ameciclo/ ├── app/ │ ├── components/ # 182+ componentes React organizados por funcionalidade │ │ ├── Commom/ # Componentes globais reutilizáveis │ │ │ ├── Navbar.tsx, Footer.tsx, Banner.tsx, Breadcrumb.tsx │ │ │ ├── AccessibilityControls.tsx, ChangeThemeButton.tsx │ │ │ ├── ErrorBoundary.tsx, ApiStatusHandler.tsx │ │ │ ├── Charts/, Maps/, Table/, Icones/ │ │ │ └── GoogleAnalytics.tsx, SEO.tsx │ │ ├── Agenda/ # FullCalendar com Google Calendar API │ │ ├── Biciclopedia/ # FAQ com busca e accordion │ │ ├── CicloDados/ # Plataforma de dados colaborativos │ │ ├── Contagens/ # Visualização de contagens de ciclistas │ │ ├── Dados/ # Hub central de dados │ │ ├── Documentacao/ # Esta documentação (11 componentes) │ │ ├── ExecucaoCicloviaria/ # Execução de infraestrutura │ │ ├── Ideciclo/ # Índice de ciclabilidade │ │ ├── PaginaInicial/ # Componentes da home │ │ ├── Projetos/ # Projetos da organização │ │ ├── QuemSomos/ # Sobre a Ameciclo │ │ ├── Samu/ # Dados de chamados do SAMU │ │ ├── SinistrosFatais/ # Análise de sinistros fatais │ │ └── ViasInseguras/ # Ranking de vias perigosas │ ├── routes/ # 27 rotas (file-based routing do Remix) │ │ ├── _index.tsx # Home com seções dinâmicas │ │ ├── agenda.tsx, biciclopedia.tsx, contato.tsx │ │ ├── dados._index.tsx, dados.ciclodados._index.tsx │ │ ├── dados.contagens.$slug.tsx, dados.via.$slug.tsx │ │ ├── dados.ideciclo.$id.tsx, dados.sinistros-fatais.tsx │ │ ├── dados.vias-inseguras.tsx, dados.samu.tsx │ │ ├── projetos.$projeto.tsx, quemsomos.tsx │ │ └── documentacao._index.tsx # Esta página │ ├── loader/ # Loaders para SSR e data fetching │ │ ├── home.ts, agenda.ts, projetos.ts, quemsomos.ts │ │ ├── dados.contagens.ts, dados.ideciclo.ts │ │ ├── dados.sinistros-fatais.ts, dados.samu.ts │ │ └── compareContagensLoader.ts │ ├── services/ # Serviços e APIs │ │ ├── fetchWithTimeout.ts # Fetch com timeout e fallback │ │ ├── cmsApi.ts # Integração com Strapi CMS │ │ ├── contagens.service.ts # Serviço de contagens │ │ ├── ciclodados.service.ts, streets.service.ts │ │ ├── cache.ts, staticFallbacks.ts │ │ └── SinistrosFatais/ │ ├── contexts/ # React Context API │ │ └── ApiStatusContext.tsx # Status global de APIs │ ├── providers/ # Providers globais │ │ └── QueryProvider.tsx # TanStack Query │ ├── hooks/ # Custom hooks │ │ ├── useApiWithAlert.ts, useFocusTrap.ts │ ├── utils/ # Utilitários │ │ ├── mapbox.server.ts, slugify.ts, translations.ts │ │ └── contagens/ │ ├── types/ # TypeScript types │ ├── root.tsx # Root component com layout global │ ├── entry.client.tsx # Entry point do cliente │ ├── entry.server.tsx # Entry point do servidor │ └── globals.css # Estilos globais ├── public/ # Assets estáticos │ ├── icons/ # Ícones SVG organizados por seção │ ├── ideciclo/, images/, pages_covers/ │ ├── data/ # GeoJSON e dados estáticos │ └── dbs/ # Databases JSON locais ├── docs/ # Documentação de APIs ├── .env # Variáveis de ambiente ├── vite.config.ts # Configuração Vite + Remix ├── tailwind.config.ts # Configuração Tailwind CSS ├── tsconfig.json # Configuração TypeScript ├── vercel.json # Deploy Vercel └── package.json # Dependências e scripts
Detalhamento das Pastas
app/components/Commom/
Pasta de componentes reutilizáveis usados em toda a aplicação:
- • Navbar.tsx - Navegação responsiva com menu mobile
- • Footer.tsx - Rodapé com links e redes sociais
- • Banner.tsx - Banners de páginas com imagens otimizadas
- • Breadcrumb.tsx - Navegação estrutural (SEO)
- • AccessibilityControls.tsx - Controles WCAG (fonte, contraste)
- • ChangeThemeButton.tsx - Toggle dark/light mode
- • ErrorBoundary.tsx - Tratamento de erros React
- • ApiStatusHandler.tsx - Monitoramento de APIs
- • Charts/ - Componentes Highcharts reutilizáveis
- • Maps/ - Componentes Mapbox GL reutilizáveis
- • Table/ - Tabelas com filtros e ordenação
- • GoogleAnalytics.tsx - GA4 tracking
app/routes/
Sistema de roteamento baseado em arquivos do Remix:
- • _index.tsx - Home (banner, carrossel, dados, CTA)
- • dados._index.tsx - Hub de dados com cards
- • dados.ciclodados._index.tsx - Plataforma colaborativa
- • dados.contagens.$slug.tsx - Contagem individual com gráficos
- • dados.contagens.$slug.$compareSlug.tsx - Comparação
- • dados.ideciclo.$id.tsx - Detalhes do Ideciclo
- • dados.sinistros-fatais.tsx - Análise de sinistros
- • dados.vias-inseguras.tsx - Ranking de vias
- • dados.via.$slug.tsx - Detalhes de via individual
- • dados.samu.tsx - Chamados SAMU com mapa coroplético
- • projetos.$projeto.tsx - Projeto individual do CMS
- • biciclopedia.$question.tsx - FAQ individual
app/loader/
Funções para carregamento de dados do servidor:
- • home.ts - Dados da home (projetos, contagens)
- • dados.contagens.ts - Lista de contagens da API
- • compareContagensLoader.ts - Comparação de contagens
- • dados.ideciclo.ts - Dados do índice de ciclabilidade
- • dados.sinistros-fatais.ts - Dados de sinistros
- • dados.vias-inseguras.ts - Ranking de vias
- • dados.samu.ts - Chamados do SAMU
- • projetos.ts - Projetos do CMS Strapi
- • agenda.ts - Google Calendar API
- • quemsomos.ts - Dados da página institucional
Componentes
Os componentes são organizados por funcionalidade e reutilizabilidade:
Organização por Funcionalidade
app/components/ ├── Commom/ # 50+ componentes globais ├── Agenda/ # 2 componentes (EventCalendar, Loading) ├── Biciclopedia/ # 3 componentes (FAQ, Search, Accordion) ├── Charts/ # 4 componentes de gráficos ├── CicloDados/ # 20+ componentes (MapView, Sidebar, Filters) ├── Contagens/ # 9 componentes (Tabelas, Gráficos, Mapas) ├── Dados/ # 3 componentes (Loading, Grid, Boxes) ├── Documentacao/ # 11 componentes desta página ├── Documentos/ # 2 componentes (Lista, Sessão) ├── ExecucaoCicloviaria/ # 3 componentes (Stats, Charts, Loading) ├── Ideciclo/ # 10 componentes (Tabelas, Mapas, Stats) ├── PaginaInicial/ # 7 componentes (Banner, Carousel, Sections) ├── Perfil/ # 1 componente (Dashboard) ├── Projetos/ # 7 componentes (Cards, Search, Language) ├── QuemSomos/ # 3 componentes (Tabs, Modal, Loading) ├── Samu/ # 2 componentes (Map, ClientSide) ├── SinistrosFatais/ # 7 componentes (Matrix, Filters, Cards) └── ViasInseguras/ # 12 componentes (Map, Ranking, Charts)Componentes Principais
CicloDados (Plataforma Colaborativa)
Sistema completo de visualização de dados com:
- • MapView.tsx - Mapa interativo Mapbox com layers
- • LeftSidebar.tsx, RightSidebar.tsx - Painéis laterais
- • FilterSection.tsx - Filtros avançados
- • FloatingChat.tsx - Chat integrado
- • MuralView.tsx - Visualização tipo mural
ViasInseguras (Análise de Vias)
Sistema de ranking e análise temporal:
- • ViasInsegurasMap.tsx - Mapa com heatmap
- • ViasRankingTable.tsx - Tabela ordenada
- • TemporalAnalysis.tsx - Análise temporal
- • ConcentrationChart.tsx - Gráficos de concentração
- • ViaSearch.tsx - Busca com autocomplete
Contagens (Visualização de Dados)
Componentes para contagens de ciclistas:
- • CountsMap.tsx - Mapa de pontos de contagem
- • HourlyCyclistsChart.tsx - Gráfico por hora
- • FlowContainer.tsx - Fluxo de ciclistas
- • CountingComparisionTable.tsx - Comparação
Exemplo de Uso
import Banner from "~/components/Commom/Banner";
import Breadcrumb from "~/components/Commom/Breadcrumb";
import { CountsMap } from "~/components/Contagens/CountsMap";
export default function ContagensPage() {
return (
<>
<Banner image="/contagens.webp" alt="Contagens" />
<Breadcrumb label="Contagens" slug="/dados/contagens" />
<CountsMap data={counts} />
</>
);
}Rotas
O Remix utiliza roteamento baseado em arquivos. Cada arquivo em app/routes/ representa uma rota. O projeto possui 27 rotas organizadas hierarquicamente:
Rotas Públicas:
/_index.tsx → /
/agenda.tsx → /agenda
/biciclopedia.tsx → /biciclopedia
/biciclopedia_.$question.tsx → /biciclopedia/:question
/contato.tsx → /contato
/participe.tsx → /participe
/quemsomos.tsx → /quemsomos
/documentacao._index.tsx → /documentacaoRotas de Dados:
/dados._index.tsx → /dados
/dados.ciclodados._index.tsx → /dados/ciclodados
/dados.contagens._index.tsx → /dados/contagens
/dados.contagens.$slug._index.tsx → /dados/contagens/:slug
/dados.contagens.$slug.$compareSlug.tsx → /dados/contagens/:slug/:compareSlug
/dados.documentos.tsx → /dados/documentos
/dados.dom.tsx → /dados/dom
/dados.loa.tsx → /dados/loa
/dados.execucaocicloviaria.tsx → /dados/execucaocicloviaria
/dados.ideciclo._index.tsx → /dados/ideciclo
/dados.ideciclo.$id.tsx → /dados/ideciclo/:id
/dados.perfil.tsx → /dados/perfil
/dados.samu.tsx → /dados/samu
/dados.sinistros-fatais.tsx → /dados/sinistros-fatais
/dados.vias-inseguras.tsx → /dados/vias-inseguras
/dados.viasinseguras.$slug.tsx → /dados/vias-inseguras/:slugRotas de Projetos:
/projetos._index.tsx → /projetos
/projetos.$projeto.tsx → /projetos/:projetoRota 404:
/$.tsx → Catch-all para páginas não encontradasPadrões de Nomenclatura:
- •
_index.tsx- Rota index (sem segmento na URL) - •
$param.tsx- Parâmetro dinâmico - •
parent.child.tsx- Rota aninhada - •
$.tsx- Catch-all (404)
Exemplo completo com loader e action:
// app/routes/contagens.$slug.tsx
import { LoaderFunctionArgs } from "@remix-run/node";
import { fetchWithTimeout } from "~/services/fetchWithTimeout";
export async function loader({ params }: LoaderFunctionArgs) {
const { slug } = params;
// Usando fetchWithTimeout para evitar timeouts
const data = await fetchWithTimeout(
"http://api.garfo.ameciclo.org/cyclist-counts",
{ cache: "no-cache" },
5000,
{ counts: [] }
);
const contagem = data.counts?.find((c: any) => c.slug === slug);
if (!contagem) {
throw new Response("Contagem não encontrada", { status: 404 });
}
return { contagem };
}API
A aplicação consome dados de múltiplas APIs externas e internas. Todas as requisições utilizam fetchWithTimeout para garantir resiliência:
APIs Externas
GET http://api.garfo.ameciclo.org/cyclist-countsRetorna lista de contagens realizadas
GET http://api.garfo.ameciclo.org/cyclist-counts/edition/:idDetalhes de uma contagem específica
GET http://do.strapi.ameciclo.org/api/projetosLista de projetos da organização
GET http://do.strapi.ameciclo.org/api/projetos/:slugDetalhes de um projeto
Integração via FullCalendarEventos da agenda da Ameciclo
Token configurado via variável de ambienteMapas interativos em várias páginas
Serviço fetchWithTimeout
// app/services/fetchWithTimeout.ts
export async function fetchWithTimeout(
url: string,
options: RequestInit = {},
timeout: number = 5000,
fallbackData: any = null,
onApiDown?: (error: string) => void,
retries: number = 1
): Promise<any> {
// Implementa timeout, retries e fallback
// Garante resiliência quando APIs estão indisponíveis
}Estrutura de Dados - Contagens
{
"counts": [
{
"id": 1,
"name": "Ponte do Jiquiá",
"slug": "ponte-do-jiquia",
"date": "2024-03-15",
"total_cyclists": 245,
"total_women": 89,
"total_helmet": 156,
"total_juveniles": 23,
"location": {
"lat": -8.0476,
"lng": -34.8770
}
}
]
}Testes
O projeto utiliza ferramentas de qualidade de código para garantir a confiabilidade:
Linting e Type Checking
Verificar qualidade do código:
npm run lintVerificar tipos TypeScript:
npm run typecheckConfiguração do ESLint
// .eslintrc.cjs
module.exports = {
extends: [
"@remix-run/eslint-config",
"@remix-run/eslint-config/node"
],
rules: {
// Regras personalizadas do projeto
}
};Dica: Execute os testes antes de fazer commit para garantir a qualidade do código.
Configuração
Variáveis de Ambiente
Configure as variáveis no arquivo .env:
# APIs Externas
API_GARFO_URL=http://api.garfo.ameciclo.org
CMS_BASE_URL=http://do.strapi.ameciclo.org
# Mapbox
MAPBOX_ACCESS_TOKEN=pk.seu_token_aqui
# Google Calendar
GOOGLE_CALENDAR_API_KEY=sua_chave_aqui
# Analytics
GOOGLE_ANALYTICS_ID=G-PQNS7S7FD3
# Ambiente
NODE_ENV=developmentConfigurações Principais
Vite + Remix (vite.config.ts)
export default defineConfig({
plugins: [
remix({
presets: [vercelPreset()],
future: { v3_singleFetch: false }
})
],
build: {
chunkSizeWarningLimit: 1200,
rollupOptions: {
output: {
manualChunks: (id) => {
if (id.includes('highcharts')) return 'highcharts';
if (id.includes('mapbox')) return 'mapbox';
if (id.includes('@fullcalendar')) return 'calendar';
}
}
}
}
});Tailwind CSS (tailwind.config.ts)
module.exports = {
content: ["./app/**/*.{ts,tsx}"],
theme: {
extend: {
colors: {
ameciclo: "#008080",
ideciclo: "#5050aa"
},
height: {
cover: "52vh",
"no-cover": "25vh"
}
}
}
};TypeScript (tsconfig.json)
{
"compilerOptions": {
"lib": ["DOM", "DOM.Iterable", "ES2022"],
"jsx": "react-jsx",
"module": "ESNext",
"target": "ES2022",
"strict": true,
"baseUrl": ".",
"paths": { "~/*": ["./app/*"] }
}
}Soluções
Problemas Comuns
Erro: "Module not found" ou problemas com node_modules
Solução:
rm -rf node_modules package-lock.json && npm installLimpa e reinstala todas as dependências.
Erro: "Port already in use" (porta 5173)
Solução:
lsof -ti:5173 | xargs kill -9Mata o processo usando a porta.
Erro de API: "Failed to fetch", "504 Gateway Timeout" ou "ECONNREFUSED"
Causa: API externa indisponível ou timeout
Solução: O sistema já usa fetchWithTimeout com fallback automático. Verifique:
- Status da API em /status (se disponível)
- Logs do console para detalhes do erro
- Dados de fallback em app/services/staticFallbacks.ts
Erro: "Mapbox token not found" ou mapa não carrega
Solução:
MAPBOX_ACCESS_TOKEN=pk.seu_token_aquiConfigure o token no .env. Obtenha em mapbox.com
Erro: TypeScript "Type error" ou "Cannot find module"
Solução:
npm run typecheckVerifica erros de tipo. Use // @ts-ignore apenas em casos extremos.
Erro: Build falha com "Chunk size warning" ou "Out of memory"
Solução: Já configurado code splitting no vite.config.ts
NODE_OPTIONS="--max-old-space-size=4096" npm run buildAumenta memória do Node se necessário.
Problema: Highcharts não renderiza ou erro "Highcharts is not defined"
Solução: Usar ClientOnly ou lazy loading
import { ClientOnly } from "remix-utils/client-only";
<ClientOnly>{() => <ChartComponent />}</ClientOnly>Dica: Se o problema persistir, consulte os issues no GitHub ou abra um novo issue com detalhes do erro.
Deploy
Dependências Principais (40+ pacotes)
Framework e Core
- @remix-run/* (v2.16.5) - Framework full-stack com SSR, file-based routing, loaders
- @vercel/remix (v2.16.7) - Adapter para deploy na Vercel
- react (v18.2.0) + react-dom - Biblioteca UI
- typescript (v5.1.6) - Superset com tipagem estática
- vite (v5.1.0) - Build tool rápido (HMR, ESM)
UI e Estilização
- tailwindcss (v3.4.4) - Framework CSS utility-first
- framer-motion (v11.18.0) - Animações declarativas
- styled-components (v6.1.14) - CSS-in-JS
- lucide-react (v0.545.0) - Ícones SVG
Visualização de Dados
- highcharts (v12.2.0) + highcharts-react-official - Gráficos interativos
- react-map-gl (v6.1.21) + mapbox-gl (v1.13.0) - Mapas interativos
- react-google-charts (v5.2.1) - Gráficos Google Charts
- @turf/* - Manipulação de dados geoespaciais
Funcionalidades Específicas
- @fullcalendar/* (v6.1.x) - Calendário com Google Calendar
- @tanstack/react-query (v5.90.7) - Cache e sincronização de dados
- keen-slider (v6.3.3) - Carrossel/slider
- swiper (v12.0.3) - Slider touch
- react-markdown (v10.1.0) - Renderização Markdown
- fuse.js (v7.1.0) - Busca fuzzy
- match-sorter (v8.0.0) - Ordenação inteligente
Utilitários
- react-spinners (v0.17.0) - Loading indicators
- react-lazyload (v3.2.1) - Lazy loading
- react-table (v7.8.0) - Tabelas avançadas
- isbot (v4.1.0) - Detecção de bots
Build para produção
1. Gerar build otimizado:
npm run build2. Iniciar servidor de produção:
npm startDeploy: O projeto está configurado para deploy automático na Vercel via GitHub. Build otimizado com code splitting (Highcharts, Mapbox, Calendar em chunks separados).
Contribuição
Contribuições são sempre bem-vindas! Siga este guia para contribuir com o projeto:
Processo de Contribuição
1. Clone o Repositório
git clone https://github.com/Ameciclo/ameciclo.git
cd ameciclo2. Crie uma branch
git checkout -b feature/nova-funcionalidadeUse nomes descritivos: feature/, fix/, docs/
3. Desenvolva e Teste
npm install
npm run dev
npm run lint
npm run typecheck4. Commit e Push
git add .
git commit -m "feat: adiciona nova funcionalidade"
git push origin feature/nova-funcionalidade5. Abra um Pull Request
Descreva claramente as alterações e inclua screenshots se necessário
Padrões de Código
Use conventional commits: feat:, fix:, docs:
Sempre tipifique variáveis e funções
Use nomes descritivos e organize por funcionalidade
Tipos de Contribuição
Identifique e corrija problemas existentes
Adicione recursos que melhorem a plataforma
Melhore ou adicione documentação
Melhore a interface e experiência do usuário
Dica: Consulte os issues abertos no GitHub para encontrar tarefas que precisam de ajuda.
Boas Práticas
Requisições de Dados
✓ Use Loaders para dados estáticos
Sempre busque dados no servidor usando loaders do Remix:
// app/loader/minha-pagina.ts
export async function loader() {
const data = await fetchWithTimeout(
"http://api.garfo.ameciclo.org/endpoint",
{ cache: "no-cache" },
5000,
{ fallback: [] }
);
return { data };
}✓ Centralize URLs em arquivos server
Mantenha URLs de APIs em arquivos de serviço:
// app/services/contagens.service.ts
const API_BASE = "http://api.garfo.ameciclo.org";
export async function getContagens() {
return fetchWithTimeout(`${API_BASE}/cyclist-counts`);
}Padrão de Cores Ameciclo
Cores Principais
text-ameciclotext-idecicloCores Sugeridas
- • Sucesso: green-500, green-600
- • Alerta: yellow-500, yellow-600
- • Erro: red-500, red-600
- • Info: blue-500, blue-600
- • Neutro: gray-500, gray-600
Padrões de Estilo
Cards
Padrão: bordas arredondadas, sombra sutil
className="rounded-lg shadow-md p-4 border"Bordas
- •
rounded- 4px (padrão) - •
rounded-lg- 8px (cards) - •
rounded-xl- 12px (destaque)
Sombras
- •
shadow-sm- Sutil - •
shadow-md- Média (padrão cards) - •
shadow-lg- Destaque
Fontes
- • Família: Open Sans (font-custom)
- • Títulos: text-2xl, text-3xl, font-bold
- • Subtítulos: text-xl, font-semibold
- • Corpo: text-base, text-sm
Estrutura de Arquivos
Criando uma Nova Página
Use _index.tsx para rotas sem segmento adicional:
app/routes/minha-secao._index.tsx → /minha-secaoO _index indica que é a rota raiz daquele segmento.
Criando um Componente
- 1. Crie em
app/components/[Secao]/ - 2. Use PascalCase:
MeuComponente.tsx - 3. Export default function
- 4. Tipagem com TypeScript
Organização de Pastas
- • components/Commom/ - Componentes globais reutilizáveis
- • components/[Secao]/ - Componentes específicos de seção
- • routes/ - Páginas (file-based routing)
- • loader/ - Funções de carregamento de dados
- • services/ - Lógica de negócio e APIs
- • hooks/ - Custom React hooks
- • contexts/ - React Context providers
- • utils/ - Funções utilitárias
Exemplo Completo
// app/routes/minha-pagina._index.tsx
import { useLoaderData } from "@remix-run/react";
import { loader } from "~/loader/minha-pagina";
import Banner from "~/components/Commom/Banner";
export { loader };
export default function MinhaPagina() {
const { data } = useLoaderData<typeof loader>();
return (
<>
<Banner image="/banner.webp" alt="Minha Página" />
<div className="max-w-7xl mx-auto px-4 py-8">
<div className="rounded-lg shadow-md p-6 bg-white border">
<h1 className="text-3xl font-bold text-ameciclo mb-4">
Título
</h1>
{/* Conteúdo */}
</div>
</div>
</>
);
}