Add demo data branch tools and tablet polish
This commit is contained in:
@@ -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]">
|
||||
|
||||
57
src/components/currency-provider.tsx
Normal file
57
src/components/currency-provider.tsx
Normal 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
|
||||
}
|
||||
25
src/components/currency-select.tsx
Normal file
25
src/components/currency-select.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user