Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
4cd3c1f
feat: adiciona aviso de erro quando chave do Mapbox não está configurada
italosergio Feb 23, 2026
d0c005a
feat(i18n): adiciona componentes de seleção de idioma
italosergio Feb 25, 2026
900b019
feat(projetos): adiciona componente ProjectSteps multilíngue
italosergio Feb 25, 2026
333fa97
feat(data): adiciona traduções do projeto Bota Pra Rodar
italosergio Feb 25, 2026
6dad147
feat(loader): implementa suporte a traduções de projetos
italosergio Feb 25, 2026
369fc98
feat(routes): integra seletor de idioma e passos na página de projeto
italosergio Feb 25, 2026
2854744
feat(projetos): adiciona badge de idiomas em cards e filtra traduções
italosergio Feb 25, 2026
196ec61
refactor(navbar): remove componente ProjectPage não utilizado
italosergio Feb 25, 2026
c18ba27
fix: corrige re-renderização e remove variável não utilizada na págin…
italosergio Mar 10, 2026
693b10e
fix: corrigir IntlDateStr para evitar bug de timezone e crash com dat…
italosergio Apr 27, 2026
7074fdf
feat: adicionar função contagemSlug para gerar slugs textuais de cont…
italosergio Apr 27, 2026
6bef46e
feat: adicionar botão 'Ver mais' no modal de detalhes do ponto
italosergio Apr 27, 2026
5e30ec1
feat: implementar slugs textuais nas páginas de contagens
italosergio Apr 27, 2026
2cd5582
feat: permitir comparação entre contagens do mesmo local com datas di…
italosergio Apr 27, 2026
ce3de8d
feat: implementar slugs textuais na rota e página de comparação de co…
italosergio Apr 27, 2026
902ec94
fix: resolver slugs textuais nos loaders de comparação
italosergio Apr 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# APIs Externas
API_GARFO_URL=http://api.garfo.ameciclo.org
CMS_BASE_URL=http://do.strapi.ameciclo.org

# Mapbox (obtenha em https://mapbox.com)
MAPBOX_ACCESS_TOKEN=seu_token_mapbox_aqui

# Google Calendar
GOOGLE_CALENDAR_API_KEY=sua_chave_google_calendar_aqui
GOOGLE_CALENDAR_EXTERNAL_ID=seu_calendar_id_externo_aqui
GOOGLE_CALENDAR_INTERNAL_ID=seu_calendar_id_interno_aqui

# Analytics
GOOGLE_ANALYTICS_ID=G-PQNS7S7FD3

# Ambiente
NODE_ENV=development
12 changes: 12 additions & 0 deletions app/components/Commom/Maps/AmecicloMap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import * as turf from "@turf/helpers";
import { pointData } from "../../../../typings";
import * as Remix from "@remix-run/react";
import { Move } from 'lucide-react';
import { MapboxKeyWarning } from './MapboxKeyWarning';



Expand Down Expand Up @@ -588,6 +589,17 @@ export const AmecicloMap = ({
}));
};

// Verificar se o token do Mapbox está disponível
if (!MAPBOXTOKEN) {
return (
<section className={width === "100%" ? "w-full" : "container mx-auto"} style={{height: height === "100%" ? "100%" : "auto"}}>
<div className={`relative bg-gray-200 map-container ${isFullscreen ? 'fixed inset-0 z-50 w-screen h-screen rounded shadow-2xl' : width === "100%" ? 'w-full h-full' : 'rounded shadow-2xl'}`} style={{height: height === "100%" ? "100%" : height}}>
<MapboxKeyWarning />
</div>
</section>
);
}

return (
<section className={width === "100%" ? "w-full" : "container mx-auto"} style={{height: height === "100%" ? "100%" : "auto"}}>

Expand Down
59 changes: 59 additions & 0 deletions app/components/Commom/Maps/MapboxKeyWarning.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { AlertTriangle, Info, Mail } from 'lucide-react';
import { Link, useLocation } from '@remix-run/react';

export const MapboxKeyWarning = () => {
const location = useLocation();
const currentPage = location.pathname;
const errorMessage = encodeURIComponent(`Erro no mapa da página: ${currentPage}\n\nDescrição: A chave de acesso do serviço de mapas não está configurada.`);
const subject = encodeURIComponent('Erro Técnico - Mapa não carrega');

return (
<div className="w-full h-full flex items-center justify-center bg-gray-100 p-6">
<div className="max-w-md w-full bg-white rounded-lg shadow-lg border border-gray-200 overflow-hidden">
<div className="bg-yellow-50 border-b border-yellow-100 px-6 py-4">
<div className="flex items-center gap-3">
<AlertTriangle className="w-6 h-6 text-yellow-600 flex-shrink-0" />
<h3 className="text-lg font-semibold text-gray-900">
Problemas Técnicos com o Mapa
</h3>
</div>
</div>

<div className="p-6 space-y-4">
<p className="text-gray-700 leading-relaxed">
Não foi possível carregar o mapa interativo no momento devido a problemas de configuração técnica.
</p>

<div className="bg-blue-50 border border-blue-100 rounded-lg p-4">
<div className="flex gap-3">
<Info className="w-5 h-5 text-blue-600 flex-shrink-0 mt-0.5" />
<div className="space-y-2">
<p className="text-sm font-medium text-blue-900">
Mais informações
</p>
<p className="text-sm text-blue-800">
A chave de acesso do serviço de mapas não está configurada.
Entre em contato com o administrador do sistema.
</p>
</div>
</div>
</div>

<Link
to={`/contato?subject=${subject}&message=${errorMessage}`}
className="flex items-center justify-center gap-2 w-full bg-teal-600 hover:bg-teal-700 text-white font-medium py-3 px-4 rounded-lg transition-colors"
>
<Mail className="w-5 h-5" />
Entrar em Contato
</Link>

<div className="pt-2">
<p className="text-sm text-gray-500 text-center">
Pedimos desculpas pelo inconveniente.
</p>
</div>
</div>
</div>
</div>
);
};
2 changes: 2 additions & 0 deletions app/components/Commom/Maps/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { MapboxKeyWarning } from './MapboxKeyWarning';
export { AmecicloMap } from './AmecicloMap';
2 changes: 1 addition & 1 deletion app/components/Commom/NavBar/NavbarLogo.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export function NavbarLogo() {
return (
<svg
className="relative w-36 h-8 text-ameciclo"
className="px-0 relative w-44 h-14 lg:w-36 lg:h-8 text-ameciclo"
fill="currentColor"
viewBox="0 0 100.00014 29.999868"
>
Expand Down
13 changes: 8 additions & 5 deletions app/components/Commom/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,15 +70,18 @@ export const Navbar = ({ pages }: any) => {
return (
<>
<motion.nav
animate={{ y: isMenuOpen ? -120 : 0 }}
animate={{ y: 0 }}
transition={{ duration: 0.3, ease: "easeInOut" }}
className={"flex fixed top-0 left-0 right-0 items-center h-14 z-[80] text-white transition-shadow duration-300 bg-ameciclo shadow-sm"}
role="navigation mt-0"
>
<div className="w-full flex items-center justify-between px-8 py-0 m-0 lg:px-32 xl:px-32">
<Link to="/" aria-label="Ir para o site da Ameciclo" className="relative z-[85] pointer-events-auto" prefetch="intent">
<AmecicloLogo isScrolled={isHeaderScrolled || isSubmenuVisible || location.pathname === '/dados/ciclodados'} />
</Link>
<div className="w-full flex items-center justify-between px-4 lg:px-32 xl:px-32 py-0 m-0">
{!isMenuOpen && (
<Link to="/" aria-label="Ir para o site da Ameciclo" className="relative z-[85] pointer-events-auto" prefetch="intent">
<AmecicloLogo isScrolled={isHeaderScrolled || isSubmenuVisible || location.pathname === '/dados/ciclodados'} />
</Link>
)}
{isMenuOpen && <div className="lg:hidden" />}

<div className="hidden lg:flex space-x-8">
<BigMenu pages={pages} setIsSubmenuVisible={setIsSubmenuVisible} isSubmenuVisible={isSubmenuVisible} />
Expand Down
47 changes: 29 additions & 18 deletions app/components/Contagens/CountingComparisionTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Link } from "@remix-run/react";
import { ColumnFilter, NumberRangeColumnFilter } from "~/components/Commom/Table/TableFilters";
import Table from "~/components/Commom/Table/Table";
import { IntlDateStr } from "~/services/utils";
import { contagemSlug } from "~/utils/slugify";

function fuzzyTextFilterFn(rows: any[], id: string, filterValue: string) {
return matchSorter(rows, filterValue, { keys: [(row: any) => row.values[id]] });
Expand Down Expand Up @@ -55,15 +56,20 @@ export const CountingComparisionTable = ({ data, firstSlug }: { data: any[], fir
{
Header: "Nome",
accessor: "name",
Cell: ({ row }: { row: any }) => (
<Link
className="text-ameciclo"
to={`/dados/contagens/${row.original.id}`}
key={row.original.id}
>
{row.original.name}
</Link>
),
Cell: ({ row }: { row: any }) => {
const slug = row.original.date
? contagemSlug(row.original.date, row.original.name)
: String(row.original.id);
return (
<Link
className="text-ameciclo"
to={`/dados/contagens/${slug}`}
key={row.original.id}
>
{row.original.name}
</Link>
);
},
Filter: ColumnFilter,
},
{
Expand All @@ -87,15 +93,20 @@ export const CountingComparisionTable = ({ data, firstSlug }: { data: any[], fir
},
{
Header: "COMPARE",
accessor: "compare", // Adiciona accessor
Cell: ({ row }: { row: any }) => (
<Link
className="text-ameciclo hover:underline font-medium"
to={`/dados/contagens/${firstSlug}/compare/${row.original.id}`}
>
COMPARE
</Link>
),
accessor: "compare",
Cell: ({ row }: { row: any }) => {
const compareSlug = row.original.date
? contagemSlug(row.original.date, row.original.name)
: String(row.original.id);
return (
<Link
className="text-ameciclo hover:underline font-medium"
to={`/dados/contagens/${firstSlug}/compare/${compareSlug}`}
>
COMPARE
</Link>
);
},
disableFilters: true,
disableSortBy: true,
},
Expand Down
6 changes: 4 additions & 2 deletions app/components/Contagens/CountsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { ContagemData } from "~/services/contagens.service";
import { IntlDateStr } from "~/services/utils";
import Table from "~/components/Commom/Table/Table";
import { ColumnFilter } from "~/components/Commom/Table/TableFilters";
import { contagemSlug } from "~/utils/slugify";

interface ContagensTableProps {
data: ContagemData[];
Expand Down Expand Up @@ -50,8 +51,9 @@ export function CountsTable({ data }: ContagensTableProps) {
Header: "Nome",
accessor: "name",
Cell: ({ row }: any) => {
// Generate slug from ID and name if slug doesn't exist
const slug = row.original.id || `${row.original.id}`;
const slug = row.original.date
? contagemSlug(row.original.date, row.original.name)
: String(row.original.id);
return (
<Link
className="text-ameciclo hover:underline"
Expand Down
10 changes: 10 additions & 0 deletions app/components/Contagens/PointDetailsModal.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Link } from "@remix-run/react";
import { pointData } from "typings";

interface PointDetailsModalProps {
Expand Down Expand Up @@ -54,6 +55,15 @@ export function PointDetailsModal({ point, onClose }: PointDetailsModalProps) {
<div className="text-sm text-gray-700">{point.popup.obs}</div>
</div>
)}

{point.popup?.url && (
<Link
to={point.popup.url}
className="block w-full text-center bg-teal-600 hover:bg-teal-700 text-white font-semibold py-2 rounded-lg transition-colors"
>
Ver mais
</Link>
)}
</div>
</div>
</div>
Expand Down
40 changes: 40 additions & 0 deletions app/components/Projetos/LanguageBadge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Link } from "@remix-run/react";
import { Globe } from "lucide-react";

interface LanguageBadgeProps {
currentSlug: string;
}

export const LanguageBadge = ({ currentSlug }: LanguageBadgeProps) => {
let baseSlug = currentSlug;

if (currentSlug.endsWith('_en')) {
baseSlug = currentSlug.replace('_en', '');
} else if (currentSlug.endsWith('_es')) {
baseSlug = currentSlug.replace('_es', '');
}

const translations = [
{ lang: 'en', flag: '🇬🇧', slug: `${baseSlug}_en`, label: 'English' },
{ lang: 'es', flag: '🇪🇸', slug: `${baseSlug}_es`, label: 'Español' },
];

return (
<div className="flex items-center gap-2">
<Globe className="w-4 h-4 text-gray-500" />
{translations.map((t) => (
<Link
key={t.lang}
to={`/projetos/${t.slug}`}
className="flex items-center gap-1 px-2 py-1 bg-gray-100 hover:bg-teal-50 rounded-md transition-colors group"
title={t.label}
>
<span className="text-base">{t.flag}</span>
<span className="text-xs text-gray-600 group-hover:text-teal-600 font-medium">
{t.lang.toUpperCase()}
</span>
</Link>
))}
</div>
);
};
Loading