This commit is contained in:
@@ -2,11 +2,13 @@ import {
|
||||
Box,
|
||||
Avatar,
|
||||
Typography,
|
||||
Tooltip
|
||||
Tooltip,
|
||||
Skeleton
|
||||
} from "@mui/material";
|
||||
import PersonIcon from '@mui/icons-material/Person';
|
||||
import { formatDistanceToNow, format } from "date-fns";
|
||||
import { AuditEvent, decodeAuditEvent as auditEventMessage } from "@/app/ts/AuditEvent";
|
||||
import { useUserThumbnail } from "@/app/hooks/useThumbnails";
|
||||
|
||||
interface AuditEventItemProps {
|
||||
event: AuditEvent;
|
||||
@@ -14,17 +16,39 @@ interface AuditEventItemProps {
|
||||
}
|
||||
|
||||
export default function AuditEventItem({ event, validatorUser }: AuditEventItemProps) {
|
||||
const isValidator = event.User === validatorUser;
|
||||
const { thumbnailUrl, isLoading } = useUserThumbnail(isValidator ? undefined : event.User, '150x150');
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'flex', gap: 2 }}>
|
||||
<Avatar
|
||||
src={event.User === validatorUser ? undefined : `/thumbnails/user/${event.User}`}
|
||||
>
|
||||
<PersonIcon />
|
||||
</Avatar>
|
||||
<Box sx={{ position: 'relative', width: 40, height: 40 }}>
|
||||
<Skeleton
|
||||
variant="circular"
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
width: 40,
|
||||
height: 40,
|
||||
opacity: isLoading ? 1 : 0,
|
||||
transition: 'opacity 0.3s ease-in-out',
|
||||
}}
|
||||
animation="wave"
|
||||
/>
|
||||
<Avatar
|
||||
src={isValidator ? undefined : (thumbnailUrl || undefined)}
|
||||
sx={{
|
||||
width: 40,
|
||||
height: 40,
|
||||
opacity: isLoading ? 0 : 1,
|
||||
transition: 'opacity 0.3s ease-in-out',
|
||||
}}
|
||||
>
|
||||
<PersonIcon />
|
||||
</Avatar>
|
||||
</Box>
|
||||
<Box sx={{ flexGrow: 1 }}>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
<Typography variant="subtitle2">
|
||||
{event.User === validatorUser ? "Validator" : event.Username || "Unknown"}
|
||||
{isValidator ? "Validator" : event.Username || "Unknown"}
|
||||
</Typography>
|
||||
<DateDisplay date={event.Date} />
|
||||
</Box>
|
||||
|
||||
@@ -21,18 +21,21 @@ export default function AuditEventsTabPanel({
|
||||
);
|
||||
|
||||
return (
|
||||
<Box role="tabpanel" hidden={activeTab !== 1}>
|
||||
{activeTab === 1 && (
|
||||
<Stack spacing={2}>
|
||||
{filteredEvents.map((event, index) => (
|
||||
<AuditEventItem
|
||||
key={index}
|
||||
event={event}
|
||||
validatorUser={validatorUser}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
)}
|
||||
<Box
|
||||
role="tabpanel"
|
||||
sx={{
|
||||
display: activeTab === 1 ? 'block' : 'none'
|
||||
}}
|
||||
>
|
||||
<Stack spacing={2}>
|
||||
{filteredEvents.map((event, index) => (
|
||||
<AuditEventItem
|
||||
key={index}
|
||||
event={event}
|
||||
validatorUser={validatorUser}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -2,11 +2,13 @@ import {
|
||||
Box,
|
||||
Avatar,
|
||||
Typography,
|
||||
Tooltip
|
||||
Tooltip,
|
||||
Skeleton
|
||||
} from "@mui/material";
|
||||
import PersonIcon from '@mui/icons-material/Person';
|
||||
import { formatDistanceToNow, format } from "date-fns";
|
||||
import { AuditEvent, decodeAuditEvent } from "@/app/ts/AuditEvent";
|
||||
import { useUserThumbnail } from "@/app/hooks/useThumbnails";
|
||||
|
||||
interface CommentItemProps {
|
||||
event: AuditEvent;
|
||||
@@ -14,17 +16,39 @@ interface CommentItemProps {
|
||||
}
|
||||
|
||||
export default function CommentItem({ event, validatorUser }: CommentItemProps) {
|
||||
const isValidator = event.User === validatorUser;
|
||||
const { thumbnailUrl, isLoading } = useUserThumbnail(isValidator ? undefined : event.User, '150x150');
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'flex', gap: 2 }}>
|
||||
<Avatar
|
||||
src={event.User === validatorUser ? undefined : `/thumbnails/user/${event.User}`}
|
||||
>
|
||||
<PersonIcon />
|
||||
</Avatar>
|
||||
<Box sx={{ position: 'relative', width: 40, height: 40 }}>
|
||||
<Skeleton
|
||||
variant="circular"
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
width: 40,
|
||||
height: 40,
|
||||
opacity: isLoading ? 1 : 0,
|
||||
transition: 'opacity 0.3s ease-in-out',
|
||||
}}
|
||||
animation="wave"
|
||||
/>
|
||||
<Avatar
|
||||
src={isValidator ? undefined : (thumbnailUrl || undefined)}
|
||||
sx={{
|
||||
width: 40,
|
||||
height: 40,
|
||||
opacity: isLoading ? 0 : 1,
|
||||
transition: 'opacity 0.3s ease-in-out',
|
||||
}}
|
||||
>
|
||||
<PersonIcon />
|
||||
</Avatar>
|
||||
</Box>
|
||||
<Box sx={{ flexGrow: 1 }}>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
<Typography variant="subtitle2">
|
||||
{event.User === validatorUser ? "Validator" : event.Username || "Unknown"}
|
||||
{isValidator ? "Validator" : event.Username || "Unknown"}
|
||||
</Typography>
|
||||
<DateDisplay date={event.Date} />
|
||||
</Box>
|
||||
|
||||
@@ -3,11 +3,13 @@ import {
|
||||
Stack,
|
||||
Avatar,
|
||||
TextField,
|
||||
IconButton
|
||||
IconButton,
|
||||
Skeleton
|
||||
} from "@mui/material";
|
||||
import SendIcon from '@mui/icons-material/Send';
|
||||
import { AuditEvent, AuditEventType } from "@/app/ts/AuditEvent";
|
||||
import CommentItem from './CommentItem';
|
||||
import { useUserThumbnail } from "@/app/hooks/useThumbnails";
|
||||
|
||||
interface CommentsTabPanelProps {
|
||||
activeTab: number;
|
||||
@@ -33,34 +35,35 @@ export default function CommentsTabPanel({
|
||||
);
|
||||
|
||||
return (
|
||||
<Box role="tabpanel" hidden={activeTab !== 0}>
|
||||
{activeTab === 0 && (
|
||||
<>
|
||||
<Stack spacing={2} sx={{ mb: 3 }}>
|
||||
{commentEvents.length > 0 ? (
|
||||
commentEvents.map((event, index) => (
|
||||
<CommentItem
|
||||
key={index}
|
||||
event={event}
|
||||
validatorUser={validatorUser}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<Box sx={{ textAlign: 'center', py: 2, color: 'text.secondary' }}>
|
||||
No Comments
|
||||
</Box>
|
||||
)}
|
||||
</Stack>
|
||||
|
||||
{userId !== null && (
|
||||
<CommentInput
|
||||
newComment={newComment}
|
||||
setNewComment={setNewComment}
|
||||
handleCommentSubmit={handleCommentSubmit}
|
||||
userId={userId}
|
||||
<Box
|
||||
role="tabpanel"
|
||||
sx={{
|
||||
display: activeTab === 0 ? 'block' : 'none'
|
||||
}}
|
||||
>
|
||||
<Stack spacing={2} sx={{ mb: 3 }}>
|
||||
{commentEvents.length > 0 ? (
|
||||
commentEvents.map((event, index) => (
|
||||
<CommentItem
|
||||
key={index}
|
||||
event={event}
|
||||
validatorUser={validatorUser}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
))
|
||||
) : (
|
||||
<Box sx={{ textAlign: 'center', py: 2, color: 'text.secondary' }}>
|
||||
No Comments
|
||||
</Box>
|
||||
)}
|
||||
</Stack>
|
||||
|
||||
{userId !== null && (
|
||||
<CommentInput
|
||||
newComment={newComment}
|
||||
setNewComment={setNewComment}
|
||||
handleCommentSubmit={handleCommentSubmit}
|
||||
userId={userId}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
@@ -74,11 +77,32 @@ interface CommentInputProps {
|
||||
}
|
||||
|
||||
function CommentInput({ newComment, setNewComment, handleCommentSubmit, userId }: CommentInputProps) {
|
||||
const { thumbnailUrl, isLoading } = useUserThumbnail(userId || undefined, '150x150');
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'flex', gap: 2, alignItems: 'flex-start' }}>
|
||||
<Avatar
|
||||
src={`/thumbnails/user/${userId}`}
|
||||
/>
|
||||
<Box sx={{ position: 'relative', width: 40, height: 40 }}>
|
||||
<Skeleton
|
||||
variant="circular"
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
width: 40,
|
||||
height: 40,
|
||||
opacity: isLoading ? 1 : 0,
|
||||
transition: 'opacity 0.3s ease-in-out',
|
||||
}}
|
||||
animation="wave"
|
||||
/>
|
||||
<Avatar
|
||||
src={thumbnailUrl || undefined}
|
||||
sx={{
|
||||
width: 40,
|
||||
height: 40,
|
||||
opacity: isLoading ? 0 : 1,
|
||||
transition: 'opacity 0.3s ease-in-out',
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<TextField
|
||||
fullWidth
|
||||
multiline
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {Typography, Box, Avatar, keyframes} from "@mui/material";
|
||||
import {Typography, Box, Avatar, keyframes, Skeleton} from "@mui/material";
|
||||
import { StatusChip } from "@/app/_components/statusChip";
|
||||
import { SubmissionStatus } from "@/app/ts/Submission";
|
||||
import { MapfixStatus } from "@/app/ts/Mapfix";
|
||||
@@ -6,6 +6,7 @@ import {Status, StatusMatches} from "@/app/ts/Status";
|
||||
import { useState, useEffect } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import LaunchIcon from '@mui/icons-material/Launch';
|
||||
import { useUserThumbnail } from "@/app/hooks/useThumbnails";
|
||||
|
||||
function SubmitterName({ submitterId }: { submitterId: number }) {
|
||||
const [name, setName] = useState<string | null>(null);
|
||||
@@ -50,6 +51,7 @@ interface ReviewItemHeaderProps {
|
||||
|
||||
export const ReviewItemHeader = ({ displayName, assetId, statusId, creator, submitterId }: ReviewItemHeaderProps) => {
|
||||
const isProcessing = StatusMatches(statusId, [Status.Validating, Status.Uploading, Status.Submitting]);
|
||||
const { thumbnailUrl, isLoading } = useUserThumbnail(submitterId, '150x150');
|
||||
const pulse = keyframes`
|
||||
0%, 100% { opacity: 0.2; transform: scale(0.8); }
|
||||
50% { opacity: 1; transform: scale(1); }
|
||||
@@ -111,10 +113,28 @@ export const ReviewItemHeader = ({ displayName, assetId, statusId, creator, subm
|
||||
</Box>
|
||||
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', mb: 1 }}>
|
||||
<Avatar
|
||||
src={`/thumbnails/user/${submitterId}`}
|
||||
sx={{ mr: 1, width: 24, height: 24 }}
|
||||
/>
|
||||
<Box sx={{ position: 'relative', mr: 1, width: 24, height: 24 }}>
|
||||
<Skeleton
|
||||
variant="circular"
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
width: 24,
|
||||
height: 24,
|
||||
opacity: isLoading ? 1 : 0,
|
||||
transition: 'opacity 0.3s ease-in-out',
|
||||
}}
|
||||
animation="wave"
|
||||
/>
|
||||
<Avatar
|
||||
src={thumbnailUrl || undefined}
|
||||
sx={{
|
||||
width: 24,
|
||||
height: 24,
|
||||
opacity: isLoading ? 0 : 1,
|
||||
transition: 'opacity 0.3s ease-in-out',
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<SubmitterName submitterId={submitterId} />
|
||||
</Box>
|
||||
</>
|
||||
|
||||
@@ -15,7 +15,6 @@ export default defineConfig({
|
||||
'/v1': {
|
||||
target: process.env.VITE_API_HOST || 'http://localhost:8080',
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path.replace(/^\/v1/, ''),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user