All files / src/app/student-page/page/dashboard/components AssignmentsCalendar.jsx

96.87% Statements 31/32
85% Branches 17/20
100% Functions 7/7
100% Lines 29/29

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 107 108 109 110 111 112 113 114 115 116 117              1x 9x 9x 9x     1x 39x 39x 39x   39x 13x 13x 13x 13x 13x 13x 11x 10x 13x   2x 2x 2x 2x   13x     13x 13x 13x       39x                                                               9x                       10x                                                          
import { useEffect, useState } from "react";
import axios from "axios";
import { Box, Card, CardContent, Chip, CircularProgress, Stack, Typography } from "@mui/material";
import CalendarMonthIcon from "@mui/icons-material/CalendarMonth";
import { API_ENDPOINTS } from "@/api/endpoints";
import { groupAssignmentsByDate } from "./groupAssignmentsByDate";
 
const _formatDayHeading = (yyyymmdd) => {
    const [y, m, d] = yyyymmdd.split("-").map(Number);
    const date = new Date(y, m - 1, d);
    return date.toLocaleDateString(undefined, { weekday: "short", month: "short", day: "numeric" });
};
 
const AssignmentsCalendar = ({ refreshKey }) => {
    const [buckets, setBuckets] = useState([]);
    const [isLoading, setIsLoading] = useState(false);
    const [hasError, setHasError] = useState(false);
 
    useEffect(() => {
        let cancelled = false;
        const fetchUpcoming = async () => {
            setIsLoading(true);
            setHasError(false);
            try {
                const response = await axios.get(API_ENDPOINTS.student.dashboard.upcomingAssignments);
                if (cancelled) return;
                const upcoming = Array.isArray(response?.data?.upcoming) ? response.data.upcoming : [];
                setBuckets(groupAssignmentsByDate(upcoming));
            } catch (error) {
                Iif (cancelled) return;
                console.error("EI-2966 calendar fetch error:", error?.response?.data || error?.message);
                setHasError(true);
                setBuckets([]);
            } finally {
                if (!cancelled) setIsLoading(false);
            }
        };
        fetchUpcoming();
        return () => {
            cancelled = true;
        };
    }, [refreshKey]);
 
    return (
        <Card id="student-dashboard-calendar-widget" sx={{ borderRadius: 2 }}>
            <CardContent>
                <Stack direction="row" alignItems="center" spacing={1} sx={{ mb: 2 }}>
                    <CalendarMonthIcon color="primary" />
                    <Typography variant="h6" sx={{ fontWeight: 600 }}>
                        Upcoming Assignments
                    </Typography>
                </Stack>
                {isLoading ? (
                    <Box sx={{ py: 3, textAlign: "center" }}>
                        <CircularProgress size={24} />
                    </Box>
                ) : hasError ? (
                    <Typography
                        id="student-dashboard-calendar-error"
                        variant="body2"
                        color="error"
                    >
                        Unable to load upcoming assignments. Please try again later.
                    </Typography>
                ) : buckets.length === 0 ? (
                    <Typography
                        id="student-dashboard-calendar-empty"
                        variant="body2"
                        color="text.secondary"
                    >
                        No upcoming due dates.
                    </Typography>
                ) : (
                    <Stack spacing={2}>
                        {buckets.map(({ day, items }) => (
                            <Box key={day} data-day-bucket={day}>
                                <Stack direction="row" alignItems="center" spacing={1} sx={{ mb: 0.5 }}>
                                    <Typography
                                        variant="subtitle2"
                                        sx={{ fontWeight: 600, color: "primary.main" }}
                                    >
                                        {_formatDayHeading(day)}
                                    </Typography>
                                    <Chip label={items.length} size="small" />
                                </Stack>
                                <Stack spacing={0.5} sx={{ pl: 1 }}>
                                    {items.map((a) => (
                                        <Typography
                                            key={a._id || `${day}-${a.title}`}
                                            variant="body2"
                                            color="text.primary"
                                        >
                                            {a.title}
                                            {a.class_title ? (
                                                <Typography
                                                    component="span"
                                                    variant="caption"
                                                    color="text.secondary"
                                                    sx={{ ml: 1 }}
                                                >
                                                    ยท {a.class_title}
                                                </Typography>
                                            ) : null}
                                        </Typography>
                                    ))}
                                </Stack>
                            </Box>
                        ))}
                    </Stack>
                )}
            </CardContent>
        </Card>
    );
};
 
export default AssignmentsCalendar;