@@ -135,87 +135,108 @@ export default memo(function DesignPanel({ design, onChange, saveStatus }: Props
135135 </ Button >
136136 </ Stack >
137137
138- { ( design . openings || [ ] ) . map ( ( opening , index ) => (
139- < Box key = { `${ opening . type } -${ opening . wall } -${ index } ` } sx = { { mb : 2 , p : 1.5 , border : '1px solid' , borderColor : 'divider' , borderRadius : 1 } } >
140- < Stack direction = "row" justifyContent = "space-between" alignItems = "center" mb = { 1 } >
141- < Typography variant = "body2" fontWeight = "bold" >
142- { opening . type } #{ index + 1 }
143- </ Typography >
144- < IconButton size = "small" onClick = { ( ) => removeOpening ( index ) } aria-label = { `Remove ${ opening . type } #${ index + 1 } ` } >
145- < DeleteIcon fontSize = "small" />
146- </ IconButton >
147- </ Stack >
148-
149- < Stack direction = "row" spacing = { 1 } mb = { 1 } >
150- < FormControl size = "small" sx = { { minWidth : 80 } } >
151- < InputLabel > Type</ InputLabel >
152- < Select
153- value = { opening . type }
154- label = "Type"
155- onChange = { ( e ) => updateOpening ( index , { type : e . target . value as OpeningType } ) }
156- >
157- < MenuItem value = "Door" > Door</ MenuItem >
158- < MenuItem value = "Window" > Window</ MenuItem >
159- </ Select >
160- </ FormControl >
161- < FormControl size = "small" sx = { { minWidth : 80 } } >
162- < InputLabel > Wall</ InputLabel >
163- < Select
164- value = { opening . wall }
165- label = "Wall"
166- onChange = { ( e ) => updateOpening ( index , { wall : e . target . value as WallSide } ) }
167- >
168- < MenuItem value = "Front" > Front</ MenuItem >
169- < MenuItem value = "Back" > Back</ MenuItem >
170- < MenuItem value = "Left" > Left</ MenuItem >
171- < MenuItem value = "Right" > Right</ MenuItem >
172- </ Select >
173- </ FormControl >
174- </ Stack >
175-
176- < Stack direction = "row" spacing = { 1 } mb = { 1 } >
177- < TextField
178- label = "Width (in)"
179- type = "number"
180- size = "small"
181- value = { opening . widthInches }
182- onChange = { ( e ) => updateOpening ( index , { widthInches : Number ( e . target . value ) } ) }
183- inputProps = { { min : 12 , max : 120 } }
184- sx = { { flex : 1 } }
185- />
186- < TextField
187- label = "Height (in)"
188- type = "number"
189- size = "small"
190- value = { opening . heightInches }
191- onChange = { ( e ) => updateOpening ( index , { heightInches : Number ( e . target . value ) } ) }
192- inputProps = { { min : 12 , max : 120 } }
193- sx = { { flex : 1 } }
194- />
195- </ Stack >
196-
197- < Stack direction = "row" spacing = { 1 } >
198- < TextField
199- label = "Offset (in)"
200- type = "number"
201- size = "small"
202- value = { opening . offsetInches }
203- onChange = { ( e ) => updateOpening ( index , { offsetInches : Number ( e . target . value ) } ) }
204- inputProps = { { min : 0 } }
205- sx = { { flex : 1 } }
206- />
207- < TextField
208- label = "Sill (in)"
209- type = "number"
210- size = "small"
211- value = { opening . sillHeightInches }
212- onChange = { ( e ) => updateOpening ( index , { sillHeightInches : Number ( e . target . value ) } ) }
213- inputProps = { { min : 0 } }
214- sx = { { flex : 1 } }
215- />
216- </ Stack >
217- </ Box >
218- ) ) }
138+ { ( design . openings || [ ] ) . map ( ( opening , index ) => {
139+ const wallWidthIn = ( opening . wall === 'Front' || opening . wall === 'Back' )
140+ ? design . widthFeet * 12 + design . widthInches
141+ : design . depthFeet * 12 + design . depthInches ;
142+ const wallHeightIn = design . heightFeet * 12 + design . heightInches ;
143+ const tooWide = opening . offsetInches + opening . widthInches > wallWidthIn ;
144+ const tooTall = opening . sillHeightInches + opening . heightInches > wallHeightIn ;
145+
146+ return (
147+ < Box key = { `${ opening . type } -${ opening . wall } -${ index } ` } sx = { { mb : 2 , p : 1.5 , border : '1px solid' , borderColor : tooWide || tooTall ? 'error.main' : 'divider' , borderRadius : 1 } } >
148+ < Stack direction = "row" justifyContent = "space-between" alignItems = "center" mb = { 1 } >
149+ < Typography variant = "body2" fontWeight = "bold" >
150+ { opening . type } #{ index + 1 }
151+ </ Typography >
152+ < IconButton size = "small" onClick = { ( ) => removeOpening ( index ) } aria-label = { `Remove ${ opening . type } #${ index + 1 } ` } >
153+ < DeleteIcon fontSize = "small" />
154+ </ IconButton >
155+ </ Stack >
156+
157+ < Stack direction = "row" spacing = { 1 } mb = { 1 } >
158+ < FormControl size = "small" sx = { { minWidth : 80 } } >
159+ < InputLabel > Type</ InputLabel >
160+ < Select
161+ value = { opening . type }
162+ label = "Type"
163+ onChange = { ( e ) => updateOpening ( index , { type : e . target . value as OpeningType } ) }
164+ >
165+ < MenuItem value = "Door" > Door</ MenuItem >
166+ < MenuItem value = "Window" > Window</ MenuItem >
167+ </ Select >
168+ </ FormControl >
169+ < FormControl size = "small" sx = { { minWidth : 80 } } >
170+ < InputLabel > Wall</ InputLabel >
171+ < Select
172+ value = { opening . wall }
173+ label = "Wall"
174+ onChange = { ( e ) => updateOpening ( index , { wall : e . target . value as WallSide } ) }
175+ >
176+ < MenuItem value = "Front" > Front</ MenuItem >
177+ < MenuItem value = "Back" > Back</ MenuItem >
178+ < MenuItem value = "Left" > Left</ MenuItem >
179+ < MenuItem value = "Right" > Right</ MenuItem >
180+ </ Select >
181+ </ FormControl >
182+ </ Stack >
183+
184+ < Stack direction = "row" spacing = { 1 } mb = { 1 } >
185+ < TextField
186+ label = "Width (in)"
187+ type = "number"
188+ size = "small"
189+ value = { opening . widthInches }
190+ onChange = { ( e ) => updateOpening ( index , { widthInches : Number ( e . target . value ) } ) }
191+ inputProps = { { min : 12 , max : 120 } }
192+ error = { tooWide }
193+ helperText = { tooWide ? 'Exceeds wall' : undefined }
194+ sx = { { flex : 1 } }
195+ />
196+ < TextField
197+ label = "Height (in)"
198+ type = "number"
199+ size = "small"
200+ value = { opening . heightInches }
201+ onChange = { ( e ) => updateOpening ( index , { heightInches : Number ( e . target . value ) } ) }
202+ inputProps = { { min : 12 , max : 120 } }
203+ error = { tooTall }
204+ helperText = { tooTall ? 'Exceeds wall' : undefined }
205+ sx = { { flex : 1 } }
206+ />
207+ </ Stack >
208+
209+ < Stack direction = "row" spacing = { 1 } >
210+ < TextField
211+ label = "Offset (in)"
212+ type = "number"
213+ size = "small"
214+ value = { opening . offsetInches }
215+ onChange = { ( e ) => updateOpening ( index , { offsetInches : Number ( e . target . value ) } ) }
216+ inputProps = { { min : 0 } }
217+ error = { tooWide }
218+ sx = { { flex : 1 } }
219+ />
220+ < TextField
221+ label = "Sill (in)"
222+ type = "number"
223+ size = "small"
224+ value = { opening . sillHeightInches }
225+ onChange = { ( e ) => updateOpening ( index , { sillHeightInches : Number ( e . target . value ) } ) }
226+ inputProps = { { min : 0 } }
227+ error = { tooTall }
228+ sx = { { flex : 1 } }
229+ />
230+ </ Stack >
231+
232+ { ( tooWide || tooTall ) && (
233+ < Typography variant = "caption" color = "error" sx = { { mt : 0.5 , display : 'block' } } >
234+ Opening does not fit on the { opening . wall . toLowerCase ( ) } wall ({ wallWidthIn } " × { wallHeightIn } ")
235+ </ Typography >
236+ ) }
237+ </ Box >
238+ ) ;
239+ } ) }
219240 </ Box >
220241 ) ;
221242} ) ;
0 commit comments