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

main
Last change on this file since fc8a8b1 was 1bf6e1f, checked in by Mihail <mihail2.naumov@…>, 7 months ago

Implemented part of the frontend (index, user, completedBuilds).

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