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 118 119 120 121 122 123 124 125 126 127 128 129 | 2x 2x 2x 34x 34x 34x 34x 34x 34x 34x 12x 12x 2x 2x 10x 10x 10x 3x 3x 7x 7x 5x 5x 5x 10x 34x 12x 34x 34x 34x | import { useEffect, useMemo, useState } from "react";
import { MaterialReactTable, useMaterialReactTable } from "material-react-table";
import { API_ENDPOINTS } from "@/api/endpoints";
import { useLocation, useParams } from "react-router-dom";
import axios from "axios";
import React from "react";
import SimpleLoading from "@/utils/components/SimpleLoading";
import { useUpgradeDialog } from "@/utils/components/feature-access/UpgradeDialogProvider";
import useFeatureFlag from "@/utils/hooks/useFeatureFlag";
import {
getFeatureMeta,
isFeatureNotEnabled403,
parseFeatureKeyFrom403,
} from "@/utils/features/featureAccess";
import { sanitizeRichText } from "@/utils/helpers/sanitizeHtml";
import { transformData } from "./itemAnalysisTransform";
// Answer-choice cell values may contain pre-rendered math-formula markup (see
// itemAnalysisTransform.extractContentHtml — MathLive's static output, e.g.
// `<span class="mfe-formula" data-latex="…">…</span>`) instead of plain text.
// MaterialReactTable's default cell rendering would print that markup as raw
// text, so these columns render through this instead, sanitized before
// insertion since it's staff-authored question content.
const RichAnalysisCell = ({ cell }) => (
<span
dangerouslySetInnerHTML={{ __html: sanitizeRichText(String(cell.getValue() ?? "")) }}
style={{ display: "inline" }}
/>
);
// Item Analysis table columns. Exported so the layout can be unit-tested without
// rendering MaterialReactTable. NOTE (EI-3390): there must be NO phantom leading
// column (a stray `accessorKey:"test"` with an empty header used to corrupt the
// table) — the first column is "No." and every column maps to a real row field.
export const ITEM_ANALYSIS_COLUMNS = [
{ accessorKey: "question_number", header: "No.", size: 10 },
{ accessorKey: "question_type", header: "Question Type", size: 10 },
{ accessorKey: "category", header: "Category", size: 10 },
{ accessorKey: "student_expectations", header: "S.E.", size: 10 },
{ accessorKey: "answer_choiceA", header: "A", size: 10, Cell: RichAnalysisCell },
{ accessorKey: "answer_choiceB", header: "B", size: 10, Cell: RichAnalysisCell },
{ accessorKey: "answer_choiceC", header: "C", size: 10, Cell: RichAnalysisCell },
{ accessorKey: "answer_choiceD", header: "D", size: 10, Cell: RichAnalysisCell },
{ accessorKey: "correct_answer", header: "Correct Answer", size: 10 },
{ accessorKey: "points", header: "Points", size: 10 },
{ accessorKey: "percent_correct", header: "% Correct", size: 10 },
{ accessorKey: "percent_incorrect", header: "% Incorrect", size: 10 },
];
const MyTable = () => {
const location = useLocation();
const { classCode } = useParams();
const { openUpgradeDialog } = useUpgradeDialog();
const { features, getFeature } = useFeatureFlag("teacher");
const [data, setData] = useState([]);
const [loading, setLoading] = useState(false);
const fetchItemAnalysis = async () => {
// The page receives the assignment id via in-app navigation state. On a
// hard reload / deep-link there is no `location.state`, so reading
// `location.state.assignmentId` directly threw → blank table. Guard it and
// render the empty table (headers, no rows) instead of crashing. (EI-3390)
const assignmentId = location.state?.assignmentId;
if (!assignmentId) {
setData([]);
return;
}
try {
setLoading(true);
const response = await axios.get(
API_ENDPOINTS.teacher.assignment.analytics.itemAnalysis(classCode, assignmentId)
);
Eif (response.status == 200) {
setData(transformData(response.data));
}
} catch (error) {
console.error("Error:", error.response?.data || error.message);
if (isFeatureNotEnabled403(error)) {
// `isFeatureNotEnabled403` above is true only when `parseFeatureKeyFrom403` already
// produced a key, so the old `?? fallback` here could never be reached.
// Removed rather than left to imply the fallback matters. (Allan Ninal)
const featureKey = parseFeatureKeyFrom403(error);
const featureMeta = getFeature(featureKey) || getFeatureMeta(features, featureKey);
openUpgradeDialog({
title: "Item analysis is locked",
featureKey,
featureLabel: "Item Analysis",
featureMeta,
minPlan: featureMeta?.min_plan ?? "intermediate",
});
}
} finally {
// Always clear loading — a non-200 2xx used to leave the spinner stuck. (EI-188 r2)
setLoading(false);
}
};
useEffect(() => {
fetchItemAnalysis();
}, []);
const columns = useMemo(() => ITEM_ANALYSIS_COLUMNS, []);
const table = useMaterialReactTable({
columns,
data,
muiTableHeadCellProps: {
sx: {
fontWeight: "normal",
fontSize: "12px",
},
},
muiTableBodyCellProps: {
sx: {
fontSize: "12px",
},
},
});
return (
<React.Fragment>
{loading ? <SimpleLoading showLoading={loading} /> : <MaterialReactTable table={table} />}
</React.Fragment>
);
};
export default MyTable;
|