mirror of
https://github.com/R0m1k3/PleinR.git
synced 2026-10-18 12:47:56 +02:00
Refonte des horaires et contacts adhérents
This commit is contained in:
1 parent
5e7233f98d
commit
dd9697019c
7 files changed
+960
-41
No files matched your search
+148
-35
@@ -7,6 +7,13 @@ import { SiteFooter } from "@/components/SiteFooter";
|
||||
import { VitrineImage } from "@/components/VitrineImage";
|
||||
import { PromoImage } from "@/components/PromoImage";
|
||||
import { getMemberLivePromotions, getPublicMember } from "@/lib/queries";
|
||||
import {
|
||||
formatSlots,
|
||||
getOpenStatus,
|
||||
normalizeWebsite,
|
||||
parseMemberHours,
|
||||
websiteLabel,
|
||||
} from "@/lib/member-profile";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
@@ -31,6 +38,52 @@ function initialsOf(name: string) {
|
||||
return name.split(/\s+/).map((w) => w[0]).join("").slice(0, 2).toUpperCase();
|
||||
}
|
||||
|
||||
type ContactIconName = "phone" | "mail" | "website" | "pin";
|
||||
|
||||
function ContactIcon({ name }: { name: ContactIconName }) {
|
||||
const paths: Record<ContactIconName, React.ReactNode> = {
|
||||
phone: <path d="M7.4 3.8 9 7.2 7.2 8.6a14.4 14.4 0 0 0 6.2 6.2L14.8 13l3.4 1.6-.8 3.6c-.2.9-1 1.5-1.9 1.5C8.2 19.7 2.3 13.8 2.3 6.5c0-.9.6-1.7 1.5-1.9l3.6-.8Z" />,
|
||||
mail: <><rect x="2.5" y="4.5" width="19" height="15" rx="2.5" /><path d="m4 6 8 6 8-6" /></>,
|
||||
website: <><circle cx="12" cy="12" r="9.5" /><path d="M2.8 12h18.4M12 2.5c2.4 2.6 3.6 5.8 3.6 9.5S14.4 18.9 12 21.5M12 2.5C9.6 5.1 8.4 8.3 8.4 12s1.2 6.9 3.6 9.5" /></>,
|
||||
pin: <><path d="M20 10c0 5.5-8 11.5-8 11.5S4 15.5 4 10a8 8 0 1 1 16 0Z" /><circle cx="12" cy="10" r="2.5" /></>,
|
||||
};
|
||||
return (
|
||||
<svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
|
||||
{paths[name]}
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function ContactAction({
|
||||
href,
|
||||
icon,
|
||||
label,
|
||||
detail,
|
||||
external = false,
|
||||
}: {
|
||||
href: string;
|
||||
icon: ContactIconName;
|
||||
label: string;
|
||||
detail: string;
|
||||
external?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<a
|
||||
href={href}
|
||||
className="contact-action"
|
||||
target={external ? "_blank" : undefined}
|
||||
rel={external ? "noopener noreferrer" : undefined}
|
||||
>
|
||||
<span className="contact-action__icon"><ContactIcon name={icon} /></span>
|
||||
<span className="contact-action__copy">
|
||||
<strong>{label}</strong>
|
||||
<span>{detail}</span>
|
||||
</span>
|
||||
<span className="contact-action__arrow" aria-hidden="true">{external ? "↗" : "→"}</span>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: {
|
||||
@@ -65,10 +118,11 @@ export default async function FicheAdherentPage({
|
||||
);
|
||||
const paragraphs = (member.description ?? "").split("\n").map((s) => s.trim()).filter(Boolean);
|
||||
const tags = (member.tags ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
||||
const hours = (member.hours ?? "")
|
||||
.split("\n")
|
||||
.map((line) => line.split("|").map((s) => s.trim()))
|
||||
.filter((p) => p[0]);
|
||||
const hours = parseMemberHours(member.hours);
|
||||
const openStatus = getOpenStatus(hours);
|
||||
const normalizedWebsite = normalizeWebsite(member.website);
|
||||
const shortWebsite = websiteLabel(member.website);
|
||||
const phoneHref = member.phone ? `tel:${member.phone.replace(/[^\d+]/g, "")}` : null;
|
||||
|
||||
const sectionCard: React.CSSProperties = {
|
||||
background: "#fff",
|
||||
@@ -149,11 +203,14 @@ export default async function FicheAdherentPage({
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ display: "flex", gap: 10, paddingBottom: 4, flexWrap: "wrap" }}>
|
||||
{member.phone && (
|
||||
<a href={`tel:${member.phone.replace(/\s/g, "")}`} className="font-display" style={{ textDecoration: "none", background: "#2C6FB3", color: "#fff", fontWeight: 700, fontSize: 14, padding: "11px 18px", borderRadius: 11 }}>Appeler</a>
|
||||
{phoneHref && (
|
||||
<a href={phoneHref} className="font-display profile-hero-action profile-hero-action--primary">Appeler</a>
|
||||
)}
|
||||
{normalizedWebsite && (
|
||||
<a href={normalizedWebsite} target="_blank" rel="noopener noreferrer" className="font-display profile-hero-action">Site web ↗</a>
|
||||
)}
|
||||
{mapQuery && (
|
||||
<a href={`https://www.google.com/maps/dir/?api=1&destination=${mapQuery}`} target="_blank" rel="noopener" className="font-display" style={{ textDecoration: "none", background: "#fff", border: "1px solid #d8cdb4", color: "#3c3322", fontWeight: 700, fontSize: 14, padding: "11px 18px", borderRadius: 11 }}>Itinéraire</a>
|
||||
<a href={`https://www.google.com/maps/dir/?api=1&destination=${mapQuery}`} target="_blank" rel="noopener noreferrer" className="font-display profile-hero-action">Itinéraire</a>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -186,44 +243,90 @@ export default async function FicheAdherentPage({
|
||||
</section>
|
||||
|
||||
{/* practical info */}
|
||||
<section style={sectionCard}>
|
||||
<h2 className="font-display" style={{ ...h2, marginBottom: 16 }}>Informations pratiques</h2>
|
||||
<div className="grid grid-2" style={{ gap: 22 }}>
|
||||
<section className="practical-section">
|
||||
<div className="practical-section__heading">
|
||||
<div>
|
||||
<div style={{ fontSize: 12.5, letterSpacing: "0.06em", textTransform: "uppercase", color: "#a99c82", fontWeight: 700, marginBottom: 10 }}>Horaires</div>
|
||||
<span className="eyebrow">Préparer votre visite</span>
|
||||
<h2 className="font-display" style={{ ...h2, margin: "5px 0 0" }}>Informations pratiques</h2>
|
||||
</div>
|
||||
{openStatus && (
|
||||
<div className={`open-status${openStatus.isOpen ? " is-open" : ""}`}>
|
||||
<span />
|
||||
<div>
|
||||
<strong>{openStatus.label}</strong>
|
||||
{openStatus.detail && <small>{openStatus.detail}</small>}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="practical-grid">
|
||||
<section className="schedule-card" aria-labelledby="schedule-title">
|
||||
<div className="practical-card-title">
|
||||
<span className="practical-card-title__icon" aria-hidden="true">◷</span>
|
||||
<div>
|
||||
<span>Cette semaine</span>
|
||||
<h3 id="schedule-title" className="font-display">Horaires d'ouverture</h3>
|
||||
</div>
|
||||
</div>
|
||||
{hours.length > 0 ? (
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 7, fontSize: 14 }}>
|
||||
{hours.map(([label, range], i) => {
|
||||
const closed = (range ?? "").toLowerCase().includes("ferm");
|
||||
<div className="schedule-list">
|
||||
{hours.map((day) => {
|
||||
const isToday = openStatus?.todayKey === day.key;
|
||||
return (
|
||||
<div key={i} style={{ display: "flex", justifyContent: "space-between", gap: 12 }}>
|
||||
<span style={{ color: "#6c6150" }}>{label}</span>
|
||||
<span style={{ color: closed ? "#c0392b" : "#3c3322", fontWeight: 600 }}>{range}</span>
|
||||
<div
|
||||
key={day.key}
|
||||
className={`schedule-row${isToday ? " is-today" : ""}`}
|
||||
aria-current={isToday ? "date" : undefined}
|
||||
>
|
||||
<span className="schedule-row__day">
|
||||
{day.label}
|
||||
{isToday && <small>Aujourd'hui</small>}
|
||||
</span>
|
||||
<strong className={day.closed ? "is-closed" : undefined}>
|
||||
{day.closed ? "Fermé" : formatSlots(day.slots)}
|
||||
</strong>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ fontSize: 14, color: "#a99c82" }}>Horaires non communiqués.</div>
|
||||
<div className="practical-empty">Horaires non communiqués.</div>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<div style={{ fontSize: 12.5, letterSpacing: "0.06em", textTransform: "uppercase", color: "#a99c82", fontWeight: 700, marginBottom: 10 }}>Contact</div>
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 9, fontSize: 14, color: "#5e5444" }}>
|
||||
{member.phone && (
|
||||
<div style={{ display: "flex", alignItems: "center", gap: 9 }}><span style={{ width: 7, height: 7, borderRadius: "50%", background: "#E0A63C" }} />{member.phone}</div>
|
||||
</section>
|
||||
|
||||
<section className="contact-card" aria-labelledby="contact-title">
|
||||
<div className="practical-card-title">
|
||||
<span className="practical-card-title__icon practical-card-title__icon--blue" aria-hidden="true">↗</span>
|
||||
<div>
|
||||
<span>Contact direct</span>
|
||||
<h3 id="contact-title" className="font-display">Joindre l'établissement</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div className="contact-actions">
|
||||
{phoneHref && member.phone && (
|
||||
<ContactAction href={phoneHref} icon="phone" label="Appeler" detail={member.phone} />
|
||||
)}
|
||||
{member.email && (
|
||||
<a href={`mailto:${member.email}`} style={{ display: "flex", alignItems: "center", gap: 9, color: "#5e5444", textDecoration: "none" }}><span style={{ width: 7, height: 7, borderRadius: "50%", background: "#6FB0C6" }} />{member.email}</a>
|
||||
<ContactAction href={`mailto:${member.email}`} icon="mail" label="Envoyer un e-mail" detail={member.email} />
|
||||
)}
|
||||
{member.website && (
|
||||
<a href={member.website} target="_blank" rel="noopener" style={{ display: "flex", alignItems: "center", gap: 9, color: "#2C6FB3", textDecoration: "none", fontWeight: 600 }}><span style={{ width: 7, height: 7, borderRadius: "50%", background: "#2C6FB3" }} />{member.website.replace(/^https?:\/\//, "")}</a>
|
||||
{normalizedWebsite && shortWebsite && (
|
||||
<ContactAction href={normalizedWebsite} icon="website" label="Visiter le site" detail={shortWebsite} external />
|
||||
)}
|
||||
{!member.phone && !member.email && !member.website && (
|
||||
<div style={{ color: "#a99c82" }}>Coordonnées non communiquées.</div>
|
||||
{mapQuery && fullAddress && (
|
||||
<ContactAction
|
||||
href={`https://www.google.com/maps/dir/?api=1&destination=${mapQuery}`}
|
||||
icon="pin"
|
||||
label="Itinéraire"
|
||||
detail={fullAddress}
|
||||
external
|
||||
/>
|
||||
)}
|
||||
{!phoneHref && !member.email && !normalizedWebsite && !fullAddress && (
|
||||
<div className="practical-empty">Coordonnées non communiquées.</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -287,11 +390,21 @@ export default async function FicheAdherentPage({
|
||||
</section>
|
||||
|
||||
{/* contact CTA */}
|
||||
<section style={{ background: "linear-gradient(120deg,#13324F,#1d4a72)", borderRadius: 18, padding: 22, color: "#fff" }}>
|
||||
<h3 className="font-display" style={{ fontWeight: 700, fontSize: 17, margin: "0 0 6px" }}>Une question pour ce commerçant ?</h3>
|
||||
<p style={{ margin: "0 0 14px", fontSize: 13.5, color: "#bcd3e6" }}>Contactez {member.name} directement.</p>
|
||||
<a href={member.email ? `mailto:${member.email}` : "#"} className="font-display" style={{ textDecoration: "none", display: "block", textAlign: "center", background: "#E0A63C", color: "#33291D", fontWeight: 700, fontSize: 14.5, padding: 12, borderRadius: 11 }}>Envoyer un message</a>
|
||||
</section>
|
||||
{(member.email || phoneHref || normalizedWebsite) && (
|
||||
<section style={{ background: "linear-gradient(120deg,#13324F,#1d4a72)", borderRadius: 18, padding: 22, color: "#fff" }}>
|
||||
<h3 className="font-display" style={{ fontWeight: 700, fontSize: 17, margin: "0 0 6px" }}>Une question pour ce commerçant ?</h3>
|
||||
<p style={{ margin: "0 0 14px", fontSize: 13.5, color: "#bcd3e6" }}>Contactez {member.name} directement.</p>
|
||||
<a
|
||||
href={member.email ? `mailto:${member.email}` : phoneHref ?? normalizedWebsite ?? undefined}
|
||||
target={!member.email && !phoneHref ? "_blank" : undefined}
|
||||
rel={!member.email && !phoneHref ? "noopener noreferrer" : undefined}
|
||||
className="font-display"
|
||||
style={{ textDecoration: "none", display: "block", textAlign: "center", background: "#E0A63C", color: "#33291D", fontWeight: 700, fontSize: 14.5, padding: 12, borderRadius: 11 }}
|
||||
>
|
||||
{member.email ? "Envoyer un message" : phoneHref ? "Appeler maintenant" : "Visiter le site"}
|
||||
</a>
|
||||
</section>
|
||||
)}
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
users,
|
||||
} from "@/db/schema";
|
||||
import { can, LABEL_TO_ROLE } from "@/lib/rbac";
|
||||
import { normalizeWebsite } from "@/lib/member-profile";
|
||||
import { SITE_SETTING_DEFAULTS } from "@/lib/site-settings";
|
||||
import type { AppRole } from "@/types/next-auth";
|
||||
|
||||
@@ -267,7 +268,7 @@ export async function updateMember(formData: FormData) {
|
||||
description: String(formData.get("description") ?? "").trim() || null,
|
||||
postalCode: String(formData.get("postalCode") ?? "").trim() || null,
|
||||
phone: String(formData.get("phone") ?? "").trim() || null,
|
||||
website: String(formData.get("website") ?? "").trim() || null,
|
||||
website: normalizeWebsite(String(formData.get("website") ?? "")),
|
||||
memberSince: formData.get("memberSince") ? Number(formData.get("memberSince")) : null,
|
||||
coverUrl: String(formData.get("coverUrl") ?? "").trim() || null,
|
||||
logoUrl: String(formData.get("logoUrl") ?? "").trim() || null,
|
||||
@@ -797,7 +798,7 @@ export async function updateOwnProfile(formData: FormData) {
|
||||
postalCode: String(formData.get("postalCode") ?? "").trim() || null,
|
||||
city: String(formData.get("city") ?? "").trim() || null,
|
||||
phone: String(formData.get("phone") ?? "").trim() || null,
|
||||
website: String(formData.get("website") ?? "").trim() || null,
|
||||
website: normalizeWebsite(String(formData.get("website") ?? "")),
|
||||
hours: String(formData.get("hours") ?? "").trim() || null,
|
||||
tags: String(formData.get("tags") ?? "").trim() || null,
|
||||
coverUrl: String(formData.get("coverUrl") ?? "").trim() || null,
|
||||
|
||||
@@ -6,6 +6,7 @@ import { db } from "@/db";
|
||||
import { categories, members, users } from "@/db/schema";
|
||||
import { can } from "@/lib/rbac";
|
||||
import { ImageField } from "@/components/ImageField";
|
||||
import { HoursEditor } from "@/components/HoursEditor";
|
||||
import { communeOptions } from "@/lib/communes";
|
||||
import { deleteMember, resetMemberPassword, updateMember } from "../../actions";
|
||||
|
||||
@@ -148,8 +149,7 @@ export default async function EditMemberPage({
|
||||
</div>
|
||||
|
||||
<div style={{ marginTop: 16 }}>
|
||||
<label className="field-label">Horaires (une ligne par jour : « Jour|plage », ex : Mardi – Vendredi|7h – 19h30)</label>
|
||||
<textarea name="hours" rows={4} defaultValue={member.hours ?? ""} className="field" style={{ resize: "vertical" }} placeholder={"Mardi – Vendredi|7h – 19h30\nSamedi|7h – 19h\nDimanche|7h – 13h\nLundi|Fermé"} />
|
||||
<HoursEditor defaultValue={member.hours} />
|
||||
</div>
|
||||
|
||||
<div style={{ display: "flex", gap: 10, marginTop: 22 }}>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { db } from "@/db";
|
||||
import { categories, imageConsents, meetingRegistrations, meetings, members, promotions, type Member } from "@/db/schema";
|
||||
import { ImageField } from "@/components/ImageField";
|
||||
import { ImageConsentForm } from "@/components/ImageConsentForm";
|
||||
import { HoursEditor } from "@/components/HoursEditor";
|
||||
import { communeOptions } from "@/lib/communes";
|
||||
import { saveImageConsent, updateOwnProfile } from "../actions";
|
||||
import { MemberSpaceForm } from "./MemberSpaceForm";
|
||||
@@ -196,8 +197,7 @@ export default async function EspacePage() {
|
||||
</div>
|
||||
|
||||
<div style={{ marginTop: 16 }}>
|
||||
<label className="field-label">Horaires (une ligne par jour : « Jour|plage »)</label>
|
||||
<textarea name="hours" rows={4} defaultValue={profile.hours ?? ""} className="field" style={{ resize: "vertical" }} placeholder={"Mardi – Vendredi|7h – 19h30\nSamedi|7h – 19h"} />
|
||||
<HoursEditor defaultValue={profile.hours} />
|
||||
</div>
|
||||
|
||||
<button
|
||||
|
||||
@@ -551,3 +551,409 @@ img {
|
||||
.email-preview-shell iframe { height: 620px; }
|
||||
}
|
||||
|
||||
/* ---- Fiche adhérent : horaires et contact ---- */
|
||||
.profile-hero-action {
|
||||
padding: 11px 18px;
|
||||
border: 1px solid #d8cdb4;
|
||||
border-radius: 11px;
|
||||
background: #fff;
|
||||
color: #3c3322;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
text-decoration: none;
|
||||
transition: border-color 160ms ease, color 160ms ease, transform 160ms ease;
|
||||
}
|
||||
.profile-hero-action:hover {
|
||||
border-color: #2c6fb3;
|
||||
color: #2c6fb3;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
.profile-hero-action--primary {
|
||||
border-color: #2c6fb3;
|
||||
background: #2c6fb3;
|
||||
color: #fff;
|
||||
}
|
||||
.profile-hero-action--primary:hover {
|
||||
background: #235d97;
|
||||
color: #fff;
|
||||
}
|
||||
.practical-section {
|
||||
overflow: hidden;
|
||||
padding: 26px 28px 28px;
|
||||
border: 1px solid #e6dcc6;
|
||||
border-radius: 18px;
|
||||
background:
|
||||
radial-gradient(circle at 100% 0, rgba(224, 166, 60, 0.12), transparent 190px),
|
||||
#fff;
|
||||
}
|
||||
.practical-section__heading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 18px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.eyebrow {
|
||||
color: #9a6638;
|
||||
font-size: 10.5px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.14em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.open-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 9px;
|
||||
min-width: 154px;
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #e7d3ce;
|
||||
border-radius: 12px;
|
||||
background: #fff8f6;
|
||||
color: #9f3e2e;
|
||||
}
|
||||
.open-status > span {
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
flex: 0 0 auto;
|
||||
border-radius: 50%;
|
||||
background: #d8472b;
|
||||
box-shadow: 0 0 0 4px rgba(216, 71, 43, 0.12);
|
||||
}
|
||||
.open-status div {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
flex-direction: column;
|
||||
}
|
||||
.open-status strong {
|
||||
font-size: 12.5px;
|
||||
line-height: 1.25;
|
||||
}
|
||||
.open-status small {
|
||||
margin-top: 2px;
|
||||
color: #916a61;
|
||||
font-size: 10.5px;
|
||||
}
|
||||
.open-status.is-open {
|
||||
border-color: #cce7d7;
|
||||
background: #f1faf5;
|
||||
color: #176c48;
|
||||
}
|
||||
.open-status.is-open > span {
|
||||
background: #1f8a5b;
|
||||
box-shadow: 0 0 0 4px rgba(31, 138, 91, 0.12);
|
||||
}
|
||||
.open-status.is-open small { color: #5d7f6e; }
|
||||
.practical-grid {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.08fr) minmax(0, 0.92fr);
|
||||
gap: 14px;
|
||||
}
|
||||
.schedule-card,
|
||||
.contact-card {
|
||||
min-width: 0;
|
||||
border: 1px solid #eadfca;
|
||||
border-radius: 16px;
|
||||
padding: 18px;
|
||||
}
|
||||
.schedule-card {
|
||||
background: linear-gradient(145deg, #fffdf8, #fbf5e8);
|
||||
}
|
||||
.contact-card {
|
||||
background: linear-gradient(145deg, #fff, #f3f7fa);
|
||||
}
|
||||
.practical-card-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 11px;
|
||||
padding-bottom: 14px;
|
||||
border-bottom: 1px solid rgba(216, 205, 180, 0.68);
|
||||
}
|
||||
.practical-card-title__icon {
|
||||
display: grid;
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
flex: 0 0 auto;
|
||||
place-items: center;
|
||||
border-radius: 11px;
|
||||
background: #f3dfb6;
|
||||
color: #8c5f2e;
|
||||
font-size: 20px;
|
||||
font-weight: 800;
|
||||
}
|
||||
.practical-card-title__icon--blue {
|
||||
background: #dfeef5;
|
||||
color: #2c6fb3;
|
||||
}
|
||||
.practical-card-title span:not(.practical-card-title__icon) {
|
||||
display: block;
|
||||
margin-bottom: 2px;
|
||||
color: #a09279;
|
||||
font-size: 9.5px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.practical-card-title h3 {
|
||||
margin: 0;
|
||||
color: #26201a;
|
||||
font-size: 15.5px;
|
||||
line-height: 1.25;
|
||||
}
|
||||
.schedule-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.schedule-row {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(90px, 0.75fr) minmax(120px, 1.25fr);
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
min-height: 38px;
|
||||
padding: 7px 8px;
|
||||
border-radius: 9px;
|
||||
color: #655b4c;
|
||||
font-size: 12.5px;
|
||||
}
|
||||
.schedule-row.is-today {
|
||||
background: #fff;
|
||||
box-shadow: 0 5px 16px -12px rgba(80, 55, 22, 0.58);
|
||||
}
|
||||
.schedule-row__day {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
flex-direction: column;
|
||||
font-weight: 600;
|
||||
}
|
||||
.schedule-row__day small {
|
||||
margin-top: 1px;
|
||||
color: #9a6638;
|
||||
font-size: 9px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.06em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.schedule-row > strong {
|
||||
color: #352d22;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
text-align: right;
|
||||
}
|
||||
.schedule-row > strong.is-closed {
|
||||
color: #b16a5e;
|
||||
font-weight: 600;
|
||||
}
|
||||
.contact-actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.contact-action {
|
||||
display: grid;
|
||||
grid-template-columns: 34px minmax(0, 1fr) auto;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
min-width: 0;
|
||||
padding: 10px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 11px;
|
||||
color: #4d4437;
|
||||
text-decoration: none;
|
||||
transition: background 160ms ease, border-color 160ms ease, transform 160ms ease;
|
||||
}
|
||||
.contact-action:hover {
|
||||
border-color: #d7e4eb;
|
||||
background: #fff;
|
||||
transform: translateX(2px);
|
||||
}
|
||||
.contact-action__icon {
|
||||
display: grid;
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
place-items: center;
|
||||
border-radius: 10px;
|
||||
background: #eaf2f7;
|
||||
color: #2c6fb3;
|
||||
}
|
||||
.contact-action__icon svg {
|
||||
width: 17px;
|
||||
height: 17px;
|
||||
}
|
||||
.contact-action__copy {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
flex-direction: column;
|
||||
}
|
||||
.contact-action__copy strong {
|
||||
color: #33291d;
|
||||
font-size: 12.5px;
|
||||
}
|
||||
.contact-action__copy > span {
|
||||
overflow: hidden;
|
||||
margin-top: 2px;
|
||||
color: #7d715f;
|
||||
font-size: 10.5px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.contact-action__arrow {
|
||||
color: #7f9db5;
|
||||
font-size: 14px;
|
||||
}
|
||||
.practical-empty {
|
||||
padding: 20px 4px 8px;
|
||||
color: #9b8e78;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* ---- Éditeur hebdomadaire des horaires ---- */
|
||||
.hours-editor {
|
||||
min-width: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
}
|
||||
.hours-editor__hint {
|
||||
margin: 5px 0 12px;
|
||||
color: #93866f;
|
||||
font-size: 12px;
|
||||
}
|
||||
.hours-editor__days {
|
||||
overflow: hidden;
|
||||
border: 1px solid #e2d7c1;
|
||||
border-radius: 13px;
|
||||
background: #faf7ef;
|
||||
}
|
||||
.hours-editor__day {
|
||||
display: grid;
|
||||
grid-template-columns: 150px minmax(0, 1fr);
|
||||
min-height: 58px;
|
||||
padding: 11px 13px;
|
||||
border-bottom: 1px solid #ebe2d0;
|
||||
}
|
||||
.hours-editor__day:last-child { border-bottom: 0; }
|
||||
.hours-editor__day.is-open { background: #fff; }
|
||||
.hours-editor__day-head {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
gap: 5px;
|
||||
}
|
||||
.hours-editor__day-head > strong {
|
||||
color: #3a3125;
|
||||
font-size: 13px;
|
||||
}
|
||||
.hours-editor__toggle {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
color: #9b5c51;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
}
|
||||
.hours-editor__toggle:has(input:checked) { color: #1f7a53; }
|
||||
.hours-editor__toggle input {
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
accent-color: #1f8a5b;
|
||||
}
|
||||
.hours-editor__slots {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
gap: 7px;
|
||||
}
|
||||
.hours-editor__slot {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(112px, 1fr) minmax(112px, 1fr) 28px;
|
||||
align-items: end;
|
||||
gap: 7px;
|
||||
}
|
||||
.hours-editor__slot label {
|
||||
display: grid;
|
||||
grid-template-columns: 22px minmax(0, 1fr);
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
color: #93866f;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.hours-editor__slot input {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
padding: 8px 7px;
|
||||
border: 1px solid #ddd2bb;
|
||||
border-radius: 8px;
|
||||
background: #faf7ef;
|
||||
color: #3c3322;
|
||||
font: inherit;
|
||||
}
|
||||
.hours-editor__slot input:focus {
|
||||
outline: 2px solid rgba(44, 111, 179, 0.3);
|
||||
outline-offset: 1px;
|
||||
}
|
||||
.hours-editor__remove {
|
||||
width: 28px;
|
||||
height: 34px;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: #b06a5e;
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.hours-editor__add {
|
||||
align-self: flex-start;
|
||||
padding: 2px 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: #2c6fb3;
|
||||
font: inherit;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.practical-section { padding: 21px 16px; }
|
||||
.practical-section__heading {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
}
|
||||
.practical-grid { grid-template-columns: 1fr; }
|
||||
.schedule-card,
|
||||
.contact-card { padding: 15px; }
|
||||
.profile-hero-action {
|
||||
flex: 1 1 130px;
|
||||
text-align: center;
|
||||
}
|
||||
.hours-editor__day {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 10px;
|
||||
}
|
||||
.hours-editor__day-head {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 460px) {
|
||||
.schedule-row {
|
||||
grid-template-columns: minmax(82px, 0.7fr) minmax(0, 1.3fr);
|
||||
}
|
||||
.schedule-row > strong {
|
||||
line-height: 1.4;
|
||||
}
|
||||
.hours-editor__slot {
|
||||
grid-template-columns: 1fr 1fr 28px;
|
||||
}
|
||||
.hours-editor__slot label {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user