mirror of
https://github.com/R0m1k3/PleinR.git
synced 2026-10-11 17:27:54 +02:00
Ajoute la page Annuaire publique (/annuaire) reliée aux fiches adhérents
Liste des adhérents actifs avec recherche (nom/métier), filtre par commune, filtres catégories (chips + compteurs), tri (A–Z / avec promotions), état vide et CTA d'adhésion — fidèle à la maquette. Chaque carte ouvre la fiche adhérent (/adherents/[id]). La recherche de l'accueil et la nav pointent vers /annuaire (la barre de recherche pré-remplit via ?q=). Nouvelles requêtes annuaire. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XwfhYHzC9hQWPDnQ2p53GL
This commit is contained in:
5 files changed
+455
-6
No files matched your search
@@ -72,7 +72,7 @@ export default async function FicheAdherentPage({
|
|||||||
</span>
|
</span>
|
||||||
</Link>
|
</Link>
|
||||||
<nav style={{ display: "flex", alignItems: "center", gap: 30, flexWrap: "wrap" }}>
|
<nav style={{ display: "flex", alignItems: "center", gap: 30, flexWrap: "wrap" }}>
|
||||||
<Link href="/#annuaire" style={{ textDecoration: "none", color: "#3c3322", fontWeight: 600, fontSize: 15 }}>
|
<Link href="/annuaire" style={{ textDecoration: "none", color: "#3c3322", fontWeight: 600, fontSize: 15 }}>
|
||||||
Annuaire
|
Annuaire
|
||||||
</Link>
|
</Link>
|
||||||
<Link href="/#promotions" style={{ textDecoration: "none", color: "#6f6450", fontWeight: 500, fontSize: 15 }}>
|
<Link href="/#promotions" style={{ textDecoration: "none", color: "#6f6450", fontWeight: 500, fontSize: 15 }}>
|
||||||
@@ -97,7 +97,7 @@ export default async function FicheAdherentPage({
|
|||||||
|
|
||||||
<div className="container" style={{ paddingBottom: 50 }}>
|
<div className="container" style={{ paddingBottom: 50 }}>
|
||||||
<div style={{ padding: "10px 0 18px" }}>
|
<div style={{ padding: "10px 0 18px" }}>
|
||||||
<Link href="/#annuaire" className="link-arrow" style={{ textDecoration: "none", color: "#9a6638", fontWeight: 700, fontSize: 14.5 }}>
|
<Link href="/annuaire" className="link-arrow" style={{ textDecoration: "none", color: "#9a6638", fontWeight: 700, fontSize: 14.5 }}>
|
||||||
← Retour à l'annuaire
|
← Retour à l'annuaire
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,251 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useMemo, useState } from "react";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
|
export type DirectoryMember = {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
description: string | null;
|
||||||
|
city: string | null;
|
||||||
|
address: string | null;
|
||||||
|
categoryLabel: string | null;
|
||||||
|
accent: string | null;
|
||||||
|
hasPromo: boolean;
|
||||||
|
promoBadge: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const STRIPE_WARM =
|
||||||
|
"repeating-linear-gradient(45deg,#efe9da,#efe9da 12px,#e6ddc9 12px,#e6ddc9 24px)";
|
||||||
|
const STRIPE_COOL =
|
||||||
|
"repeating-linear-gradient(45deg,#eef0ec,#eef0ec 12px,#e2e8e6 12px,#e2e8e6 24px)";
|
||||||
|
|
||||||
|
export function AnnuaireClient({
|
||||||
|
members,
|
||||||
|
categories,
|
||||||
|
initialQuery = "",
|
||||||
|
}: {
|
||||||
|
members: DirectoryMember[];
|
||||||
|
categories: { label: string; accent: string }[];
|
||||||
|
initialQuery?: string;
|
||||||
|
}) {
|
||||||
|
const [query, setQuery] = useState(initialQuery);
|
||||||
|
const [city, setCity] = useState("Toutes les communes");
|
||||||
|
const [cat, setCat] = useState("Tous");
|
||||||
|
const [sort, setSort] = useState<"az" | "promo">("az");
|
||||||
|
|
||||||
|
const communes = useMemo(() => {
|
||||||
|
const set = new Set<string>();
|
||||||
|
members.forEach((m) => m.city && set.add(m.city));
|
||||||
|
return Array.from(set).sort((a, b) => a.localeCompare(b, "fr"));
|
||||||
|
}, [members]);
|
||||||
|
|
||||||
|
const counts = useMemo(() => {
|
||||||
|
const c: Record<string, number> = { Tous: members.length };
|
||||||
|
members.forEach((m) => {
|
||||||
|
if (m.categoryLabel) c[m.categoryLabel] = (c[m.categoryLabel] ?? 0) + 1;
|
||||||
|
});
|
||||||
|
return c;
|
||||||
|
}, [members]);
|
||||||
|
|
||||||
|
const chips = useMemo(() => {
|
||||||
|
const used = categories.filter((c) => (counts[c.label] ?? 0) > 0);
|
||||||
|
return [{ label: "Tous", accent: "#13324F" }, ...used];
|
||||||
|
}, [categories, counts]);
|
||||||
|
|
||||||
|
const list = useMemo(() => {
|
||||||
|
const q = query.trim().toLowerCase();
|
||||||
|
let out = members.filter((m) => {
|
||||||
|
if (cat !== "Tous" && m.categoryLabel !== cat) return false;
|
||||||
|
if (city !== "Toutes les communes" && m.city !== city) return false;
|
||||||
|
if (
|
||||||
|
q &&
|
||||||
|
!(
|
||||||
|
m.name.toLowerCase().includes(q) ||
|
||||||
|
(m.categoryLabel ?? "").toLowerCase().includes(q) ||
|
||||||
|
(m.description ?? "").toLowerCase().includes(q)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
if (sort === "az") out = [...out].sort((a, b) => a.name.localeCompare(b.name, "fr"));
|
||||||
|
else if (sort === "promo")
|
||||||
|
out = [...out].sort((a, b) => Number(b.hasPromo) - Number(a.hasPromo));
|
||||||
|
return out;
|
||||||
|
}, [members, query, city, cat, sort]);
|
||||||
|
|
||||||
|
const filterLabel =
|
||||||
|
(cat !== "Tous" ? " · " + cat : "") +
|
||||||
|
(city !== "Toutes les communes" ? " · " + city : "");
|
||||||
|
|
||||||
|
function reset() {
|
||||||
|
setQuery("");
|
||||||
|
setCity("Toutes les communes");
|
||||||
|
setCat("Tous");
|
||||||
|
setSort("az");
|
||||||
|
}
|
||||||
|
|
||||||
|
const fieldStyle: React.CSSProperties = {
|
||||||
|
border: "1px solid #ddd2bb",
|
||||||
|
borderRadius: 11,
|
||||||
|
padding: "0 13px",
|
||||||
|
background: "#faf7ef",
|
||||||
|
fontSize: 14,
|
||||||
|
fontFamily: "'Public Sans'",
|
||||||
|
color: "#3c3322",
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/* search + filters */}
|
||||||
|
<section
|
||||||
|
style={{
|
||||||
|
background: "#fff",
|
||||||
|
border: "1px solid #e6dcc6",
|
||||||
|
borderRadius: 18,
|
||||||
|
padding: 18,
|
||||||
|
boxShadow: "0 18px 40px -30px rgba(40,30,15,0.4)",
|
||||||
|
marginBottom: 14,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ display: "flex", gap: 11, marginBottom: 14, flexWrap: "wrap" }}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
flex: "1.6 1 240px",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: 9,
|
||||||
|
border: "1px solid #ddd2bb",
|
||||||
|
borderRadius: 11,
|
||||||
|
padding: "0 13px",
|
||||||
|
background: "#faf7ef",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span style={{ width: 10, height: 10, border: "2px solid #9a8d72", borderRadius: "50%", display: "inline-block" }} />
|
||||||
|
<input
|
||||||
|
value={query}
|
||||||
|
onChange={(e) => setQuery(e.target.value)}
|
||||||
|
placeholder="Nom du commerçant, métier…"
|
||||||
|
style={{ border: "none", background: "transparent", outline: "none", padding: "13px 0", fontSize: 14.5, fontFamily: "'Public Sans'", width: "100%", color: "#3c3322" }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<select value={city} onChange={(e) => setCity(e.target.value)} style={{ ...fieldStyle, flex: "0.9 1 160px", padding: "12px 13px" }}>
|
||||||
|
<option>Toutes les communes</option>
|
||||||
|
{communes.map((c) => (
|
||||||
|
<option key={c}>{c}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: "flex", flexWrap: "wrap", gap: 9 }}>
|
||||||
|
{chips.map((c) => {
|
||||||
|
const active = cat === c.label;
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={c.label}
|
||||||
|
onClick={() => setCat(c.label)}
|
||||||
|
style={{
|
||||||
|
cursor: "pointer",
|
||||||
|
fontFamily: "'Public Sans'",
|
||||||
|
fontSize: 13.5,
|
||||||
|
fontWeight: 600,
|
||||||
|
padding: "9px 15px",
|
||||||
|
borderRadius: 999,
|
||||||
|
transition: "all .15s",
|
||||||
|
...(active
|
||||||
|
? { background: "#13324F", color: "#fff", border: "1px solid #13324F" }
|
||||||
|
: { background: "#faf7ef", color: "#3c3322", border: "1px solid #e6dcc6" }),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{c.label}
|
||||||
|
<span style={{ opacity: 0.6, marginLeft: 6 }}>{counts[c.label] ?? 0}</span>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* result count + sort */}
|
||||||
|
<div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", padding: "14px 4px 18px", gap: 12, flexWrap: "wrap" }}>
|
||||||
|
<div style={{ fontSize: 14, color: "#6c6150" }}>
|
||||||
|
<strong style={{ color: "#26201a" }}>{list.length}</strong> adhérent(s)
|
||||||
|
{filterLabel}
|
||||||
|
</div>
|
||||||
|
<div style={{ display: "flex", alignItems: "center", gap: 8, fontSize: 13.5, color: "#9a8d72" }}>
|
||||||
|
Trier par
|
||||||
|
<select
|
||||||
|
value={sort}
|
||||||
|
onChange={(e) => setSort(e.target.value as "az" | "promo")}
|
||||||
|
style={{ border: "1px solid #ddd2bb", borderRadius: 9, padding: "7px 11px", background: "#fff", fontSize: 13.5, fontFamily: "'Public Sans'", color: "#3c3322" }}
|
||||||
|
>
|
||||||
|
<option value="az">Nom (A–Z)</option>
|
||||||
|
<option value="promo">Avec promotions</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* grid / empty */}
|
||||||
|
{list.length > 0 ? (
|
||||||
|
<div className="grid grid-3" style={{ gap: 20 }}>
|
||||||
|
{list.map((m, i) => (
|
||||||
|
<Link
|
||||||
|
key={m.id}
|
||||||
|
href={`/adherents/${m.id}`}
|
||||||
|
className="lift-card"
|
||||||
|
style={{ textDecoration: "none", color: "inherit", background: "#fff", border: "1px solid #e6dcc6", borderRadius: 18, overflow: "hidden", display: "flex", flexDirection: "column" }}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: "relative",
|
||||||
|
height: 150,
|
||||||
|
background: i % 2 === 0 ? STRIPE_WARM : STRIPE_COOL,
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span style={{ fontSize: 10, letterSpacing: "0.12em", color: "#a99c82", textTransform: "uppercase" }}>
|
||||||
|
photo vitrine
|
||||||
|
</span>
|
||||||
|
{m.categoryLabel && (
|
||||||
|
<span style={{ position: "absolute", top: 12, left: 12, background: m.accent ?? "#9a6638", color: "#fff", borderRadius: 999, padding: "5px 12px", fontSize: 11, fontWeight: 700 }}>
|
||||||
|
{m.categoryLabel}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{m.hasPromo && m.promoBadge && (
|
||||||
|
<span className="font-display" style={{ position: "absolute", top: 0, right: 0, background: "#d8472b", color: "#fff", fontWeight: 800, fontSize: 12.5, padding: "6px 12px", borderRadius: "0 0 0 14px" }}>
|
||||||
|
{m.promoBadge}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div style={{ padding: "16px 18px 18px", display: "flex", flexDirection: "column", flex: 1 }}>
|
||||||
|
<h3 className="font-display" style={{ fontWeight: 700, fontSize: 18, margin: 0, color: "#26201a" }}>
|
||||||
|
{m.name}
|
||||||
|
</h3>
|
||||||
|
<p style={{ margin: "6px 0 12px", fontSize: 13, color: "#8c8068", lineHeight: 1.5, flex: 1 }}>
|
||||||
|
{m.description}
|
||||||
|
</p>
|
||||||
|
<div style={{ display: "flex", alignItems: "center", gap: 7, fontSize: 12.5, color: "#6c6150", borderTop: "1px solid #f0e8d6", paddingTop: 11 }}>
|
||||||
|
<span style={{ width: 7, height: 7, borderRadius: "50%", background: m.accent ?? "#E0A63C" }} />
|
||||||
|
{[m.address, m.city].filter(Boolean).join(" · ")}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div style={{ background: "#fff", border: "1px dashed #d8cdb4", borderRadius: 18, padding: 54, textAlign: "center" }}>
|
||||||
|
<div className="font-display" style={{ fontWeight: 700, fontSize: 19, color: "#26201a", marginBottom: 6 }}>
|
||||||
|
Aucun adhérent trouvé
|
||||||
|
</div>
|
||||||
|
<p style={{ margin: "0 0 16px", fontSize: 14, color: "#8c8068" }}>
|
||||||
|
Essayez un autre mot-clé, une autre commune ou catégorie.
|
||||||
|
</p>
|
||||||
|
<button onClick={reset} className="font-display" style={{ border: "none", background: "#9a6638", color: "#fff", fontWeight: 700, fontSize: 14, padding: "11px 20px", borderRadius: 11, cursor: "pointer" }}>
|
||||||
|
Réinitialiser les filtres
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,166 @@
|
|||||||
|
import Link from "next/link";
|
||||||
|
import type { Metadata } from "next";
|
||||||
|
import { Sparkle } from "@/components/Sparkle";
|
||||||
|
import {
|
||||||
|
getActiveMembersWithCategory,
|
||||||
|
getAllCategories,
|
||||||
|
getLiveBadgesByMember,
|
||||||
|
} from "@/lib/queries";
|
||||||
|
import { AnnuaireClient, type DirectoryMember } from "./AnnuaireClient";
|
||||||
|
|
||||||
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Annuaire — Plein R",
|
||||||
|
description:
|
||||||
|
"L'annuaire des commerçants et entreprises du Bassin de Pompey. Trouvez un professionnel et ses bons plans.",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async function AnnuairePage({
|
||||||
|
searchParams,
|
||||||
|
}: {
|
||||||
|
searchParams: Promise<{ q?: string }>;
|
||||||
|
}) {
|
||||||
|
const { q } = await searchParams;
|
||||||
|
const [rawMembers, categories, badges] = await Promise.all([
|
||||||
|
getActiveMembersWithCategory(),
|
||||||
|
getAllCategories(),
|
||||||
|
getLiveBadgesByMember(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const badgeByMember = new Map<number, string | null>();
|
||||||
|
for (const b of badges) {
|
||||||
|
if (b.memberId != null && !badgeByMember.has(b.memberId)) {
|
||||||
|
badgeByMember.set(b.memberId, b.badge);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const members: DirectoryMember[] = rawMembers.map((m) => ({
|
||||||
|
...m,
|
||||||
|
hasPromo: badgeByMember.has(m.id),
|
||||||
|
promoBadge: badgeByMember.get(m.id) ?? null,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ background: "#F6F2E8", minHeight: "100vh", fontFamily: "'Public Sans',sans-serif", color: "#33291D" }}>
|
||||||
|
{/* header */}
|
||||||
|
<header
|
||||||
|
className="container"
|
||||||
|
style={{ display: "flex", alignItems: "center", justifyContent: "space-between", padding: "18px 56px", gap: 20 }}
|
||||||
|
>
|
||||||
|
<Link href="/" style={{ display: "flex", alignItems: "center", gap: 10, textDecoration: "none" }}>
|
||||||
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||||
|
<img src="/assets/logo.png" alt="Plein R" style={{ height: 44, width: "auto" }} />
|
||||||
|
<span className="font-display" style={{ fontWeight: 800, fontSize: 20, color: "#13324F" }}>
|
||||||
|
Plein R
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
|
<nav style={{ display: "flex", alignItems: "center", gap: 30, flexWrap: "wrap" }}>
|
||||||
|
<Link href="/" style={{ textDecoration: "none", color: "#6f6450", fontWeight: 500, fontSize: 15 }}>
|
||||||
|
Accueil
|
||||||
|
</Link>
|
||||||
|
<Link href="/annuaire" style={{ textDecoration: "none", color: "#3c3322", fontWeight: 600, fontSize: 15 }}>
|
||||||
|
Annuaire
|
||||||
|
</Link>
|
||||||
|
<Link href="/#promotions" style={{ textDecoration: "none", color: "#6f6450", fontWeight: 500, fontSize: 15 }}>
|
||||||
|
Promotions
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
href="/backend"
|
||||||
|
style={{ textDecoration: "none", color: "#fff", background: "#9a6638", fontWeight: 600, fontSize: 14.5, padding: "10px 18px", borderRadius: 999 }}
|
||||||
|
>
|
||||||
|
Espace adhérent
|
||||||
|
</Link>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div className="container" style={{ paddingBottom: 56 }}>
|
||||||
|
{/* title block */}
|
||||||
|
<section style={{ position: "relative", padding: "14px 0 22px", overflow: "hidden" }}>
|
||||||
|
<Sparkle color="#E0A63C" size={18} style={{ top: 22, right: 30 }} duration={3.2} />
|
||||||
|
<Sparkle color="#6FB0C6" size={12} style={{ top: 60, right: 90 }} duration={2.7} delay={0.5} />
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "inline-flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: 9,
|
||||||
|
background: "#fff",
|
||||||
|
border: "1px solid #e6dcc6",
|
||||||
|
color: "#9a6638",
|
||||||
|
fontSize: 12,
|
||||||
|
letterSpacing: "0.12em",
|
||||||
|
textTransform: "uppercase",
|
||||||
|
fontWeight: 800,
|
||||||
|
padding: "6px 13px",
|
||||||
|
borderRadius: 999,
|
||||||
|
marginBottom: 14,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span style={{ width: 7, height: 7, borderRadius: "50%", background: "#E0A63C" }} />
|
||||||
|
{members.length} adhérents
|
||||||
|
</div>
|
||||||
|
<h1 className="font-display" style={{ fontWeight: 800, fontSize: 40, letterSpacing: "-0.02em", margin: "0 0 8px", color: "#26201a" }}>
|
||||||
|
L'annuaire des commerçants & entreprises
|
||||||
|
</h1>
|
||||||
|
<p style={{ margin: 0, fontSize: 16.5, color: "#6c6150", maxWidth: 560 }}>
|
||||||
|
Trouvez un professionnel du Bassin de Pompey, découvrez sa fiche et ses bons plans.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<AnnuaireClient members={members} categories={categories} initialQuery={q ?? ""} />
|
||||||
|
|
||||||
|
{/* join CTA */}
|
||||||
|
<section
|
||||||
|
style={{
|
||||||
|
marginTop: 34,
|
||||||
|
position: "relative",
|
||||||
|
background: "linear-gradient(120deg,#13324F,#1d4a72)",
|
||||||
|
borderRadius: 20,
|
||||||
|
padding: "34px 40px",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
gap: 28,
|
||||||
|
overflow: "hidden",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Sparkle color="#E0A63C" size={18} style={{ top: 20, right: 40 }} duration={3.2} />
|
||||||
|
<div>
|
||||||
|
<div style={{ fontSize: 12.5, letterSpacing: "0.12em", textTransform: "uppercase", color: "#9fc6e6", fontWeight: 700, marginBottom: 8 }}>
|
||||||
|
Vous n'êtes pas encore référencé ?
|
||||||
|
</div>
|
||||||
|
<h2 className="font-display" style={{ fontWeight: 800, fontSize: 26, margin: 0, color: "#fff" }}>
|
||||||
|
Rejoignez l'annuaire Plein R
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<Link
|
||||||
|
href="/backend"
|
||||||
|
className="font-display"
|
||||||
|
style={{ textDecoration: "none", background: "#E0A63C", color: "#33291D", fontWeight: 700, fontSize: 15.5, padding: "14px 26px", borderRadius: 12, whiteSpace: "nowrap" }}
|
||||||
|
>
|
||||||
|
Adhérer à l'association
|
||||||
|
</Link>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* footer */}
|
||||||
|
<footer style={{ background: "#EFE9DA", borderTop: "1px solid #e2d6bd", padding: "28px 56px" }}>
|
||||||
|
<div className="container" style={{ display: "flex", justifyContent: "space-between", alignItems: "center", gap: 16, flexWrap: "wrap" }}>
|
||||||
|
<div style={{ display: "flex", alignItems: "center", gap: 10 }}>
|
||||||
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||||
|
<img src="/assets/logo.png" alt="Plein R" style={{ height: 36, width: "auto" }} />
|
||||||
|
<span className="font-display" style={{ fontWeight: 800, fontSize: 16, color: "#13324F" }}>
|
||||||
|
Plein R
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div style={{ fontSize: 13, color: "#8c8068", textAlign: "right", lineHeight: 1.7 }}>
|
||||||
|
contact@plein-r.fr · Bassin de Pompey
|
||||||
|
<br />
|
||||||
|
Réseau · Rencontre · Réussite
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
+4
-3
@@ -53,7 +53,7 @@ export default async function AccueilPage() {
|
|||||||
</span>
|
</span>
|
||||||
</Link>
|
</Link>
|
||||||
<nav style={{ display: "flex", alignItems: "center", gap: 30, flexWrap: "wrap" }}>
|
<nav style={{ display: "flex", alignItems: "center", gap: 30, flexWrap: "wrap" }}>
|
||||||
<a href="#annuaire" style={{ textDecoration: "none", color: "#3c3322", fontWeight: 600, fontSize: 15 }}>
|
<a href="/annuaire" style={{ textDecoration: "none", color: "#3c3322", fontWeight: 600, fontSize: 15 }}>
|
||||||
Annuaire
|
Annuaire
|
||||||
</a>
|
</a>
|
||||||
<a href="#promotions" style={{ textDecoration: "none", color: "#6f6450", fontWeight: 500, fontSize: 15 }}>
|
<a href="#promotions" style={{ textDecoration: "none", color: "#6f6450", fontWeight: 500, fontSize: 15 }}>
|
||||||
@@ -153,7 +153,8 @@ export default async function AccueilPage() {
|
|||||||
leurs bons plans et faites vivre le commerce de proximité.
|
leurs bons plans et faites vivre le commerce de proximité.
|
||||||
</p>
|
</p>
|
||||||
<form
|
<form
|
||||||
action="#annuaire"
|
action="/annuaire"
|
||||||
|
method="get"
|
||||||
style={{
|
style={{
|
||||||
display: "inline-flex",
|
display: "inline-flex",
|
||||||
gap: 10,
|
gap: 10,
|
||||||
@@ -464,7 +465,7 @@ export default async function AccueilPage() {
|
|||||||
<h2 className="font-display" style={{ fontWeight: 800, fontSize: 26, margin: 0, color: "#26201a" }}>
|
<h2 className="font-display" style={{ fontWeight: 800, fontSize: 26, margin: 0, color: "#26201a" }}>
|
||||||
À l'honneur ce mois-ci
|
À l'honneur ce mois-ci
|
||||||
</h2>
|
</h2>
|
||||||
<a href="#annuaire" className="link-arrow" style={{ textDecoration: "none", color: "#9a6638", fontWeight: 700, fontSize: 14.5 }}>
|
<a href="/annuaire" className="link-arrow" style={{ textDecoration: "none", color: "#9a6638", fontWeight: 700, fontSize: 14.5 }}>
|
||||||
Tout l'annuaire →
|
Tout l'annuaire →
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+32
-1
@@ -1,7 +1,38 @@
|
|||||||
import { and, count, desc, eq } from "drizzle-orm";
|
import { and, asc, count, desc, eq } from "drizzle-orm";
|
||||||
import { db } from "@/db";
|
import { db } from "@/db";
|
||||||
import { categories, members, promotions } from "@/db/schema";
|
import { categories, members, promotions } from "@/db/schema";
|
||||||
|
|
||||||
|
export async function getActiveMembersWithCategory() {
|
||||||
|
return db
|
||||||
|
.select({
|
||||||
|
id: members.id,
|
||||||
|
name: members.name,
|
||||||
|
description: members.description,
|
||||||
|
city: members.city,
|
||||||
|
address: members.address,
|
||||||
|
categoryLabel: categories.label,
|
||||||
|
accent: categories.accent,
|
||||||
|
})
|
||||||
|
.from(members)
|
||||||
|
.leftJoin(categories, eq(members.categoryId, categories.id))
|
||||||
|
.where(eq(members.status, "active"))
|
||||||
|
.orderBy(asc(members.name));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAllCategories() {
|
||||||
|
return db
|
||||||
|
.select({ id: categories.id, label: categories.label, accent: categories.accent })
|
||||||
|
.from(categories)
|
||||||
|
.orderBy(asc(categories.sort));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getLiveBadgesByMember() {
|
||||||
|
return db
|
||||||
|
.select({ memberId: promotions.memberId, badge: promotions.badge })
|
||||||
|
.from(promotions)
|
||||||
|
.where(eq(promotions.status, "live"));
|
||||||
|
}
|
||||||
|
|
||||||
export async function getCategoriesWithCounts(limit = 6) {
|
export async function getCategoriesWithCounts(limit = 6) {
|
||||||
const rows = await db
|
const rows = await db
|
||||||
.select({
|
.select({
|
||||||
|
|||||||
Reference in new issue
Block a user