source: components/BuildDetailsDialog.tsx@ 67186d2

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

Added frontend elements

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