Clickable titles and show active mapfix (#211)
All checks were successful
continuous-integration/drone/push Build is passing

Closes #144

Co-authored-by: ic3w0lf <bob@ic3.space>
Reviewed-on: #211
Reviewed-by: itzaname <itzaname@noreply@itzana.me>
Co-authored-by: ic3w0lf22 <ic3w0lf22@noreply@itzana.me>
Co-committed-by: ic3w0lf22 <ic3w0lf22@noreply@itzana.me>
This commit was merged in pull request #211.
This commit is contained in:
2025-06-29 19:06:52 +00:00
committed by itzaname
parent 6f9cd952d4
commit 825b2aa91a
3 changed files with 90 additions and 4 deletions

View File

@@ -49,6 +49,7 @@ export function ReviewItem({
<Paper elevation={3} sx={{ p: 3, borderRadius: 2, mb: 4 }}>
<ReviewItemHeader
displayName={item.DisplayName}
assetId={isMapfix ? item.TargetAssetID : undefined}
statusId={item.StatusID}
creator={item.Creator}
submitterId={item.Submitter}

View File

@@ -42,12 +42,13 @@ function SubmitterName({ submitterId }: { submitterId: number }) {
interface ReviewItemHeaderProps {
displayName: string;
assetId: number | null | undefined,
statusId: SubmissionStatus | MapfixStatus;
creator: string | null | undefined;
submitterId: number;
}
export const ReviewItemHeader = ({ displayName, statusId, creator, submitterId }: ReviewItemHeaderProps) => {
export const ReviewItemHeader = ({ displayName, assetId, statusId, creator, submitterId }: ReviewItemHeaderProps) => {
const isProcessing = StatusMatches(statusId, [Status.Validating, Status.Uploading, Status.Submitting]);
const pulse = keyframes`
0%, 100% { opacity: 0.2; transform: scale(0.8); }
@@ -57,9 +58,30 @@ export const ReviewItemHeader = ({ displayName, statusId, creator, submitterId }
return (
<>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}>
<Typography variant="h4" component="h1" gutterBottom>
{assetId != null ? (
<Link href={`/maps/${assetId}`} passHref legacyBehavior>
<Box sx={{ display: 'flex', alignItems: 'center', cursor: 'pointer' }} title="View related map">
<Typography
variant="h4"
component="h1"
gutterBottom
sx={{ color: 'inherit', textDecoration: 'none', mr: 1 }}
>
{displayName} by {creator}
</Typography>
<LaunchIcon sx={{ fontSize: '1.5rem', color: 'text.secondary' }} />
</Box>
</Link>
) : (
<Typography
variant="h4"
component="h1"
gutterBottom
sx={{ color: 'inherit', textDecoration: 'none', mr: 1 }}
>
{displayName} by {creator}
</Typography>
</Typography>
)}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
{isProcessing && (
<Box sx={{

View File

@@ -6,6 +6,8 @@ import { useParams, useRouter } from "next/navigation";
import React, { useState, useEffect } from "react";
import Link from "next/link";
import { Snackbar, Alert } from "@mui/material";
import { MapfixStatus, type MapfixInfo } from "@/app/ts/Mapfix";
import LaunchIcon from '@mui/icons-material/Launch';
// MUI Components
import {
@@ -44,6 +46,7 @@ export default function MapDetails() {
const [copySuccess, setCopySuccess] = useState(false);
const [roles, setRoles] = useState(RolesConstants.Empty);
const [downloading, setDownloading] = useState(false);
const [mapfixes, setMapfixes] = useState<MapfixInfo[]>([]);
useTitle(map ? `${map.DisplayName}` : 'Loading Map...');
@@ -87,6 +90,33 @@ export default function MapDetails() {
getRoles()
}, [mapId]);
useEffect(() => {
if (!map) return;
const targetAssetId = map.ID;
async function fetchMapfixes() {
try {
const limit = 100;
let page = 1;
let allMapfixes: MapfixInfo[] = [];
let total = 0;
do {
const res = await fetch(`/api/mapfixes?Page=${page}&Limit=${limit}&TargetAssetID=${targetAssetId}`);
if (!res.ok) break;
const data = await res.json();
if (page === 1) total = data.Total;
allMapfixes = allMapfixes.concat(data.Mapfixes);
page++;
} while (allMapfixes.length < total);
// Filter out rejected, uploading, uploaded (StatusID > 7)
const active = allMapfixes.filter((fix: MapfixInfo) => fix.StatusID <= MapfixStatus.Validated);
setMapfixes(active);
} catch {
setMapfixes([]);
}
}
fetchMapfixes();
}, [map]);
const formatDate = (timestamp: number) => {
return new Date(timestamp * 1000).toLocaleDateString('en-US', {
year: 'numeric',
@@ -361,13 +391,46 @@ export default function MapDetails() {
<IconButton
size="small"
onClick={() => handleCopyId(mapId as string)}
sx={{ ml: 1 }}
sx={{ ml: 1, p: 0 }}
>
<ContentCopyIcon fontSize="small" />
</IconButton>
</Tooltip>
</Box>
</Box>
{/* Active Mapfix in Map Details */}
{mapfixes.length > 0 && (() => {
const active = mapfixes.find(fix => fix.StatusID <= MapfixStatus.Validated);
const latest = mapfixes.reduce((a, b) => (a.CreatedAt > b.CreatedAt ? a : b));
const showFix = active || latest;
return (
<Box>
<Typography variant="subtitle2" color="text.secondary">
Active Mapfix
</Typography>
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<Typography
variant="body2"
component={Link}
href={`/mapfixes/${showFix.ID}`}
sx={{
textDecoration: 'underline',
cursor: 'pointer',
color: 'primary.main',
display: 'flex',
alignItems: 'center',
gap: 0.5,
mt: 0.5
}}
>
{showFix.Description}
<LaunchIcon sx={{ fontSize: '1rem', ml: 0.5 }} />
</Typography>
</Box>
</Box>
);
})()}
</Stack>
</Paper>