@@ -5,10 +5,13 @@ import { motion } from 'motion/react'
55import { useState } from 'react'
66
77import { optimizeCutting } from '@/lib/optimizer'
8+ import { saveToHistory } from '@/lib/utils/history'
9+ import { validateConfiguration } from '@/lib/utils/validation'
810
911import BoardInput from './BoardInput'
1012import BoardVisualizer from './BoardVisualizer'
1113import ExportPanel from './ExportPanel'
14+ import HistoryPanel from './HistoryPanel'
1215import HowItWorks from './HowItWorks'
1316import PieceInput from './PieceInput'
1417import PositioningReport from './PositioningReport'
@@ -43,9 +46,14 @@ export default function CuttingOptimizer() {
4346 { id : 'D' , w : 200 , h : 300 , qty : 6 } ,
4447 ] )
4548
49+ // Validate configuration
50+ const validation = validateConfiguration ( config , pieces )
51+ const hasErrors = validation . errors . length > 0
52+ const hasWarnings = validation . warnings . length > 0
53+
4654 // Calculate optimization when button is clicked
4755 const handleOptimize = ( ) => {
48- if ( pieces . length === 0 ) return
56+ if ( pieces . length === 0 || hasErrors ) return
4957 setIsCalculating ( true )
5058 setShowResults ( false )
5159
@@ -55,6 +63,9 @@ export default function CuttingOptimizer() {
5563 setIsCalculating ( false )
5664 setShowResults ( true )
5765
66+ // Save to history
67+ saveToHistory ( config , pieces , res )
68+
5869 // Scroll to visualization on mobile devices
5970 if ( window . innerWidth < 1024 ) {
6071 // lg breakpoint
@@ -82,6 +93,13 @@ export default function CuttingOptimizer() {
8293 setConfig ( ( prev ) => ( { ...prev , ...updates } ) )
8394 }
8495
96+ const handleRestore = ( restoredConfig : OptimizationConfig , restoredPieces : PieceSpec [ ] ) => {
97+ setConfig ( restoredConfig )
98+ setPieces ( restoredPieces )
99+ setShowResults ( false )
100+ setResult ( null )
101+ }
102+
85103 const resetExample = ( ) => {
86104 setConfig ( {
87105 boardWidth : 1500 ,
@@ -179,11 +197,42 @@ export default function CuttingOptimizer() {
179197 < PieceInput pieces = { pieces } onChange = { setPieces } />
180198 </ div >
181199
200+ { /* History panel */ }
201+ < HistoryPanel onRestore = { handleRestore } />
202+
182203 { /* Optimize button in sidebar */ }
183204 < div className = "rounded-xl border border-neutral-200 bg-white p-5" >
205+ { /* Validation messages */ }
206+ { ( hasErrors || hasWarnings ) && (
207+ < div className = "mb-3 space-y-2" >
208+ { validation . errors . map ( ( error , i ) => (
209+ < motion . div
210+ key = { `error-${ i } ` }
211+ initial = { { opacity : 0 , x : - 10 } }
212+ animate = { { opacity : 1 , x : 0 } }
213+ className = "flex items-start gap-2 rounded-lg bg-red-50 p-2 text-xs"
214+ >
215+ < span className = "text-red-600" > ⚠️</ span >
216+ < span className = "text-red-700" > { error } </ span >
217+ </ motion . div >
218+ ) ) }
219+ { validation . warnings . map ( ( warning , i ) => (
220+ < motion . div
221+ key = { `warning-${ i } ` }
222+ initial = { { opacity : 0 , x : - 10 } }
223+ animate = { { opacity : 1 , x : 0 } }
224+ className = "flex items-start gap-2 rounded-lg bg-amber-50 p-2 text-xs"
225+ >
226+ < span className = "text-amber-600" > ⚡</ span >
227+ < span className = "text-amber-700" > { warning } </ span >
228+ </ motion . div >
229+ ) ) }
230+ </ div >
231+ ) }
232+
184233 < button
185234 onClick = { handleOptimize }
186- disabled = { pieces . length === 0 || isCalculating }
235+ disabled = { pieces . length === 0 || isCalculating || hasErrors }
187236 className = "w-full transform rounded-lg bg-gradient-to-r from-amber-500 to-orange-500 px-4 py-3 font-medium text-white transition-all hover:scale-[1.02] hover:shadow-lg disabled:cursor-not-allowed disabled:opacity-50"
188237 >
189238 { isCalculating ? (
@@ -368,12 +417,31 @@ export default function CuttingOptimizer() {
368417 < p className = "mx-auto mb-8 max-w-md text-neutral-600" >
369418 { pieces . length === 0
370419 ? 'Ajoutez des pièces à découper dans le panneau de gauche pour commencer'
371- : "Cliquez sur le bouton pour lancer l'optimisation" }
420+ : hasErrors
421+ ? 'Corrigez les erreurs de configuration pour continuer'
422+ : "Cliquez sur le bouton pour lancer l'optimisation" }
372423 </ p >
373424
425+ { /* Show validation errors in center view */ }
426+ { hasErrors && (
427+ < div className = "mx-auto mb-6 max-w-md space-y-2" >
428+ { validation . errors . map ( ( error , i ) => (
429+ < motion . div
430+ key = { `center-error-${ i } ` }
431+ initial = { { opacity : 0 , y : - 10 } }
432+ animate = { { opacity : 1 , y : 0 } }
433+ className = "flex items-start gap-2 rounded-lg bg-red-50 p-3 text-sm"
434+ >
435+ < span className = "text-red-600" > ⚠️</ span >
436+ < span className = "text-red-700" > { error } </ span >
437+ </ motion . div >
438+ ) ) }
439+ </ div >
440+ ) }
441+
374442 < button
375443 onClick = { handleOptimize }
376- disabled = { pieces . length === 0 || isCalculating }
444+ disabled = { pieces . length === 0 || isCalculating || hasErrors }
377445 className = "transform rounded-xl bg-gradient-to-r from-amber-500 to-orange-500 px-8 py-4 text-lg font-medium text-white transition-all hover:scale-105 hover:shadow-2xl disabled:cursor-not-allowed disabled:opacity-50"
378446 >
379447 { isCalculating ? (
0 commit comments