Changeset 1b5c18b for components
- Timestamp:
- 01/28/26 21:15:38 (5 months ago)
- Branches:
- main
- Children:
- adc31ef
- Parents:
- 1d8f088
- File:
-
- 1 edited
-
components/ComponentDialog.tsx (modified) (11 diffs)
Legend:
- Unmodified
- Added
- Removed
-
components/ComponentDialog.tsx
r1d8f088 r1b5c18b 1 1 import React, {useEffect, useState} from 'react'; 2 2 import { 3 Dialog, DialogContent, IconButton, Box, Typography, Chip,3 Dialog, DialogContent, IconButton, Box, Typography, 4 4 Slider, FormControl, InputLabel, Select, MenuItem, Checkbox, ListItemText, 5 5 Card, CardContent, CardMedia, Button, CircularProgress, AppBar, Toolbar, InputAdornment, TextField, 6 TextField as MuiTextField, DialogTitle, DialogActions, Snackbar, Alert 6 TextField as MuiTextField, DialogTitle, DialogActions, Snackbar, Alert, Drawer, Badge 7 7 } from '@mui/material'; 8 8 import CloseIcon from '@mui/icons-material/Close'; 9 9 import AddCircleIcon from '@mui/icons-material/AddCircle'; 10 import FilterListIcon from '@mui/icons-material/FilterList'; 11 import SearchIcon from "@mui/icons-material/Search"; 10 12 11 13 import {onGetAllComponents, onGetAuthState, onSuggestComponent} from '../pages/+Layout.telefunc' 12 14 import {onGetCompatibleComponents} from '../pages/forge/forge.telefunc'; 13 15 import ComponentDetailsDialog from "./ComponentDetailsDialog"; 14 import SearchIcon from "@mui/icons-material/Search";15 16 16 17 const formatMoney = (amount: number) => `$${Number(amount).toFixed(2)}`; … … 38 39 const [snackbarMessage, setSnackbarMessage] = useState(""); 39 40 const [userId, setUserId] = useState<number | null>(null); 41 const [mobileFiltersOpen, setMobileFiltersOpen] = useState(false); 40 42 41 43 useEffect(() => { … … 108 110 } 109 111 110 111 112 112 const submitSuggestion = async () => { 113 113 if (!userId) { … … 165 165 }; 166 166 167 const activeFiltersCount = selectedBrands.length + (sortOrder !== 'default' ? 1 : 0); 168 169 const FilterContent = React.useMemo(() => ( 170 <Box sx={{width: {xs: 280, md: 300}, p: 3, bgcolor: 'transparent'}}> 171 <TextField 172 fullWidth 173 size="small" 174 placeholder="Search components..." 175 value={tempSearchQuery} 176 onChange={(e) => setTempSearchQuery(e.target.value)} 177 sx={{mb: 2}} 178 InputProps={{ 179 startAdornment: <InputAdornment position="start"><SearchIcon/></InputAdornment>, 180 }} 181 /> 182 183 <FormControl fullWidth size="small" sx={{mb: 2}}> 184 <InputLabel>Sort By</InputLabel> 185 <Select 186 value={sortOrder} 187 label="Sort By" 188 onChange={(e) => setSortOrder(e.target.value)} 189 MenuProps={{ 190 container: typeof window !== 'undefined' ? document.body : undefined, 191 disablePortal: false, 192 sx: { 193 zIndex: 1500 194 } 195 }} 196 > 197 <MenuItem value="price_asc">Price: Low to High</MenuItem> 198 <MenuItem value="price_desc">Price: High to Low</MenuItem> 199 </Select> 200 </FormControl> 201 202 <FormControl fullWidth size="small" sx={{mb: 2}}> 203 <InputLabel>Brands</InputLabel> 204 <Select 205 multiple 206 value={selectedBrands} 207 onChange={handleBrandChange} 208 label="Brands" 209 renderValue={(s) => s.join(', ')} 210 MenuProps={{ 211 container: typeof window !== 'undefined' ? document.body : undefined, 212 disablePortal: false, 213 sx: { 214 zIndex: 1500 215 }, 216 PaperProps: { 217 sx: { 218 maxHeight: 300 219 } 220 } 221 }} 222 > 223 {availableBrands.map((brand) => ( 224 <MenuItem key={brand} value={brand}> 225 <Checkbox checked={selectedBrands.indexOf(brand) > -1}/> 226 <ListItemText primary={brand}/> 227 </MenuItem> 228 ))} 229 </Select> 230 </FormControl> 231 232 <Typography gutterBottom fontWeight="bold">Price Range</Typography> 233 <Slider value={priceRange} onChange={(_, v) => setPriceRange(v as number[])} min={0} max={2000} 234 sx={{color: '#ff8201'}}/> 235 <Box sx={{display: 'flex', justifyContent: 'space-between', mt: 1}}> 236 <Typography variant="caption">${priceRange[0]}</Typography> 237 <Typography variant="caption">${priceRange[1]}+</Typography> 238 </Box> 239 240 <Button 241 fullWidth 242 variant="contained" 243 startIcon={<AddCircleIcon/>} 244 onClick={() => setSuggestOpen(true)} 245 sx={{ 246 mt: 2, 247 bgcolor: '#ff8201', 248 fontWeight: 'bold', 249 fontSize: '0.875rem', 250 '&:hover': {bgcolor: '#e67300'} 251 }} 252 > 253 Suggest Component 254 </Button> 255 </Box> 256 ), [tempSearchQuery, sortOrder, selectedBrands, availableBrands, priceRange]); 257 167 258 if (!open) return null; 168 259 169 260 return ( 170 261 <> 171 <Dialog open={open} onClose={onClose} maxWidth="xl" fullWidth sx={{height: '90vh'}}> 262 <Dialog 263 open={open} 264 onClose={onClose} 265 maxWidth="xl" 266 fullWidth 267 fullScreen 268 sx={{ 269 '& .MuiDialog-paper': { 270 height: {xs: '100%', md: '90vh'}, 271 m: {xs: 0, md: 2} 272 } 273 }} 274 > 172 275 <AppBar position="relative" sx={{bgcolor: '#ff8201'}}> 173 276 <Toolbar> 174 <Typography sx={{ml: 2, flex: 1, textTransform: 'capitalize'}} variant="h6" component="div"> 175 Browsing: <b>{category === 'gpu' ? 'Graphics Cards' : category?.toUpperCase()}</b> 277 <IconButton 278 edge="start" 279 color="inherit" 280 onClick={() => setMobileFiltersOpen(true)} 281 sx={{ 282 display: {xs: 'flex', md: 'none'}, 283 mr: 2 284 }} 285 > 286 <Badge badgeContent={activeFiltersCount} color="error"> 287 <FilterListIcon/> 288 </Badge> 289 </IconButton> 290 291 <Typography 292 sx={{ml: {xs: 0, md: 2}, flex: 1, fontSize: {xs: '1rem', sm: '1.25rem'}}} 293 variant="h6" 294 component="div" 295 > 296 <Box component="span" sx={{display: {xs: 'none', sm: 'inline'}}}> 297 Browsing: 298 </Box> 299 <b> {category === 'gpu' ? 'Graphics Cards' : category?.toUpperCase()}</b> 176 300 {mode === 'forge' && currentBuildId && ( 177 <Typography variant="caption" 178 sx={{ml: 2, bgcolor: 'rgba(0,0,0,0.2)', px: 1, borderRadius: 1}}> 179 COMPATIBILITY MODE ON 301 <Typography 302 variant="caption" 303 sx={{ 304 ml: {xs: 1, sm: 2}, 305 bgcolor: 'rgba(0,0,0,0.2)', 306 px: 1, 307 py: 0.5, 308 borderRadius: 1, 309 display: {xs: 'none', sm: 'inline-block'} 310 }} 311 > 312 COMPATIBILITY MODE 180 313 </Typography> 181 314 )} 182 315 </Typography> 183 <IconButton edge=" start" color="inherit" onClick={onClose}>316 <IconButton edge="end" color="inherit" onClick={onClose}> 184 317 <CloseIcon/> 185 318 </IconButton> … … 187 320 </AppBar> 188 321 189 <DialogContent sx={{p: 0, display: 'flex', height: '100%' }}>322 <DialogContent sx={{p: 0, display: 'flex', height: '100%', overflow: 'hidden'}}> 190 323 <Box sx={{ 191 width: 300,324 display: {xs: 'none', md: 'block'}, 192 325 borderRight: '1px solid #ddd', 193 p: 3, 194 bgcolor: '#1e1e1e', 195 display: {xs: 'none', md: 'block'}, 196 overflowY: 'auto' 326 overflowY: 'auto', 327 bgcolor: '#1e1e1e' 197 328 }}> 198 <TextField 199 fullWidth 200 size="small" 201 placeholder="Search components..." 202 value={tempSearchQuery} 203 onChange={(e) => setTempSearchQuery(e.target.value)} 204 sx={{mb: 2}} 205 InputProps={{ 206 startAdornment: <InputAdornment position="start"><SearchIcon/></InputAdornment>, 207 }} 208 /> 209 210 211 <FormControl fullWidth size="small" sx={{mb: 2}}> 212 <InputLabel>Sort By</InputLabel> 213 <Select value={sortOrder} label="Sort By" onChange={(e) => setSortOrder(e.target.value)}> 214 <MenuItem value="price_asc">Price: Low to High</MenuItem> 215 <MenuItem value="price_desc">Price: High to Low</MenuItem> 216 </Select> 217 </FormControl> 218 219 <FormControl fullWidth size="small" sx={{mb: 2}}> 220 <InputLabel>Brands</InputLabel> 221 <Select multiple value={selectedBrands} onChange={handleBrandChange} label="Brands" 222 renderValue={(s) => s.join(', ')}> 223 {availableBrands.map((brand) => ( 224 <MenuItem key={brand} value={brand}> 225 <Checkbox checked={selectedBrands.indexOf(brand) > -1}/> 226 <ListItemText primary={brand}/> 227 </MenuItem> 228 ))} 229 </Select> 230 </FormControl> 231 232 <Typography gutterBottom fontWeight="bold">Price Range</Typography> 233 <Slider value={priceRange} onChange={(_, v) => setPriceRange(v as number[])} min={0} max={2000} 234 sx={{color: '#ff8201'}}/> 235 <Box sx={{display: 'flex', justifyContent: 'space-between', mt: 1}}> 236 <Typography variant="caption">${priceRange[0]}</Typography> 237 <Typography variant="caption">${priceRange[1]}+</Typography> 238 </Box> 239 {/*TO BE FINISHED*/} 240 <Button 241 fullWidth 242 variant="contained" 243 startIcon={<AddCircleIcon/>} 244 onClick={() => setSuggestOpen(true)} 245 sx={{ 246 mt: 2, 247 bgcolor: '#ff8201', 248 fontWeight: 'bold', 249 fontSize: '0.875rem', 250 '&:hover': {bgcolor: '#e67300'} 251 }} 252 > 253 Suggest Component 254 </Button> 329 {FilterContent} 255 330 </Box> 256 257 <Box sx={{flex: 1, p: 3, overflowY: 'auto', bgcolor: '#121212'}}> 331 <Box sx={{flex: 1, p: {xs: 1.5, sm: 2, md: 3}, overflowY: 'auto', bgcolor: '#121212'}}> 258 332 {loading ? ( 259 333 <Box sx={{display: 'flex', justifyContent: 'center', mt: 10}}> … … 266 340 xs: '1fr', 267 341 sm: 'repeat(2, 1fr)', 268 md: 'repeat( 3, 1fr)',269 lg: 'repeat( 4, 1fr)',270 xl: 'repeat( 5, 1fr)'342 md: 'repeat(2, 1fr)', 343 lg: 'repeat(3, 1fr)', 344 xl: 'repeat(4, 1fr)' 271 345 }, 272 gap: 3,346 gap: {xs: 1.5, sm: 2, md: 3}, 273 347 width: '100%' 274 348 }}> … … 301 375 height: '100%', 302 376 objectFit: 'contain', 303 p: 2377 p: {xs: 1, sm: 2} 304 378 }} 305 379 /> … … 310 384 display: 'flex', 311 385 flexDirection: 'column', 312 justifyContent: 'space-between' 386 justifyContent: 'space-between', 387 p: {xs: 1.5, sm: 2} 313 388 }}> 314 389 <Box> 315 <Typography variant="caption" color="text.secondary" 316 sx={{textTransform: 'uppercase', letterSpacing: 0.5}}> 390 <Typography 391 variant="caption" 392 color="text.secondary" 393 sx={{ 394 textTransform: 'uppercase', 395 letterSpacing: 0.5, 396 fontSize: {xs: '0.65rem', sm: '0.75rem'} 397 }} 398 > 317 399 {comp.brand} 318 400 </Typography> 319 <Typography variant="subtitle1" fontWeight="bold" sx={{ 320 lineHeight: 1.2, 321 mt: 0.5, 322 mb: 1, 323 overflow: 'hidden', 324 textOverflow: 'ellipsis', 325 display: '-webkit-box', 326 WebkitLineClamp: 2, 327 WebkitBoxOrient: 'vertical', 328 minHeight: '2.4em' 329 }}> 401 <Typography 402 variant="subtitle1" 403 fontWeight="bold" 404 sx={{ 405 lineHeight: 1.2, 406 mt: 0.5, 407 mb: 1, 408 overflow: 'hidden', 409 textOverflow: 'ellipsis', 410 display: '-webkit-box', 411 WebkitLineClamp: 2, 412 WebkitBoxOrient: 'vertical', 413 minHeight: '2.4em', 414 fontSize: {xs: '0.875rem', sm: '1rem'} 415 }} 416 > 330 417 {comp.name} 331 418 </Typography> 332 419 </Box> 333 <Typography variant="h6" color="primary.main" fontWeight="bold"> 420 <Typography 421 variant="h6" 422 color="primary.main" 423 fontWeight="bold" 424 sx={{fontSize: {xs: '1rem', sm: '1.25rem'}}} 425 > 334 426 {formatMoney(comp.price)} 335 427 </Typography> 336 428 </CardContent> 337 429 338 <Box sx={{p: 2, pt: 0, display: 'flex', flexDirection: 'column', gap: 1}}> 430 <Box sx={{ 431 p: {xs: 1.5, sm: 2}, 432 pt: 0, 433 display: 'flex', 434 flexDirection: 'column', 435 gap: 1 436 }}> 339 437 <Button 340 438 fullWidth 341 439 variant="contained" 342 440 onClick={() => setSelectedComponent(comp)} 441 size="small" 343 442 sx={{ 344 443 bgcolor: '#ff8201', 345 444 fontWeight: 'bold', 445 fontSize: {xs: '0.75rem', sm: '0.875rem'}, 346 446 '&:hover': {bgcolor: '#e67300'} 347 447 }} … … 354 454 variant="contained" 355 455 onClick={() => onSelect(comp)} 456 size="small" 356 457 sx={{ 357 458 bgcolor: '#4caf50', 358 459 fontWeight: 'bold', 460 fontSize: {xs: '0.75rem', sm: '0.875rem'}, 359 461 '&:hover': {bgcolor: '#388e3c'} 360 462 }} … … 369 471 {processedComponents.length === 0 && ( 370 472 <Box sx={{gridColumn: '1 / -1', width: '100%', textAlign: 'center', mt: 5}}> 371 <Typography variant="h6" color="text.secondary"> 473 <Typography variant="h6" color="text.secondary" 474 sx={{fontSize: {xs: '1rem', sm: '1.25rem'}}}> 372 475 No compatible components found. 373 476 </Typography> … … 390 493 /> 391 494 </Dialog> 495 496 <Drawer 497 anchor="left" 498 open={mobileFiltersOpen && open} 499 onClose={() => setMobileFiltersOpen(false)} 500 sx={{zIndex: 1400}} 501 PaperProps={{ 502 sx: { 503 bgcolor: '#1e1e1e' 504 } 505 }} 506 disableScrollLock={true} 507 > 508 <Box sx={{pt: 2, width: 280}}> 509 <Box sx={{ 510 display: 'flex', 511 justifyContent: 'space-between', 512 alignItems: 'center', 513 px: 3, 514 pb: 2 515 }}> 516 <Typography variant="h6" fontWeight="bold">Filters</Typography> 517 <IconButton onClick={() => setMobileFiltersOpen(false)}> 518 <CloseIcon/> 519 </IconButton> 520 </Box> 521 {FilterContent} 522 </Box> 523 </Drawer> 392 524 393 525 <Dialog open={suggestOpen} onClose={() => setSuggestOpen(false)} maxWidth="sm" fullWidth>
Note:
See TracChangeset
for help on using the changeset viewer.
