source: components/AddComponentDialog.tsx@ 4f2900a

main
Last change on this file since 4f2900a was 87b79bc, checked in by Mihail <mihail2.naumov@…>, 6 months ago

Fixed suggests component and also added hamburger menu for small screens for the navbar.

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