Vendored
+1510
File diff suppressed because one or more lines are too long
Vendored
-1510
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -5,7 +5,7 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Omnex — Plateforme de gestion de commandes & livraison</title>
|
<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." />
|
<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>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@@ -298,6 +298,18 @@ export function MockStack({ children, spacing = 1.5 }: { children: ReactNode; sp
|
|||||||
return <Stack spacing={spacing}>{children}</Stack>
|
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.
|
// Interrupteur cliquable (paramètres, liaisons de compte...) avec vrai état.
|
||||||
export function MockSwitch({ isOn, onToggle }: { isOn: boolean; onToggle: () => void }) {
|
export function MockSwitch({ isOn, onToggle }: { isOn: boolean; onToggle: () => void }) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -51,7 +51,8 @@ export function LivraisonSection() {
|
|||||||
borderRadius: 999,
|
borderRadius: 999,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
● {driver.name} — {driver.distance} · {driver.eta}
|
<MockDot color={STATUS_TONE[driver.status]} size="7px" />
|
||||||
|
{driver.name} — {driver.distance} · {driver.eta}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<MockText variant="muted" size="0.62rem">
|
<MockText variant="muted" size="0.62rem">
|
||||||
|
|||||||
@@ -1,73 +1,87 @@
|
|||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import {
|
import {
|
||||||
|
faBan,
|
||||||
|
faBell,
|
||||||
faChevronDown,
|
faChevronDown,
|
||||||
faChevronUp,
|
faChevronUp,
|
||||||
faCreditCard,
|
faCreditCard,
|
||||||
|
faGift,
|
||||||
|
faMapLocationDot,
|
||||||
faPalette,
|
faPalette,
|
||||||
faPaperPlane,
|
faPaperPlane,
|
||||||
|
faStar,
|
||||||
|
faTags,
|
||||||
faTruck,
|
faTruck,
|
||||||
|
faUserPlus,
|
||||||
} from '@fortawesome/free-solid-svg-icons'
|
} from '@fortawesome/free-solid-svg-icons'
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
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 = [
|
interface Section {
|
||||||
{
|
id: string
|
||||||
id: 'delivery',
|
icon: typeof faTruck
|
||||||
icon: faTruck,
|
title: string
|
||||||
title: 'Horaires de livraison',
|
body: string
|
||||||
body: 'Lundi → Vendredi : 11h00 – 22h30. Samedi : 12h00 – 23h00. Dimanche : fermé.',
|
hasSwitch?: boolean
|
||||||
},
|
}
|
||||||
{
|
|
||||||
id: 'crypto',
|
// Les 13 sections réelles de l'écran Paramètres (frontend-admin/src/screens/admin/SettingsScreen.tsx) :
|
||||||
icon: faCreditCard,
|
// personnalisation, amendes, parrainage, points, attribution catégories/points,
|
||||||
title: 'Paiement crypto',
|
// barèmes par pool de points, récompense par palier, horaires de livraison,
|
||||||
body: 'BTC, ETH, LTC, USDT acceptés via NowPayments. Clé API et secret IPN configurés séparément.',
|
// 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 l’app · dégradé de couleur du titre (2 couleurs, aperçu en direct).' },
|
||||||
id: 'telegram',
|
{ id: 'amendes', icon: faBan, title: 'Amendes', body: 'Barème par nombre d’annulations : 0 → 20€, 1 → 50€, 2 → 100€, 3 → 150€. Score affichable au client ou non.', hasSwitch: true },
|
||||||
icon: faPaperPlane,
|
{ id: 'parrainage', icon: faUserPlus, title: 'Parrainage', body: 'Active le solde de parrainage, utilisable directement au moment du paiement par le filleul.', hasSwitch: true },
|
||||||
title: 'Notifications Telegram',
|
{ 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 },
|
||||||
body: 'Bot configuré — @votre_bot. Authentification à deux facteurs activée pour les commandes sensibles.',
|
{ 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. 0–20€ = 5 pts, 20–50€ = 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: 'colors',
|
{ id: 'horaires', icon: faTruck, title: 'Horaires de livraison', body: 'Lundi → Vendredi : 11h00–22h30 · Samedi : 12h00–23h00 · Dimanche : fermé.' },
|
||||||
icon: faPalette,
|
{ 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.' },
|
||||||
title: "Couleurs de l'interface",
|
{ id: 'crypto', icon: faCreditCard, title: 'Paiement crypto', body: 'BTC, ETH, LTC, USDT acceptés (NowPayments). Clé API et secret IPN masqués.', hasSwitch: true },
|
||||||
body: 'Personnalisez séparément les couleurs de votre espace admin et de l’app client.',
|
{ 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 l’espace admin et pour l’app client — 5 couleurs, restaurables en un clic.' },
|
||||||
]
|
]
|
||||||
|
|
||||||
// Vrai accordéon : un seul panneau ouvert à la fois, clic pour
|
// Accordéon complet et cliquable — toutes les vraies sections de l'écran
|
||||||
// déplier/replier — comme la page Paramètres réelle.
|
// Paramètres, pas un extrait.
|
||||||
export function ParametresSection() {
|
export function ParametresSection() {
|
||||||
const [openId, setOpenId] = useState<string | null>('crypto')
|
const [openId, setOpenId] = useState<string | null>('crypto')
|
||||||
const [cryptoOn, setCryptoOn] = useState(true)
|
const [cryptoOn, setCryptoOn] = useState(true)
|
||||||
|
const [telegramOn, setTelegramOn] = useState(true)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MockupDevice>
|
<MockupDevice>
|
||||||
<ScreenTitle title="Paramètres" subtitle="Cliquez une section pour l'ouvrir" />
|
<ScreenTitle title="Paramètres" subtitle={`${SECTIONS.length} sections — cliquez pour ouvrir`} />
|
||||||
|
<div style={{ maxHeight: 480, overflowY: 'auto', paddingRight: 2 }}>
|
||||||
{SECTIONS.map((s) => {
|
{SECTIONS.map((s) => {
|
||||||
const isOpen = openId === s.id
|
const isOpen = openId === s.id
|
||||||
|
const switchState = s.id === 'crypto' ? cryptoOn : s.id === 'telegram' ? telegramOn : true
|
||||||
|
const setSwitchState = s.id === 'crypto' ? setCryptoOn : setTelegramOn
|
||||||
return (
|
return (
|
||||||
<MockCard key={s.id} onClick={() => setOpenId(isOpen ? null : s.id)}>
|
<MockCard key={s.id} onClick={() => setOpenId(isOpen ? null : s.id)}>
|
||||||
<MockRow>
|
<MockRow>
|
||||||
<MockText>
|
<MockText size="0.72rem">
|
||||||
<FontAwesomeIcon icon={s.icon} style={{ color: COLORS.accentLight, marginRight: 8, fontSize: '0.72rem' }} />
|
<FontAwesomeIcon icon={s.icon} style={{ color: COLORS.accentLight, marginRight: 8, fontSize: '0.68rem' }} />
|
||||||
{s.title}
|
{s.title}
|
||||||
</MockText>
|
</MockText>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||||
{s.id === 'crypto' && (
|
{s.hasSwitch && (s.id === 'crypto' || s.id === 'telegram') && (
|
||||||
<span onClick={(e) => e.stopPropagation()}>
|
<span onClick={(e) => e.stopPropagation()}>
|
||||||
<MockSwitch isOn={cryptoOn} onToggle={() => setCryptoOn((v) => !v)} />
|
<MockSwitch isOn={switchState} onToggle={() => setSwitchState((v) => !v)} />
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
<FontAwesomeIcon icon={isOpen ? faChevronUp : faChevronDown} style={{ color: COLORS.text3, fontSize: '0.62rem' }} />
|
{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>
|
</div>
|
||||||
</MockRow>
|
</MockRow>
|
||||||
{isOpen && (
|
{isOpen && (
|
||||||
<div style={{ marginTop: 8, paddingTop: 8, borderTop: `1px solid ${COLORS.border}` }}>
|
<div style={{ marginTop: 8, paddingTop: 8, borderTop: `1px solid ${COLORS.border}` }}>
|
||||||
<MockText variant="muted" size="0.68rem">
|
<MockText variant="muted" size="0.66rem">
|
||||||
<MockDot color={COLORS.text3} size="5px" />
|
|
||||||
{s.body}
|
{s.body}
|
||||||
</MockText>
|
</MockText>
|
||||||
</div>
|
</div>
|
||||||
@@ -75,6 +89,7 @@ export function ParametresSection() {
|
|||||||
</MockCard>
|
</MockCard>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
</div>
|
||||||
</MockupDevice>
|
</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 là
|
||||||
|
</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 l’ordre. 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
+161
-79
@@ -1,3 +1,4 @@
|
|||||||
|
import { useState } from 'react'
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
@@ -15,8 +16,9 @@ import {
|
|||||||
} from '@chakra-ui/react'
|
} from '@chakra-ui/react'
|
||||||
import { Link as RouterLink } from 'react-router-dom'
|
import { Link as RouterLink } from 'react-router-dom'
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
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 type { ReactNode } from 'react'
|
||||||
|
|
||||||
import { DashboardSection } from '../components/docs/sections/DashboardSection'
|
import { DashboardSection } from '../components/docs/sections/DashboardSection'
|
||||||
import { CommandesSection } from '../components/docs/sections/CommandesSection'
|
import { CommandesSection } from '../components/docs/sections/CommandesSection'
|
||||||
import { LivraisonSection } from '../components/docs/sections/LivraisonSection'
|
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 { AlertesSection } from '../components/docs/sections/AlertesSection'
|
||||||
import { AdressesSection } from '../components/docs/sections/AdressesSection'
|
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 {
|
interface FeatureSpec {
|
||||||
id: string
|
id: string
|
||||||
kicker: string
|
kicker: string
|
||||||
@@ -38,21 +51,17 @@ interface FeatureSpec {
|
|||||||
reverse?: boolean
|
reverse?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const FEATURES: FeatureSpec[] = [
|
const ADMIN_FEATURES: FeatureSpec[] = [
|
||||||
{
|
{
|
||||||
id: 'tableau-de-bord',
|
id: 'admin-dashboard',
|
||||||
kicker: "Vue d'ensemble",
|
kicker: "Vue d'ensemble",
|
||||||
title: 'Toute votre activité en un coup d’œil',
|
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.",
|
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: [
|
points: ['Chiffres mis à jour en temps réel', 'Notifications Telegram liées à votre compte', 'Accès identique sur mobile et sur ordinateur'],
|
||||||
'Chiffres mis à jour en temps réel',
|
|
||||||
'Notifications Telegram directement liées à votre compte',
|
|
||||||
"Accès identique sur mobile et sur ordinateur",
|
|
||||||
],
|
|
||||||
mockup: <DashboardSection />,
|
mockup: <DashboardSection />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'commandes',
|
id: 'admin-commandes',
|
||||||
kicker: 'Suivi opérationnel',
|
kicker: 'Suivi opérationnel',
|
||||||
title: 'Suivez chaque commande, de la validation à la livraison',
|
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.",
|
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,
|
reverse: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'livraison',
|
id: 'admin-livraison',
|
||||||
kicker: 'Géolocalisation en direct',
|
kicker: 'Géolocalisation en direct',
|
||||||
title: 'Localisez vos livreurs en temps réel, sur la carte',
|
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.",
|
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: [
|
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é"'],
|
||||||
'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 />,
|
mockup: <LivraisonSection />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'produits',
|
id: 'admin-produits',
|
||||||
kicker: 'Catalogue',
|
kicker: 'Catalogue',
|
||||||
title: 'Un catalogue que vous gérez vous-même',
|
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.",
|
lead: 'Ajoutez, modifiez ou retirez un produit en quelques secondes : photos, vidéos, stock, description, et plusieurs tarifs par produit.',
|
||||||
points: [
|
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'],
|
||||||
'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 />,
|
mockup: <ProduitsSection />,
|
||||||
reverse: true,
|
reverse: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'categories',
|
id: 'admin-categories',
|
||||||
kicker: 'Organisation',
|
kicker: 'Organisation',
|
||||||
title: 'Organisez votre catalogue comme vous le souhaitez',
|
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.",
|
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: [
|
points: ['Couleur personnalisée par catégorie', "Réorganisation manuelle de l'ordre d'affichage", 'Aperçu immédiat du rendu côté client'],
|
||||||
'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 />,
|
mockup: <CategoriesSection />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'statistiques',
|
id: 'admin-statistiques',
|
||||||
kicker: 'Pilotage',
|
kicker: 'Pilotage',
|
||||||
title: 'Des chiffres clairs pour piloter votre activité',
|
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.",
|
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: [
|
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"],
|
||||||
'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 />,
|
mockup: <StatistiquesSection />,
|
||||||
reverse: true,
|
reverse: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'utilisateurs',
|
id: 'admin-utilisateurs',
|
||||||
kicker: 'Équipe & clients',
|
kicker: 'Équipe & clients',
|
||||||
title: 'Clients, livreurs, équipe : tout au même endroit',
|
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.",
|
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: [
|
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"],
|
||||||
'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 />,
|
mockup: <UtilisateursSection />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'parametres',
|
id: 'admin-parametres',
|
||||||
kicker: 'Personnalisation',
|
kicker: 'Personnalisation',
|
||||||
title: "Une plateforme qui s'adapte à votre activité",
|
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.',
|
lead: '13 sections de réglages : fidélité, parrainage, pénalités, zones et horaires de livraison, paiement crypto, Telegram, couleurs de l’interface... Tout est configurable vous-même.',
|
||||||
points: [
|
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"],
|
||||||
'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 />,
|
mockup: <ParametresSection />,
|
||||||
reverse: true,
|
reverse: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'alertes',
|
id: 'admin-alertes',
|
||||||
kicker: 'Réactivité',
|
kicker: 'Réactivité',
|
||||||
title: 'Réagissez immédiatement en cas de problème',
|
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.",
|
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 />,
|
mockup: <AlertesSection />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'adresses',
|
id: 'admin-adresses',
|
||||||
kicker: 'Fiabilité livraison',
|
kicker: 'Fiabilité livraison',
|
||||||
title: 'Zéro commande perdue à cause d’une adresse mal saisie',
|
title: 'Zéro commande perdue à cause d’une 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.",
|
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 d’adresse — 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 à l’admin.',
|
||||||
|
points: ['Carte et statuts en temps réel', 'Suivi d’itiné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 d’annuler une pénalité ou d’offrir 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 → J’arrive → 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 d’urgence, 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 d’alerte : 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() {
|
export function Documentation() {
|
||||||
|
const [role, setRole] = useState<RoleKey>('admin')
|
||||||
|
const activeRole = ROLES.find((r) => r.key === role)!
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
{/* Hero */}
|
{/* Hero */}
|
||||||
@@ -167,45 +245,44 @@ export function Documentation() {
|
|||||||
<Stack spacing={5} textAlign="center" align="center">
|
<Stack spacing={5} textAlign="center" align="center">
|
||||||
<HStack spacing={2}>
|
<HStack spacing={2}>
|
||||||
<Box as="span" px={3} py={1} borderRadius="full" fontSize="xs" fontWeight="bold" bg="primary.500" color="white">
|
<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>
|
||||||
<Box as="span" px={3} py={1} borderRadius="full" fontSize="xs" fontWeight="bold" borderWidth="1px">
|
<Box as="span" px={3} py={1} borderRadius="full" fontSize="xs" fontWeight="bold" borderWidth="1px">
|
||||||
Démo interactive
|
Démo interactive
|
||||||
</Box>
|
</Box>
|
||||||
</HStack>
|
</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">
|
<Text fontSize="lg" color="gray.500" maxW="2xl">
|
||||||
Commandes, livraisons, produits, équipe, statistiques et paramètres. Chaque aperçu ci-dessous est
|
Chaque rôle a exactement les écrans dont il a besoin — rien de plus. Choisissez un espace ci-dessous
|
||||||
interactif : cliquez, filtrez, essayez — comme dans l'application réelle.
|
et essayez les aperçus : ils sont interactifs, comme dans l'application réelle.
|
||||||
</Text>
|
</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>
|
</Stack>
|
||||||
</Container>
|
</Container>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* Sommaire */}
|
{/* Sélecteur de rôle + sommaire */}
|
||||||
<Box borderTopWidth="1px" borderBottomWidth="1px" bg="chakra-subtle-bg" position="sticky" top={0} zIndex={2}>
|
<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}>
|
||||||
<Wrap spacing={5} shouldWrapChildren>
|
<HStack spacing={2} mb={2} overflowX="auto">
|
||||||
{FEATURES.map((f) => (
|
{ROLES.map((r) => (
|
||||||
<WrapItem key={f.id}>
|
<Button
|
||||||
<Text
|
key={r.key}
|
||||||
as="a"
|
size="sm"
|
||||||
href={`#${f.id}`}
|
leftIcon={<FontAwesomeIcon icon={r.icon} />}
|
||||||
fontSize="sm"
|
colorScheme={role === r.key ? 'primary' : undefined}
|
||||||
fontWeight="semibold"
|
variant={role === r.key ? 'solid' : 'outline'}
|
||||||
color="primary.500"
|
onClick={() => setRole(r.key)}
|
||||||
whiteSpace="nowrap"
|
flexShrink={0}
|
||||||
_hover={{ textDecoration: 'underline' }}
|
|
||||||
>
|
>
|
||||||
{f.title.length > 28 ? f.kicker : f.title}
|
{r.label}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</HStack>
|
||||||
|
<Wrap spacing={5} shouldWrapChildren>
|
||||||
|
{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.kicker}
|
||||||
</Text>
|
</Text>
|
||||||
</WrapItem>
|
</WrapItem>
|
||||||
))}
|
))}
|
||||||
@@ -213,9 +290,14 @@ export function Documentation() {
|
|||||||
</Container>
|
</Container>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* Sections */}
|
{/* Sections du rôle actif */}
|
||||||
<Container maxW="container.lg">
|
<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
|
<Stack
|
||||||
key={f.id}
|
key={f.id}
|
||||||
id={f.id}
|
id={f.id}
|
||||||
@@ -224,7 +306,7 @@ export function Documentation() {
|
|||||||
align="center"
|
align="center"
|
||||||
py={{ base: 14, md: 20 }}
|
py={{ base: 14, md: 20 }}
|
||||||
borderBottomWidth="1px"
|
borderBottomWidth="1px"
|
||||||
scrollMarginTop="60px"
|
scrollMarginTop="120px"
|
||||||
>
|
>
|
||||||
<Box flex="0.9" minW={0}>
|
<Box flex="0.9" minW={0}>
|
||||||
<Text fontSize="xs" fontWeight="extrabold" letterSpacing="wide" textTransform="uppercase" color="primary.500" mb={2}>
|
<Text fontSize="xs" fontWeight="extrabold" letterSpacing="wide" textTransform="uppercase" color="primary.500" mb={2}>
|
||||||
|
|||||||
@@ -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"}
|
||||||
Reference in New Issue
Block a user