Add user nudges for certain statuses #308
@@ -20,7 +20,12 @@ export default function AuditEventItem({ event, validatorUser }: AuditEventItemP
|
||||
const { thumbnailUrl, isLoading } = useUserThumbnail(isValidator ? undefined : event.User, '150x150');
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'flex', gap: 2 }}>
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
gap: 2,
|
||||
p: 2,
|
||||
borderRadius: 1
|
||||
}}>
|
||||
<Box sx={{ position: 'relative', width: 40, height: 40 }}>
|
||||
<Skeleton
|
||||
variant="circular"
|
||||
|
||||
@@ -4,10 +4,22 @@ import {
|
||||
Box,
|
||||
Tabs,
|
||||
Tab,
|
||||
keyframes
|
||||
} from "@mui/material";
|
||||
import CommentsTabPanel from './CommentsTabPanel';
|
||||
import AuditEventsTabPanel from './AuditEventsTabPanel';
|
||||
import { AuditEvent } from "@/app/ts/AuditEvent";
|
||||
import { AuditEvent, AuditEventType } from "@/app/ts/AuditEvent";
|
||||
|
||||
const pulse = keyframes`
|
||||
0%, 100% {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
opacity: 0.6;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
`;
|
||||
|
||||
interface CommentsAndAuditSectionProps {
|
||||
auditEvents: AuditEvent[];
|
||||
@@ -16,6 +28,7 @@ interface CommentsAndAuditSectionProps {
|
||||
handleCommentSubmit: () => void;
|
||||
validatorUser: number;
|
||||
userId: number | null;
|
||||
currentStatus?: number;
|
||||
}
|
||||
|
||||
export default function CommentsAndAuditSection({
|
||||
@@ -25,6 +38,7 @@ export default function CommentsAndAuditSection({
|
||||
handleCommentSubmit,
|
||||
validatorUser,
|
||||
userId,
|
||||
currentStatus,
|
||||
}: CommentsAndAuditSectionProps) {
|
||||
|
||||
const [activeTab, setActiveTab] = useState(0);
|
||||
@@ -32,6 +46,16 @@ export default function CommentsAndAuditSection({
|
||||
setActiveTab(newValue);
|
||||
};
|
||||
|
||||
// Check if there's validator feedback for changes requested status
|
||||
// Show badge if status is ChangesRequested and there are validator events
|
||||
const hasValidatorFeedback = currentStatus === 1 && auditEvents.some(event =>
|
||||
event.User === validatorUser &&
|
||||
(
|
||||
event.EventType === AuditEventType.Error ||
|
||||
event.EventType === AuditEventType.CheckList
|
||||
)
|
||||
);
|
||||
|
||||
return (
|
||||
<Paper sx={{ p: 3, mt: 3 }}>
|
||||
<Box sx={{ borderBottom: 1, borderColor: 'divider', mb: 2 }}>
|
||||
@@ -41,7 +65,24 @@ export default function CommentsAndAuditSection({
|
||||
aria-label="comments and audit tabs"
|
||||
>
|
||||
<Tab label="Comments" />
|
||||
<Tab label="Audit Events" />
|
||||
<Tab
|
||||
label={
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
Audit Events
|
||||
{hasValidatorFeedback && (
|
||||
<Box
|
||||
sx={{
|
||||
width: 8,
|
||||
height: 8,
|
||||
borderRadius: '50%',
|
||||
backgroundColor: '#ff9800',
|
||||
animation: `${pulse} 2s ease-in-out infinite`
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
</Tabs>
|
||||
</Box>
|
||||
|
||||
|
||||
@@ -18,11 +18,13 @@ type ReviewItemType = SubmissionInfo | MapfixInfo;
|
||||
interface ReviewItemProps {
|
||||
item: ReviewItemType;
|
||||
handleCopyValue: (value: string) => void;
|
||||
currentUserId?: number;
|
||||
}
|
||||
|
||||
export function ReviewItem({
|
||||
item,
|
||||
handleCopyValue
|
||||
handleCopyValue,
|
||||
currentUserId
|
||||
}: ReviewItemProps) {
|
||||
// Type guard to check if item is valid
|
||||
if (!item) return null;
|
||||
@@ -105,6 +107,8 @@ export function ReviewItem({
|
||||
<WorkflowStepper
|
||||
currentStatus={item.StatusID}
|
||||
type={isMapfix ? 'mapfix' : 'submission'}
|
||||
submitterId={item.Submitter}
|
||||
currentUserId={currentUserId}
|
||||
/>
|
||||
</Paper>
|
||||
</>
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import React from 'react';
|
||||
import { Stepper, Step, StepLabel, Box, StepConnector, stepConnectorClasses, StepIconProps, styled, keyframes } from '@mui/material';
|
||||
import { Stepper, Step, StepLabel, Box, StepConnector, stepConnectorClasses, StepIconProps, styled, keyframes, Typography, Paper } from '@mui/material';
|
||||
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
import PendingIcon from '@mui/icons-material/Pending';
|
||||
import WarningIcon from '@mui/icons-material/Warning';
|
||||
import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
|
||||
import { Status } from '@/app/ts/Status';
|
||||
|
||||
const pulse = keyframes`
|
||||
@@ -18,6 +19,8 @@ const pulse = keyframes`
|
||||
interface WorkflowStepperProps {
|
||||
currentStatus: number;
|
||||
type: 'submission' | 'mapfix';
|
||||
submitterId?: number;
|
||||
currentUserId?: number;
|
||||
}
|
||||
|
||||
// Define the workflow steps
|
||||
@@ -164,19 +167,49 @@ const CustomStepIcon = (props: StepIconProps & { isRejected?: boolean; isChanges
|
||||
);
|
||||
};
|
||||
|
||||
const WorkflowStepper: React.FC<WorkflowStepperProps> = ({ currentStatus, type }) => {
|
||||
const WorkflowStepper: React.FC<WorkflowStepperProps> = ({ currentStatus, type, submitterId, currentUserId }) => {
|
||||
const workflow = type === 'mapfix' ? mapfixWorkflow : submissionWorkflow;
|
||||
|
||||
// Check if rejected or released
|
||||
const isRejected = currentStatus === Status.Rejected;
|
||||
const isReleased = currentStatus === Status.Release || currentStatus === Status.Releasing;
|
||||
const isChangesRequested = currentStatus === Status.ChangesRequested;
|
||||
const isUnderConstruction = currentStatus === Status.UnderConstruction;
|
||||
|
||||
// Find the active step
|
||||
const activeStep = workflow.findIndex(step =>
|
||||
step.statuses.includes(currentStatus)
|
||||
);
|
||||
|
||||
// Determine nudge message
|
||||
const getNudgeContent = () => {
|
||||
if (isUnderConstruction) {
|
||||
return {
|
||||
icon: InfoOutlinedIcon,
|
||||
title: 'Not Yet Submitted',
|
||||
message: 'Your submission has been created but has not been submitted. Click "Submit" to submit it.',
|
||||
color: '#2196f3',
|
||||
bgColor: 'rgba(33, 150, 243, 0.08)'
|
||||
};
|
||||
}
|
||||
if (isChangesRequested) {
|
||||
return {
|
||||
icon: WarningIcon,
|
||||
title: 'Changes Requested',
|
||||
message: 'Review comments and audit events, make modifications, and submit again.',
|
||||
color: '#ff9800',
|
||||
bgColor: 'rgba(255, 152, 0, 0.08)'
|
||||
};
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const nudge = getNudgeContent();
|
||||
|
||||
// Only show nudge if current user is the submitter
|
||||
const isSubmitter = submitterId !== undefined && currentUserId !== undefined && submitterId === currentUserId;
|
||||
const shouldShowNudge = nudge && isSubmitter;
|
||||
|
||||
// If rejected, show all steps as incomplete with error state
|
||||
if (isRejected) {
|
||||
return (
|
||||
@@ -245,6 +278,36 @@ const WorkflowStepper: React.FC<WorkflowStepperProps> = ({ currentStatus, type }
|
||||
);
|
||||
})}
|
||||
</Stepper>
|
||||
|
||||
{/* Action Nudge */}
|
||||
{shouldShowNudge && (
|
||||
<Paper
|
||||
elevation={0}
|
||||
sx={{
|
||||
mt: 3,
|
||||
p: 2,
|
||||
borderRadius: 2,
|
||||
borderLeft: 4,
|
||||
borderColor: nudge.color,
|
||||
backgroundColor: nudge.bgColor,
|
||||
display: 'flex',
|
||||
gap: 1.5,
|
||||
alignItems: 'flex-start'
|
||||
}}
|
||||
>
|
||||
<Box sx={{ color: nudge.color, display: 'flex', alignItems: 'center', pt: 0.25 }}>
|
||||
<nudge.icon sx={{ fontSize: 24 }} />
|
||||
</Box>
|
||||
<Box sx={{ flex: 1 }}>
|
||||
<Typography variant="body2" fontWeight={600} sx={{ color: nudge.color, mb: 0.5 }}>
|
||||
{nudge.title}
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{ color: 'text.secondary', fontSize: '0.875rem' }}>
|
||||
{nudge.message}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Paper>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -365,6 +365,7 @@ export default function MapfixDetailsPage() {
|
||||
<ReviewItem
|
||||
item={mapfix}
|
||||
handleCopyValue={handleCopyId}
|
||||
currentUserId={user ?? undefined}
|
||||
/>
|
||||
|
||||
{/* Comments Section */}
|
||||
@@ -375,6 +376,7 @@ export default function MapfixDetailsPage() {
|
||||
handleCommentSubmit={handleCommentSubmit}
|
||||
validatorUser={validatorUser}
|
||||
userId={user}
|
||||
currentStatus={mapfix.StatusID}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
@@ -267,6 +267,7 @@ export default function SubmissionDetailsPage() {
|
||||
<ReviewItem
|
||||
item={submission}
|
||||
handleCopyValue={handleCopyId}
|
||||
currentUserId={user ?? undefined}
|
||||
/>
|
||||
|
||||
{/* Comments Section */}
|
||||
@@ -277,6 +278,7 @@ export default function SubmissionDetailsPage() {
|
||||
handleCommentSubmit={handleCommentSubmit}
|
||||
validatorUser={validatorUser}
|
||||
userId={user}
|
||||
currentStatus={submission.StatusID}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
Reference in New Issue
Block a user