source: components/AddComponentDialog.tsx@ 41a2f81

main
Last change on this file since 41a2f81 was 958bc89, checked in by Mihail <mihail2.naumov@…>, 6 months ago

Added add component button, fixed several things

  • Property mode set to 100644
File size: 14.7 KB
Line 
1import React, { useState, useEffect } from 'react';
2import {
3 Dialog, DialogTitle, DialogContent, DialogActions, Button, TextField,
4 Grid, Box, Typography, MenuItem, IconButton, Avatar, Alert
5} from '@mui/material';
6import AddIcon from '@mui/icons-material/Add';
7import DeleteIcon from '@mui/icons-material/Delete';
8import CloseIcon from '@mui/icons-material/Close';
9import { onCreateNewComponent, onGetDetailsForNewComponent } from '../pages/dashboard/admin/adminDashboard.telefunc';
10
11const COMPONENT_TYPES = [
12 { value: 'cpu', label: 'CPU' },
13 { value: 'gpu', label: 'Graphics Card' },
14 { value: 'motherboard', label: 'Motherboard' },
15 { value: 'memory', label: 'Memory' },
16 { value: 'storage', label: 'Storage' },
17 { value: 'power_supply', label: 'Power Supply' },
18 { value: 'case', label: 'Case' },
19 { value: 'cooler', label: 'CPU Cooler' },
20];
21
22export default function AddComponentDialog({ open, onClose, onSuccess }: {
23 open: boolean;
24 onClose: () => void;
25 onSuccess: () => void;
26}) {
27 const [name, setName] = useState('');
28 const [brand, setBrand] = useState('');
29 const [price, setPrice] = useState('');
30 const [imgUrl, setImgUrl] = useState('');
31 const [type, setType] = useState('');
32
33 const [requiredFields, setRequiredFields] = useState<any[]>([]);
34 const [multiTables, setMultiTables] = useState<any>({});
35 const [specificData, setSpecificData] = useState<any>({});
36
37 const [loading, setLoading] = useState(false);
38 const [error, setError] = useState('');
39
40 useEffect(() => {
41 if (type) {
42 setLoading(true);
43 onGetDetailsForNewComponent({ type })
44 .then((details) => {
45 console.log('Component details:', details);
46
47 setRequiredFields(details.requiredFields || []);
48 setMultiTables(details.multiTables || {});
49
50 const initialData: any = {};
51
52 (details.requiredFields || []).forEach((field: any) => {
53 const fieldName = typeof field === 'string' ? field : field.name;
54 initialData[fieldName] = '';
55 });
56
57 Object.keys(details.multiTables || {}).forEach((tableName) => {
58 initialData[tableName] = [];
59 });
60
61 setSpecificData(initialData);
62 })
63 .catch((err) => {
64 console.error('Field load error:', err);
65 setError('Failed to load component fields');
66 })
67 .finally(() => setLoading(false));
68 }
69 }, [type]);
70
71 const handleFieldChange = (fieldName: string, value: any) => {
72 setSpecificData((prev: any) => ({
73 ...prev,
74 [fieldName]: value
75 }));
76 };
77
78 const handleAddMultiTableRow = (tableName: string) => {
79 const tableConfig = multiTables[tableName];
80 const newRow: any = {};
81
82 tableConfig.forEach((field: string) => {
83 newRow[field] = '';
84 });
85
86 setSpecificData((prev: any) => ({
87 ...prev,
88 [tableName]: [...(prev[tableName] || []), newRow]
89 }));
90 };
91
92 const handleRemoveMultiTableRow = (tableName: string, index: number) => {
93 setSpecificData((prev: any) => ({
94 ...prev,
95 [tableName]: prev[tableName].filter((_: any, i: number) => i !== index)
96 }));
97 };
98
99 const handleMultiTableFieldChange = (tableName: string, index: number, fieldName: string, value: any) => {
100 setSpecificData((prev: any) => {
101 const updated = [...prev[tableName]];
102 updated[index] = { ...updated[index], [fieldName]: value };
103 return { ...prev, [tableName]: updated };
104 });
105 };
106
107 const handleSubmit = async () => {
108 setError('');
109
110 if (!name.trim()) return setError('Name is required');
111 if (!brand.trim()) return setError('Brand is required');
112 if (!price || isNaN(Number(price)) || Number(price) <= 0) return setError('Valid price is required');
113 if (!imgUrl.trim()) return setError('Image URL is required');
114 if (!type) return setError('Component type is required');
115
116 setLoading(true);
117 try {
118 await onCreateNewComponent({
119 name: name.trim(),
120 brand: brand.trim(),
121 price: Number(price),
122 imgUrl: imgUrl.trim(),
123 type,
124 specificData
125 });
126
127 onSuccess();
128 handleClose();
129 } catch (err) {
130 setError('Failed to create component. Please check all fields.');
131 } finally {
132 setLoading(false);
133 }
134 };
135
136 const handleClose = () => {
137 setName('');
138 setBrand('');
139 setPrice('');
140 setImgUrl('');
141 setType('');
142 setSpecificData({});
143 setRequiredFields([]);
144 setMultiTables({});
145 setError('');
146 onClose();
147 };
148
149 const formatFieldLabel = (fieldName: string): string => {
150 return fieldName
151 .replace(/([A-Z])/g, ' $1')
152 .replace(/^./, (str: string) => str.toUpperCase());
153 };
154
155 const renderAllFields = () => {
156 const allFields = [...(requiredFields || []), ...(Object.values(multiTables).flat() || [])];
157
158 return (
159 <Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
160 {allFields.map((field: any, index: number) => {
161 const fieldName = typeof field === 'string' ? field : field.name;
162
163 const isNumber = fieldName.includes('height') || fieldName.includes('length') ||
164 fieldName.includes('wattage') || fieldName.includes('capacity') ||
165 fieldName.includes('speed') || fieldName.includes('cores') ||
166 fieldName.includes('threads') || fieldName.includes('vram') ||
167 fieldName.includes('slots') || fieldName.includes('numSlots');
168
169 return (
170 <TextField
171 key={`${fieldName}-${index}`}
172 fullWidth
173 label={formatFieldLabel(fieldName)}
174 value={specificData[fieldName] || ''}
175 onChange={(e) => handleFieldChange(fieldName, isNumber ? Number(e.target.value) : e.target.value)}
176 type={isNumber ? 'number' : 'text'}
177 required
178 sx={{ mb: 2 }}
179 />
180 );
181 })}
182 </Box>
183 );
184 };
185
186 return (
187 <Dialog open={open} onClose={handleClose} maxWidth="md" fullWidth scroll="paper">
188 <DialogTitle sx={{ bgcolor: '#ff8201', color: 'white', display: 'flex', justifyContent: 'space-between', alignItems: 'column' }}>
189 <Typography variant="h6" fontWeight="bold">Add New Component</Typography>
190 <IconButton onClick={handleClose} sx={{ color: 'white' }}>
191 <CloseIcon />
192 </IconButton>
193 </DialogTitle>
194
195 <DialogContent sx={{ p: 3 }}>
196 {error && <Alert severity="error" sx={{ mb: 2 }}>{error}</Alert>}
197
198 <Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
199 <Box>
200 <Typography variant="subtitle1" fontWeight="bold" gutterBottom sx={{ mb: 2 }}>
201 Basic Information
202 </Typography>
203
204 <TextField
205 fullWidth
206 select
207 label="Component Type"
208 value={type}
209 onChange={(e) => setType(e.target.value)}
210 required
211 sx={{ mb: 2 }}
212 >
213 {COMPONENT_TYPES.map((option) => (
214 <MenuItem key={option.value} value={option.value}>
215 {option.label}
216 </MenuItem>
217 ))}
218 </TextField>
219
220 <Grid container spacing={2} sx={{ mb: 2 }}>
221 <Grid item xs={12} md={6}>
222 <TextField
223 fullWidth
224 label="Brand"
225 value={brand}
226 onChange={(e) => setBrand(e.target.value)}
227 required
228 />
229 </Grid>
230 <Grid item xs={12} md={6}>
231 <TextField
232 fullWidth
233 label="Name"
234 value={name}
235 onChange={(e) => setName(e.target.value)}
236 required
237 />
238 </Grid>
239 </Grid>
240
241 <Grid container spacing={2}>
242 <Grid item xs={12} md={6}>
243 <TextField
244 fullWidth
245 label="Price (EUR)"
246 type="number"
247 value={price}
248 onChange={(e) => setPrice(e.target.value)}
249 required
250 inputProps={{ step: '0.01', min: '0' }}
251 />
252 </Grid>
253 <Grid item xs={12} md={6}>
254 <TextField
255 fullWidth
256 label="Image URL"
257 value={imgUrl}
258 onChange={(e) => setImgUrl(e.target.value)}
259 required
260 />
261 </Grid>
262 </Grid>
263 </Box>
264
265 {imgUrl && (
266 <Box sx={{ display: 'flex', justifyContent: 'center', mt: 1 }}>
267 <Avatar
268 src={imgUrl}
269 variant="rounded"
270 sx={{ width: 120, height: 120, bgcolor: '#f5f5f5' }}
271 />
272 </Box>
273 )}
274
275 {type && (requiredFields.length > 0 || Object.keys(multiTables).length > 0) && (
276 <Box>
277 <Typography variant="subtitle1" fontWeight="bold" gutterBottom sx={{ mb: 2 }}>
278 {COMPONENT_TYPES.find(ct => ct.value === type)?.label} Specifications
279 </Typography>
280 {renderAllFields()}
281 </Box>
282 )}
283
284 {Object.keys(multiTables).map((tableName) => (
285 <Box key={tableName}>
286 <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}>
287 <Typography variant="subtitle1" fontWeight="bold">
288 {tableName.replace(/([A-Z])/g, ' $1').replace(/^./, str => str.toUpperCase())}
289 </Typography>
290 <Button
291 size="small"
292 startIcon={<AddIcon />}
293 onClick={() => handleAddMultiTableRow(tableName)}
294 variant="outlined"
295 >
296 Add Row
297 </Button>
298 </Box>
299
300 {(specificData[tableName] || []).map((row: any, index: number) => (
301 <Box key={index} sx={{ p: 3, mb: 2, border: '1px solid #e0e0e0', borderRadius: 2, bgcolor: '#fafafa' }}>
302 <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}>
303 <Typography variant="subtitle2" fontWeight="medium">
304 Row {index + 1}
305 </Typography>
306 <IconButton
307 size="small"
308 color="error"
309 onClick={() => handleRemoveMultiTableRow(tableName, index)}
310 >
311 <DeleteIcon />
312 </IconButton>
313 </Box>
314
315 <Grid container spacing={2}>
316 {multiTables[tableName].map((fieldName: string) => (
317 <Grid item xs={12} sm={6} md={4} key={fieldName}>
318 <TextField
319 fullWidth
320 size="small"
321 label={fieldName.replace(/([A-Z])/g, ' $1').replace(/^./, str => str.toUpperCase())}
322 value={row[fieldName] || ''}
323 onChange={(e) => handleMultiTableFieldChange(tableName, index, fieldName, e.target.value)}
324 />
325 </Grid>
326 ))}
327 </Grid>
328 </Box>
329 ))}
330 </Box>
331 ))}
332 </Box>
333 </DialogContent>
334
335 <DialogActions sx={{ p: 3 }}>
336 <Button onClick={handleClose} disabled={loading}>
337 Cancel
338 </Button>
339 <Button
340 variant="contained"
341 color="primary"
342 onClick={handleSubmit}
343 disabled={loading || !type}
344 >
345 {loading ? 'Creating...' : 'Create Component'}
346 </Button>
347 </DialogActions>
348 </Dialog>
349 );
350}
Note: See TracBrowser for help on using the repository browser.