All files / src/app/teacher-page/page/assignments/new-assignment/student-preview/components Timer.jsx

100% Statements 33/33
100% Branches 16/16
100% Functions 13/13
100% Lines 26/26

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106                                                                  3x 48x 48x   68x     68x   48x 48x   48x 48x       48x 18x 18x     48x 16x   14x 61x     14x         48x 31x     48x 1x 1x     48x   48x                       1x         2x                    
/**
 * Modified by Allan Ninal — 2026-08-14
 *
 * Two fixes and a de-duplication. This is the teacher's "view as student" preview
 * of the take-assignment countdown, and it had drifted from the real one:
 *
 *  1. It seeded from `remainingTime` in the STUDENT slice, which nothing on the
 *     preview path ever sets (StudentPreviewPage.jsx imports `_setRemainingTime`
 *     and never dispatches it), so it always fell through to a hardcoded 3600 and
 *     showed 01:00:00 whatever the teacher had configured. It now reads the same
 *     `step3.timeAllowed` it already consults one line up to decide `isUnlimited`.
 *  2. At zero it POSTed to the real STUDENT submit endpoint. Nothing is submitted
 *     from a preview — the assignment does not exist yet — and the only thing that
 *     stopped a live request was `specificAssignmentData` being null in a teacher
 *     session, which made it throw inside the try. The dialog also told the teacher
 *     their work "will now be submitted automatically", which was never true here.
 *
 * The parse/format helpers were private copies of the student ones, minus their
 * guards; they now come from takeAssignmentTimer.js, which is the tested original.
 * That module is where the drift started, so sharing it is the fix that lasts.
 */
 
import { Box, Button, Typography } from "@mui/material";
import React, { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { _updateShowTimer } from "@/store/slices/student/StudentQuestionPage";
import { useNavigate } from "react-router-dom";
import OKOption from "@/utils/components/dialogs/OKOption";
import {
    formatSecondsToTime,
    parseTimeStringToSeconds,
} from "@/app/student-page/page/assignments/take-assignment/components/takeAssignmentTimer";
 
const Timer = () => {
    const dispatch = useDispatch();
    const navigate = useNavigate();
 
    const { settings } = useSelector((state) => state.StudentQuestionPage);
    // This preview runs off the in-progress wizard data (assignment isn't saved yet),
    // same source PageHeader.jsx in this directory uses for the other settings.
    const { assignmentData } = useSelector((state) => state.TeacherCreateAssignment);
 
    const timeAllowed = assignmentData?.step3?.timeAllowed;
    const isUnlimited = !timeAllowed;
 
    const [totalSeconds, setTotalSeconds] = useState(() => parseTimeStringToSeconds(timeAllowed) ?? 0);
    const [timeOver, setTimeOver] = useState(false);
 
    // The wizard's Step 3 can be edited while this preview is mounted, so the clock
    // follows the configured limit rather than only reading it once.
    useEffect(() => {
        setTotalSeconds(parseTimeStringToSeconds(timeAllowed) ?? 0);
        setTimeOver(false);
    }, [timeAllowed]);
 
    useEffect(() => {
        if (isUnlimited) return;
 
        const interval = setInterval(() => {
            setTotalSeconds((prev) => (prev > 1 ? prev - 1 : 0));
        }, 1000);
 
        return () => clearInterval(interval);
    }, [isUnlimited]);
 
    // Raising the dialog from an effect rather than from inside the setState updater:
    // an updater must be pure, and React may run it more than once per tick.
    useEffect(() => {
        if (!isUnlimited && totalSeconds === 0) setTimeOver(true);
    }, [isUnlimited, totalSeconds]);
 
    const clickOk = () => {
        setTimeOver(false);
        navigate(-1);
    };
 
    const formattedTime = formatSecondsToTime(totalSeconds);
 
    return (
        <React.Fragment>
            <OKOption
                isDialog={timeOver}
                setDialog={clickOk}
                title="Time Expired"
                message="Time has expired for this assignment. This is a preview, so nothing was submitted."
            />
            <Box sx={{ display: "flex", alignItems: "center", justifyContent: "center" }}>
                {settings.showTimer ? (
                    <>
                        <Typography>{isUnlimited ? "Unlimited time" : `Remaining time: ${formattedTime}`}</Typography>
                        <Button variant="text" color="primary" onClick={() => dispatch(_updateShowTimer())}>
                            Hide
                        </Button>
                    </>
                ) : (
                    <Button variant="text" color="primary" onClick={() => dispatch(_updateShowTimer())}>
                        Show Timer
                    </Button>
                )}
            </Box>
        </React.Fragment>
    );
};
 
export default Timer;