Merge pull request 'feat: critères opérationnels Guyane' (#71) from feat/operational-criteria into main
All checks were successful
CI / test (push) Successful in 2m5s

This commit is contained in:
tarzzan 2026-06-02 02:26:04 +00:00
commit 1f8250ad7e
11 changed files with 494 additions and 8 deletions

View file

@ -0,0 +1,15 @@
CREATE TYPE "RoadAccess" AS ENUM ('NONE', 'DRY_SEASON_ONLY', 'ALL_YEAR');
CREATE TYPE "Electricity" AS ENUM ('NONE', 'SOLAR', 'GENERATOR_READY', 'EDF');
ALTER TABLE "Carbet" ADD COLUMN "roadAccess" "RoadAccess";
ALTER TABLE "Carbet" ADD COLUMN "electricity" "Electricity";
ALTER TABLE "Carbet" ADD COLUMN "gsmAtCarbet" BOOLEAN NOT NULL DEFAULT false;
ALTER TABLE "Carbet" ADD COLUMN "gsmExitDistanceKm" DECIMAL(4,2);
-- Seed des 6 carbets démo avec valeurs réalistes
UPDATE "Carbet" SET "roadAccess" = 'NONE', "electricity" = 'SOLAR', "gsmAtCarbet" = false, "gsmExitDistanceKm" = 1.5 WHERE id = 'demo-carbet-awara';
UPDATE "Carbet" SET "roadAccess" = 'ALL_YEAR', "electricity" = 'EDF', "gsmAtCarbet" = true WHERE id = 'demo-carbet-kourou';
UPDATE "Carbet" SET "roadAccess" = 'ALL_YEAR', "electricity" = 'EDF', "gsmAtCarbet" = true WHERE id = 'demo-carbet-mahury';
UPDATE "Carbet" SET "roadAccess" = 'NONE', "electricity" = 'GENERATOR_READY', "gsmAtCarbet" = false, "gsmExitDistanceKm" = 4.0 WHERE id = 'demo-carbet-maripa';
UPDATE "Carbet" SET "roadAccess" = 'DRY_SEASON_ONLY', "electricity" = 'SOLAR', "gsmAtCarbet" = false, "gsmExitDistanceKm" = 0.5 WHERE id = 'demo-carbet-paripou';
UPDATE "Carbet" SET "roadAccess" = 'ALL_YEAR', "electricity" = 'EDF', "gsmAtCarbet" = true WHERE id = 'demo-carbet-wapa';

View file

@ -124,6 +124,11 @@ model Carbet {
// Détails d'accès route pour ROAD_AND_RIVER (GPS, distance, type de piste).
roadAccessNote String?
capacity Int
// 4 critères opérationnels dealbreakers (dispo en filtres + badges UI)
roadAccess RoadAccess?
electricity Electricity?
gsmAtCarbet Boolean @default(false)
gsmExitDistanceKm Decimal? @db.Decimal(4, 2)
// Prix par nuit pour le carbet entier (toute capacité). En euros.
nightlyPrice Decimal @db.Decimal(10, 2) @default(0)
// Contraintes séjour (plugin min-stay). null = pas de contrainte.
@ -381,3 +386,16 @@ model Favorite {
@@index([userId])
@@index([carbetId])
}
enum RoadAccess {
NONE
DRY_SEASON_ONLY
ALL_YEAR
}
enum Electricity {
NONE
SOLAR
GENERATOR_READY
EDF
}

View file

@ -20,6 +20,7 @@ import { CarbetMap } from "../_components/carbet-map";
import { ReviewsSection } from "../_components/reviews-section";
import { StarRating } from "../_components/star-rating";
import { AccessTypeBadge } from "@/components/AccessTypeBadge";
import { OperationalBadges } from "@/components/OperationalBadges";
import { StayConstraints } from "@/components/StayConstraints";
import { PirogueTransportBlock } from "@/components/PirogueTransportBlock";
@ -131,6 +132,20 @@ export default async function PublicCarbetPage({ params }: PageProps) {
<CarbetGallery title={carbet.title} media={carbet.media} />
</section>
<section className="mt-6">
<h2 className="mb-3 text-base font-semibold uppercase tracking-wider text-zinc-500">
Critères opérationnels
</h2>
<OperationalBadges
roadAccess={carbet.roadAccess}
capacity={carbet.capacity}
electricity={carbet.electricity}
gsmAtCarbet={carbet.gsmAtCarbet}
gsmExitDistanceKm={carbet.gsmExitDistanceKm}
variant="full"
/>
</section>
<div className="mt-10 grid gap-10 lg:grid-cols-3">
<div className="lg:col-span-2">
<section>

View file

@ -5,6 +5,7 @@ import { formatPirogueDuration, truncate } from "@/lib/format";
import { formatAverageRating } from "@/lib/reviews";
import { buildSrcSet } from "@/lib/image-variants";
import { AccessTypeBadge } from "@/components/AccessTypeBadge";
import { OperationalBadges } from "@/components/OperationalBadges";
import { StayConstraints } from "@/components/StayConstraints";
import { StarRating } from "./star-rating";
@ -41,9 +42,18 @@ export function CarbetCard({ carbet }: { carbet: CarbetSearchResult }) {
<AccessTypeBadge accessType={carbet.accessType} />
</div>
<p className="mt-1 text-sm text-zinc-600">
Fleuve {carbet.river} · {carbet.capacity} voyageur
{carbet.capacity > 1 ? "s" : ""}
Fleuve {carbet.river}
</p>
<div className="mt-2">
<OperationalBadges
roadAccess={carbet.roadAccess}
capacity={carbet.capacity}
electricity={carbet.electricity}
gsmAtCarbet={carbet.gsmAtCarbet}
gsmExitDistanceKm={carbet.gsmExitDistanceKm}
variant="compact"
/>
</div>
<div className="mt-1 flex flex-wrap gap-1">
<StayConstraints
minNights={carbet.minStayNights}

View file

@ -2,6 +2,7 @@ import Link from "next/link";
import type { CarbetSearchFilters } from "@/lib/carbet-search";
import { AMENITY_CATALOG } from "@/lib/amenities";
import { Electricity, RoadAccess } from "@/generated/prisma/enums";
type SearchFiltersProps = {
filters: CarbetSearchFilters;
@ -62,14 +63,27 @@ export function SearchFilters({ filters, rivers }: SearchFiltersProps) {
</label>
<label className="flex flex-col gap-1 text-sm">
<span className="font-medium text-zinc-700">Voyageurs</span>
<span className="font-medium text-zinc-700">Voyageurs min</span>
<input
type="number"
name="capacity"
min={1}
max={100}
defaultValue={filters.capacity ?? ""}
placeholder="Nombre min."
placeholder="Au moins"
className="rounded-md border border-zinc-300 px-3 py-2 text-zinc-900 focus:border-emerald-500 focus:outline-none"
/>
</label>
<label className="flex flex-col gap-1 text-sm">
<span className="font-medium text-zinc-700">Voyageurs max</span>
<input
type="number"
name="capacityMax"
min={1}
max={100}
defaultValue={filters.capacityMax ?? ""}
placeholder="Au plus"
className="rounded-md border border-zinc-300 px-3 py-2 text-zinc-900 focus:border-emerald-500 focus:outline-none"
/>
</label>
@ -87,6 +101,98 @@ export function SearchFilters({ filters, rivers }: SearchFiltersProps) {
/>
</label>
<fieldset className="flex flex-col gap-1 text-sm sm:col-span-2 lg:col-span-5">
<legend className="font-medium text-zinc-700">Accès route</legend>
<div className="flex flex-wrap gap-2 pt-1">
{[
{ value: RoadAccess.ALL_YEAR, label: "🛣️ Route toute saison" },
{ value: RoadAccess.DRY_SEASON_ONLY, label: "🟠 Route saison sèche" },
{ value: RoadAccess.NONE, label: "🛶 Pirogue uniquement" },
].map((opt) => {
const checked = (filters.roadAccess ?? []).includes(opt.value);
return (
<label
key={opt.value}
className={
"flex cursor-pointer items-center gap-1 rounded-full border px-2.5 py-1 text-xs " +
(checked
? "border-emerald-600 bg-emerald-50 text-emerald-900"
: "border-zinc-300 bg-white text-zinc-700 hover:border-zinc-400")
}
>
<input
type="checkbox"
name="roadAccess"
value={opt.value}
defaultChecked={checked}
className="sr-only"
/>
{opt.label}
</label>
);
})}
</div>
</fieldset>
<fieldset className="flex flex-col gap-1 text-sm sm:col-span-2 lg:col-span-5">
<legend className="font-medium text-zinc-700">Électricité</legend>
<div className="flex flex-wrap gap-2 pt-1">
{[
{ value: Electricity.EDF, label: "⚡ EDF / raccordé" },
{ value: Electricity.GENERATOR_READY, label: "🔌 Préinstall groupe" },
{ value: Electricity.SOLAR, label: "☀️ Solaire" },
{ value: Electricity.NONE, label: "🕯️ Aucune" },
].map((opt) => {
const checked = (filters.electricity ?? []).includes(opt.value);
return (
<label
key={opt.value}
className={
"flex cursor-pointer items-center gap-1 rounded-full border px-2.5 py-1 text-xs " +
(checked
? "border-emerald-600 bg-emerald-50 text-emerald-900"
: "border-zinc-300 bg-white text-zinc-700 hover:border-zinc-400")
}
>
<input
type="checkbox"
name="electricity"
value={opt.value}
defaultChecked={checked}
className="sr-only"
/>
{opt.label}
</label>
);
})}
</div>
</fieldset>
<label className="flex flex-col gap-1 text-sm sm:col-span-2 lg:col-span-5">
<span className="font-medium text-zinc-700">
📶 Réseau GSM accessible distance max{" "}
<span className="font-mono text-emerald-700">
{filters.gsmMaxKm === 0 ? "(au carbet)" : filters.gsmMaxKm ? `${filters.gsmMaxKm} km` : "(non filtré)"}
</span>
</span>
<div className="flex items-center gap-3">
<span className="text-xs text-zinc-500">Au carbet</span>
<input
type="range"
name="gsmMaxKm"
min={0}
max={10}
step={0.5}
defaultValue={filters.gsmMaxKm ?? ""}
className="flex-1 accent-emerald-600"
/>
<span className="text-xs text-zinc-500">10 km</span>
</div>
<span className="text-[11px] text-zinc-500">
0 km = exige le réseau directement au carbet · 10 km = peu importe.
</span>
</label>
<fieldset className="flex flex-col gap-1 text-sm sm:col-span-2 lg:col-span-5">
<legend className="font-medium text-zinc-700">Équipements souhaités</legend>
<div className="flex flex-wrap gap-2 pt-1">

View file

@ -0,0 +1,29 @@
"use client";
import Link from "next/link";
import { SEARCH_PROFILES, buildProfileUrl } from "@/lib/search-profiles";
export function SearchProfiles() {
return (
<div className="mb-4">
<div className="mb-2 text-xs uppercase tracking-wider text-zinc-500">
Profils de séjour
</div>
<ul className="-mx-1 flex flex-wrap gap-1.5 px-1">
{SEARCH_PROFILES.map((p) => (
<li key={p.id}>
<Link
href={buildProfileUrl(p.id)}
title={p.description}
className="inline-flex items-center gap-1.5 rounded-full border border-zinc-200 bg-white px-3 py-1.5 text-sm text-zinc-800 transition hover:border-emerald-400 hover:bg-emerald-50 hover:text-emerald-900"
>
<span aria-hidden>{p.emoji}</span>
<span className="font-medium">{p.label}</span>
</Link>
</li>
))}
</ul>
</div>
);
}

View file

@ -10,6 +10,7 @@ import {
import { CarbetCard } from "./_components/carbet-card";
import { CatalogMap } from "./_components/catalog-map";
import { SearchFilters } from "./_components/search-filters";
import { SearchProfiles } from "./_components/search-profiles";
export const metadata: Metadata = {
title: "Rechercher un carbet",
@ -57,6 +58,7 @@ export default async function CarbetsSearchPage({
</p>
</header>
<SearchProfiles />
<SearchFilters filters={filters} rivers={rivers} />
<section className="mt-8" aria-live="polite">

View file

@ -0,0 +1,120 @@
/**
* Badges opérationnels Karbé : 4 critères dealbreakers affichés en compact
* sur les cards catalog + en gros sur la fiche carbet.
*
* - Route (NONE / DRY_SEASON_ONLY / ALL_YEAR)
* - Capacité (X voyageurs max)
* - Électricité (NONE / SOLAR / GENERATOR_READY / EDF)
* - GSM (au carbet OUI / à X km / zone blanche)
*/
import { Electricity, RoadAccess } from "@/generated/prisma/enums";
type Props = {
roadAccess: RoadAccess | null;
capacity: number;
electricity: Electricity | null;
gsmAtCarbet: boolean;
gsmExitDistanceKm: number | null;
/** "compact" pour les cards, "full" pour la fiche détail. */
variant?: "compact" | "full";
};
type Badge = {
emoji: string;
label: string;
tone: "good" | "neutral" | "warn";
};
function roadBadge(r: RoadAccess | null): Badge {
if (r === RoadAccess.ALL_YEAR) return { emoji: "🛣️", label: "Route toute saison", tone: "good" };
if (r === RoadAccess.DRY_SEASON_ONLY) return { emoji: "🛣️", label: "Route saison sèche", tone: "warn" };
if (r === RoadAccess.NONE) return { emoji: "🛶", label: "Pirogue uniquement", tone: "neutral" };
return { emoji: "🛣️", label: "Accès non précisé", tone: "neutral" };
}
function capacityBadge(c: number): Badge {
return { emoji: "👥", label: `${c} voyageur${c > 1 ? "s" : ""}`, tone: "neutral" };
}
function electricityBadge(e: Electricity | null): Badge {
if (e === Electricity.EDF) return { emoji: "⚡", label: "EDF / raccordé", tone: "good" };
if (e === Electricity.GENERATOR_READY) return { emoji: "🔌", label: "Préinstall groupe", tone: "good" };
if (e === Electricity.SOLAR) return { emoji: "☀️", label: "Solaire", tone: "neutral" };
if (e === Electricity.NONE) return { emoji: "🕯️", label: "Aucune électricité", tone: "warn" };
return { emoji: "⚡", label: "Électricité non précisée", tone: "neutral" };
}
function gsmBadge(atCarbet: boolean, exitKm: number | null): Badge {
if (atCarbet) return { emoji: "📶", label: "Réseau au carbet", tone: "good" };
if (exitKm !== null) {
const tone: Badge["tone"] = exitKm <= 1 ? "neutral" : "warn";
return { emoji: "📵", label: `Réseau à ${exitKm.toFixed(exitKm < 1 ? 1 : 0)} km`, tone };
}
return { emoji: "📵", label: "Zone blanche", tone: "warn" };
}
const TONE_CLASSES_COMPACT: Record<Badge["tone"], string> = {
good: "bg-emerald-50 text-emerald-800 ring-emerald-200",
neutral: "bg-zinc-100 text-zinc-700 ring-zinc-200",
warn: "bg-amber-50 text-amber-800 ring-amber-200",
};
const TONE_CLASSES_FULL: Record<Badge["tone"], string> = {
good: "bg-emerald-50 text-emerald-900 ring-emerald-300 border-emerald-200",
neutral: "bg-white text-zinc-900 ring-zinc-300 border-zinc-200",
warn: "bg-amber-50 text-amber-900 ring-amber-300 border-amber-200",
};
export function OperationalBadges({
roadAccess,
capacity,
electricity,
gsmAtCarbet,
gsmExitDistanceKm,
variant = "compact",
}: Props) {
const badges: Badge[] = [
roadBadge(roadAccess),
capacityBadge(capacity),
electricityBadge(electricity),
gsmBadge(gsmAtCarbet, gsmExitDistanceKm),
];
if (variant === "compact") {
return (
<ul className="flex flex-wrap gap-1.5">
{badges.map((b, i) => (
<li
key={i}
className={
"inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[10px] font-semibold ring-1 ring-inset " +
TONE_CLASSES_COMPACT[b.tone]
}
>
<span aria-hidden>{b.emoji}</span>
<span>{b.label}</span>
</li>
))}
</ul>
);
}
// full : grille 2×2 pour la fiche
return (
<ul className="grid grid-cols-1 gap-2 sm:grid-cols-2">
{badges.map((b, i) => (
<li
key={i}
className={
"flex items-center gap-3 rounded-lg border px-3 py-2 ring-1 ring-inset " +
TONE_CLASSES_FULL[b.tone]
}
>
<span aria-hidden className="text-xl">{b.emoji}</span>
<span className="text-sm font-medium">{b.label}</span>
</li>
))}
</ul>
);
}

View file

@ -28,6 +28,10 @@ export type PublicCarbetDetail = {
roadAccessNote: string | null;
capacity: number;
nightlyPrice: string;
roadAccess: import("@/generated/prisma/enums").RoadAccess | null;
electricity: import("@/generated/prisma/enums").Electricity | null;
gsmAtCarbet: boolean;
gsmExitDistanceKm: number | null;
minStayNights: number | null;
maxStayNights: number | null;
minCapacity: number | null;
@ -62,6 +66,10 @@ export const getPublicCarbet = cache(
roadAccessNote: true,
capacity: true,
nightlyPrice: true,
roadAccess: true,
electricity: true,
gsmAtCarbet: true,
gsmExitDistanceKm: true,
minStayNights: true,
maxStayNights: true,
minCapacity: true,
@ -113,6 +121,10 @@ export const getPublicCarbet = cache(
roadAccessNote: carbet.roadAccessNote,
capacity: carbet.capacity,
nightlyPrice: carbet.nightlyPrice.toString(),
roadAccess: carbet.roadAccess,
electricity: carbet.electricity,
gsmAtCarbet: carbet.gsmAtCarbet,
gsmExitDistanceKm: carbet.gsmExitDistanceKm !== null ? Number(carbet.gsmExitDistanceKm) : null,
minStayNights: carbet.minStayNights,
maxStayNights: carbet.maxStayNights,
minCapacity: carbet.minCapacity,

View file

@ -5,6 +5,8 @@ import {
AvailabilityBlockReason,
AvailabilityScope,
CarbetStatus,
Electricity,
RoadAccess,
} from "@/generated/prisma/enums";
import { getCarbetReviewStatsMany } from "@/lib/reviews-server";
@ -13,11 +15,16 @@ export type CarbetSearchFilters = {
startDate?: Date;
endDate?: Date;
capacity?: number;
// Filtre plugin access-type : si "river-only" exclu, on garde uniquement
// ROAD_AND_RIVER. Si "all" ou non spécifié, tout passe.
capacityMax?: number;
accessibility?: "road-only" | "all";
priceMax?: number;
amenities?: string[];
/** Niveaux d'accès route acceptés (multi). */
roadAccess?: RoadAccess[];
/** Niveaux d'électricité acceptés (multi). */
electricity?: Electricity[];
/** Distance max en km pour atteindre le réseau GSM. 0 = exige le réseau au carbet. */
gsmMaxKm?: number;
};
export type RawSearchParams = {
@ -71,6 +78,45 @@ export function parseSearchFilters(
filters.accessibility = accessibility;
}
const capacityMaxRaw = pickString(searchParams.capacityMax);
if (capacityMaxRaw) {
const cmax = Number(capacityMaxRaw);
if (Number.isInteger(cmax) && cmax > 0 && cmax <= 100) filters.capacityMax = cmax;
}
const roadRaw = searchParams.roadAccess;
if (roadRaw) {
const arr = Array.isArray(roadRaw) ? roadRaw : [roadRaw];
const keys = arr
.flatMap((s) => s.split(","))
.map((s) => s.trim())
.filter((s): s is RoadAccess =>
s === RoadAccess.NONE || s === RoadAccess.DRY_SEASON_ONLY || s === RoadAccess.ALL_YEAR,
);
if (keys.length > 0) filters.roadAccess = Array.from(new Set(keys));
}
const elecRaw = searchParams.electricity;
if (elecRaw) {
const arr = Array.isArray(elecRaw) ? elecRaw : [elecRaw];
const keys = arr
.flatMap((s) => s.split(","))
.map((s) => s.trim())
.filter((s): s is Electricity =>
s === Electricity.NONE ||
s === Electricity.SOLAR ||
s === Electricity.GENERATOR_READY ||
s === Electricity.EDF,
);
if (keys.length > 0) filters.electricity = Array.from(new Set(keys));
}
const gsmMaxRaw = pickString(searchParams.gsmMaxKm);
if (gsmMaxRaw) {
const km = Number(gsmMaxRaw);
if (Number.isFinite(km) && km >= 0 && km <= 50) filters.gsmMaxKm = km;
}
const priceMaxRaw = pickString(searchParams.priceMax);
if (priceMaxRaw) {
const priceMax = Number(priceMaxRaw);
@ -113,6 +159,10 @@ export type CarbetSearchResult = {
nightlyPrice: string;
latitude: number;
longitude: number;
roadAccess: RoadAccess | null;
electricity: Electricity | null;
gsmAtCarbet: boolean;
gsmExitDistanceKm: number | null;
};
// Build the Prisma where-clause for a public carbet search. A carbet is only
@ -127,8 +177,30 @@ function buildWhere(filters: CarbetSearchFilters): Prisma.CarbetWhereInput {
where.river = { contains: filters.river, mode: "insensitive" };
}
if (filters.capacity) {
where.capacity = { gte: filters.capacity };
if (filters.capacity || filters.capacityMax) {
where.capacity = {};
if (filters.capacity) where.capacity.gte = filters.capacity;
if (filters.capacityMax) where.capacity.lte = filters.capacityMax;
}
if (filters.roadAccess && filters.roadAccess.length > 0) {
where.roadAccess = { in: filters.roadAccess };
}
if (filters.electricity && filters.electricity.length > 0) {
where.electricity = { in: filters.electricity };
}
if (filters.gsmMaxKm !== undefined) {
if (filters.gsmMaxKm === 0) {
where.gsmAtCarbet = true;
} else {
where.OR = [
...(where.OR ?? []),
{ gsmAtCarbet: true },
{ gsmExitDistanceKm: { lte: filters.gsmMaxKm } },
];
}
}
if (filters.accessibility === "road-only") {
@ -182,6 +254,10 @@ export async function searchCarbets(
maxStayNights: true,
minCapacity: true,
description: true,
roadAccess: true,
electricity: true,
gsmAtCarbet: true,
gsmExitDistanceKm: true,
nightlyPrice: true,
latitude: true,
longitude: true,
@ -222,6 +298,10 @@ export async function searchCarbets(
nightlyPrice: carbet.nightlyPrice.toString(),
latitude: Number(carbet.latitude),
longitude: Number(carbet.longitude),
roadAccess: carbet.roadAccess,
electricity: carbet.electricity,
gsmAtCarbet: carbet.gsmAtCarbet,
gsmExitDistanceKm: carbet.gsmExitDistanceKm !== null ? Number(carbet.gsmExitDistanceKm) : null,
};
});
}

View file

@ -0,0 +1,79 @@
/**
* Profils de séjour prédéfinis chips au-dessus des facettes.
* Chaque profil pose un set de query params qui pré-cochent les filtres.
*/
import { Electricity, RoadAccess } from "@/generated/prisma/enums";
export type SearchProfile = {
id: string;
emoji: string;
label: string;
description: string;
params: Record<string, string>;
};
export const SEARCH_PROFILES: SearchProfile[] = [
{
id: "deconnexion",
emoji: "🌿",
label: "Déconnexion totale",
description: "Zone blanche, pas d'électricité, accès pirogue, 2-4 personnes.",
params: {
roadAccess: RoadAccess.NONE,
electricity: `${Electricity.NONE},${Electricity.SOLAR}`,
capacityMax: "4",
},
},
{
id: "teletravail",
emoji: "💻",
label: "Télétravail nature",
description: "Route, EDF, 4G au carbet — bureau au bord du fleuve.",
params: {
roadAccess: RoadAccess.ALL_YEAR,
electricity: Electricity.EDF,
gsmMaxKm: "0",
},
},
{
id: "famille-weekend",
emoji: "🏝️",
label: "Famille week-end",
description: "Route toute saison, électricité, capacité 4-8.",
params: {
roadAccess: RoadAccess.ALL_YEAR,
electricity: `${Electricity.EDF},${Electricity.GENERATOR_READY}`,
capacity: "4",
capacityMax: "8",
},
},
{
id: "astreinte",
emoji: "📞",
label: "Astreinte sereine",
description: "Réseau accessible (au max 1 km), EDF, route saison sèche min.",
params: {
gsmMaxKm: "1",
electricity: `${Electricity.EDF},${Electricity.GENERATOR_READY}`,
roadAccess: `${RoadAccess.DRY_SEASON_ONLY},${RoadAccess.ALL_YEAR}`,
},
},
{
id: "aventure",
emoji: "🛶",
label: "Aventure expédition",
description: "Accès pirogue uniquement, petit groupe 2-4.",
params: {
roadAccess: RoadAccess.NONE,
capacityMax: "4",
},
},
];
export function buildProfileUrl(profileId: string): string {
const profile = SEARCH_PROFILES.find((p) => p.id === profileId);
if (!profile) return "/carbets";
const search = new URLSearchParams(profile.params);
return `/carbets?${search.toString()}`;
}