Add demo data branch tools and tablet polish

This commit is contained in:
2026-05-31 21:14:42 +01:00
parent 86beb8a5dd
commit 865c4a78ea
8 changed files with 459 additions and 59 deletions

View File

@@ -241,35 +241,6 @@ export default function AtelierModule() {
}
}
// Seed sample data
const seedSampleData = async () => {
try {
const response = await fetch('/api/atelier/seed?XTransformPort=3000', {
method: 'POST'
})
if (response.ok) {
toast({
title: 'Données ajoutées',
description: 'Les données de test ont été créées avec succès'
})
loadWorkOrders()
} else {
const error = await response.json()
toast({
title: 'Erreur',
description: error.error || 'Impossible de créer les données de test',
variant: 'destructive'
})
}
} catch (error) {
console.error('Error seeding data:', error)
toast({
title: 'Erreur',
description: 'Impossible de créer les données de test',
variant: 'destructive'
})
}
}
useEffect(() => {
loadWorkOrders()
@@ -480,12 +451,6 @@ export default function AtelierModule() {
: `Aucune commande avec le statut "${statusFilter === 'EN_ATTENTE' ? 'En attente' : statusFilter === 'EN_COURS' ? 'En cours' : statusFilter === 'TERMINE' ? 'Terminé' : 'Prêt'}"`
}
</p>
{statusFilter === 'ALL' && workOrders.length === 0 && (
<Button onClick={seedSampleData} variant="outline">
<Wrench className="h-4 w-4 mr-2" />
Ajouter des données de test
</Button>
)}
</div>
) : (
<ScrollArea className="max-h-[600px]">

View File

@@ -0,0 +1,57 @@
'use client'
import { createContext, useContext, useEffect, useMemo, useState } from 'react'
export type CurrencyCode = 'MAD' | 'EUR' | 'USD'
const currencyLocales: Record<CurrencyCode, string> = {
MAD: 'fr-MA',
EUR: 'fr-FR',
USD: 'en-US',
}
interface CurrencyContextValue {
currency: CurrencyCode
setCurrency: (currency: CurrencyCode) => void
formatCurrency: (value: number) => string
}
const CurrencyContext = createContext<CurrencyContextValue | null>(null)
export function CurrencyProvider({ children }: { children: React.ReactNode }) {
const [currency, setCurrencyState] = useState<CurrencyCode>('MAD')
useEffect(() => {
const stored = window.localStorage.getItem('optiquestock-currency') as CurrencyCode | null
if (stored === 'MAD' || stored === 'EUR' || stored === 'USD') {
setCurrencyState(stored)
}
}, [])
const value = useMemo<CurrencyContextValue>(() => {
return {
currency,
setCurrency(nextCurrency) {
setCurrencyState(nextCurrency)
window.localStorage.setItem('optiquestock-currency', nextCurrency)
},
formatCurrency(amount) {
return new Intl.NumberFormat(currencyLocales[currency], {
style: 'currency',
currency,
maximumFractionDigits: 2,
}).format(amount)
},
}
}, [currency])
return <CurrencyContext.Provider value={value}>{children}</CurrencyContext.Provider>
}
export function useCurrency() {
const context = useContext(CurrencyContext)
if (!context) {
throw new Error('useCurrency must be used within CurrencyProvider')
}
return context
}

View File

@@ -0,0 +1,25 @@
'use client'
import { BadgeDollarSign } from 'lucide-react'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
import { CurrencyCode, useCurrency } from '@/components/currency-provider'
export function CurrencySelect() {
const { currency, setCurrency } = useCurrency()
return (
<div className="flex items-center gap-2">
<BadgeDollarSign className="h-4 w-4 text-muted-foreground" />
<Select value={currency} onValueChange={(value: CurrencyCode) => setCurrency(value)}>
<SelectTrigger className="h-9 w-[92px]">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="MAD">MAD</SelectItem>
<SelectItem value="EUR">EUR</SelectItem>
<SelectItem value="USD">USD</SelectItem>
</SelectContent>
</Select>
</div>
)
}

View File

@@ -28,6 +28,7 @@ import {
Wrench,
X,
} from 'lucide-react'
import { useCurrency } from '@/components/currency-provider'
type Step = 'client' | 'service' | 'details' | 'final'
type ServiceType = 'COMBO' | 'REPAIR'
@@ -104,11 +105,8 @@ const paymentLabels: Record<PaymentMode, string> = {
BON_CAISSE: 'Bon de caisse',
}
function currency(value: number) {
return `${value.toFixed(2)} EUR`
}
export function SellerWizard() {
const { formatCurrency } = useCurrency()
const [step, setStep] = useState<Step>('client')
const [clients, setClients] = useState<Client[]>([])
const [products, setProducts] = useState<Product[]>([])
@@ -266,18 +264,26 @@ export function SellerWizard() {
setError('')
if (clientFieldIndex < clientFields.length - 1) {
setClientFieldIndex((index) => index + 1)
focusActiveClientInput()
holdKeyboardOpen()
} else {
createClient()
}
}
function focusActiveClientInput() {
window.requestAnimationFrame(() => {
window.requestAnimationFrame(() => {
activeClientInputRef.current?.focus({ preventScroll: true })
})
})
activeClientInputRef.current?.focus({ preventScroll: true })
}
function holdKeyboardOpen() {
focusActiveClientInput()
window.requestAnimationFrame(focusActiveClientInput)
window.setTimeout(focusActiveClientInput, 40)
window.setTimeout(focusActiveClientInput, 120)
}
function keepTouchOnInput(event: React.MouseEvent | React.PointerEvent | React.TouchEvent) {
event.preventDefault()
holdKeyboardOpen()
}
async function createClient() {
@@ -440,7 +446,7 @@ export function SellerWizard() {
nextClientField()
} else {
setClientFieldIndex((index) => Math.max(0, index - 1))
focusActiveClientInput()
holdKeyboardOpen()
}
}
@@ -582,6 +588,11 @@ export function SellerWizard() {
nextClientField()
}
}}
onBlur={() => {
if (createClientOpen && !loading) {
holdKeyboardOpen()
}
}}
className={`${
keyboardOpen ? 'h-16 text-2xl' : 'h-20 text-3xl'
} min-w-0`}
@@ -589,7 +600,11 @@ export function SellerWizard() {
/>
<Button
className={`${keyboardOpen ? 'h-16 px-5' : 'h-20 px-6'} text-xl`}
onPointerDown={(event) => event.preventDefault()}
type="button"
tabIndex={-1}
onMouseDown={keepTouchOnInput}
onPointerDown={keepTouchOnInput}
onTouchStart={keepTouchOnInput}
onClick={nextClientField}
disabled={loading}
>
@@ -615,10 +630,14 @@ export function SellerWizard() {
variant="outline"
className={`${keyboardOpen ? 'h-12 text-base' : 'h-16 text-xl'}`}
disabled={clientFieldIndex === 0}
onPointerDown={(event) => event.preventDefault()}
type="button"
tabIndex={-1}
onMouseDown={keepTouchOnInput}
onPointerDown={keepTouchOnInput}
onTouchStart={keepTouchOnInput}
onClick={() => {
setClientFieldIndex((index) => Math.max(0, index - 1))
focusActiveClientInput()
holdKeyboardOpen()
}}
>
<ArrowLeft className="h-6 w-6" />
@@ -627,7 +646,11 @@ export function SellerWizard() {
<Button
variant="outline"
className={`${keyboardOpen ? 'h-12 text-base' : 'h-16 text-xl'}`}
onPointerDown={(event) => event.preventDefault()}
type="button"
tabIndex={-1}
onMouseDown={keepTouchOnInput}
onPointerDown={keepTouchOnInput}
onTouchStart={keepTouchOnInput}
onClick={nextClientField}
disabled={loading}
>
@@ -712,7 +735,7 @@ export function SellerWizard() {
{product.reference} - Stock {product.stock}
</div>
</div>
<div className="text-xl font-bold">{currency(product.prixVenteTTC)}</div>
<div className="text-xl font-bold">{formatCurrency(product.prixVenteTTC)}</div>
</Button>
))}
</div>
@@ -798,7 +821,7 @@ export function SellerWizard() {
<div className="rounded-md bg-muted p-4">
<div className="text-muted-foreground">Total</div>
<div className="text-4xl font-bold">{currency(total)}</div>
<div className="text-4xl font-bold">{formatCurrency(total)}</div>
</div>
<div className="grid grid-cols-2 gap-3">
@@ -839,21 +862,21 @@ export function SellerWizard() {
<span>
{line.quantite} x {line.produit.designation}
</span>
<strong>{currency(line.montantTTC)}</strong>
<strong>{formatCurrency(line.montantTTC)}</strong>
</div>
))}
{sale.notes && <p className="muted">{sale.notes}</p>}
<div className="row">
<span>HT</span>
<span>{currency(sale.montantHT)}</span>
<span>{formatCurrency(sale.montantHT)}</span>
</div>
<div className="row">
<span>TVA</span>
<span>{currency(sale.montantTVA)}</span>
<span>{formatCurrency(sale.montantTVA)}</span>
</div>
<div className="row total">
<span>Total TTC</span>
<span>{currency(sale.montantTTC)}</span>
<span>{formatCurrency(sale.montantTTC)}</span>
</div>
<p className="muted">Paiement: {paymentLabels[sale.paiements[0]?.mode || 'ESPECES']}</p>
</div>