chore: add documentation
ci-web / test (push) Successful in 14m26s

This commit is contained in:
Xor290
2026-08-03 15:32:59 +02:00
parent f0cb4d9045
commit f4b6ef7563
17 changed files with 2496 additions and 1648 deletions
+1510
View File
File diff suppressed because one or more lines are too long
-1510
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Omnex — Plateforme de gestion de commandes & livraison</title>
<meta name="description" content="Déployez en un clic une démo complète de la plateforme de gestion de commandes et de livraison." />
<script type="module" crossorigin src="/assets/index-Cfob2vGb.js"></script>
<script type="module" crossorigin src="/assets/index-CbtPOOTw.js"></script>
</head>
<body>
<div id="root"></div>
+12
View File
@@ -298,6 +298,18 @@ export function MockStack({ children, spacing = 1.5 }: { children: ReactNode; sp
return <Stack spacing={spacing}>{children}</Stack>
}
// Encart "accès restreint" — met en avant qu'un rôle (cabine, livreur) ne
// voit qu'un sous-ensemble volontairement limité des actions admin.
export function RestrictedNote({ children }: { children: ReactNode }) {
return (
<Box border="1px dashed" borderColor={COLORS.border} borderRadius="10px" p={2.5} mt={2}>
<Text color={COLORS.text3} fontSize="0.64rem" fontStyle="italic">
{children}
</Text>
</Box>
)
}
// Interrupteur cliquable (paramètres, liaisons de compte...) avec vrai état.
export function MockSwitch({ isOn, onToggle }: { isOn: boolean; onToggle: () => void }) {
return (
@@ -51,7 +51,8 @@ export function LivraisonSection() {
borderRadius: 999,
}}
>
{driver.name} {driver.distance} · {driver.eta}
<MockDot color={STATUS_TONE[driver.status]} size="7px" />
{driver.name} {driver.distance} · {driver.eta}
</span>
</div>
<MockText variant="muted" size="0.62rem">
@@ -1,80 +1,95 @@
import { useState } from 'react'
import {
faBan,
faBell,
faChevronDown,
faChevronUp,
faCreditCard,
faGift,
faMapLocationDot,
faPalette,
faPaperPlane,
faStar,
faTags,
faTruck,
faUserPlus,
} from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { COLORS, MockCard, MockDot, MockRow, MockSwitch, MockText, MockupDevice, ScreenTitle } from '../AdminMockup'
import { COLORS, MockCard, MockRow, MockSwitch, MockText, MockupDevice, ScreenTitle } from '../AdminMockup'
const SECTIONS = [
{
id: 'delivery',
icon: faTruck,
title: 'Horaires de livraison',
body: 'Lundi → Vendredi : 11h00 22h30. Samedi : 12h00 23h00. Dimanche : fermé.',
},
{
id: 'crypto',
icon: faCreditCard,
title: 'Paiement crypto',
body: 'BTC, ETH, LTC, USDT acceptés via NowPayments. Clé API et secret IPN configurés séparément.',
},
{
id: 'telegram',
icon: faPaperPlane,
title: 'Notifications Telegram',
body: 'Bot configuré — @votre_bot. Authentification à deux facteurs activée pour les commandes sensibles.',
},
{
id: 'colors',
icon: faPalette,
title: "Couleurs de l'interface",
body: 'Personnalisez séparément les couleurs de votre espace admin et de lapp client.',
},
interface Section {
id: string
icon: typeof faTruck
title: string
body: string
hasSwitch?: boolean
}
// Les 13 sections réelles de l'écran Paramètres (frontend-admin/src/screens/admin/SettingsScreen.tsx) :
// personnalisation, amendes, parrainage, points, attribution catégories/points,
// barèmes par pool de points, récompense par palier, horaires de livraison,
// zones de livraison, paiement crypto, Telegram, mode de livraison, couleurs.
const SECTIONS: Section[] = [
{ id: 'perso', icon: faPalette, title: 'Personnalisation', body: 'Nom du shop affiché dans lapp · dégradé de couleur du titre (2 couleurs, aperçu en direct).' },
{ id: 'amendes', icon: faBan, title: 'Amendes', body: 'Barème par nombre dannulations : 0 → 20€, 1 → 50€, 2 → 100€, 3 → 150€. Score affichable au client ou non.', hasSwitch: true },
{ id: 'parrainage', icon: faUserPlus, title: 'Parrainage', body: 'Active le solde de parrainage, utilisable directement au moment du paiement par le filleul.', hasSwitch: true },
{ id: 'points', icon: faStar, title: 'Système de points', body: 'Créez vos propres types de points (nom + couleur), chacun avec son propre barème €→points.', hasSwitch: true },
{ id: 'attribution', icon: faTags, title: 'Attribution catégories → points', body: 'Chaque catégorie de produit peut être reliée à un type de points précis, ou à aucun.' },
{ id: 'baremes', icon: faStar, title: 'Barème — Points Fidélité', body: 'Paliers Min € / Max € / Points : ex. 020€ = 5 pts, 2050€ = 15 pts, 50€+ = 40 pts.' },
{ id: 'recompense', icon: faGift, title: 'Récompenses par palier', body: 'Seuil de points → produit offert ou à -50%. Récapitulatif généré automatiquement.', hasSwitch: true },
{ id: 'horaires', icon: faTruck, title: 'Horaires de livraison', body: 'Lundi → Vendredi : 11h0022h30 · Samedi : 12h0023h00 · Dimanche : fermé.' },
{ id: 'zones', icon: faMapLocationDot, title: 'Zones de livraison', body: '3 zones actives · minimum de commande et liste de codes postaux par zone, ajout en masse.' },
{ id: 'crypto', icon: faCreditCard, title: 'Paiement crypto', body: 'BTC, ETH, LTC, USDT acceptés (NowPayments). Clé API et secret IPN masqués.', hasSwitch: true },
{ id: 'telegram', icon: faPaperPlane, title: 'Notifications Telegram', body: 'Bot configuré — @votre_bot · authentification à deux facteurs activée.', hasSwitch: true },
{ id: 'mode-livraison', icon: faTruck, title: 'Mode de livraison', body: '« Par catégorie » : chaque livreur ne reçoit que les commandes des catégories qui lui sont assignées.' },
{ id: 'couleurs', icon: faPalette, title: "Couleurs de l'interface", body: 'Palette séparée pour lespace admin et pour lapp client — 5 couleurs, restaurables en un clic.' },
]
// Vrai accordéon : un seul panneau ouvert à la fois, clic pour
// déplier/replier — comme la page Paramètres réelle.
// Accordéon complet et cliquable — toutes les vraies sections de l'écran
// Paramètres, pas un extrait.
export function ParametresSection() {
const [openId, setOpenId] = useState<string | null>('crypto')
const [cryptoOn, setCryptoOn] = useState(true)
const [telegramOn, setTelegramOn] = useState(true)
return (
<MockupDevice>
<ScreenTitle title="Paramètres" subtitle="Cliquez une section pour l'ouvrir" />
{SECTIONS.map((s) => {
const isOpen = openId === s.id
return (
<MockCard key={s.id} onClick={() => setOpenId(isOpen ? null : s.id)}>
<MockRow>
<MockText>
<FontAwesomeIcon icon={s.icon} style={{ color: COLORS.accentLight, marginRight: 8, fontSize: '0.72rem' }} />
{s.title}
</MockText>
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
{s.id === 'crypto' && (
<span onClick={(e) => e.stopPropagation()}>
<MockSwitch isOn={cryptoOn} onToggle={() => setCryptoOn((v) => !v)} />
</span>
)}
<FontAwesomeIcon icon={isOpen ? faChevronUp : faChevronDown} style={{ color: COLORS.text3, fontSize: '0.62rem' }} />
</div>
</MockRow>
{isOpen && (
<div style={{ marginTop: 8, paddingTop: 8, borderTop: `1px solid ${COLORS.border}` }}>
<MockText variant="muted" size="0.68rem">
<MockDot color={COLORS.text3} size="5px" />
{s.body}
<ScreenTitle title="Paramètres" subtitle={`${SECTIONS.length} sections — cliquez pour ouvrir`} />
<div style={{ maxHeight: 480, overflowY: 'auto', paddingRight: 2 }}>
{SECTIONS.map((s) => {
const isOpen = openId === s.id
const switchState = s.id === 'crypto' ? cryptoOn : s.id === 'telegram' ? telegramOn : true
const setSwitchState = s.id === 'crypto' ? setCryptoOn : setTelegramOn
return (
<MockCard key={s.id} onClick={() => setOpenId(isOpen ? null : s.id)}>
<MockRow>
<MockText size="0.72rem">
<FontAwesomeIcon icon={s.icon} style={{ color: COLORS.accentLight, marginRight: 8, fontSize: '0.68rem' }} />
{s.title}
</MockText>
</div>
)}
</MockCard>
)
})}
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
{s.hasSwitch && (s.id === 'crypto' || s.id === 'telegram') && (
<span onClick={(e) => e.stopPropagation()}>
<MockSwitch isOn={switchState} onToggle={() => setSwitchState((v) => !v)} />
</span>
)}
{s.hasSwitch && s.id !== 'crypto' && s.id !== 'telegram' && (
<FontAwesomeIcon icon={faBell} style={{ color: COLORS.success, fontSize: '0.55rem' }} />
)}
<FontAwesomeIcon icon={isOpen ? faChevronUp : faChevronDown} style={{ color: COLORS.text3, fontSize: '0.6rem' }} />
</div>
</MockRow>
{isOpen && (
<div style={{ marginTop: 8, paddingTop: 8, borderTop: `1px solid ${COLORS.border}` }}>
<MockText variant="muted" size="0.66rem">
{s.body}
</MockText>
</div>
)}
</MockCard>
)
})}
</div>
</MockupDevice>
)
}
@@ -0,0 +1,66 @@
import { useState } from 'react'
import { faTriangleExclamation } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { COLORS, MockCard, MockRow, MockTabs, MockText, MockupDevice, RestrictedNote, ScreenTitle } from '../../AdminMockup'
const ALERTS = [
{ id: '1', driver: 'karim_d', message: 'Accès bloqué, portail fermé', time: '14:32', active: true },
{ id: '2', driver: 'sofia_l', message: 'Client injoignable', time: 'hier 19:05', active: false },
]
type Filter = 'toutes' | 'actives'
// Flux de lecture seule : la cabine consulte les alertes des livreurs mais
// ne peut pas les résoudre (contrairement à l'admin).
export function CabineAlertsSection() {
const [filter, setFilter] = useState<Filter>('toutes')
const visible = filter === 'toutes' ? ALERTS : ALERTS.filter((a) => a.active)
return (
<MockupDevice>
<ScreenTitle title="Alertes livreurs" />
<MockTabs
tabs={[
{ key: 'toutes', label: 'Toutes' },
{ key: 'actives', label: 'Actives' },
]}
active={filter}
onChange={setFilter}
/>
{visible.map((a) => (
<MockCard key={a.id}>
<MockRow>
<MockText variant={a.active ? 'danger' : 'secondary'}>
<FontAwesomeIcon
icon={faTriangleExclamation}
style={{ color: a.active ? COLORS.danger : COLORS.text3, marginRight: 6, fontSize: '0.72rem' }}
/>
{a.driver}
</MockText>
<span
style={{
background: a.active ? `${COLORS.danger}26` : `${COLORS.success}26`,
color: a.active ? COLORS.danger : COLORS.success,
fontSize: '0.6rem',
fontWeight: 800,
padding: '2px 8px',
borderRadius: 999,
}}
>
{a.active ? 'Active' : 'Terminée'}
</span>
</MockRow>
{a.active && (
<MockText variant="danger" size="0.7rem">
"{a.message}"
</MockText>
)}
<MockText variant="muted" size="0.62rem">
{a.time}
</MockText>
</MockCard>
))}
<RestrictedNote>La résolution des alertes reste réservée à l'espace admin.</RestrictedNote>
</MockupDevice>
)
}
@@ -0,0 +1,50 @@
import { useState } from 'react'
import {
faBell,
faBox,
faCheckDouble,
faClock,
faLink,
faLinkSlash,
faLocationArrow,
faUserCheck,
faUserClock,
} from '@fortawesome/free-solid-svg-icons'
import { SimpleGrid } from '@chakra-ui/react'
import { COLORS, MockButton, MockCard, MockIcon, MockRow, MockText, MockupDevice, ScreenTitle, StatTile } from '../../AdminMockup'
// Même schéma que le tableau de bord admin, mais recentré sur les
// opérations du jour — pas de chiffre d'affaires ni de réglages.
export function CabineDashboardSection() {
const [telegramLinked, setTelegramLinked] = useState(false)
return (
<MockupDevice>
<ScreenTitle title="Cabine — Nadia" subtitle="Suivi des opérations" />
<SimpleGrid columns={2} spacing={2} mb={2.5}>
<StatTile icon={faBox} value={11} label="Commandes actives" color={COLORS.info} />
<StatTile icon={faLocationArrow} value={4} label="En route" color={COLORS.warning} />
<StatTile icon={faClock} value={3} label="En attente" color={COLORS.accentLight} />
<StatTile icon={faUserCheck} value={4} label="Livreurs dispo" color={COLORS.success} />
<StatTile icon={faUserClock} value={2} label="Livreurs occupés" color={COLORS.warning} />
<StatTile icon={faCheckDouble} value={299} label="Total terminées" color={COLORS.success} />
</SimpleGrid>
<MockCard mb={0}>
<MockRow>
<MockText>
<MockIcon icon={faBell} color={COLORS.secondary} />
Notifications Telegram
</MockText>
<MockButton
tone={telegramLinked ? 'outlineDanger' : 'accent'}
icon={telegramLinked ? faLinkSlash : faLink}
onClick={() => setTelegramLinked((v) => !v)}
>
{telegramLinked ? 'Délier' : 'Lier Telegram'}
</MockButton>
</MockRow>
</MockCard>
</MockupDevice>
)
}
@@ -0,0 +1,80 @@
import { useState } from 'react'
import { faLocationDot, faPowerOff, faRoute, faUserClock } from '@fortawesome/free-solid-svg-icons'
import { SimpleGrid } from '@chakra-ui/react'
import {
COLORS,
MockButton,
MockCard,
MockDot,
MockRow,
MockText,
MockupDevice,
RestrictedNote,
StatTile,
} from '../../AdminMockup'
const DRIVERS = [
{ name: 'karim_d', status: 'busy' as const, queue: 1, total: 512 },
{ name: 'sofia_l', status: 'available' as const, queue: 0, total: 340 },
]
const STATUS_TONE = { available: COLORS.success, busy: COLORS.warning, offline: COLORS.text3 }
const STATUS_LABEL = { available: 'Disponible', busy: 'Occupé', offline: 'Hors ligne' }
// Suivi en direct des livreurs, sans les fonctions de gestion (créer,
// modifier, contacter) réservées à l'admin.
export function CabineDeliverySection() {
const [tracking, setTracking] = useState<string | null>('karim_d')
return (
<MockupDevice>
<SimpleGrid columns={3} spacing={2} mb={2.5}>
<StatTile icon={faLocationDot} value={1} label="Dispo" color={COLORS.success} />
<StatTile icon={faUserClock} value={1} label="Occupés" color={COLORS.warning} />
<StatTile icon={faPowerOff} value={0} label="Offline" color={COLORS.text3} />
</SimpleGrid>
{DRIVERS.map((d) => {
const isTracking = tracking === d.name
return (
<MockCard key={d.name} active={isTracking}>
<MockRow>
<MockText>
<MockDot color={STATUS_TONE[d.status]} />
{d.name}
</MockText>
<span
style={{
background: `${STATUS_TONE[d.status]}26`,
color: STATUS_TONE[d.status],
fontSize: '0.6rem',
fontWeight: 800,
padding: '2px 8px',
borderRadius: 999,
}}
>
{STATUS_LABEL[d.status]}
</span>
</MockRow>
<MockText variant="muted" size="0.62rem">
Queue: {d.queue} · Total: {d.total}
</MockText>
<div style={{ marginTop: 8 }}>
<MockButton
tone={isTracking ? 'accent' : 'outline'}
icon={faRoute}
onClick={() => setTracking(isTracking ? null : d.name)}
>
{isTracking ? 'Arrêter le suivi' : 'Suivre'}
</MockButton>
</div>
</MockCard>
)
})}
<RestrictedNote>
La cabine visualise et suit les livreurs en direct, mais l'assignation d'une commande se fait depuis
l'écran Commandes.
</RestrictedNote>
</MockupDevice>
)
}
@@ -0,0 +1,79 @@
import { useState } from 'react'
import { faBellConcierge, faLocationDot, faTrash, faUserCheck } from '@fortawesome/free-solid-svg-icons'
import {
COLORS,
MockButton,
MockCard,
MockRow,
MockText,
MockupDevice,
RestrictedNote,
ScreenTitle,
} from '../../AdminMockup'
const ORDERS = [
{ id: '#1042', client: 'client_marie', address: '12 rue des Lilas', total: '46,00 €', label: 'En attente', tone: COLORS.warning },
{ id: '#1040', client: 'client_lea', address: '8 avenue Foch', total: '58,50 €', label: 'Livreur arrivé', tone: COLORS.accentLight },
]
// Même liste de commandes actives que l'admin, mais l'action "Modifier"
// n'existe pas ici — la cabine fait avancer une commande, pas ne la modifie
// pas.
export function CabineOrdersSection() {
const [expandedId, setExpandedId] = useState<string | null>('#1042')
return (
<MockupDevice>
<ScreenTitle title="Commandes actives" subtitle="Cliquez une commande" />
{ORDERS.map((o) => {
const isOpen = expandedId === o.id
return (
<MockCard key={o.id} active={isOpen} onClick={() => setExpandedId(isOpen ? null : o.id)}>
<MockRow>
<MockText mono>{o.id}</MockText>
<span
style={{
background: `${o.tone}26`,
color: o.tone,
fontSize: '0.6rem',
fontWeight: 800,
padding: '2px 8px',
borderRadius: 999,
}}
>
{o.label}
</span>
</MockRow>
<MockText variant="secondary" size="0.7rem">
{o.client} · {o.address}
</MockText>
<MockText variant="primary" size="0.75rem">
{o.total}
</MockText>
{isOpen && (
<div style={{ marginTop: 10, paddingTop: 10, borderTop: `1px solid ${COLORS.border}`, display: 'flex', gap: 6, flexWrap: 'wrap' }}>
<MockButton tone="accent" icon={faBellConcierge}>
Le livreur est
</MockButton>
<MockButton tone="outline" icon={faUserCheck}>
Assigner livreur
</MockButton>
<MockButton tone="outline" icon={faLocationDot}>
Proposer adresse
</MockButton>
<MockButton tone="outlineDanger" icon={faTrash}>
Supprimer
</MockButton>
</div>
)}
</MockCard>
)
})}
<RestrictedNote>
Contrairement à l'espace admin, la cabine ne peut pas modifier le contenu d'une commande seulement la
faire avancer ou la supprimer.
</RestrictedNote>
</MockupDevice>
)
}
@@ -0,0 +1,46 @@
import { useState } from 'react'
import { faArrowRotateLeft } from '@fortawesome/free-solid-svg-icons'
import { MockButton, MockCard, MockText, MockupDevice, RestrictedNote, ScreenTitle } from '../../AdminMockup'
// Console de service client réduite : reset pénalités / points uniquement,
// aucune création ni modification de compte.
export function CabineUsersSection() {
const [fine, setFine] = useState('35 €')
const [points, setPoints] = useState(340)
return (
<MockupDevice>
<ScreenTitle title="Clients" subtitle="Réinitialisations rapides" />
<MockCard mb={0}>
<MockText variant="primary" size="0.82rem">
client_marie
</MockText>
<MockText variant="muted" size="0.66rem">
Marie D. · 06 12 34 56 78
</MockText>
<div style={{ display: 'flex', gap: 12, margin: '8px 0' }}>
<MockText variant="danger" size="0.68rem">
Annul.: 2
</MockText>
<MockText variant="secondary" size="0.68rem" mono>
Amende: {fine}
</MockText>
<MockText variant="success" size="0.68rem" mono>
Points: {points}
</MockText>
</div>
<div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
<MockButton tone="outline" icon={faArrowRotateLeft} onClick={() => setFine('0 €')}>
Reset pénalités
</MockButton>
<MockButton tone="outline" icon={faArrowRotateLeft} onClick={() => setPoints(0)}>
Reset points
</MockButton>
</div>
</MockCard>
<RestrictedNote>
Pas de création, modification ni suppression de compte ici uniquement des remises à zéro ponctuelles.
</RestrictedNote>
</MockupDevice>
)
}
@@ -0,0 +1,121 @@
import { useState } from 'react'
import { faCheck, faShieldHalved, faTriangleExclamation } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { COLORS, MockButton, MockCard, MockText, MockupDevice, ScreenTitle } from '../../AdminMockup'
type Step = 'idle' | 'pick' | 'confirm' | 'sent'
type AlertType = 'police' | 'ambush'
const ALERT_META: Record<AlertType, { title: string; icon: typeof faShieldHalved; hint: string }> = {
police: {
title: 'Contrôle de police',
icon: faShieldHalved,
hint: 'Restez calme, coopérez avec les forces de lordre. L’équipe est prévenue.',
},
ambush: {
title: 'Guet-apens',
icon: faTriangleExclamation,
hint: 'Éloignez-vous du danger si possible. L’équipe et les secours sont prévenus.',
},
}
// Reproduit le vrai parcours en 3 étapes de l'app : sélection du type
// d'alerte → confirmation → message de succès avec consignes de sécurité.
export function DeliveryAlertsSection() {
const [step, setStep] = useState<Step>('idle')
const [type, setType] = useState<AlertType>('police')
return (
<MockupDevice>
<ScreenTitle title="Alerte" subtitle="Bouton d'urgence pour les livreurs" />
{step === 'idle' && (
<MockButton tone="outlineDanger" icon={faTriangleExclamation} onClick={() => setStep('pick')}>
Déclencher alerte police
</MockButton>
)}
{step === 'pick' && (
<MockCard mb={0}>
<MockText size="0.78rem" variant="primary">
Type d'alerte
</MockText>
<MockText variant="muted" size="0.65rem">
Sélectionnez la raison de l'alerte.
</MockText>
<div style={{ display: 'flex', flexDirection: 'column', gap: 6, marginTop: 8 }}>
{(Object.keys(ALERT_META) as AlertType[]).map((key) => (
<div
key={key}
onClick={() => {
setType(key)
setStep('confirm')
}}
style={{
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
gap: 8,
padding: '8px 10px',
borderRadius: 8,
border: `1px solid ${COLORS.border}`,
}}
>
<FontAwesomeIcon icon={ALERT_META[key].icon} style={{ color: COLORS.danger, fontSize: '0.8rem' }} />
<span style={{ color: COLORS.text, fontSize: '0.74rem' }}>{ALERT_META[key].title}</span>
</div>
))}
</div>
<div style={{ marginTop: 10 }}>
<MockButton tone="outline" onClick={() => setStep('idle')}>
Annuler
</MockButton>
</div>
</MockCard>
)}
{step === 'confirm' && (
<MockCard mb={0}>
<MockText size="0.78rem" variant="danger">
<FontAwesomeIcon icon={ALERT_META[type].icon} style={{ marginRight: 6 }} />
Alerte {ALERT_META[type].title}
</MockText>
<MockText variant="muted" size="0.66rem">
{ALERT_META[type].hint}
</MockText>
<MockText variant="primary" size="0.74rem">
Confirmez-vous le déclenchement ?
</MockText>
<div style={{ display: 'flex', gap: 6, marginTop: 10 }}>
<MockButton tone="outline" onClick={() => setStep('idle')}>
Annuler
</MockButton>
<MockButton tone="outlineDanger" onClick={() => setStep('sent')}>
Déclencher
</MockButton>
</div>
</MockCard>
)}
{step === 'sent' && (
<MockCard mb={0}>
<FontAwesomeIcon icon={faCheck} style={{ color: COLORS.success, fontSize: '1.4rem', marginBottom: 8 }} />
<MockText size="0.8rem" variant="success">
Alerte envoyée
</MockText>
<MockText variant="muted" size="0.66rem">
L'équipe admin a été notifiée immédiatement.
</MockText>
<MockText variant="secondary" size="0.66rem">
{ALERT_META[type].hint}
</MockText>
<div style={{ marginTop: 10 }}>
<MockButton tone="accent" onClick={() => setStep('idle')}>
Compris
</MockButton>
</div>
</MockCard>
)}
</MockupDevice>
)
}
@@ -0,0 +1,165 @@
import { useState } from 'react'
import {
faBan,
faCheck,
faLocationDot,
faPhone,
faRoute,
faUserClock,
} from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { COLORS, MockButton, MockCard, MockDot, MockRow, MockText, MockupDevice, ScreenTitle } from '../../AdminMockup'
type Status = 'pending' | 'in_progress' | 'arrived' | 'completed'
const FLOW: Status[] = ['pending', 'in_progress', 'arrived', 'completed']
const LABELS: Record<Status, { label: string; tone: string }> = {
pending: { label: 'En attente', tone: COLORS.info },
in_progress: { label: 'En cours', tone: COLORS.warning },
arrived: { label: 'Arrivé', tone: COLORS.accentLight },
completed: { label: 'Terminée', tone: COLORS.success },
}
const DRIVER_STATUS: { key: 'available' | 'busy' | 'offline'; label: string; tone: string }[] = [
{ key: 'available', label: 'Disponible', tone: COLORS.success },
{ key: 'busy', label: 'Occupé', tone: COLORS.warning },
{ key: 'offline', label: 'Hors ligne', tone: COLORS.text3 },
]
// Le cœur de l'app livreur : chaque commande avance dans son cycle de vie
// au clic (Démarrer → J'arrive → Terminer), exactement comme sur le
// terrain.
export function DeliveryDashboardSection() {
const [myStatus, setMyStatus] = useState<'available' | 'busy' | 'offline'>('available')
const [orderStatus, setOrderStatus] = useState<Status>('in_progress')
const advance = () => {
const idx = FLOW.indexOf(orderStatus)
if (idx < FLOW.length - 1) setOrderStatus(FLOW[idx + 1])
}
return (
<MockupDevice>
<ScreenTitle title="Mon statut" />
<div style={{ display: 'flex', gap: 6, marginBottom: 12, flexWrap: 'wrap' }}>
{DRIVER_STATUS.map((s) => {
const active = myStatus === s.key
return (
<div
key={s.key}
onClick={() => setMyStatus(s.key)}
style={{
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
padding: '5px 10px',
borderRadius: 999,
fontSize: '0.65rem',
fontWeight: 700,
border: `1px solid ${active ? s.tone : COLORS.border}`,
background: active ? `${s.tone}22` : 'transparent',
color: active ? s.tone : COLORS.text3,
}}
>
<MockDot color={s.tone} size="7px" />
{s.label}
</div>
)
})}
</div>
<MockCard>
<div
style={{
height: 96,
borderRadius: 8,
position: 'relative',
backgroundImage:
'linear-gradient(135deg,#161022 25%,#1c1330 25%,#1c1330 50%,#161022 50%,#161022 75%,#1c1330 75%)',
backgroundSize: '18px 18px',
marginBottom: 6,
}}
>
<span
style={{
position: 'absolute',
top: 8,
left: 8,
background: `${COLORS.success}26`,
color: COLORS.success,
fontSize: '0.6rem',
fontWeight: 800,
padding: '3px 9px',
borderRadius: 999,
}}
>
<MockDot color={COLORS.success} size="7px" />
GPS actif 48.85, 2.35
</span>
</div>
</MockCard>
<MockText variant="muted" size="0.66rem">
Mes livraisons (1) cliquez « avancer » pour tester le cycle complet
</MockText>
<MockCard mb={0}>
<MockRow>
<MockText mono>Commande #1041</MockText>
<span
style={{
background: `${LABELS[orderStatus].tone}26`,
color: LABELS[orderStatus].tone,
fontSize: '0.6rem',
fontWeight: 800,
padding: '2px 8px',
borderRadius: 999,
}}
>
{LABELS[orderStatus].label}
</span>
</MockRow>
<MockText variant="secondary" size="0.7rem">
client_paul · 12 rue des Lilas
</MockText>
<div style={{ display: 'flex', gap: 6, marginTop: 8, flexWrap: 'wrap' }}>
<MockButton tone="outline" icon={faLocationDot}>
Itinéraire
</MockButton>
<MockButton tone="outline" icon={faPhone}>
Appeler
</MockButton>
</div>
<div style={{ marginTop: 10, paddingTop: 10, borderTop: `1px solid ${COLORS.border}`, display: 'flex', gap: 6, flexWrap: 'wrap' }}>
{orderStatus === 'pending' && (
<MockButton tone="success" icon={faRoute} onClick={advance}>
Démarrer la livraison
</MockButton>
)}
{orderStatus === 'in_progress' && (
<MockButton tone="accent" icon={faUserClock} onClick={advance}>
J'arrive
</MockButton>
)}
{orderStatus === 'arrived' && (
<>
<MockButton tone="accent" icon={faCheck} onClick={advance}>
Terminer
</MockButton>
<MockButton tone="outlineDanger" icon={faBan}>
Annuler
</MockButton>
</>
)}
{orderStatus === 'completed' && (
<MockText variant="success" size="0.7rem">
<FontAwesomeIcon icon={faCheck} style={{ marginRight: 6 }} />
Livraison terminée
</MockText>
)}
</div>
</MockCard>
</MockupDevice>
)
}
@@ -0,0 +1,57 @@
import { faStar } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { COLORS, MockCard, MockText, MockupDevice, ScreenTitle } from '../../AdminMockup'
function Stars({ value }: { value: number }) {
return (
<span>
{[1, 2, 3, 4, 5].map((i) => (
<FontAwesomeIcon
key={i}
icon={faStar}
style={{ color: i <= value ? COLORS.warning : COLORS.border, fontSize: '0.7rem', marginRight: 2 }}
/>
))}
</span>
)
}
const REVIEWS = [
{ client: 'client_marie', order: '#1038', rating: 5, comment: 'Livraison rapide et sympa, merci !' },
{ client: 'client_theo', order: '#1029', rating: 4, comment: 'Tout est arrivé nickel.' },
]
export function DeliveryRatingsSection() {
return (
<MockupDevice>
<ScreenTitle title="Mes avis" />
<MockCard>
<MockText size="1.5rem" variant="primary">
4.7
</MockText>
<div style={{ marginTop: 4 }}>
<Stars value={5} />
</div>
<MockText variant="muted" size="0.66rem">
38 avis clients
</MockText>
</MockCard>
{REVIEWS.map((r) => (
<MockCard key={r.order}>
<MockText size="0.74rem" variant="primary">
{r.client}
</MockText>
<MockText variant="muted" size="0.62rem">
Commande {r.order}
</MockText>
<div style={{ margin: '4px 0' }}>
<Stars value={r.rating} />
</div>
<MockText variant="secondary" size="0.68rem">
« {r.comment} »
</MockText>
</MockCard>
))}
</MockupDevice>
)
}
@@ -0,0 +1,74 @@
import { useMemo, useState } from 'react'
import { faBoxesStacked, faCheck, faClock, faHourglassHalf } from '@fortawesome/free-solid-svg-icons'
import { SimpleGrid } from '@chakra-ui/react'
import { COLORS, MockCard, MockTabs, MockText, MockupDevice, ScreenTitle, StatTile } from '../../AdminMockup'
type Period = 'jour' | 'semaine' | 'mois'
const DATA: Record<Period, number[]> = {
jour: [2, 4, 3, 6, 5, 8, 4],
semaine: [18, 24, 21, 27, 15, 30, 22],
mois: [80, 92, 76, 101],
}
const LABELS: Record<Period, string[]> = {
jour: ['L', 'M', 'M', 'J', 'V', 'S', 'D'],
semaine: ['S1', 'S2', 'S3', 'S4', 'S5', 'S6', 'S7'],
mois: ['Mai', 'Juin', 'Juil', 'Août'],
}
// Le sélecteur de période recalcule vraiment le graphique (state), comme
// le fait l'app pour les stats personnelles du livreur.
export function DeliveryStatsSection() {
const [period, setPeriod] = useState<Period>('semaine')
const values = DATA[period]
const labels = LABELS[period]
const max = useMemo(() => Math.max(...values), [values])
return (
<MockupDevice>
<ScreenTitle title="Mes performances" />
<SimpleGrid columns={2} spacing={2} mb={2.5}>
<StatTile icon={faBoxesStacked} value={512} label="Total livraisons" color={COLORS.accentLight} />
<StatTile icon={faCheck} value={489} label="Complétées" color={COLORS.success} />
<StatTile icon={faClock} value={14} label="Livraisons du jour" color={COLORS.accentLight} />
<StatTile icon={faHourglassHalf} value={3} label="En attente" color={COLORS.info} />
</SimpleGrid>
<MockCard mb={0}>
<MockText variant="secondary" size="0.72rem">
Évolution
</MockText>
<div style={{ marginTop: 8 }}>
<MockTabs
tabs={[
{ key: 'jour', label: 'Jour' },
{ key: 'semaine', label: 'Semaine' },
{ key: 'mois', label: 'Mois' },
]}
active={period}
onChange={setPeriod}
/>
</div>
<div style={{ display: 'flex', alignItems: 'flex-end', gap: 6, height: 90, marginTop: 6 }}>
{values.map((v, i) => (
<div key={i} style={{ flex: 1, textAlign: 'center' }}>
<div
style={{
height: `${(v / max) * 70}px`,
background: COLORS.accent,
borderRadius: 4,
transition: 'height .3s ease',
}}
/>
<div style={{ color: COLORS.text3, fontSize: '0.55rem', marginTop: 4 }}>{labels[i]}</div>
</div>
))}
</div>
<MockText variant="muted" size="0.62rem">
{values.reduce((a, b) => a + b, 0)} livraisons sur la période
</MockText>
</MockCard>
</MockupDevice>
)
}
+160 -78
View File
@@ -1,3 +1,4 @@
import { useState } from 'react'
import {
Box,
Button,
@@ -15,8 +16,9 @@ import {
} from '@chakra-ui/react'
import { Link as RouterLink } from 'react-router-dom'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faCheck } from '@fortawesome/free-solid-svg-icons'
import { faCheck, faDesktop, faShieldHalved, faUtensils } from '@fortawesome/free-solid-svg-icons'
import type { ReactNode } from 'react'
import { DashboardSection } from '../components/docs/sections/DashboardSection'
import { CommandesSection } from '../components/docs/sections/CommandesSection'
import { LivraisonSection } from '../components/docs/sections/LivraisonSection'
@@ -28,6 +30,17 @@ import { ParametresSection } from '../components/docs/sections/ParametresSection
import { AlertesSection } from '../components/docs/sections/AlertesSection'
import { AdressesSection } from '../components/docs/sections/AdressesSection'
import { CabineDashboardSection } from '../components/docs/sections/cabine/CabineDashboardSection'
import { CabineOrdersSection } from '../components/docs/sections/cabine/CabineOrdersSection'
import { CabineDeliverySection } from '../components/docs/sections/cabine/CabineDeliverySection'
import { CabineAlertsSection } from '../components/docs/sections/cabine/CabineAlertsSection'
import { CabineUsersSection } from '../components/docs/sections/cabine/CabineUsersSection'
import { DeliveryDashboardSection } from '../components/docs/sections/delivery/DeliveryDashboardSection'
import { DeliveryAlertsSection } from '../components/docs/sections/delivery/DeliveryAlertsSection'
import { DeliveryRatingsSection } from '../components/docs/sections/delivery/DeliveryRatingsSection'
import { DeliveryStatsSection } from '../components/docs/sections/delivery/DeliveryStatsSection'
interface FeatureSpec {
id: string
kicker: string
@@ -38,21 +51,17 @@ interface FeatureSpec {
reverse?: boolean
}
const FEATURES: FeatureSpec[] = [
const ADMIN_FEATURES: FeatureSpec[] = [
{
id: 'tableau-de-bord',
id: 'admin-dashboard',
kicker: "Vue d'ensemble",
title: 'Toute votre activité en un coup d’œil',
lead: "Dès l'ouverture, vous voyez l'essentiel : commandes en attente ou en route, clients et livreurs actifs — sans avoir à chercher l'information.",
points: [
'Chiffres mis à jour en temps réel',
'Notifications Telegram directement liées à votre compte',
"Accès identique sur mobile et sur ordinateur",
],
points: ['Chiffres mis à jour en temps réel', 'Notifications Telegram liées à votre compte', 'Accès identique sur mobile et sur ordinateur'],
mockup: <DashboardSection />,
},
{
id: 'commandes',
id: 'admin-commandes',
kicker: 'Suivi opérationnel',
title: 'Suivez chaque commande, de la validation à la livraison',
lead: "Chaque commande apparaît avec son statut, son montant et le livreur assigné. Un menu d'actions par commande permet d'assigner un livreur ou de la finaliser en un geste.",
@@ -65,82 +74,58 @@ const FEATURES: FeatureSpec[] = [
reverse: true,
},
{
id: 'livraison',
id: 'admin-livraison',
kicker: 'Géolocalisation en direct',
title: 'Localisez vos livreurs en temps réel, sur la carte',
lead: "La position de chaque livreur s'affiche en direct, avec la distance et le temps restant estimé. Vue d'ensemble de toute la flotte : disponible, occupée ou hors ligne.",
points: [
'Distance et temps de trajet calculés automatiquement',
'Avis clients et historique de connexion par livreur',
'Notification client en un tap : "le livreur est arrivé"',
],
points: ['Distance et temps de trajet calculés automatiquement', 'Avis clients et historique de connexion par livreur', 'Notification client en un tap : "le livreur est arrivé"'],
mockup: <LivraisonSection />,
},
{
id: 'produits',
id: 'admin-produits',
kicker: 'Catalogue',
title: 'Un catalogue que vous gérez vous-même',
lead: "Ajoutez, modifiez ou retirez un produit en quelques secondes : photos, vidéos, stock, description, et plusieurs tarifs par produit.",
points: [
'Photos et vidéos multiples par produit',
'Plusieurs paliers de prix, activables indépendamment',
'Statut « à venir » pour annoncer un produit avant sa mise en vente',
],
lead: 'Ajoutez, modifiez ou retirez un produit en quelques secondes : photos, vidéos, stock, description, et plusieurs tarifs par produit.',
points: ['Photos et vidéos multiples par produit', 'Plusieurs paliers de prix, activables indépendamment', 'Statut « à venir » pour annoncer un produit avant sa mise en vente'],
mockup: <ProduitsSection />,
reverse: true,
},
{
id: 'categories',
id: 'admin-categories',
kicker: 'Organisation',
title: 'Organisez votre catalogue comme vous le souhaitez',
lead: "Créez vos propres catégories, attribuez-leur une couleur, réordonnez-les en un clic. Une catégorie peut être marquée « prochainement » avant sa mise en ligne.",
points: [
'Couleur personnalisée par catégorie',
"Réorganisation manuelle de l'ordre d'affichage",
'Aperçu immédiat du rendu côté client',
],
lead: 'Créez vos propres catégories, attribuez-leur une couleur, réordonnez-les en un clic. Une catégorie peut être marquée « prochainement » avant sa mise en ligne.',
points: ['Couleur personnalisée par catégorie', "Réorganisation manuelle de l'ordre d'affichage", 'Aperçu immédiat du rendu côté client'],
mockup: <CategoriesSection />,
},
{
id: 'statistiques',
id: 'admin-statistiques',
kicker: 'Pilotage',
title: 'Des chiffres clairs pour piloter votre activité',
lead: "Chiffre d'affaires, produits les plus vendus, heures et jours d'affluence : tout est visualisé simplement, sans éplucher vos commandes une par une.",
points: [
'Historique mensuel navigable, jour par jour',
'Classement des meilleurs produits (quantité, commandes ou revenus)',
"Repérage automatique du jour et de l'heure de pointe",
],
points: ['Historique mensuel navigable, jour par jour', 'Classement des meilleurs produits (quantité, commandes ou revenus)', "Repérage automatique du jour et de l'heure de pointe"],
mockup: <StatistiquesSection />,
reverse: true,
},
{
id: 'utilisateurs',
id: 'admin-utilisateurs',
kicker: 'Équipe & clients',
title: 'Clients, livreurs, équipe : tout au même endroit',
lead: "Un seul espace pour gérer tous les comptes. Ajustez les points de fidélité, appliquez une pénalité ou consultez l'historique d'un client.",
points: [
'Filtre par rôle et recherche instantanée',
'Gestion des points de fidélité et du parrainage',
"Suivi des annulations et de l'historique de connexion",
],
points: ['Filtre par rôle et recherche instantanée', 'Gestion des points de fidélité et du parrainage', "Suivi des annulations et de l'historique de connexion"],
mockup: <UtilisateursSection />,
},
{
id: 'parametres',
id: 'admin-parametres',
kicker: 'Personnalisation',
title: "Une plateforme qui s'adapte à votre activité",
lead: 'Zones et horaires de livraison, moyens de paiement (dont le paiement crypto), Telegram, fidélité, couleurs : tout est configurable vous-même.',
points: [
'Zones de livraison par code postal avec minimum de commande',
'Paiement crypto (NowPayments) et bot Telegram intégrés',
"Couleurs de l'espace admin et de l'app client personnalisables",
],
lead: '13 sections de réglages : fidélité, parrainage, pénalités, zones et horaires de livraison, paiement crypto, Telegram, couleurs de linterface... Tout est configurable vous-même.',
points: ['Zones de livraison par code postal avec minimum de commande', 'Paiement crypto (NowPayments) et bot Telegram intégrés', "Couleurs de l'espace admin et de l'app client personnalisables"],
mockup: <ParametresSection />,
reverse: true,
},
{
id: 'alertes',
id: 'admin-alertes',
kicker: 'Réactivité',
title: 'Réagissez immédiatement en cas de problème',
lead: "Si un livreur rencontre un souci sur le terrain, l'alerte remonte instantanément — avec son message et l'horodatage — jusqu'à ce qu'elle soit résolue.",
@@ -148,7 +133,7 @@ const FEATURES: FeatureSpec[] = [
mockup: <AlertesSection />,
},
{
id: 'adresses',
id: 'admin-adresses',
kicker: 'Fiabilité livraison',
title: 'Zéro commande perdue à cause dune adresse mal saisie',
lead: "Quand un client tape une adresse imprécise, corrigez-la une bonne fois pour toutes : les prochaines commandes utiliseront automatiquement la bonne adresse.",
@@ -158,7 +143,100 @@ const FEATURES: FeatureSpec[] = [
},
]
const CABINE_FEATURES: FeatureSpec[] = [
{
id: 'cabine-dashboard',
kicker: 'Opérations du jour',
title: 'Un espace dédié pour votre équipe en cuisine',
lead: "Vos préparateurs voient uniquement ce qui les concerne : commandes en cours, livreurs disponibles — sans chiffre d'affaires ni réglages sensibles.",
points: ['Aucune donnée financière exposée', 'Notifications Telegram propres à chaque compte', 'Rafraîchissement en direct'],
mockup: <CabineDashboardSection />,
},
{
id: 'cabine-commandes',
kicker: 'Suivi des commandes',
title: 'Faire avancer une commande, en toute sécurité',
lead: 'La cabine assigne un livreur, prévient le client, propose une correction dadresse — mais ne peut ni modifier ni voir les réglages de la commande.',
points: ['Actions limitées et guidées, pas de menu complexe', 'Confirmation demandée avant chaque action sensible', 'Zéro risque de modification accidentelle'],
mockup: <CabineOrdersSection />,
reverse: true,
},
{
id: 'cabine-livraison',
kicker: 'Suivi livreurs',
title: 'Suivre les livreurs en direct, sans les gérer',
lead: 'La cabine voit la carte, la disponibilité et la position de chaque livreur pour coordonner la préparation — la gestion des comptes reste réservée à ladmin.',
points: ['Carte et statuts en temps réel', 'Suivi ditinéraire en un clic', 'Aucun accès à la création ou modification de compte'],
mockup: <CabineDeliverySection />,
},
{
id: 'cabine-alertes',
kicker: 'Information',
title: 'Rester informé, sans pouvoir de décision',
lead: 'Les alertes remontées par les livreurs sont visibles par la cabine pour garder tout le monde informé — seule l’équipe admin peut les résoudre.',
points: ['Filtre "Toutes" / "Actives"', 'Lecture seule, aucune action destructrice possible'],
mockup: <CabineAlertsSection />,
reverse: true,
},
{
id: 'cabine-utilisateurs',
kicker: 'Service client',
title: 'Un geste commercial, sans accès aux comptes',
lead: 'Besoin dannuler une pénalité ou doffrir des points en guise de geste commercial ? La cabine peut le faire en un clic, sans jamais toucher au reste du compte client.',
points: ['Reset pénalités et points uniquement', 'Aucune création, édition ou suppression de compte'],
mockup: <CabineUsersSection />,
},
]
const DELIVERY_FEATURES: FeatureSpec[] = [
{
id: 'livreur-dashboard',
kicker: 'Terrain',
title: 'Le compagnon de route de vos livreurs',
lead: 'Statut en un tap (disponible / occupé / hors ligne), position GPS en direct, et un cycle de livraison guidé étape par étape jusqu’à la remise au client.',
points: ['Cycle complet : Démarrer → Jarrive → Terminer', 'Appel client et itinéraire en un tap', 'Gestion des cas « client absent » avec compte à rebours'],
mockup: <DeliveryDashboardSection />,
},
{
id: 'livreur-alertes',
kicker: 'Sécurité',
title: 'Un bouton durgence, toujours à portée de main',
lead: 'En cas de contrôle de police ou de situation dangereuse, le livreur alerte l’équipe admin en 2 taps — avec des consignes de sécurité affichées immédiatement.',
points: ['Deux types dalerte : contrôle de police / guet-apens', 'Confirmation avant envoi pour éviter les fausses alertes', 'Consignes de sécurité affichées après envoi'],
mockup: <DeliveryAlertsSection />,
reverse: true,
},
{
id: 'livreur-avis',
kicker: 'Reconnaissance',
title: 'Chaque livreur voit ses propres avis clients',
lead: 'Note moyenne et commentaires clients, pour valoriser le travail de vos livreurs et repérer rapidement un souci de service.',
points: ['Note moyenne calculée automatiquement', 'Commentaires clients horodatés'],
mockup: <DeliveryRatingsSection />,
},
{
id: 'livreur-stats',
kicker: 'Performance',
title: 'Un livreur qui suit sa propre performance reste motivé',
lead: 'Nombre de livraisons, taux de complétion, évolution jour / semaine / mois : chaque livreur a une vision claire de son activité.',
points: ['Bascule Jour / Semaine / Mois', 'Totaux recalculés automatiquement'],
mockup: <DeliveryStatsSection />,
reverse: true,
},
]
type RoleKey = 'admin' | 'cabine' | 'livreur'
const ROLES: { key: RoleKey; label: string; icon: typeof faDesktop; features: FeatureSpec[]; blurb: string }[] = [
{ key: 'admin', label: 'Espace Admin', icon: faDesktop, features: ADMIN_FEATURES, blurb: 'Contrôle total de la plateforme.' },
{ key: 'cabine', label: 'Espace Cabine', icon: faUtensils, features: CABINE_FEATURES, blurb: 'Équipe de préparation, accès restreint.' },
{ key: 'livreur', label: 'Espace Livreur', icon: faShieldHalved, features: DELIVERY_FEATURES, blurb: 'Application dédiée aux livreurs.' },
]
export function Documentation() {
const [role, setRole] = useState<RoleKey>('admin')
const activeRole = ROLES.find((r) => r.key === role)!
return (
<Box>
{/* Hero */}
@@ -167,45 +245,44 @@ export function Documentation() {
<Stack spacing={5} textAlign="center" align="center">
<HStack spacing={2}>
<Box as="span" px={3} py={1} borderRadius="full" fontSize="xs" fontWeight="bold" bg="primary.500" color="white">
Espace admin
3 espaces, 1 seule plateforme
</Box>
<Box as="span" px={3} py={1} borderRadius="full" fontSize="xs" fontWeight="bold" borderWidth="1px">
Démo interactive
</Box>
</HStack>
<Heading size="2xl">Pilotez toute votre activité depuis un seul espace</Heading>
<Heading size="2xl">Admin, cabine, livreur : chacun son espace</Heading>
<Text fontSize="lg" color="gray.500" maxW="2xl">
Commandes, livraisons, produits, équipe, statistiques et paramètres. Chaque aperçu ci-dessous est
interactif : cliquez, filtrez, essayez comme dans l'application réelle.
Chaque rôle a exactement les écrans dont il a besoin rien de plus. Choisissez un espace ci-dessous
et essayez les aperçus : ils sont interactifs, comme dans l'application réelle.
</Text>
<Stack direction={{ base: 'column', sm: 'row' }} spacing={4}>
<Button as={RouterLink} to="/register" colorScheme="primary" size="lg">
Créer un compte
</Button>
<Button as={RouterLink} to="/tarifs" variant="outline" size="lg">
Voir les tarifs
</Button>
</Stack>
</Stack>
</Container>
</Box>
{/* Sommaire */}
{/* Sélecteur de rôle + sommaire */}
<Box borderTopWidth="1px" borderBottomWidth="1px" bg="chakra-subtle-bg" position="sticky" top={0} zIndex={2}>
<Container maxW="container.lg" py={3} overflowX="auto">
<Container maxW="container.lg" py={3}>
<HStack spacing={2} mb={2} overflowX="auto">
{ROLES.map((r) => (
<Button
key={r.key}
size="sm"
leftIcon={<FontAwesomeIcon icon={r.icon} />}
colorScheme={role === r.key ? 'primary' : undefined}
variant={role === r.key ? 'solid' : 'outline'}
onClick={() => setRole(r.key)}
flexShrink={0}
>
{r.label}
</Button>
))}
</HStack>
<Wrap spacing={5} shouldWrapChildren>
{FEATURES.map((f) => (
{activeRole.features.map((f) => (
<WrapItem key={f.id}>
<Text
as="a"
href={`#${f.id}`}
fontSize="sm"
fontWeight="semibold"
color="primary.500"
whiteSpace="nowrap"
_hover={{ textDecoration: 'underline' }}
>
{f.title.length > 28 ? f.kicker : f.title}
<Text as="a" href={`#${f.id}`} fontSize="sm" fontWeight="semibold" color="primary.500" whiteSpace="nowrap" _hover={{ textDecoration: 'underline' }}>
{f.kicker}
</Text>
</WrapItem>
))}
@@ -213,9 +290,14 @@ export function Documentation() {
</Container>
</Box>
{/* Sections */}
{/* Sections du rôle actif */}
<Container maxW="container.lg">
{FEATURES.map((f) => (
<Box py={6}>
<Text color="gray.500" fontSize="sm" textAlign="center">
{activeRole.blurb}
</Text>
</Box>
{activeRole.features.map((f) => (
<Stack
key={f.id}
id={f.id}
@@ -224,7 +306,7 @@ export function Documentation() {
align="center"
py={{ base: 14, md: 20 }}
borderBottomWidth="1px"
scrollMarginTop="60px"
scrollMarginTop="120px"
>
<Box flex="0.9" minW={0}>
<Text fontSize="xs" fontWeight="extrabold" letterSpacing="wide" textTransform="uppercase" color="primary.500" mb={2}>
+1 -1
View File
@@ -1 +1 @@
{"root":["./src/App.tsx","./src/main.tsx","./src/setupTests.ts","./src/theme.ts","./src/vite-env.d.ts","./src/components/BackofficeLayout.tsx","./src/components/ColorModeToggle.tsx","./src/components/ConfirmDialog.test.tsx","./src/components/ConfirmDialog.tsx","./src/components/CreateDemoModal.tsx","./src/components/DemoCard.tsx","./src/components/EditDomainModal.tsx","./src/components/Footer.tsx","./src/components/Header.tsx","./src/components/PasswordInput.tsx","./src/components/PodStatusPanel.tsx","./src/components/PublicLayout.tsx","./src/components/docs/AdminMockup.tsx","./src/components/docs/sections/AdressesSection.tsx","./src/components/docs/sections/AlertesSection.tsx","./src/components/docs/sections/CategoriesSection.tsx","./src/components/docs/sections/CommandesSection.tsx","./src/components/docs/sections/DashboardSection.tsx","./src/components/docs/sections/LivraisonSection.tsx","./src/components/docs/sections/ParametresSection.tsx","./src/components/docs/sections/ProduitsSection.tsx","./src/components/docs/sections/StatistiquesSection.tsx","./src/components/docs/sections/UtilisateursSection.tsx","./src/lib/api.ts","./src/lib/auth.tsx","./src/lib/format.test.ts","./src/lib/format.ts","./src/pages/AdminLogin.tsx","./src/pages/Documentation.tsx","./src/pages/Landing.tsx","./src/pages/Login.tsx","./src/pages/Pricing.tsx","./src/pages/Register.tsx","./src/pages/backoffice/Codes.tsx","./src/pages/backoffice/Demos.tsx","./src/pages/backoffice/PremiumDemos.tsx","./src/pages/backoffice/Profile.tsx","./src/pages/backoffice/Subscription.tsx"],"version":"5.9.3"}
{"root":["./src/App.tsx","./src/main.tsx","./src/setupTests.ts","./src/theme.ts","./src/vite-env.d.ts","./src/components/BackofficeLayout.tsx","./src/components/ColorModeToggle.tsx","./src/components/ConfirmDialog.test.tsx","./src/components/ConfirmDialog.tsx","./src/components/CreateDemoModal.tsx","./src/components/DemoCard.tsx","./src/components/EditDomainModal.tsx","./src/components/Footer.tsx","./src/components/Header.tsx","./src/components/PasswordInput.tsx","./src/components/PodStatusPanel.tsx","./src/components/PublicLayout.tsx","./src/components/docs/AdminMockup.tsx","./src/components/docs/sections/AdressesSection.tsx","./src/components/docs/sections/AlertesSection.tsx","./src/components/docs/sections/CategoriesSection.tsx","./src/components/docs/sections/CommandesSection.tsx","./src/components/docs/sections/DashboardSection.tsx","./src/components/docs/sections/LivraisonSection.tsx","./src/components/docs/sections/ParametresSection.tsx","./src/components/docs/sections/ProduitsSection.tsx","./src/components/docs/sections/StatistiquesSection.tsx","./src/components/docs/sections/UtilisateursSection.tsx","./src/components/docs/sections/cabine/CabineAlertsSection.tsx","./src/components/docs/sections/cabine/CabineDashboardSection.tsx","./src/components/docs/sections/cabine/CabineDeliverySection.tsx","./src/components/docs/sections/cabine/CabineOrdersSection.tsx","./src/components/docs/sections/cabine/CabineUsersSection.tsx","./src/components/docs/sections/delivery/DeliveryAlertsSection.tsx","./src/components/docs/sections/delivery/DeliveryDashboardSection.tsx","./src/components/docs/sections/delivery/DeliveryRatingsSection.tsx","./src/components/docs/sections/delivery/DeliveryStatsSection.tsx","./src/lib/api.ts","./src/lib/auth.tsx","./src/lib/format.test.ts","./src/lib/format.ts","./src/pages/AdminLogin.tsx","./src/pages/Documentation.tsx","./src/pages/Landing.tsx","./src/pages/Login.tsx","./src/pages/Pricing.tsx","./src/pages/Register.tsx","./src/pages/backoffice/Codes.tsx","./src/pages/backoffice/Demos.tsx","./src/pages/backoffice/PremiumDemos.tsx","./src/pages/backoffice/Profile.tsx","./src/pages/backoffice/Subscription.tsx"],"version":"5.9.3"}