Changeset 958bc89 for pages/dashboard
- Timestamp:
- 12/29/25 04:34:44 (6 months ago)
- Branches:
- main
- Children:
- 41a2f81
- Parents:
- f46bf5c
- Location:
- pages/dashboard/admin
- Files:
-
- 2 edited
-
+Page.tsx (modified) (22 diffs)
-
adminDashboard.telefunc.ts (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
pages/dashboard/admin/+Page.tsx
rf46bf5c r958bc89 10 10 import BuildIcon from '@mui/icons-material/Build'; 11 11 import MemoryIcon from '@mui/icons-material/Memory'; 12 import AddIcon from '@mui/icons-material/Add'; 13 import DeleteIcon from '@mui/icons-material/Delete'; 12 14 13 15 import { … … 19 21 import BuildCard from '../../../components/BuildCard'; 20 22 import BuildDetailsDialog from '../../../components/BuildDetailsDialog'; 21 import DeleteIcon from "@mui/icons-material/Delete";22 23 import {onDeleteBuild} from "../user/userDashboard.telefunc"; 24 import AddComponentDialog from "../../../components/AddComponentDialog"; 23 25 24 26 export default function AdminDashboard() { … … 26 28 const [loading, setLoading] = useState(true); 27 29 const [selectedBuildId, setSelectedBuildId] = useState<number | null>(null); 28 const [deleteDialog, setDeleteDialog] = useState<{ open: boolean, buildId: number | null, buildName: string }>({ open: false, buildId: null, buildName: '' }); 30 const [deleteDialog, setDeleteDialog] = useState<{ 31 open: boolean, 32 buildId: number | null, 33 buildName: string 34 }>({open: false, buildId: null, buildName: ''}); 29 35 const [deleteLoading, setDeleteLoading] = useState(false); 36 const [addDialogOpen, setAddDialogOpen] = useState(false); 30 37 31 38 const [buildApprovalDialog, setBuildApprovalDialog] = useState<{ … … 45 52 46 53 const loadData = () => { 54 setLoading(true); 47 55 getAdminInfoAndData() 48 56 .then(res => { … … 53 61 console.error(err); 54 62 alert("Failed to load admin data. Are you an admin?"); 63 setLoading(false); 55 64 }); 56 65 }; … … 114 123 115 124 const openDeleteDialog = (buildId: number, buildName: string) => { 116 setDeleteDialog({ open: true, buildId, buildName});125 setDeleteDialog({open: true, buildId, buildName}); 117 126 }; 118 127 … … 122 131 try { 123 132 await onDeleteBuild({buildId: deleteDialog.buildId}); 124 setDeleteDialog({ open: false, buildId: null, buildName: ''});125 loadData(); // refresh na user i favorite builds133 setDeleteDialog({open: false, buildId: null, buildName: ''}); 134 loadData(); 126 135 setSelectedBuildId(null); 127 136 } catch (e) { … … 132 141 }; 133 142 134 if (loading) return <Box sx={{p: 5, textAlign: 'center'}}><CircularProgress/></Box>; 135 if (!data) return <Box sx={{p: 5, textAlign: 'center'}}>Access Denied</Box>; 143 if (loading) { 144 return ( 145 <Container maxWidth="xl" sx={{mt: 4, mb: 10, display: 'flex', justifyContent: 'center', alignItems: 'center', minHeight: '50vh'}}> 146 <CircularProgress /> 147 </Container> 148 ); 149 } 150 151 if (!data) { 152 return ( 153 <Container maxWidth="xl" sx={{mt: 4, mb: 10, textAlign: 'center', p: 5}}> 154 <Typography variant="h6" color="error"> 155 Access Denied - Admin privileges required 156 </Typography> 157 </Container> 158 ); 159 } 136 160 137 161 return ( … … 151 175 <AdminPanelSettingsIcon sx={{fontSize: 70}}/> 152 176 </Avatar> 153 <Typography variant="h5" fontWeight="bold">{data.admin.username}</Typography> 154 <Typography variant="body2" sx={{opacity: 0.7, mb: 3}}>{data.admin.email}</Typography> 177 <Typography variant="h5" fontWeight="bold">{data.admin?.username || 'Admin'}</Typography> 178 <Typography variant="body2" sx={{opacity: 0.7, mb: 3}}> 179 {data.admin?.email || 'admin@example.com'} 180 </Typography> 155 181 156 182 <Chip label="ADMINISTRATOR" color="error" variant="outlined" sx={{fontWeight: 'bold'}}/> … … 162 188 </Typography> 163 189 <Typography variant="caption">Pending Builds</Typography> 190 </Box> 191 192 <Box sx={{width: '100%', textAlign: 'center', mt: 3}}> 193 <Button 194 variant="contained" 195 color="warning" 196 size="large" 197 fullWidth 198 startIcon={<BuildIcon />} 199 onClick={() => setAddDialogOpen(true)} 200 sx={{ mb: 2 }} 201 > 202 Add Component 203 </Button> 164 204 </Box> 165 205 </Paper> … … 190 230 </TableHead> 191 231 <TableBody> 192 {data.componentSuggestions .map((sug: any) => (232 {data.componentSuggestions?.map((sug: any) => ( 193 233 <TableRow key={sug.id}> 194 234 <TableCell sx={{maxWidth: 300}}> 195 <a href={sug.link} title={sug.link} 196 style={{textDecoration: 'none', color: 'inherit'}}> 235 <a 236 href={sug.link} 237 target="_blank" 238 rel="noopener noreferrer" 239 title={sug.link} 240 style={{textDecoration: 'none', color: 'inherit'}} 241 > 197 242 {sug.description || sug.link} 198 243 </a> 199 244 </TableCell> 200 <TableCell>{sug.componentType .toUpperCase()}</TableCell>245 <TableCell>{sug.componentType?.toUpperCase()}</TableCell> 201 246 <TableCell>{sug.userId}</TableCell> 202 247 <TableCell align="right"> 203 <IconButton size="small" color="success" 204 onClick={() => openSuggestionReview(sug.id, 'approved')}> 248 <IconButton 249 size="small" 250 color="success" 251 onClick={() => openSuggestionReview(sug.id, 'approved')} 252 > 205 253 <CheckCircleIcon/> 206 254 </IconButton> 207 <IconButton size="small" color="error" 208 onClick={() => openSuggestionReview(sug.id, 'rejected')}> 255 <IconButton 256 size="small" 257 color="error" 258 onClick={() => openSuggestionReview(sug.id, 'rejected')} 259 > 209 260 <CancelIcon/> 210 261 </IconButton> … … 232 283 <Grid container spacing={2}> 233 284 {data.pendingBuilds.map((build: any) => ( 234 <Grid item xs={12} md={4} key={build.id}>285 <Grid item xs={12} sm={6} md={4} lg={3} key={build.id}> 235 286 <Box sx={{position: 'relative'}}> 236 287 <BuildCard … … 242 293 display: 'flex', 243 294 gap: 1, 244 justifyContent: 'center', 245 bgcolor: '#292929', 246 p: 1, 247 borderRadius: 1 295 justifyContent: 'center' 248 296 }}> 249 297 <Button … … 251 299 color="warning" 252 300 size="small" 253 startIcon={<BuildIcon/>} 254 onClick={() => openBuildApproval(build.id, build.name)} 301 startIcon={<BuildIcon />} 302 onClick={(e) => { 303 e.stopPropagation(); 304 openBuildApproval(build.id, build.name); 305 }} 255 306 > 256 307 Review … … 275 326 <Grid container spacing={2}> 276 327 {data.userBuilds.slice(0, 4).map((build: any) => ( 277 <Grid item xs={12} sm={6} md={4} key={build.id}>278 <Box sx={{ position: 'relative'}}>328 <Grid item xs={12} sm={6} md={4} lg={3} key={build.id}> 329 <Box sx={{position: 'relative'}}> 279 330 <BuildCard 280 331 build={build} 281 332 onClick={() => setSelectedBuildId(build.id)} 282 333 /> 283 <Box sx={{ mt: 1, display: 'flex', justifyContent: 'center'}}>334 <Box sx={{mt: 1, display: 'flex', justifyContent: 'center'}}> 284 335 <Button 285 336 variant="outlined" … … 291 342 openDeleteDialog(build.id, build.name); 292 343 }} 293 sx={{ width: '100%' }}344 fullWidth 294 345 > 295 346 Delete … … 307 358 </Grid> 308 359 309 <Dialog open={suggestionDialog.open} 310 onClose={() => setSuggestionDialog({...suggestionDialog, open: false})}> 360 <Dialog open={suggestionDialog.open} onClose={() => setSuggestionDialog({...suggestionDialog, open: false})}> 311 361 <DialogTitle>Review Component Suggestion</DialogTitle> 312 362 <DialogContent> 313 363 <Typography gutterBottom>Status: <b>{suggestionDialog.action.toUpperCase()}</b></Typography> 314 364 <TextField 315 fullWidth label="Admin Comment" multiline rows={3} 316 value={adminComment} onChange={(e) => setAdminComment(e.target.value)} 365 fullWidth 366 label="Admin Comment" 367 multiline 368 rows={3} 369 value={adminComment} 370 onChange={(e) => setAdminComment(e.target.value)} 371 sx={{ mt: 2 }} 317 372 /> 318 373 </DialogContent> … … 323 378 </Dialog> 324 379 325 <Dialog open={buildApprovalDialog.open} 326 onClose={() => setBuildApprovalDialog({open: false, buildId: null, buildName: ''})}> 380 <Dialog 381 open={buildApprovalDialog.open} 382 onClose={() => setBuildApprovalDialog({open: false, buildId: null, buildName: ''})} 383 > 327 384 <DialogTitle>Review Build</DialogTitle> 328 385 <DialogContent> … … 368 425 </Dialog> 369 426 370 <Dialog open={deleteDialog.open} onClose={() => setDeleteDialog({ open: false, buildId: null, buildName: '' })}> 427 <Dialog 428 open={deleteDialog.open} 429 onClose={() => setDeleteDialog({open: false, buildId: null, buildName: ''})} 430 > 371 431 <DialogTitle>Delete Build</DialogTitle> 372 432 <DialogContent> … … 378 438 <DialogActions> 379 439 <Button 380 onClick={() => setDeleteDialog({ open: false, buildId: null, buildName: ''})}440 onClick={() => setDeleteDialog({open: false, buildId: null, buildName: ''})} 381 441 disabled={deleteLoading} 382 442 > … … 388 448 color="error" 389 449 disabled={deleteLoading} 390 startIcon={deleteLoading ? <CircularProgress size={20} /> : <DeleteIcon />}450 startIcon={deleteLoading ? <CircularProgress size={20}/> : <DeleteIcon />} 391 451 > 392 452 {deleteLoading ? 'Deleting...' : 'Delete Build'} … … 397 457 <BuildDetailsDialog 398 458 open={!!selectedBuildId} 399 buildId={selectedBuildId }400 currentUser={data.admin ?.id}459 buildId={selectedBuildId!} 460 currentUser={data.admin} 401 461 onClose={() => setSelectedBuildId(null)} 402 onClone={() => alert("Clone disabled in admin view")}403 462 isDashboardView={true} 463 /> 464 465 <AddComponentDialog 466 open={addDialogOpen} 467 onClose={() => setAddDialogOpen(false)} 468 onSuccess={() => { 469 loadData(); 470 setAddDialogOpen(false); 471 }} 404 472 /> 405 473 </Container> -
pages/dashboard/admin/adminDashboard.telefunc.ts
rf46bf5c r958bc89 1 1 import * as drizzleQueries from "../../../database/drizzle/queries"; 2 import { requireAdmin} from "../../../server/telefunc/ctx";2 import {requireAdmin} from "../../../server/telefunc/ctx"; 3 3 import {Abort} from "telefunc"; 4 4 import { validateComponentSpecificData } from "../../../database/drizzle/util/componentFieldConfig";
Note:
See TracChangeset
for help on using the changeset viewer.
