source: components/ComponentDetailsDialog.tsx@ 8a7f936

main
Last change on this file since 8a7f936 was 8a7f936, checked in by Mihail <mihail2.naumov@…>, 6 months ago

Added Forge Page, added suggest component, fixed styling

  • Property mode set to 100644
File size: 8.2 KB
Line 
1import React, {useEffect, useState} from 'react';
2import {
3 Dialog, DialogTitle, DialogContent, DialogActions, Button,
4 Typography, Box, Grid, Chip, CircularProgress, IconButton,
5 Table, TableBody, TableCell, TableContainer, TableRow, Paper
6} from '@mui/material';
7import CloseIcon from '@mui/icons-material/Close';
8import {onGetComponentDetails} from '../pages/+Layout.telefunc';
9
10export default function ComponentDetailsDialog({open, component, onClose}: any) {
11 const [fullData, setFullData] = useState<any>(null);
12 const [loading, setLoading] = useState(false);
13
14 useEffect(() => {
15 if (open && component) {
16 setLoading(true);
17 onGetComponentDetails({componentId: component.id})
18 .then(data => setFullData(data))
19 .catch(console.error)
20 .finally(() => setLoading(false));
21 } else {
22 setFullData(null);
23 }
24 }, [open, component]);
25
26 if (!open || !component) return null;
27
28 const displayData = fullData || component;
29 const specs = fullData?.details || {};
30
31 const formatMoney = (amount: number) => `$${Number(amount).toFixed(2)}`;
32
33 const renderValue = (key: string, val: any) => {
34 if (Array.isArray(val)) {
35 if (val.length === 0) return 'None';
36
37 if (typeof val[0] === 'string') {
38 return val.join(', ');
39 }
40
41 if (typeof val[0] === 'object') {
42 const parts = val
43 .map((v: any) =>
44 v.socket ||
45 v.formFactor ||
46 v.name ||
47 v.type ||
48 Object.values(v)[0]
49 )
50 .filter(Boolean);
51 return parts.length > 0 ? parts.join(', ') : 'None';
52 }
53
54 return val.join(', ');
55 }
56
57 const strVal = String(val);
58 const lowerKey = key.toLowerCase();
59
60 if (lowerKey.includes('capacity') || lowerKey.includes('vram') || lowerKey === 'memory') {
61 return `${strVal} GB`;
62 }
63
64 if (lowerKey.includes('tdp') || lowerKey.includes('wattage')) {
65 return `${strVal} W`;
66 }
67
68 if (lowerKey.includes('length') || lowerKey.includes('height') || lowerKey.includes('width')) {
69 return `${strVal} mm`;
70 }
71
72 if (lowerKey.includes('clock')) {
73 return `${strVal} GHz`;
74 }
75
76 if (lowerKey.includes('speed')) {
77 return `${strVal} MHz`;
78 }
79
80 return strVal;
81 };
82
83 const formatKey = (key: string) => {
84 return key
85 .replace(/([A-Z])/g, ' $1')
86 .replace(/_/g, ' ')
87 .replace(/\b\w/g, c => c.toUpperCase());
88 };
89
90 return (
91 <Dialog
92 open={open}
93 onClose={onClose}
94 maxWidth="lg"
95 // fullWidth
96 sx={{zIndex: 1400}}
97 >
98 <DialogTitle sx={{
99 display: 'flex',
100 justifyContent: 'space-between',
101 alignItems: 'center',
102 bgcolor: '#333',
103 color: 'white'
104 }}>
105 <Box>
106 <Typography variant="caption" sx={{textTransform: 'uppercase', opacity: 0.7}}>
107 {displayData.brand}
108 </Typography>
109 <Typography variant="h6" fontWeight="bold">
110 {displayData.name}
111 </Typography>
112 </Box>
113 <IconButton onClick={onClose} sx={{color: 'white'}}>
114 <CloseIcon/>
115 </IconButton>
116 </DialogTitle>
117
118 <DialogContent dividers>
119 {loading ? (
120 <Box sx={{display: 'flex', justifyContent: 'center', p: 5}}>
121 <CircularProgress/>
122 </Box>
123 ) : (
124 <Grid container spacing={4}>
125 <Grid item xs={12} md={4} sx={{display: 'flex', flexDirection: 'column', alignItems: 'center'}}>
126 <Box
127 component="img"
128 // src={`https://placehold.co/400x400?text=${encodeURIComponent(displayData.name)}`}
129 src={displayData.imgUrl}
130 alt={displayData.name}
131 sx={{
132 width: '100%',
133 maxHeight: 300,
134 objectFit: 'contain',
135 mb: 2,
136 // border: '1px solid #eee',
137 // borderRadius: 2,
138 p: 2
139 }}
140 />
141 <Chip
142 label={displayData.type?.toUpperCase()}
143 color="primary"
144 sx={{fontWeight: 'bold'}}
145 />
146 </Grid>
147
148 <Grid item xs={12} md={8}>
149 <Typography variant="subtitle1" fontWeight="bold" gutterBottom
150 sx={{borderBottom: '2px solid #ff8201', display: 'inline-block', mb: 0.5}}>
151 Technical Specifications
152 </Typography>
153
154 <TableContainer component={Paper} variant="outlined">
155 <Table size="small">
156 <TableBody>
157 <TableRow>
158 <TableCell component="th" scope="row"
159 sx={{fontWeight: 'bold', width: '30%', bgcolor: '#1e1e1e'}}>
160 Brand
161 </TableCell>
162 <TableCell>{displayData.brand}</TableCell>
163 </TableRow>
164
165 {Object.entries(specs).map(([key, val]) => {
166 if (key === 'componentId' || key === 'id') return null;
167
168 return (
169 <TableRow key={key}>
170 <TableCell component="th" scope="row" sx={{
171 fontWeight: 'bold',
172 width: '30%',
173 bgcolor: '#1e1e1e'
174 }}>
175 {formatKey(key)}
176 </TableCell>
177 <TableCell>{renderValue(key, val)}</TableCell>
178 </TableRow>
179 );
180 })}
181 </TableBody>
182 </Table>
183 </TableContainer>
184 <Box sx={{display: 'flex', justifyContent: 'space-between', alignItems: 'center', mt: 0.5}}>
185 <Typography variant="h4" color="primary.main" fontWeight="bold">
186 {formatMoney(displayData.price)}
187 </Typography>
188 </Box>
189 </Grid>
190 </Grid>
191 )}
192 </DialogContent>
193
194 <DialogActions sx={{p: 2}}>
195 <Button onClick={onClose} variant="contained"
196 sx={{
197 backgroundColor: '#ff8201',
198 color: 'white',
199 borderColor: '#ff8201',
200 onHover: {backgroundColor: '#ba5d02', borderColor: '#ba5d02'}
201 }}
202 >Close</Button>
203 </DialogActions>
204 </Dialog>
205 );
206}
Note: See TracBrowser for help on using the repository browser.