source: pages/completed-builds/+Page.tsx@ e599341

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

Added frontend elements

  • Property mode set to 100644
File size: 8.6 KB
Line 
1import React, { useEffect, useState } from 'react';
2import {
3 Container, Box, Typography, TextField, MenuItem, Select,
4 Slider, Button, Paper, InputAdornment, CircularProgress
5} from '@mui/material';
6import SearchIcon from '@mui/icons-material/Search';
7import FilterListIcon from '@mui/icons-material/FilterList';
8
9import BuildCard from '../../components/BuildCard';
10import BuildDetailsDialog from '../../components/BuildDetailsDialog';
11import { onGetApprovedBuilds, onCloneBuild, onGetAuthState } from '../+Layout.telefunc';
12
13export default function CompletedBuildsPage() {
14 const [builds, setBuilds] = useState<any[]>([]);
15 const [loading, setLoading] = useState(true);
16 const [userId, setUserId] = useState<number | null>(null);
17 const [selectedBuildId, setSelectedBuildId] = useState<number | null>(null);
18
19 const [sortBy, setSortBy] = useState('price_asc');
20 const [priceRange, setPriceRange] = useState<number[]>([0, 5000]);
21 const [searchQuery, setSearchQuery] = useState("");
22
23 const loadBuilds = async () => {
24 setLoading(true);
25 try {
26 const [userData, data] = await Promise.all([
27 onGetAuthState(),
28 onGetApprovedBuilds({q: searchQuery})
29 ]);
30 setUserId(userData.userId);
31
32 let sortedData = [...data];
33
34 switch (sortBy) {
35 case 'price_asc':
36 sortedData.sort((a, b) => Number(a.total_price) - Number(b.total_price));
37 break;
38 case 'price_desc':
39 sortedData.sort((a, b) => Number(b.total_price) - Number(a.total_price));
40 break;
41 case 'rating_desc':
42 sortedData.sort((a, b) => (Number(b.avgRating) || 0) - (Number(a.avgRating) || 0));
43 break;
44 case 'oldest':
45 sortedData.sort((a, b) => new Date(a.created_at).getTime() - new Date(b.created_at).getTime());
46 break;
47 case 'newest':
48 default:
49 sortedData.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());
50 break;
51 }
52
53 sortedData = sortedData.filter(b => {
54 const price = Number(b.total_price);
55 return price >= priceRange[0] && price <= priceRange[1];
56 });
57
58 setBuilds(sortedData);
59 } catch (e) {
60 console.error("Failed to load builds", e);
61 } finally {
62 setLoading(false);
63 }
64 };
65
66 useEffect(() => {
67 loadBuilds();
68 }, [sortBy, searchQuery]);
69
70 const handleClone = async (buildId: number) => {
71 if (!userId) return alert("Please login to clone builds!");
72 if (confirm(`Clone this build?`)) {
73 await onCloneBuild({buildId});
74 alert("Build cloned!");
75 setSelectedBuildId(null);
76 }
77 };
78
79 return (
80 <Container maxWidth={false} sx={{ mt: 4, mb: 10, px: { xs: 2, md: 4 } }}>
81 <Typography variant="h4" fontWeight="bold" gutterBottom>Completed Builds</Typography>
82 <Typography color="text.secondary" sx={{ mb: 4 }}>
83 Browse community configurations. Filter by price, components, or popularity.
84 </Typography>
85
86 <Box sx={{ display: 'flex', flexDirection: { xs: 'column', md: 'row' }, gap: 4 }}>
87
88 <Box sx={{
89 width: { xs: '100%', md: '280px' },
90 flexShrink: 0
91 }}>
92 <Paper variant="outlined" sx={{ p: 3, position: 'sticky', top: 20 }}>
93 <Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
94 <FilterListIcon sx={{ mr: 1 }} />
95 <Typography variant="h6" fontWeight="bold">Filters</Typography>
96 </Box>
97
98 <TextField
99 fullWidth
100 size="small"
101 placeholder="Search builds..."
102 value={searchQuery}
103 onChange={(e) => setSearchQuery(e.target.value)}
104 InputProps={{
105 startAdornment: <InputAdornment position="start"><SearchIcon /></InputAdornment>,
106 }}
107 sx={{ mb: 3 }}
108 />
109
110 <Typography gutterBottom fontWeight="bold">Price Range</Typography>
111 <Box sx={{ px: 1, mb: 3 }}>
112 <Slider
113 value={priceRange}
114 onChange={(_, newValue) => setPriceRange(newValue as number[])}
115 valueLabelDisplay="auto"
116 min={0}
117 max={5000}
118 step={100}
119 />
120 <Box sx={{ display: 'flex', justifyContent: 'space-between', mt: 1 }}>
121 <Typography variant="caption">${priceRange[0]}</Typography>
122 <Typography variant="caption">${priceRange[1]}+</Typography>
123 </Box>
124 </Box>
125
126 <Button variant="contained" fullWidth onClick={loadBuilds}>
127 Apply Filters
128 </Button>
129 </Paper>
130 </Box>
131
132 <Box sx={{ flexGrow: 1 }}>
133 <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 3 }}>
134 <Typography fontWeight="bold">{builds.length} Builds Found</Typography>
135
136 <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
137 <Typography variant="body2" color="text.secondary">Sort by:</Typography>
138 <Select
139 size="small"
140 value={sortBy}
141 onChange={(e) => setSortBy(e.target.value)}
142 sx={{ minWidth: 150, bgcolor: 'background.paper' }}
143 >
144 <MenuItem value="newest">Newest First</MenuItem>
145 <MenuItem value="oldest">Oldest First</MenuItem>
146 <MenuItem value="price_desc">Price: High to Low</MenuItem>
147 <MenuItem value="price_asc">Price: Low to High</MenuItem>
148 <MenuItem value="rating_desc">Highest Rated</MenuItem>
149 </Select>
150 </Box>
151 </Box>
152
153 {loading ? (
154 <Box sx={{ p: 5, textAlign: 'center' }}>
155 <CircularProgress />
156 </Box>
157 ) : (
158 <Box
159 sx={{
160 display: 'grid',
161 gridTemplateColumns: {
162 xs: '1fr',
163 sm: 'repeat(2, 1fr)',
164 md: 'repeat(3, 1fr)',
165 lg: 'repeat(4, 1fr)',
166 xl: 'repeat(5, 1fr)'
167 },
168 gap: 3,
169 width: '100%'
170 }}
171 >
172 {builds.map((build) => (
173 <BuildCard
174 key={build.id}
175 build={build}
176 onClick={() => setSelectedBuildId(build.id)}
177 />
178 ))}
179 {builds.length === 0 && (
180 <Box sx={{ gridColumn: '1 / -1', p: 5, textAlign: 'center', bgcolor: '#f5f5f5', borderRadius: 2 }}>
181 <Typography>No builds found matching your filters.</Typography>
182 </Box>
183 )}
184 </Box>
185 )}
186 </Box>
187 </Box>
188
189 {/* @ts-ignore */}
190 <BuildDetailsDialog
191 open={!!selectedBuildId}
192 buildId={selectedBuildId}
193 currentUser={userId}
194 onClose={() => setSelectedBuildId(null)}
195 onClone={handleClone}
196 />
197 </Container>
198 );
199
200}
Note: See TracBrowser for help on using the repository browser.