source: components/BuildDetailsDialog.tsx@ 1bf6e1f

main
Last change on this file since 1bf6e1f 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: 13.1 KB
RevLine 
[1bf6e1f]1import React, { useEffect, useState } from 'react';
2import {
3 Dialog, DialogTitle, DialogContent, Button, Grid, Box, Typography,
4 IconButton, Tab, Tabs, Table, TableBody, TableCell, TableRow, Rating, TextField, Avatar, Divider, Chip, CardMedia
5} from '@mui/material';
6import CloseIcon from '@mui/icons-material/Close';
7import AutoFixHighIcon from '@mui/icons-material/AutoFixHigh';
8import FavoriteIcon from '@mui/icons-material/Favorite';
9import FavoriteBorderIcon from '@mui/icons-material/FavoriteBorder';
10import { onGetBuildDetails, onSetReview, onToggleFavorite } from '../pages/+Layout.telefunc';
11import PersonIcon from "@mui/icons-material/Person";
12
13const formatPrice = (price: any) => new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(Number(price) || 0);
14
15export default function BuildDetailsDialog({ open, buildId, onClose, onClone, currentUser }: any) {
16 const [details, setDetails] = useState<any>(null);
17 const [loading, setLoading] = useState(false);
18 const [tabIndex, setTabIndex] = useState(0);
19
20 const [reviewText, setReviewText] = useState("");
21 const [ratingVal, setRatingVal] = useState(5);
22
23 useEffect(() => {
24 if (open && buildId) {
25 setLoading(true);
26 onGetBuildDetails({ buildId })
27 .then(data => {
28 setDetails(data);
29 if (data.userRating) setRatingVal(data.userRating);
30 if (data.userReview) setReviewText(data.userReview);
31 })
32 .finally(() => setLoading(false));
33 }
34 }, [open, buildId]);
35
36 const handleFavorite = async () => {
37 if (!currentUser) return alert("Please login to favorite builds.");
38 const res = await onToggleFavorite({ buildId });
39 setDetails((prev: any) => ({ ...prev, isFavorite: res }));
40 };
41
42 const handleSubmitReview = async () => {
43 if (!currentUser) return alert("Please login to review.");
44 await onSetReview({ buildId, content: reviewText });
45
46 const refreshed = await onGetBuildDetails({ buildId });
47 setDetails(refreshed);
48 };
49
50 if (!open) return null;
51
52 // @ts-ignore
53 return (
54 <Dialog open={open} onClose={onClose} maxWidth="md" fullWidth scroll="paper">
55 {loading || !details ? (
56 <Box sx={{ p: 5, textAlign: 'center' }}>Loading Forge Schematics...</Box>
57 ) : (
58 <>
59 <DialogTitle sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', bgcolor: '#ff8201' }}>
60 <Box>
61 <Typography variant="h5" fontWeight="bold">{details.name}</Typography>
62 <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
63 <PersonIcon sx={{ fontSize: 16 }} />
64 <Typography variant="subtitle2" color="text.secondary" fontWeight="bold">
65 by {details.creator}
66 </Typography>
67 <Chip label={formatPrice(details.totalPrice)} size="small" color="primary" variant="outlined" />
68 </Box>
69 </Box>
70 <IconButton onClick={onClose}><CloseIcon /></IconButton>
71 </DialogTitle>
72
73 <DialogContent sx={{ p: 0 }}>
74 <Box sx={{ borderBottom: 1, borderColor: 'divider', px: 2, bgcolor: 'primary', position: 'sticky', top: 0, zIndex: 1 }}>
75 <Tabs value={tabIndex} onChange={(_, v) => setTabIndex(v)}>
76 <Tab label="Specs" />
77 <Tab label={`Reviews (${details.ratingStatistics.ratingCount})`} />
78 </Tabs>
79 </Box>
80
81 <Box sx={{ p: 3 }}>
82 {tabIndex === 0 && (
83 <Grid container spacing={4}>
84 <Grid item xs={12} md={8}>
85 <Table size="small">
86 <TableBody>
87 {details.components.map((comp: any) => (
88 <TableRow key={comp.id}>
89 <TableCell sx={{ width: 50 }}>
90 {/*<Avatar*/}
91 {/* src={comp.img_url || undefined}*/}
92 {/* variant="rounded"*/}
93 {/* sx={{ width: 40, height: 40, bgcolor: '#ff8201' }}*/}
94 {/*>*/}
95 {/* {comp.type?.[0]?.toUpperCase()}*/}
96 {/*</Avatar>*/}
97 {/*<PersonIcon sx={{ fontSize: 16 }} />*/}
98 <CardMedia
99 component="img"
100 height="50"
101 width="50"
102 image={"https://placehold.co/400x400?text=CPU"}
103
104 />
105 </TableCell>
106 <TableCell>
107 <Typography variant="body2" color="text.secondary" sx={{ fontSize: '0.75rem', textTransform: 'uppercase' }}>
108 {comp.type}
109 </Typography>
110 <Typography variant="body1" fontWeight="500">
111 {comp.brand} {comp.name}
112 </Typography>
113 </TableCell>
114 <TableCell align="right" sx={{ fontWeight: 'bold', color: '#ff8201' }}>
115 {formatPrice(comp.price)}
116 </TableCell>
117 </TableRow>
118 ))}
119 <TableRow sx={{ bgcolor: '#424343' }}>
120 <TableCell colSpan={2} sx={{ fontWeight: 'bold', color: '#ff8201' }}>TOTAL</TableCell>
121 <TableCell align="right" sx={{ fontWeight: 'bold', fontSize: '1.1rem', color: 'primary.main' }}>
122 {formatPrice(details.totalPrice)}
123 </TableCell>
124 </TableRow>
125 </TableBody>
126 </Table>
127 </Grid>
128
129 <Grid item xs={12} md={4}>
130 <Box sx={{ bgcolor: '#424343', p: 2, borderRadius: 2, mb: 2 }}>
131 <Typography color="primary.main" gutterBottom fontWeight="bold">Builder's Notes</Typography>
132 <Typography color="primary.main" variant="body2" sx={{ fontStyle: 'italic' }}>
133 "{details.description || "No notes provided."}"
134 </Typography>
135 </Box>
136
137 <Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
138 <Button
139 variant="contained"
140 color="primary"
141 size="large"
142 startIcon={<AutoFixHighIcon />}
143 onClick={() => onClone({ buildId: details.id })}
144 >
145 Clone & Edit
146 </Button>
147 <Button
148 variant={details.isFavorite ? "contained" : "outlined"}
149 color={details.isFavorite ? "error" : "primary"}
150 startIcon={details.isFavorite ? <FavoriteIcon /> : <FavoriteBorderIcon />}
151 onClick={handleFavorite}
152 >
153 {details.isFavorite ? "Favorited" : "Add to Favorites"}
154 </Button>
155 </Box>
156 </Grid>
157 </Grid>
158 )}
159
160 {tabIndex === 1 && (
161 <Box>
162 <Box sx={{ display: 'flex', alignItems: 'center', gap: 2, mb: 4, p: 2, bgcolor: '#5e5e5e', borderRadius: 2 }}>
163 <Typography variant="h3" fontWeight="bold">{details.ratingStatistics.averageRating.toFixed(1)}</Typography>
164 <Box>
165 <Rating value={details.ratingStatistics.averageRating} readOnly precision={0.5} />
166 <Typography variant="body2" color="text.secondary">{details.ratingStatistics.ratingCount} ratings</Typography>
167 </Box>
168 </Box>
169
170 {currentUser && (
171 <Box sx={{ mb: 4, p: 2, border: '1px solid #ddd', borderRadius: 2 }}>
172 <Typography variant="subtitle2" gutterBottom>Your Review</Typography>
173 <Box sx={{ display: 'flex', alignItems: 'center', mb: 1 }}>
174 <Rating value={ratingVal} onChange={(_, v) => setRatingVal(v || 5)} />
175 </Box>
176 <TextField
177 fullWidth
178 multiline
179 rows={2}
180 placeholder="Share your thoughts on this build..."
181 value={reviewText}
182 onChange={(e) => setReviewText(e.target.value)}
183 sx={{ mb: 1 }}
184 />
185 <Button size="small" variant="contained" onClick={handleSubmitReview}>
186 Submit Review
187 </Button>
188 </Box>
189 )}
190
191 <Box sx={{ display: 'flex', flexDirection: 'column', gap: 2}}>
192 {details.reviews.map((rev: any, i: number) => (
193 <Box key={i} sx={{ pb: 2, borderBottom: '1px solid #eee'}}>
194 <Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 0.5 }}>
195 <Typography fontWeight="bold" variant="body2">{rev.username}</Typography>
196 <Typography variant="caption" color="text.secondary">{rev.createdAt}</Typography>
197 </Box>
198 <Typography variant="body2">{rev.content}</Typography>
199 </Box>
200 ))}
201 {details.reviews.length === 0 && (
202 <Typography color="text.secondary" align="center">No reviews yet. Be the first!</Typography>
203 )}
204 </Box>
205 </Box>
206 )}
207 </Box>
208 </DialogContent>
209 </>
210 )}
211 </Dialog>
212 );
213}
Note: See TracBrowser for help on using the repository browser.