source: pages/dashboard/admin/+Page.tsx@ a932c9e

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

Added frontend elements 2

  • Property mode set to 100644
File size: 12.8 KB
Line 
1import React, { useEffect, useState } from 'react';
2import {
3 Container, Grid, Paper, Typography, Box, Avatar, Divider, Button,
4 CircularProgress, Table, TableBody, TableCell, TableHead, TableRow,
5 Chip, IconButton, TextField, Dialog, DialogTitle, DialogContent, DialogActions
6} from '@mui/material';
7import AdminPanelSettingsIcon from '@mui/icons-material/AdminPanelSettings';
8import CheckCircleIcon from '@mui/icons-material/CheckCircle';
9import CancelIcon from '@mui/icons-material/Cancel';
10import BuildIcon from '@mui/icons-material/Build';
11import MemoryIcon from '@mui/icons-material/Memory';
12
13import {
14 getAdminInfoAndData,
15 onSetBuildApprovalStatus,
16 onSetComponentSuggestionStatus
17} from './adminDashboard.telefunc';
18
19import BuildCard from '../../../components/BuildCard';
20import BuildDetailsDialog from '../../../components/BuildDetailsDialog';
21
22export default function AdminDashboard() {
23 const [data, setData] = useState<any>(null);
24 const [loading, setLoading] = useState(true);
25 const [selectedBuildId, setSelectedBuildId] = useState<number | null>(null);
26
27 const [suggestionDialog, setSuggestionDialog] = useState<{ open: boolean, id: number | null, action: 'approved' | 'rejected' }>({ open: false, id: null, action: 'approved' });
28 const [adminComment, setAdminComment] = useState("");
29
30 const loadData = () => {
31 getAdminInfoAndData()
32 .then(res => { setData(res); setLoading(false); })
33 .catch(err => {
34 console.error(err);
35 alert("Failed to load admin data. Are you an admin?");
36 });
37 };
38
39 useEffect(() => { loadData(); }, []);
40
41 const handleBuildReview = async (buildId: number, isApproved: boolean) => {
42 if (!confirm(`Are you sure you want to ${isApproved ? 'APPROVE' : 'REJECT'} this build?`)) return;
43 try {
44 await onSetBuildApprovalStatus({ buildId, isApproved });
45 loadData();
46 } catch (e) { alert("Action failed"); }
47 };
48
49 const openSuggestionReview = (id: number, action: 'approved' | 'rejected') => {
50 setSuggestionDialog({ open: true, id, action });
51 setAdminComment(action === 'approved' ? "Approved by admin." : "Rejected: ");
52 };
53
54 const submitSuggestionReview = async () => {
55 if (!suggestionDialog.id) return;
56 try {
57 await onSetComponentSuggestionStatus({
58 suggestionId: suggestionDialog.id,
59 status: suggestionDialog.action,
60 adminComment: adminComment
61 });
62 // console.error("Test za admincomment: ", adminComment);
63 setSuggestionDialog({ ...suggestionDialog, open: false });
64 loadData();
65 } catch (e) { alert("Failed to update suggestion"); }
66 };
67
68 if (loading) return <Box sx={{ p: 5, textAlign: 'center' }}><CircularProgress /></Box>;
69 if (!data) return <Box sx={{ p: 5, textAlign: 'center' }}>Access Denied</Box>;
70
71 return (
72 <Container maxWidth="xl" sx={{ mt: 4, mb: 10 }}>
73 <Grid container spacing={4}>
74 <Grid item xs={12} md={3}>
75 <Paper elevation={3} sx={{ p: 4, display: 'flex', flexDirection: 'column', alignItems: 'center', height: '100%', bgcolor: '#1e1e1e', color: 'white' }}>
76 <Avatar sx={{ width: 120, height: 120, mb: 2, bgcolor: 'error.main' }}>
77 <AdminPanelSettingsIcon sx={{ fontSize: 70 }} />
78 </Avatar>
79 <Typography variant="h5" fontWeight="bold">{data.admin.username}</Typography>
80 <Typography variant="body2" sx={{ opacity: 0.7, mb: 3 }}>{data.admin.email}</Typography>
81
82 <Chip label="ADMINISTRATOR" color="error" variant="outlined" sx={{ fontWeight: 'bold' }} />
83 <Divider sx={{ width: '100%', my: 3, bgcolor: 'rgba(255,255,255,0.1)' }} />
84
85 <Box sx={{ width: '100%', textAlign: 'center' }}>
86 <Typography variant="h3" fontWeight="bold" color="primary.main">
87 {data.pendingBuilds?.length || 0}
88 </Typography>
89 <Typography variant="caption">Pending Builds</Typography>
90 </Box>
91 </Paper>
92 </Grid>
93
94 <Grid item xs={12} md={9}>
95 <Grid container spacing={4} direction="column">
96 <Grid item xs={12}>
97 <Paper sx={{ p: 3, borderLeft: '6px solid #9c27b0' }}>
98 <Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
99 <MemoryIcon color="secondary" sx={{ mr: 1 }} />
100 <Typography variant="h6" fontWeight="bold">
101 Suggested Components ({data.componentSuggestions?.length || 0})
102 </Typography>
103 </Box>
104
105 {data.componentSuggestions?.length === 0 ? (
106 <Typography color="text.secondary">No pending suggestions.</Typography>
107 ) : (
108 <Table size="small">
109 <TableHead>
110 <TableRow>
111 <TableCell>Link/Description</TableCell>
112 <TableCell>Type</TableCell>
113 <TableCell>User</TableCell>
114 <TableCell align="right">Actions</TableCell>
115 </TableRow>
116 </TableHead>
117 <TableBody>
118 {data.componentSuggestions.map((sug: any) => (
119 <TableRow key={sug.id}>
120 <TableCell sx={{ maxWidth: 300 }}>
121 <a href={sug.link} title={sug.link} style={{textDecoration: 'none', color: 'inherit'}}>
122 {sug.description || sug.link}
123 </a>
124 </TableCell>
125 <TableCell>{sug.componentType}</TableCell>
126 <TableCell>{sug.userId}</TableCell>
127 <TableCell align="right">
128 <IconButton size="small" color="success" onClick={() => openSuggestionReview(sug.id, 'approved')}>
129 <CheckCircleIcon />
130 </IconButton>
131 <IconButton size="small" color="error" onClick={() => openSuggestionReview(sug.id, 'rejected')}>
132 <CancelIcon />
133 </IconButton>
134 </TableCell>
135 </TableRow>
136 ))}
137 </TableBody>
138 </Table>
139 )}
140 </Paper>
141 </Grid>
142
143 <Grid item xs={12}>
144 <Paper sx={{ p: 3, borderLeft: '6px solid #ed6c02', bgcolor: '#121212' }}>
145 <Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
146 <BuildIcon color="warning" sx={{ mr: 1 }} />
147 <Typography variant="h6" fontWeight="bold">
148 Builds Waiting for Approval ({data.pendingBuilds?.length || 0})
149 </Typography>
150 </Box>
151
152 {data.pendingBuilds?.length === 0 ? (
153 <Typography color="text.secondary">No pending builds.</Typography>
154 ) : (
155 <Grid container spacing={2}>
156 {data.pendingBuilds.map((build: any) => (
157 <Grid item xs={12} md={4} key={build.id}>
158 <Box sx={{ position: 'relative' }}>
159 <BuildCard
160 build={build}
161 onClick={() => setSelectedBuildId(build.id)}
162 />
163 <Box sx={{
164 mt: 1, display: 'flex', gap: 1,
165 justifyContent: 'center', bgcolor: '#121212', p: 1, borderRadius: 1
166 }}>
167 <Button variant="contained" color="success" size="small" onClick={() => handleBuildReview(build.id, true)}>
168 Approve
169 </Button>
170 <Button variant="contained" color="error" size="small" onClick={() => handleBuildReview(build.id, false)}>
171 Reject
172 </Button>
173 </Box>
174 </Box>
175 </Grid>
176 ))}
177 </Grid>
178 )}
179 </Paper>
180 </Grid>
181
182 <Grid item xs={12}>
183 <Paper sx={{ p: 3, borderLeft: '6px solid #2e7d32' }}>
184 <Typography variant="h6" fontWeight="bold" gutterBottom>
185 My Builds / Sandbox
186 </Typography>
187 {data.userBuilds?.length === 0 ? (
188 <Typography color="text.secondary">No builds created by admin.</Typography>
189 ) : (
190 <Grid container spacing={2}>
191 {data.userBuilds.slice(0, 4).map((build: any) => (
192 <Grid item xs={12} md={3} key={build.id}>
193 <BuildCard
194 build={build}
195 onClick={() => setSelectedBuildId(build.id)}
196 />
197 </Grid>
198 ))}
199 </Grid>
200 )}
201 </Paper>
202 </Grid>
203
204 </Grid>
205 </Grid>
206 </Grid>
207
208 <Dialog open={suggestionDialog.open} onClose={() => setSuggestionDialog({...suggestionDialog, open: false})}>
209 <DialogTitle>Review Suggestion</DialogTitle>
210 <DialogContent>
211 <Typography gutterBottom>Status: <b>{suggestionDialog.action.toUpperCase()}</b></Typography>
212 <TextField
213 fullWidth label="Admin Comment" multiline rows={3}
214 value={adminComment} onChange={(e) => setAdminComment(e.target.value)}
215 />
216 </DialogContent>
217 <DialogActions>
218 <Button onClick={() => setSuggestionDialog({...suggestionDialog, open: false})}>Cancel</Button>
219 <Button variant="contained" onClick={submitSuggestionReview}>Submit</Button>
220 </DialogActions>
221 </Dialog>
222
223 <BuildDetailsDialog
224 open={!!selectedBuildId}
225 buildId={selectedBuildId}
226 currentUser={data.admin?.id}
227 onClose={() => setSelectedBuildId(null)}
228 onClone={() => alert("Clone disabled in admin view")}
229 />
230 </Container>
231 );
232}
Note: See TracBrowser for help on using the repository browser.