source: components/ComponentDialog.tsx@ 107303c

main
Last change on this file since 107303c was 6ce6739, checked in by Mihail <mihail2.naumov@…>, 7 months ago

Added real images for components

  • Property mode set to 100644
File size: 11.2 KB
RevLine 
[6ce6739]1import React, {useEffect, useState} from 'react';
[a932c9e]2import {
3 Dialog, DialogContent, IconButton, Grid, Box, Typography,
4 Slider, FormControl, InputLabel, Select, MenuItem, Checkbox, ListItemText,
5 Card, CardContent, CardMedia, Button, CircularProgress, Divider, AppBar, Toolbar
6} from '@mui/material';
7import CloseIcon from '@mui/icons-material/Close';
8import FilterListIcon from '@mui/icons-material/FilterList';
9import SortIcon from '@mui/icons-material/Sort';
10
[6ce6739]11import {onGetAllComponents} from '../pages/+Layout.telefunc';
[a932c9e]12import ComponentDetailsDialog from "./ComponentDetailsDialog";
13
14const formatMoney = (amount: number) => `$${amount}`;
15
[6ce6739]16export default function ComponentBrowserDialog({open, category, onClose}: any) {
[a932c9e]17 const [components, setComponents] = useState<any[]>([]);
18 const [loading, setLoading] = useState(false);
19
20 const [selectedComponent, setSelectedComponent] = useState<any>(null);
21 const [priceRange, setPriceRange] = useState<number[]>([0, 2000]);
22 const [selectedBrands, setSelectedBrands] = useState<string[]>([]);
23 const [availableBrands, setAvailableBrands] = useState<string[]>([]);
24
25 const [sortOrder, setSortOrder] = useState<string>('default');
26
27 useEffect(() => {
28 if (open && category) {
29 setLoading(true);
30 setSortOrder('price_desc');
31
[6ce6739]32 onGetAllComponents({componentType: category})
[a932c9e]33 .then((data) => {
34 setComponents(data);
35
36 const brands = Array.from(new Set(data.map((c: any) => c.brand)));
37 setAvailableBrands(brands as string[]);
38
39 const maxPrice = data.length > 0 ? Math.max(...data.map((c: any) => Number(c.price))) : 2000;
40 setPriceRange([0, Math.ceil(maxPrice)]);
41 })
42 .catch(console.error)
43 .finally(() => setLoading(false));
44 }
45 }, [open, category]);
46
47 let processedComponents = components.filter(comp => {
48 const matchesBrand = selectedBrands.length === 0 || selectedBrands.includes(comp.brand);
49 const matchesPrice = Number(comp.price) >= priceRange[0] && Number(comp.price) <= priceRange[1];
50 return matchesBrand && matchesPrice;
51 });
52
53 if (sortOrder === 'price_asc') {
54 processedComponents.sort((a, b) => Number(a.price) - Number(b.price));
55 } else if (sortOrder === 'price_desc') {
56 processedComponents.sort((a, b) => Number(b.price) - Number(a.price));
57 }
58
59 const handleBrandChange = (event: any) => {
60 const value = event.target.value;
61 setSelectedBrands(typeof value === 'string' ? value.split(',') : value);
62 };
63
64 if (!open) return null;
65
66 return (
67 <Dialog
68 open={open}
69 onClose={onClose}
70 maxWidth="xl"
71 fullWidth
[6ce6739]72 sx={{height: '90vh'}}
[a932c9e]73 >
[6ce6739]74 <AppBar position="relative" sx={{bgcolor: '#ff8201'}}>
[a932c9e]75 <Toolbar>
[6ce6739]76 <Typography sx={{ml: 2, flex: 1, textTransform: 'capitalize'}} variant="h6" component="div">
[a932c9e]77 Browsing: <b>{category === 'gpu' ? 'Graphics Cards' : category?.toUpperCase()}</b>
78 </Typography>
79 <IconButton edge="start" color="inherit" onClick={onClose}>
[6ce6739]80 <CloseIcon/>
[a932c9e]81 </IconButton>
82 </Toolbar>
83 </AppBar>
84
[6ce6739]85 <DialogContent sx={{p: 0, display: 'flex', height: '100%'}}>
86 <Box sx={{
87 width: 300,
88 borderRight: '1px solid #ddd',
89 p: 3,
90 bgcolor: '#1e1e1e',
91 display: {xs: 'none', md: 'block'}
92 }}>
93 <Box sx={{display: 'flex', alignItems: 'center', mb: 2}}>
94 <SortIcon sx={{mr: 1}}/>
[a932c9e]95 <Typography variant="h6">Sort By</Typography>
96 </Box>
[6ce6739]97 <FormControl fullWidth size="small" sx={{mb: 4}}>
[a932c9e]98 <InputLabel>Price</InputLabel>
99 <Select
100 value={sortOrder}
101 label="Price"
102 onChange={(e) => setSortOrder(e.target.value)}>
103 {/*<MenuItem value="default">Featured</MenuItem>*/}
104 <MenuItem value="price_asc">Price: Low to High</MenuItem>
105 <MenuItem value="price_desc">Price: High to Low</MenuItem>
106 </Select>
107 </FormControl>
108
[6ce6739]109 <Divider sx={{mb: 3}}/>
[a932c9e]110
[6ce6739]111 <Box sx={{display: 'flex', alignItems: 'center', mb: 2}}>
112 <FilterListIcon sx={{mr: 1}}/>
[a932c9e]113 <Typography variant="h6">Filters</Typography>
114 </Box>
115
116 <Typography gutterBottom fontWeight="bold">Price Range</Typography>
117 <Slider
118 value={priceRange}
119 onChange={(_, newValue) => setPriceRange(newValue as number[])}
120 valueLabelDisplay="auto"
121 min={0}
122 max={components.length > 0 ? Math.max(...components.map(c => Number(c.price))) : 2000}
[6ce6739]123 sx={{color: '#ff8201', mb: 2}}
[a932c9e]124 />
[6ce6739]125 <Box sx={{display: 'flex', justifyContent: 'space-between', mb: 2}}>
[a932c9e]126 <Typography variant="caption">{formatMoney(priceRange[0])}</Typography>
127 <Typography variant="caption">{formatMoney(priceRange[1])}</Typography>
128 </Box>
129
130 <FormControl fullWidth size="small">
131 <InputLabel>Brands</InputLabel>
132 <Select
133 multiple
134 value={selectedBrands}
135 onChange={handleBrandChange}
136 renderValue={(selected) => selected.join(', ')}
137 label="Brands"
138 >
139 {availableBrands.map((brand) => (
140 <MenuItem key={brand} value={brand}>
[6ce6739]141 <Checkbox checked={selectedBrands.indexOf(brand) > -1}/>
142 <ListItemText primary={brand}/>
[a932c9e]143 </MenuItem>
144 ))}
145 </Select>
146 </FormControl>
147 </Box>
148
[6ce6739]149 <Box sx={{flex: 1, p: 3, overflowY: 'auto'}}>
[a932c9e]150 {loading ? (
[6ce6739]151 <Box sx={{display: 'flex', justifyContent: 'center', mt: 10}}>
152 <CircularProgress sx={{color: '#ff8201'}}/>
[a932c9e]153 </Box>
154 ) : (
[6ce6739]155 <Grid container spacing={1}>
[a932c9e]156 {processedComponents.map((comp) => (
157 <Grid item xs={12} sm={6} md={4} lg={3} key={comp.id}>
[6ce6739]158 <Card elevation={1} sx={{
159 height: '100%',
160 display: 'flex',
161 flexDirection: 'column',
162 maxWidth: 220,
163 width: 220,
164 overflow: 'hidden',
165 whiteSpace: 'nowrap',
166 textOverflow: 'ellipsis'
167 }}>
[a932c9e]168 <CardMedia
169 component="img"
170 height="140"
[6ce6739]171 // image={`https://placehold.co/400x400?text=${encodeURIComponent(comp.name)}`}
172 image={comp.imgUrl}
[a932c9e]173 alt={comp.name}
[6ce6739]174 sx={{p: 2, objectFit: 'contain'}}
[a932c9e]175 />
[6ce6739]176 <CardContent sx={{flexGrow: 1}}>
177 <Typography variant="caption" color="text.secondary"
178 sx={{textTransform: 'uppercase'}}>
[a932c9e]179 {comp.brand}
180 </Typography>
[6ce6739]181 <Typography variant="subtitle1" fontWeight="bold" sx={{
182 lineHeight: 1.2,
183 mb: 1,
184 }}>
[a932c9e]185 {comp.name}
186 </Typography>
187
[6ce6739]188 <Box sx={{
189 display: 'flex',
190 justifyContent: 'space-between',
191 alignItems: 'center',
192 mt: 2
193 }}>
[a932c9e]194 <Typography variant="h6" color="primary.main">
195 {formatMoney(comp.price)}
196 </Typography>
197 </Box>
198 </CardContent>
[6ce6739]199 <Box sx={{p: 2, pt: 0}}>
[a932c9e]200 <Button
201 fullWidth
202 variant="outlined"
[6ce6739]203 onClick={() => setSelectedComponent(comp)}
204 sx={{
205 backgroundColor: '#ff8201',
206 color: 'white',
207 borderColor: '#ff8201',
208 onHover: {backgroundColor: '#e67300', borderColor: '#e67300'}
209 }}
[a932c9e]210 >
211 Details
212 </Button>
213 </Box>
214 </Card>
215 </Grid>
216 ))}
217 {processedComponents.length === 0 && (
[6ce6739]218 <Box sx={{width: '100%', textAlign: 'center', mt: 5}}>
[a932c9e]219 <Typography variant="h6" color="text.secondary">No components match.</Typography>
220 </Box>
221 )}
222 </Grid>
223 )}
224 </Box>
225 </DialogContent>
226 <ComponentDetailsDialog
227 open={!!selectedComponent}
228 component={selectedComponent}
229 onClose={() => setSelectedComponent(null)}
230 />
231 </Dialog>
232 );
233}
Note: See TracBrowser for help on using the repository browser.