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" />
|
||||
<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>
|
||||
|
||||
@@ -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,73 +1,87 @@
|
||||
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 l’app 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 l’app · dégradé de couleur du titre (2 couleurs, aperçu en direct).' },
|
||||
{ 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 },
|
||||
{ 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. 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: 'horaires', icon: faTruck, title: 'Horaires de livraison', body: 'Lundi → Vendredi : 11h00–22h30 · Samedi : 12h00–23h00 · 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 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
|
||||
// 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" />
|
||||
<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>
|
||||
<FontAwesomeIcon icon={s.icon} style={{ color: COLORS.accentLight, marginRight: 8, fontSize: '0.72rem' }} />
|
||||
<MockText size="0.72rem">
|
||||
<FontAwesomeIcon icon={s.icon} style={{ color: COLORS.accentLight, marginRight: 8, fontSize: '0.68rem' }} />
|
||||
{s.title}
|
||||
</MockText>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
{s.id === 'crypto' && (
|
||||
{s.hasSwitch && (s.id === 'crypto' || s.id === 'telegram') && (
|
||||
<span onClick={(e) => e.stopPropagation()}>
|
||||
<MockSwitch isOn={cryptoOn} onToggle={() => setCryptoOn((v) => !v)} />
|
||||
<MockSwitch isOn={switchState} onToggle={() => setSwitchState((v) => !v)} />
|
||||
</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>
|
||||
</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" />
|
||||
<MockText variant="muted" size="0.66rem">
|
||||
{s.body}
|
||||
</MockText>
|
||||
</div>
|
||||
@@ -75,6 +89,7 @@ export function ParametresSection() {
|
||||
</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 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 {
|
||||
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 l’interface... 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 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.",
|
||||
@@ -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() {
|
||||
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">
|
||||
<Wrap spacing={5} shouldWrapChildren>
|
||||
{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' }}
|
||||
<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}
|
||||
>
|
||||
{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>
|
||||
</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 @@
|
||||
{"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