@openedx/frontend-app-gradebook 2.0.0-alpha.6 → 2.0.0-alpha.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/BulkManagementHistoryView/BulkManagementAlerts.js +1 -2
- package/dist/components/BulkManagementHistoryView/BulkManagementAlerts.js.map +1 -1
- package/dist/components/GradebookHeader/index.js +6 -9
- package/dist/components/GradebookHeader/index.js.map +1 -1
- package/dist/components/GradesView/FilterMenuToggle/index.js +1 -1
- package/dist/components/GradesView/FilterMenuToggle/index.js.map +1 -1
- package/dist/components/GradesView/GradesView.scss +0 -3
- package/dist/components/GradesView/ImportResultToast/hooks.d.ts +15 -0
- package/dist/components/GradesView/ImportResultToast/hooks.js +51 -0
- package/dist/components/GradesView/ImportResultToast/hooks.js.map +1 -0
- package/dist/components/GradesView/ImportResultToast/index.d.ts +5 -0
- package/dist/components/GradesView/ImportResultToast/index.js +23 -0
- package/dist/components/GradesView/ImportResultToast/index.js.map +1 -0
- package/dist/components/GradesView/{ImportSuccessToast → ImportResultToast}/messages.d.ts +13 -6
- package/dist/components/GradesView/{ImportSuccessToast → ImportResultToast}/messages.js +8 -1
- package/dist/components/GradesView/ImportResultToast/messages.js.map +1 -0
- package/dist/components/GradesView/ScoreViewInput/index.js +1 -1
- package/dist/components/GradesView/ScoreViewInput/index.js.map +1 -1
- package/dist/components/GradesView/data/apiHook.js +6 -2
- package/dist/components/GradesView/data/apiHook.js.map +1 -1
- package/dist/components/GradesView/index.js +3 -2
- package/dist/components/GradesView/index.js.map +1 -1
- package/dist/components/GradesView/messages.d.ts +9 -1
- package/dist/components/GradesView/messages.js +10 -5
- package/dist/components/GradesView/messages.js.map +1 -1
- package/dist/data/gradebookUiContext.d.ts +3 -0
- package/dist/data/gradebookUiContext.js +6 -1
- package/dist/data/gradebookUiContext.js.map +1 -1
- package/package.json +1 -1
- package/dist/components/GradesView/ImportSuccessToast/hooks.d.ts +0 -10
- package/dist/components/GradesView/ImportSuccessToast/hooks.js +0 -31
- package/dist/components/GradesView/ImportSuccessToast/hooks.js.map +0 -1
- package/dist/components/GradesView/ImportSuccessToast/index.d.ts +0 -5
- package/dist/components/GradesView/ImportSuccessToast/index.js +0 -15
- package/dist/components/GradesView/ImportSuccessToast/index.js.map +0 -1
- package/dist/components/GradesView/ImportSuccessToast/messages.js.map +0 -1
|
@@ -9,9 +9,8 @@ import messages from './messages';
|
|
|
9
9
|
*/
|
|
10
10
|
export const BulkManagementAlerts = () => {
|
|
11
11
|
const { csvUploadSuccess, csvUploadErrorMessages } = useGradebookUi();
|
|
12
|
-
// Mirrors the old `selectors.grades.bulkImportError` string formatting.
|
|
13
12
|
const bulkImportError = csvUploadErrorMessages.length
|
|
14
|
-
? `Errors while processing: ${csvUploadErrorMessages.join('; ')}
|
|
13
|
+
? `Errors while processing: ${csvUploadErrorMessages.join('; ')}`
|
|
15
14
|
: '';
|
|
16
15
|
return (_jsxs(_Fragment, { children: [_jsx(Alert, { variant: "danger", show: !!bulkImportError, dismissible: false, children: bulkImportError }), _jsx(Alert, { variant: "success", show: csvUploadSuccess, dismissible: false, children: _jsx(FormattedMessage, Object.assign({}, messages.successDialog)) })] }));
|
|
17
16
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BulkManagementAlerts.js","sourceRoot":"","sources":["../../../src/components/BulkManagementHistoryView/BulkManagementAlerts.jsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE1D,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAEzC,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAC9D,OAAO,QAAQ,MAAM,YAAY,CAAC;AAElC;;;GAGG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,GAAG,EAAE;IACvC,MAAM,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,GAAG,cAAc,EAAE,CAAC;IACtE,
|
|
1
|
+
{"version":3,"file":"BulkManagementAlerts.js","sourceRoot":"","sources":["../../../src/components/BulkManagementHistoryView/BulkManagementAlerts.jsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE1D,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAEzC,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAC9D,OAAO,QAAQ,MAAM,YAAY,CAAC;AAElC;;;GAGG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,GAAG,EAAE;IACvC,MAAM,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,GAAG,cAAc,EAAE,CAAC;IACtE,MAAM,eAAe,GAAG,sBAAsB,CAAC,MAAM;QACnD,CAAC,CAAC,4BAA4B,sBAAsB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;QACjE,CAAC,CAAC,EAAE,CAAC;IAEP,OAAO,CACL,8BACE,KAAC,KAAK,IACJ,OAAO,EAAC,QAAQ,EAChB,IAAI,EAAE,CAAC,CAAC,eAAe,EACvB,WAAW,EAAE,KAAK,YAEjB,eAAe,GACV,EACR,KAAC,KAAK,IACJ,OAAO,EAAC,SAAS,EACjB,IAAI,EAAE,gBAAgB,EACtB,WAAW,EAAE,KAAK,YAElB,KAAC,gBAAgB,oBAAK,QAAQ,CAAC,aAAa,EAAI,GAC1C,IACP,CACJ,CAAC;AACJ,CAAC,CAAC;AAEF,oBAAoB,CAAC,SAAS,GAAG,EAAE,CAAC;AAEpC,eAAe,oBAAoB,CAAC","sourcesContent":["import { FormattedMessage } from '@openedx/frontend-base';\n\nimport { Alert } from '@openedx/paragon';\n\nimport { useGradebookUi } from '@src/data/gradebookUiContext';\nimport messages from './messages';\n\n/**\n * <BulkManagementAlerts />\n * Alerts to display at the top of the BulkManagement tab.\n */\nexport const BulkManagementAlerts = () => {\n const { csvUploadSuccess, csvUploadErrorMessages } = useGradebookUi();\n const bulkImportError = csvUploadErrorMessages.length\n ? `Errors while processing: ${csvUploadErrorMessages.join('; ')}`\n : '';\n\n return (\n <>\n <Alert\n variant=\"danger\"\n show={!!bulkImportError}\n dismissible={false}\n >\n {bulkImportError}\n </Alert>\n <Alert\n variant=\"success\"\n show={csvUploadSuccess}\n dismissible={false}\n >\n <FormattedMessage {...messages.successDialog} />\n </Alert>\n </>\n );\n};\n\nBulkManagementAlerts.propTypes = {};\n\nexport default BulkManagementAlerts;\n"]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { getLinkProps, resolveRouteByRole, useIntl } from '@openedx/frontend-base';
|
|
3
|
+
import { Button, Icon, Hyperlink } from '@openedx/paragon';
|
|
4
|
+
import { ArrowBackIos } from '@openedx/paragon/icons';
|
|
5
5
|
import { instructorDashboardUrl } from '../../data/services/lms/urls';
|
|
6
6
|
import { useGradebookNavigation } from '../../data/gradebookNavigationContext';
|
|
7
7
|
import useGradebookHeaderData from './hooks';
|
|
@@ -19,19 +19,16 @@ export const GradebookHeader = () => {
|
|
|
19
19
|
const dashboardRoute = (!onBack && courseId)
|
|
20
20
|
? resolveRouteByRole(instructorDashboardRole, { courseId })
|
|
21
21
|
: null;
|
|
22
|
-
const backLinkContent = (_jsxs(_Fragment, { children: [_jsx(
|
|
22
|
+
const backLinkContent = (_jsxs(_Fragment, { children: [_jsx(Icon, { src: ArrowBackIos, className: "mr-1", size: "sm" }), formatMessage(messages.backToDashboard)] }));
|
|
23
23
|
const renderBackLink = () => {
|
|
24
24
|
var _a;
|
|
25
25
|
// Host-provided handler wins over URL navigation (e.g. CCX Coach tab).
|
|
26
26
|
if (onBack) {
|
|
27
27
|
return (_jsx(Button, { variant: "link", className: "mb-3 p-0", onClick: onBack, children: backLinkContent }));
|
|
28
28
|
}
|
|
29
|
-
|
|
30
|
-
return _jsx(Link, { to: dashboardRoute.url, className: "mb-3", children: backLinkContent });
|
|
31
|
-
}
|
|
32
|
-
return _jsx("a", { href: (_a = dashboardRoute === null || dashboardRoute === void 0 ? void 0 : dashboardRoute.url) !== null && _a !== void 0 ? _a : instructorDashboardUrl(courseId), className: "mb-3", children: backLinkContent });
|
|
29
|
+
return (_jsx(Hyperlink, Object.assign({}, getLinkProps((_a = dashboardRoute === null || dashboardRoute === void 0 ? void 0 : dashboardRoute.url) !== null && _a !== void 0 ? _a : instructorDashboardUrl(courseId)), { className: "mb-3", children: backLinkContent })));
|
|
33
30
|
};
|
|
34
|
-
return (_jsxs("div", { className: "gradebook-header", children: [renderBackLink(), _jsx("
|
|
31
|
+
return (_jsxs("div", { className: "gradebook-header mt-2", children: [renderBackLink(), _jsx("h2", { className: "text-primary-500 mb-0", children: formatMessage(messages.gradebook) }), _jsx("p", { className: "small text-break mb-3", children: courseId }), _jsx("div", { className: "subtitle-row d-flex justify-content-between align-items-center", children: showBulkManagement && (_jsx(Button, { variant: "tertiary", onClick: handleToggleViewClick, children: formatMessage(toggleViewMessage) })) }), areGradesFrozen && (_jsx("div", { className: "alert alert-warning", role: "alert", children: formatMessage(messages.frozenWarning) })), (canUserViewGradebook === false) && (_jsx("div", { className: "alert alert-warning", role: "alert", children: formatMessage(messages.unauthorizedWarning) }))] }));
|
|
35
32
|
};
|
|
36
33
|
export default GradebookHeader;
|
|
37
34
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/components/GradebookHeader/index.jsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/components/GradebookHeader/index.jsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AACnF,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAEtD,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,EAAE,sBAAsB,EAAE,MAAM,sCAAsC,CAAC;AAC9E,OAAO,sBAAsB,MAAM,SAAS,CAAC;AAC7C,OAAO,QAAQ,MAAM,YAAY,CAAC;AAElC,MAAM,uBAAuB,GAAG,+CAA+C,CAAC;AAEhF,MAAM,CAAC,MAAM,eAAe,GAAG,GAAG,EAAE;IAClC,MAAM,EAAE,aAAa,EAAE,GAAG,OAAO,EAAE,CAAC;IACpC,MAAM,EACJ,eAAe,EACf,oBAAoB,EACpB,QAAQ,EACR,qBAAqB,EACrB,kBAAkB,EAClB,iBAAiB,GAClB,GAAG,sBAAsB,EAAE,CAAC;IAC7B,MAAM,EAAE,MAAM,EAAE,GAAG,sBAAsB,EAAE,CAAC;IAC5C,0EAA0E;IAC1E,yEAAyE;IACzE,0EAA0E;IAC1E,yEAAyE;IACzE,uCAAuC;IACvC,MAAM,cAAc,GAAG,CAAC,CAAC,MAAM,IAAI,QAAQ,CAAC;QAC1C,CAAC,CAAC,kBAAkB,CAAC,uBAAuB,EAAE,EAAE,QAAQ,EAAE,CAAC;QAC3D,CAAC,CAAC,IAAI,CAAC;IACT,MAAM,eAAe,GAAG,CACtB,8BACE,KAAC,IAAI,IAAC,GAAG,EAAE,YAAY,EAAE,SAAS,EAAC,MAAM,EAAC,IAAI,EAAC,IAAI,GAAG,EACrD,aAAa,CAAC,QAAQ,CAAC,eAAe,CAAC,IACvC,CACJ,CAAC;IACF,MAAM,cAAc,GAAG,GAAG,EAAE;;QAC1B,uEAAuE;QACvE,IAAI,MAAM,EAAE,CAAC;YACX,OAAO,CACL,KAAC,MAAM,IAAC,OAAO,EAAC,MAAM,EAAC,SAAS,EAAC,UAAU,EAAC,OAAO,EAAE,MAAM,YACxD,eAAe,GACT,CACV,CAAC;QACJ,CAAC;QACD,OAAO,CACL,KAAC,SAAS,oBACJ,YAAY,CAAC,MAAA,cAAc,aAAd,cAAc,uBAAd,cAAc,CAAE,GAAG,mCAAI,sBAAsB,CAAC,QAAQ,CAAC,CAAC,IACzE,SAAS,EAAC,MAAM,YAEf,eAAe,IACN,CACb,CAAC;IACJ,CAAC,CAAC;IACF,OAAO,CACL,eAAK,SAAS,EAAC,uBAAuB,aACnC,cAAc,EAAE,EACjB,aAAI,SAAS,EAAC,uBAAuB,YAAE,aAAa,CAAC,QAAQ,CAAC,SAAS,CAAC,GAAM,EAC9E,YAAG,SAAS,EAAC,uBAAuB,YAAE,QAAQ,GAAK,EACnD,cAAK,SAAS,EAAC,gEAAgE,YAC5E,kBAAkB,IAAI,CACrB,KAAC,MAAM,IAAC,OAAO,EAAC,UAAU,EAAC,OAAO,EAAE,qBAAqB,YACtD,aAAa,CAAC,iBAAiB,CAAC,GAC1B,CACV,GACG,EACL,eAAe,IAAI,CAClB,cAAK,SAAS,EAAC,qBAAqB,EAAC,IAAI,EAAC,OAAO,YAC9C,aAAa,CAAC,QAAQ,CAAC,aAAa,CAAC,GAClC,CACP,EACA,CAAC,oBAAoB,KAAK,KAAK,CAAC,IAAI,CACnC,cAAK,SAAS,EAAC,qBAAqB,EAAC,IAAI,EAAC,OAAO,YAC9C,aAAa,CAAC,QAAQ,CAAC,mBAAmB,CAAC,GACxC,CACP,IACG,CACP,CAAC;AACJ,CAAC,CAAC;AAEF,eAAe,eAAe,CAAC","sourcesContent":["import { getLinkProps, resolveRouteByRole, useIntl } from '@openedx/frontend-base';\nimport { Button, Icon, Hyperlink } from '@openedx/paragon';\nimport { ArrowBackIos } from '@openedx/paragon/icons';\n\nimport { instructorDashboardUrl } from '@src/data/services/lms/urls';\nimport { useGradebookNavigation } from '@src/data/gradebookNavigationContext';\nimport useGradebookHeaderData from './hooks';\nimport messages from './messages';\n\nconst instructorDashboardRole = 'org.openedx.frontend.role.instructorDashboard';\n\nexport const GradebookHeader = () => {\n const { formatMessage } = useIntl();\n const {\n areGradesFrozen,\n canUserViewGradebook,\n courseId,\n handleToggleViewClick,\n showBulkManagement,\n toggleViewMessage,\n } = useGradebookHeaderData();\n const { onBack } = useGradebookNavigation();\n // Prefer the instructor dashboard route if the running site provides one,\n // so navigation stays within the SPA; otherwise fall back to a full page\n // load of the legacy LMS dashboard. Skip resolving when the host provides\n // its own onBack or the courseId isn't ready — resolveRouteByRole throws\n // \"Missing :courseId param\" otherwise.\n const dashboardRoute = (!onBack && courseId)\n ? resolveRouteByRole(instructorDashboardRole, { courseId })\n : null;\n const backLinkContent = (\n <>\n <Icon src={ArrowBackIos} className=\"mr-1\" size=\"sm\" />\n {formatMessage(messages.backToDashboard)}\n </>\n );\n const renderBackLink = () => {\n // Host-provided handler wins over URL navigation (e.g. CCX Coach tab).\n if (onBack) {\n return (\n <Button variant=\"link\" className=\"mb-3 p-0\" onClick={onBack}>\n {backLinkContent}\n </Button>\n );\n }\n return (\n <Hyperlink\n {...getLinkProps(dashboardRoute?.url ?? instructorDashboardUrl(courseId))}\n className=\"mb-3\"\n >\n {backLinkContent}\n </Hyperlink>\n );\n };\n return (\n <div className=\"gradebook-header mt-2\">\n {renderBackLink()}\n <h2 className=\"text-primary-500 mb-0\">{formatMessage(messages.gradebook)}</h2>\n <p className=\"small text-break mb-3\">{courseId}</p>\n <div className=\"subtitle-row d-flex justify-content-between align-items-center\">\n {showBulkManagement && (\n <Button variant=\"tertiary\" onClick={handleToggleViewClick}>\n {formatMessage(toggleViewMessage)}\n </Button>\n )}\n </div>\n {areGradesFrozen && (\n <div className=\"alert alert-warning\" role=\"alert\">\n {formatMessage(messages.frozenWarning)}\n </div>\n )}\n {(canUserViewGradebook === false) && (\n <div className=\"alert alert-warning\" role=\"alert\">\n {formatMessage(messages.unauthorizedWarning)}\n </div>\n )}\n </div>\n );\n};\n\nexport default GradebookHeader;\n"]}
|
|
@@ -11,7 +11,7 @@ import messages from './messages';
|
|
|
11
11
|
export const FilterMenuToggle = () => {
|
|
12
12
|
const { toggleFilterMenu } = useGradebookUi();
|
|
13
13
|
const { formatMessage } = useIntl();
|
|
14
|
-
return (_jsxs(Button, { id: "edit-filters-btn", className: "btn-primary align-self-start", onClick: toggleFilterMenu, children: [_jsx(Icon, { src: FilterAlt, className: "mr-1" }), " ", formatMessage(messages.editFilters)] }));
|
|
14
|
+
return (_jsxs(Button, { id: "edit-filters-btn", className: "btn-primary align-self-start", onClick: toggleFilterMenu, size: "sm", children: [_jsx(Icon, { src: FilterAlt, className: "mr-1", size: "sm" }), " ", formatMessage(messages.editFilters)] }));
|
|
15
15
|
};
|
|
16
16
|
FilterMenuToggle.propTypes = {};
|
|
17
17
|
export default FilterMenuToggle;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/components/GradesView/FilterMenuToggle/index.jsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAEnD,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAE9D,OAAO,QAAQ,MAAM,YAAY,CAAC;AAElC;;;GAGG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,GAAG,EAAE;IACnC,MAAM,EAAE,gBAAgB,EAAE,GAAG,cAAc,EAAE,CAAC;IAC9C,MAAM,EAAE,aAAa,EAAE,GAAG,OAAO,EAAE,CAAC;IACpC,OAAO,CACL,MAAC,MAAM,IACL,EAAE,EAAC,kBAAkB,EACrB,SAAS,EAAC,8BAA8B,EACxC,OAAO,EAAE,gBAAgB,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/components/GradesView/FilterMenuToggle/index.jsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAEnD,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAE9D,OAAO,QAAQ,MAAM,YAAY,CAAC;AAElC;;;GAGG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,GAAG,EAAE;IACnC,MAAM,EAAE,gBAAgB,EAAE,GAAG,cAAc,EAAE,CAAC;IAC9C,MAAM,EAAE,aAAa,EAAE,GAAG,OAAO,EAAE,CAAC;IACpC,OAAO,CACL,MAAC,MAAM,IACL,EAAE,EAAC,kBAAkB,EACrB,SAAS,EAAC,8BAA8B,EACxC,OAAO,EAAE,gBAAgB,EACzB,IAAI,EAAC,IAAI,aAET,KAAC,IAAI,IAAC,GAAG,EAAE,SAAS,EAAE,SAAS,EAAC,MAAM,EAAC,IAAI,EAAC,IAAI,GAAG,OAAE,aAAa,CAAC,QAAQ,CAAC,WAAW,CAAC,IACjF,CACV,CAAC;AACJ,CAAC,CAAC;AAEF,gBAAgB,CAAC,SAAS,GAAG,EAAE,CAAC;AAEhC,eAAe,gBAAgB,CAAC","sourcesContent":["import { Button, Icon } from '@openedx/paragon';\nimport { useIntl } from '@openedx/frontend-base';\nimport { FilterAlt } from '@openedx/paragon/icons';\n\nimport { useGradebookUi } from '@src/data/gradebookUiContext';\n\nimport messages from './messages';\n\n/**\n * Controls for filtering the GradebookTable. Contains the \"Edit Filters\" button for opening the filter drawer\n * as well as the search box for searching by username/email.\n */\nexport const FilterMenuToggle = () => {\n const { toggleFilterMenu } = useGradebookUi();\n const { formatMessage } = useIntl();\n return (\n <Button\n id=\"edit-filters-btn\"\n className=\"btn-primary align-self-start\"\n onClick={toggleFilterMenu}\n size=\"sm\"\n >\n <Icon src={FilterAlt} className=\"mr-1\" size=\"sm\" /> {formatMessage(messages.editFilters)}\n </Button>\n );\n};\n\nFilterMenuToggle.propTypes = {};\n\nexport default FilterMenuToggle;\n"]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function useImportResultToastData(): {
|
|
2
|
+
action: {
|
|
3
|
+
label: string;
|
|
4
|
+
onClick: () => void;
|
|
5
|
+
};
|
|
6
|
+
onClose: () => void;
|
|
7
|
+
show: boolean;
|
|
8
|
+
toastProps: {
|
|
9
|
+
autohide: boolean;
|
|
10
|
+
} | {
|
|
11
|
+
autohide?: undefined;
|
|
12
|
+
};
|
|
13
|
+
description: string;
|
|
14
|
+
};
|
|
15
|
+
export default useImportResultToastData;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { useIntl } from '@openedx/frontend-base';
|
|
2
|
+
import { useGradebookUi } from '../../../data/gradebookUiContext';
|
|
3
|
+
import { views } from '../../../data/constants/app';
|
|
4
|
+
import messages from './messages';
|
|
5
|
+
/**
|
|
6
|
+
* <ImportResultToast />
|
|
7
|
+
* Reports the outcome of a grade upload, whether it succeeded or failed, and links to the
|
|
8
|
+
* Bulk Management History tab.
|
|
9
|
+
*/
|
|
10
|
+
export const useImportResultToastData = () => {
|
|
11
|
+
const { formatMessage } = useIntl();
|
|
12
|
+
const { showImportSuccessToast, setShowImportSuccessToast, showImportErrorToast, setShowImportErrorToast, csvUploadErrorMessages, setActiveView, } = useGradebookUi();
|
|
13
|
+
const details = csvUploadErrorMessages.join('; ');
|
|
14
|
+
// A failure needs its message to say anything useful, so it is only reportable once both
|
|
15
|
+
// are present. A flagged failure suppresses the success claim either way: with no message
|
|
16
|
+
// there is nothing to show, but reporting success alongside a known failure is the one
|
|
17
|
+
// outcome never worth risking.
|
|
18
|
+
const isError = !!showImportErrorToast && !!details;
|
|
19
|
+
const isSuccess = !showImportErrorToast && !!showImportSuccessToast;
|
|
20
|
+
// Each outcome requires its own flag, so neither can borrow the other's wording.
|
|
21
|
+
let description = '';
|
|
22
|
+
if (isError) {
|
|
23
|
+
description = formatMessage(messages.errorDescription, { details });
|
|
24
|
+
}
|
|
25
|
+
else if (isSuccess) {
|
|
26
|
+
description = formatMessage(messages.successDescription);
|
|
27
|
+
}
|
|
28
|
+
const hide = () => {
|
|
29
|
+
setShowImportSuccessToast(false);
|
|
30
|
+
setShowImportErrorToast(false);
|
|
31
|
+
};
|
|
32
|
+
return {
|
|
33
|
+
action: {
|
|
34
|
+
label: formatMessage(messages.showHistoryViewBtn),
|
|
35
|
+
onClick: () => {
|
|
36
|
+
setActiveView(views.bulkManagementHistory);
|
|
37
|
+
hide();
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
onClose: hide,
|
|
41
|
+
show: isError || isSuccess,
|
|
42
|
+
// Paragon holds `autohide` in state so that hovering or focusing the toast pauses the
|
|
43
|
+
// timer, and spreads extra props over it -- so passing the prop at all pins the value
|
|
44
|
+
// and defeats that pause. Only a failure needs pinning, to wait for the reader to act;
|
|
45
|
+
// a success is left to Paragon, which lets the reader hold it open.
|
|
46
|
+
toastProps: isError ? { autohide: false } : {},
|
|
47
|
+
description,
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
export default useImportResultToastData;
|
|
51
|
+
//# sourceMappingURL=hooks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks.js","sourceRoot":"","sources":["../../../../src/components/GradesView/ImportResultToast/hooks.js"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AAEjD,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAC9D,OAAO,EAAE,KAAK,EAAE,MAAM,yBAAyB,CAAC;AAChD,OAAO,QAAQ,MAAM,YAAY,CAAC;AAElC;;;;GAIG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,GAAG,EAAE;IAC3C,MAAM,EAAE,aAAa,EAAE,GAAG,OAAO,EAAE,CAAC;IAEpC,MAAM,EACJ,sBAAsB,EACtB,yBAAyB,EACzB,oBAAoB,EACpB,uBAAuB,EACvB,sBAAsB,EACtB,aAAa,GACd,GAAG,cAAc,EAAE,CAAC;IAErB,MAAM,OAAO,GAAG,sBAAsB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAElD,yFAAyF;IACzF,0FAA0F;IAC1F,uFAAuF;IACvF,+BAA+B;IAC/B,MAAM,OAAO,GAAG,CAAC,CAAC,oBAAoB,IAAI,CAAC,CAAC,OAAO,CAAC;IACpD,MAAM,SAAS,GAAG,CAAC,oBAAoB,IAAI,CAAC,CAAC,sBAAsB,CAAC;IAEpE,iFAAiF;IACjF,IAAI,WAAW,GAAG,EAAE,CAAC;IACrB,IAAI,OAAO,EAAE,CAAC;QACZ,WAAW,GAAG,aAAa,CAAC,QAAQ,CAAC,gBAAgB,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;IACtE,CAAC;SAAM,IAAI,SAAS,EAAE,CAAC;QACrB,WAAW,GAAG,aAAa,CAAC,QAAQ,CAAC,kBAAkB,CAAC,CAAC;IAC3D,CAAC;IAED,MAAM,IAAI,GAAG,GAAG,EAAE;QAChB,yBAAyB,CAAC,KAAK,CAAC,CAAC;QACjC,uBAAuB,CAAC,KAAK,CAAC,CAAC;IACjC,CAAC,CAAC;IAEF,OAAO;QACL,MAAM,EAAE;YACN,KAAK,EAAE,aAAa,CAAC,QAAQ,CAAC,kBAAkB,CAAC;YACjD,OAAO,EAAE,GAAG,EAAE;gBACZ,aAAa,CAAC,KAAK,CAAC,qBAAqB,CAAC,CAAC;gBAC3C,IAAI,EAAE,CAAC;YACT,CAAC;SACF;QACD,OAAO,EAAE,IAAI;QACb,IAAI,EAAE,OAAO,IAAI,SAAS;QAC1B,sFAAsF;QACtF,sFAAsF;QACtF,uFAAuF;QACvF,oEAAoE;QACpE,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE;QAC9C,WAAW;KACZ,CAAC;AACJ,CAAC,CAAC;AAEF,eAAe,wBAAwB,CAAC","sourcesContent":["import { useIntl } from '@openedx/frontend-base';\n\nimport { useGradebookUi } from '@src/data/gradebookUiContext';\nimport { views } from '@src/data/constants/app';\nimport messages from './messages';\n\n/**\n * <ImportResultToast />\n * Reports the outcome of a grade upload, whether it succeeded or failed, and links to the\n * Bulk Management History tab.\n */\nexport const useImportResultToastData = () => {\n const { formatMessage } = useIntl();\n\n const {\n showImportSuccessToast,\n setShowImportSuccessToast,\n showImportErrorToast,\n setShowImportErrorToast,\n csvUploadErrorMessages,\n setActiveView,\n } = useGradebookUi();\n\n const details = csvUploadErrorMessages.join('; ');\n\n // A failure needs its message to say anything useful, so it is only reportable once both\n // are present. A flagged failure suppresses the success claim either way: with no message\n // there is nothing to show, but reporting success alongside a known failure is the one\n // outcome never worth risking.\n const isError = !!showImportErrorToast && !!details;\n const isSuccess = !showImportErrorToast && !!showImportSuccessToast;\n\n // Each outcome requires its own flag, so neither can borrow the other's wording.\n let description = '';\n if (isError) {\n description = formatMessage(messages.errorDescription, { details });\n } else if (isSuccess) {\n description = formatMessage(messages.successDescription);\n }\n\n const hide = () => {\n setShowImportSuccessToast(false);\n setShowImportErrorToast(false);\n };\n\n return {\n action: {\n label: formatMessage(messages.showHistoryViewBtn),\n onClick: () => {\n setActiveView(views.bulkManagementHistory);\n hide();\n },\n },\n onClose: hide,\n show: isError || isSuccess,\n // Paragon holds `autohide` in state so that hovering or focusing the toast pauses the\n // timer, and spreads extra props over it -- so passing the prop at all pins the value\n // and defeats that pause. Only a failure needs pinning, to wait for the reader to act;\n // a success is left to Paragon, which lets the reader hold it open.\n toastProps: isError ? { autohide: false } : {},\n description,\n };\n};\n\nexport default useImportResultToastData;\n"]}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useRef } from 'react';
|
|
3
|
+
import { Toast } from '@openedx/paragon';
|
|
4
|
+
import useImportResultToastData from './hooks';
|
|
5
|
+
/**
|
|
6
|
+
* <ImportResultToast />
|
|
7
|
+
* Toast component triggered by a grade upload, reporting either that it succeeded or that
|
|
8
|
+
* it failed -- rejected by the server, or applied no grades at all.
|
|
9
|
+
* Provides a link to view the Bulk Management History tab.
|
|
10
|
+
*/
|
|
11
|
+
export const ImportResultToast = () => {
|
|
12
|
+
const { action, onClose, show, toastProps, description, } = useImportResultToastData();
|
|
13
|
+
// A failure waits to be dismissed, so switching tabs -- which unmounts this view -- has
|
|
14
|
+
// to retire it. Otherwise it re-announces itself on every return to the Grades tab.
|
|
15
|
+
// The ref keeps the cleanup on unmount only, rather than on every re-render.
|
|
16
|
+
const onCloseRef = useRef(onClose);
|
|
17
|
+
onCloseRef.current = onClose;
|
|
18
|
+
useEffect(() => () => onCloseRef.current(), []);
|
|
19
|
+
return (_jsx(Toast, Object.assign({ action: action, onClose: onClose, show: show }, toastProps, { children: description })));
|
|
20
|
+
};
|
|
21
|
+
ImportResultToast.propTypes = {};
|
|
22
|
+
export default ImportResultToast;
|
|
23
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/components/GradesView/ImportResultToast/index.jsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AAE1C,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAEzC,OAAO,wBAAwB,MAAM,SAAS,CAAC;AAE/C;;;;;GAKG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,GAAG,EAAE;IACpC,MAAM,EACJ,MAAM,EACN,OAAO,EACP,IAAI,EACJ,UAAU,EACV,WAAW,GACZ,GAAG,wBAAwB,EAAE,CAAC;IAE/B,wFAAwF;IACxF,oFAAoF;IACpF,6EAA6E;IAC7E,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IACnC,UAAU,CAAC,OAAO,GAAG,OAAO,CAAC;IAC7B,SAAS,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC;IAEhD,OAAO,CACL,KAAC,KAAK,kBACJ,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,IAAI,IACN,UAAU,cAEb,WAAW,IACN,CACT,CAAC;AACJ,CAAC,CAAC;AAEF,iBAAiB,CAAC,SAAS,GAAG,EAAE,CAAC;AAEjC,eAAe,iBAAiB,CAAC","sourcesContent":["import { useEffect, useRef } from 'react';\n\nimport { Toast } from '@openedx/paragon';\n\nimport useImportResultToastData from './hooks';\n\n/**\n * <ImportResultToast />\n * Toast component triggered by a grade upload, reporting either that it succeeded or that\n * it failed -- rejected by the server, or applied no grades at all.\n * Provides a link to view the Bulk Management History tab.\n */\nexport const ImportResultToast = () => {\n const {\n action,\n onClose,\n show,\n toastProps,\n description,\n } = useImportResultToastData();\n\n // A failure waits to be dismissed, so switching tabs -- which unmounts this view -- has\n // to retire it. Otherwise it re-announces itself on every return to the Grades tab.\n // The ref keeps the cleanup on unmount only, rather than on every re-render.\n const onCloseRef = useRef(onClose);\n onCloseRef.current = onClose;\n useEffect(() => () => onCloseRef.current(), []);\n\n return (\n <Toast\n action={action}\n onClose={onClose}\n show={show}\n {...toastProps}\n >\n {description}\n </Toast>\n );\n};\n\nImportResultToast.propTypes = {};\n\nexport default ImportResultToast;\n"]}
|
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
export default messages;
|
|
2
2
|
declare namespace messages {
|
|
3
|
-
namespace
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
let
|
|
7
|
-
export { description_1 as description };
|
|
3
|
+
namespace successDescription {
|
|
4
|
+
let id: string;
|
|
5
|
+
let defaultMessage: string;
|
|
6
|
+
let description: string;
|
|
8
7
|
}
|
|
9
|
-
namespace
|
|
8
|
+
namespace errorDescription {
|
|
10
9
|
let id_1: string;
|
|
11
10
|
export { id_1 as id };
|
|
12
11
|
let defaultMessage_1: string;
|
|
13
12
|
export { defaultMessage_1 as defaultMessage };
|
|
13
|
+
let description_1: string;
|
|
14
|
+
export { description_1 as description };
|
|
15
|
+
}
|
|
16
|
+
namespace showHistoryViewBtn {
|
|
17
|
+
let id_2: string;
|
|
18
|
+
export { id_2 as id };
|
|
19
|
+
let defaultMessage_2: string;
|
|
20
|
+
export { defaultMessage_2 as defaultMessage };
|
|
14
21
|
let description_2: string;
|
|
15
22
|
export { description_2 as description };
|
|
16
23
|
}
|
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import { defineMessages } from '@openedx/frontend-base';
|
|
2
|
+
// The success ids keep their original `ImportSuccessToast` names: they are already in the
|
|
3
|
+
// translation pipeline, and renaming an id orphans every translation of it.
|
|
2
4
|
const messages = defineMessages({
|
|
3
|
-
|
|
5
|
+
successDescription: {
|
|
4
6
|
id: 'gradebook.GradesView.ImportSuccessToast.description',
|
|
5
7
|
defaultMessage: 'Import Successful! Grades will be updated momentarily.',
|
|
6
8
|
description: 'A message congratulating a successful Import of grades',
|
|
7
9
|
},
|
|
10
|
+
errorDescription: {
|
|
11
|
+
id: 'gradebook.GradesView.ImportResultToast.errorDescription',
|
|
12
|
+
defaultMessage: 'Import failed. {details}',
|
|
13
|
+
description: 'Message shown when a grade import could not be applied',
|
|
14
|
+
},
|
|
8
15
|
showHistoryViewBtn: {
|
|
9
16
|
id: 'gradebook.GradesView.ImportSuccessToast.showHistoryViewBtn',
|
|
10
17
|
defaultMessage: 'View Activity Log',
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"messages.js","sourceRoot":"","sources":["../../../../src/components/GradesView/ImportResultToast/messages.js"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAExD,0FAA0F;AAC1F,4EAA4E;AAC5E,MAAM,QAAQ,GAAG,cAAc,CAAC;IAC9B,kBAAkB,EAAE;QAClB,EAAE,EAAE,qDAAqD;QACzD,cAAc,EAAE,wDAAwD;QACxE,WAAW,EAAE,wDAAwD;KACtE;IACD,gBAAgB,EAAE;QAChB,EAAE,EAAE,yDAAyD;QAC7D,cAAc,EAAE,0BAA0B;QAC1C,WAAW,EAAE,wDAAwD;KACtE;IACD,kBAAkB,EAAE;QAClB,EAAE,EAAE,4DAA4D;QAChE,cAAc,EAAE,mBAAmB;QACnC,WAAW,EAAE,4EAA4E;KAC1F;CACF,CAAC,CAAC;AAEH,eAAe,QAAQ,CAAC","sourcesContent":["import { defineMessages } from '@openedx/frontend-base';\n\n// The success ids keep their original `ImportSuccessToast` names: they are already in the\n// translation pipeline, and renaming an id orphans every translation of it.\nconst messages = defineMessages({\n successDescription: {\n id: 'gradebook.GradesView.ImportSuccessToast.description',\n defaultMessage: 'Import Successful! Grades will be updated momentarily.',\n description: 'A message congratulating a successful Import of grades',\n },\n errorDescription: {\n id: 'gradebook.GradesView.ImportResultToast.errorDescription',\n defaultMessage: 'Import failed. {details}',\n description: 'Message shown when a grade import could not be applied',\n },\n showHistoryViewBtn: {\n id: 'gradebook.GradesView.ImportSuccessToast.showHistoryViewBtn',\n defaultMessage: 'View Activity Log',\n description: 'The text on a button that loads a view of the Bulk Management Activity Log',\n },\n});\n\nexport default messages;\n"]}
|
|
@@ -13,7 +13,7 @@ export const ScoreViewInput = () => {
|
|
|
13
13
|
const { gradeFormat } = useGradeData();
|
|
14
14
|
const { setGradeFormat } = useGradebookUi();
|
|
15
15
|
const toggleFormat = (e) => setGradeFormat(e.target.value);
|
|
16
|
-
return (_jsxs(Form.Group, { controlId: "ScoreView", children: [_jsxs(Form.Label, { children: [formatMessage(messages.scoreView), ":"] }), _jsxs(Form.Control, { as: "select", value: gradeFormat, onChange: toggleFormat, children: [_jsx("option", { value: "percent", children: formatMessage(messages.percent) }), _jsx("option", { value: "absolute", children: formatMessage(messages.absolute) })] })] }));
|
|
16
|
+
return (_jsxs(Form.Group, { controlId: "ScoreView", className: "d-flex align-items-center", children: [_jsxs(Form.Label, { className: "mr-2 font-weight-normal", children: [formatMessage(messages.scoreView), ":"] }), _jsxs(Form.Control, { as: "select", value: gradeFormat, onChange: toggleFormat, size: "sm", children: [_jsx("option", { value: "percent", children: formatMessage(messages.percent) }), _jsx("option", { value: "absolute", children: formatMessage(messages.absolute) })] })] }));
|
|
17
17
|
};
|
|
18
18
|
ScoreViewInput.propTypes = {};
|
|
19
19
|
export default ScoreViewInput;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/components/GradesView/ScoreViewInput/index.jsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AACxC,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AAEjD,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAC9D,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,QAAQ,MAAM,YAAY,CAAC;AAElC;;;GAGG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,GAAG,EAAE;IACjC,MAAM,EAAE,aAAa,EAAE,GAAG,OAAO,EAAE,CAAC;IACpC,MAAM,EAAE,WAAW,EAAE,GAAG,YAAY,EAAE,CAAC;IACvC,MAAM,EAAE,cAAc,EAAE,GAAG,cAAc,EAAE,CAAC;IAC5C,MAAM,YAAY,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,cAAc,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC3D,OAAO,CACL,MAAC,IAAI,CAAC,KAAK,IAAC,SAAS,EAAC,WAAW,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/components/GradesView/ScoreViewInput/index.jsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AACxC,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AAEjD,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAC9D,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,QAAQ,MAAM,YAAY,CAAC;AAElC;;;GAGG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,GAAG,EAAE;IACjC,MAAM,EAAE,aAAa,EAAE,GAAG,OAAO,EAAE,CAAC;IACpC,MAAM,EAAE,WAAW,EAAE,GAAG,YAAY,EAAE,CAAC;IACvC,MAAM,EAAE,cAAc,EAAE,GAAG,cAAc,EAAE,CAAC;IAC5C,MAAM,YAAY,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,cAAc,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC3D,OAAO,CACL,MAAC,IAAI,CAAC,KAAK,IAAC,SAAS,EAAC,WAAW,EAAC,SAAS,EAAC,2BAA2B,aACrE,MAAC,IAAI,CAAC,KAAK,IAAC,SAAS,EAAC,yBAAyB,aAAE,aAAa,CAAC,QAAQ,CAAC,SAAS,CAAC,SAAe,EACjG,MAAC,IAAI,CAAC,OAAO,IACX,EAAE,EAAC,QAAQ,EACX,KAAK,EAAE,WAAW,EAClB,QAAQ,EAAE,YAAY,EACtB,IAAI,EAAC,IAAI,aAET,iBAAQ,KAAK,EAAC,SAAS,YAAE,aAAa,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAU,EAClE,iBAAQ,KAAK,EAAC,UAAU,YAAE,aAAa,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAU,IACvD,IACJ,CACd,CAAC;AACJ,CAAC,CAAC;AACF,cAAc,CAAC,SAAS,GAAG,EAAE,CAAC;AAE9B,eAAe,cAAc,CAAC","sourcesContent":["import { Form } from '@openedx/paragon';\nimport { useIntl } from '@openedx/frontend-base';\n\nimport { useGradebookUi } from '@src/data/gradebookUiContext';\nimport { useGradeData } from '../data/hooks';\nimport messages from './messages';\n\n/**\n * <ScoreViewInput />\n * Select control for grade format (percent vs absolute).\n */\nexport const ScoreViewInput = () => {\n const { formatMessage } = useIntl();\n const { gradeFormat } = useGradeData();\n const { setGradeFormat } = useGradebookUi();\n const toggleFormat = (e) => setGradeFormat(e.target.value);\n return (\n <Form.Group controlId=\"ScoreView\" className=\"d-flex align-items-center\">\n <Form.Label className=\"mr-2 font-weight-normal\">{formatMessage(messages.scoreView)}:</Form.Label>\n <Form.Control\n as=\"select\"\n value={gradeFormat}\n onChange={toggleFormat}\n size=\"sm\"\n >\n <option value=\"percent\">{formatMessage(messages.percent)}</option>\n <option value=\"absolute\">{formatMessage(messages.absolute)}</option>\n </Form.Control>\n </Form.Group>\n );\n};\nScoreViewInput.propTypes = {};\n\nexport default ScoreViewInput;\n"]}
|
|
@@ -8,6 +8,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
10
|
import { useMemo } from 'react';
|
|
11
|
+
import { useIntl } from '@openedx/frontend-base';
|
|
11
12
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
|
12
13
|
import lms from '../../../data/services/lms';
|
|
13
14
|
import { sortAlphaAsc } from '../../../data/formatUtils';
|
|
@@ -20,6 +21,7 @@ import { trackGradesDisplayed, trackGradeOverrideSucceeded, trackGradeOverrideFa
|
|
|
20
21
|
// Cross-feature invalidation: a successful CSV upload refreshes the bulk-operation
|
|
21
22
|
// history owned by the BulkManagementHistoryView feature.
|
|
22
23
|
import { bulkOperationHistoryQueryKeys } from '../../BulkManagementHistoryView/data/queryKeys';
|
|
24
|
+
import messages from '../messages';
|
|
23
25
|
import { getGradeOverrideHistory, getGrades, getGradesPage } from './api';
|
|
24
26
|
import { gradeMutationKeys, gradeOverrideHistoryQueryKeys, gradesQueryKeys } from './queryKeys';
|
|
25
27
|
import { buildGradesFetchParams } from './utils';
|
|
@@ -160,9 +162,10 @@ export const useUpdateGrades = () => {
|
|
|
160
162
|
* still runs on both success and failure.
|
|
161
163
|
*/
|
|
162
164
|
export const useSubmitImportGradesButtonData = () => {
|
|
165
|
+
const { formatMessage } = useIntl();
|
|
163
166
|
const queryClient = useQueryClient();
|
|
164
167
|
const courseId = useCourseId();
|
|
165
|
-
const { setShowImportSuccessToast, resetCsvUpload, markCsvUploadSuccess, setCsvUploadErrors, } = useGradebookUi();
|
|
168
|
+
const { setShowImportSuccessToast, setShowImportErrorToast, resetCsvUpload, markCsvUploadSuccess, setCsvUploadErrors, } = useGradebookUi();
|
|
166
169
|
const mutation = useMutation({
|
|
167
170
|
mutationKey: gradeMutationKeys.uploadGradeCsv,
|
|
168
171
|
mutationFn: (formData) => lms.api.uploadGradeCsv(courseId, formData),
|
|
@@ -185,8 +188,9 @@ export const useSubmitImportGradesButtonData = () => {
|
|
|
185
188
|
setCsvUploadErrors(errorMessages);
|
|
186
189
|
}
|
|
187
190
|
else {
|
|
188
|
-
setCsvUploadErrors([
|
|
191
|
+
setCsvUploadErrors([formatMessage(messages.unknownImportError)]);
|
|
189
192
|
}
|
|
193
|
+
setShowImportErrorToast(true);
|
|
190
194
|
},
|
|
191
195
|
});
|
|
192
196
|
return (formData) => mutation.mutateAsync(formData).catch(() => { });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"apiHook.js","sourceRoot":"","sources":["../../../../src/components/GradesView/data/apiHook.ts"],"names":[],"mappings":"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","sourcesContent":["import { useMemo } from 'react';\nimport { useMutation, useQueryClient } from '@tanstack/react-query';\n\nimport lms from '@src/data/services/lms';\nimport { sortAlphaAsc } from '@src/data/formatUtils';\nimport { filtersSnapshot } from '@src/data/filtersSnapshot';\nimport { useGradebookUi } from '@src/data/gradebookUiContext';\nimport { useCourseIdWithGate } from '@src/data/apiHook';\nimport { useCourseId } from '@src/data/courseIdContext';\nimport { useGradebookQuery } from '@src/data/query';\nimport {\n trackGradesDisplayed,\n trackGradeOverrideSucceeded,\n trackGradeOverrideFailed,\n trackUploadOverrideSucceeded,\n trackUploadOverrideFailed,\n} from '@src/data/services/segment/events';\n// Cross-feature invalidation: a successful CSV upload refreshes the bulk-operation\n// history owned by the BulkManagementHistoryView feature.\nimport { bulkOperationHistoryQueryKeys } from '@src/components/BulkManagementHistoryView/data/queryKeys';\n\nimport { getGradeOverrideHistory, getGrades, getGradesPage } from './api';\nimport { gradeMutationKeys, gradeOverrideHistoryQueryKeys, gradesQueryKeys } from './queryKeys';\nimport { buildGradesFetchParams } from './utils';\n\n/**\n * Builds the `getGrades` params from the live `filtersSnapshot`. Reads at fetch\n * time (not from the query key) so imperative refetches pick up the latest\n * filter values — the FiltersProvider keeps `filtersSnapshot` in sync.\n */\nconst buildGradebookDataParams = () => buildGradesFetchParams(filtersSnapshot);\n\n/**\n * useGrades()\n * Single-responsibility query for the main gradebook rows. Caller supplies\n * `courseId`, the current pagination cursor (`gradesPageEndpoint` from\n * `GradebookUi`, or `null` for the base/unpaged page), and decides when it may\n * run via `enabled`. The base grades key is a prefix of the paged key, so\n * invalidating `byCourse(courseId)` still refetches any page. The first\n * (unpaged) page reads the current filter params at fetch time (so an\n * imperative `invalidateQueries` on filter apply / search refetches with the\n * latest values); a non-null cursor fetches that opaque prev/next URL —\n * replacing the legacy `fetchPrevNextGrades` thunk.\n */\nexport const useGrades = (\n courseId: string,\n gradesPageEndpoint: string | null,\n { enabled = true }: { enabled?: boolean } = {},\n) => useGradebookQuery({\n queryKey: [...gradesQueryKeys.byCourse(courseId), gradesPageEndpoint ?? 'base'],\n queryFn: async () => {\n const data = gradesPageEndpoint\n ? await getGradesPage(gradesPageEndpoint)\n : await getGrades({ courseId, ...buildGradebookDataParams() });\n // Mirrors the legacy `receivedGrades` redux-beacon trigger: one event per\n // actual fetch, with the filters it ran under and the resulting cursors.\n trackGradesDisplayed(courseId, {\n assignmentType: filtersSnapshot.assignmentType,\n cohort: filtersSnapshot.cohort,\n track: filtersSnapshot.track,\n prev: data.previous,\n next: data.next,\n });\n return data;\n },\n enabled: !!courseId && enabled,\n});\n\n/** One subsection cell within a learner's grade row. */\nexport interface SubsectionBreakdown {\n label: string;\n category?: string;\n subsection_name?: string;\n module_id?: string;\n score_possible?: number;\n score_earned?: number;\n percent?: number;\n attempted?: boolean;\n [key: string]: unknown;\n}\n\n/** One learner's grade row from the gradebook response. */\nexport interface GradeEntry {\n user_id?: number;\n username?: string;\n external_user_key?: string;\n email?: string;\n percent?: number;\n section_breakdown: SubsectionBreakdown[];\n [key: string]: unknown;\n}\n\n/** Shaped grades read-model derived from the `useGrades` query. */\nexport interface GradesData {\n results: GradeEntry[];\n prevPage: string | null;\n nextPage: string | null;\n totalUsersCount: number;\n filteredUsersCount: number;\n isFetching: boolean;\n isError: boolean;\n isSuccess: boolean;\n}\n\n/**\n * useGradesData()\n * Shapes the `useGrades` query into the read-model the grades table / counts /\n * pagination consume — the React Query equivalent of the Redux `grades` slice\n * fields that `useGradebookData` used to mirror. Results are sorted by username\n * (matching the legacy `fetching.received` handling).\n */\nexport const useGradesData = (): GradesData => {\n const { courseId, enabled } = useCourseIdWithGate();\n const { gradesPageEndpoint } = useGradebookUi();\n const query = useGrades(courseId, gradesPageEndpoint, { enabled });\n const data = query.data as {\n results?: GradeEntry[];\n previous?: string | null;\n next?: string | null;\n total_users_count?: number;\n filtered_users_count?: number;\n } | undefined;\n const results = useMemo(\n () => [...(data?.results ?? [])].sort(sortAlphaAsc),\n [data],\n );\n return {\n results,\n prevPage: data?.previous ?? null,\n nextPage: data?.next ?? null,\n totalUsersCount: data?.total_users_count ?? 0,\n filteredUsersCount: data?.filtered_users_count ?? 0,\n isFetching: query.isFetching,\n isError: query.isError,\n isSuccess: query.isSuccess,\n };\n};\n\n/**\n * useGradeOverrideHistory(subsectionId, userId)\n * Grade-override history for a single learner/subsection, used by the edit modal.\n * Enabled only once both identifiers are present (i.e. the modal has been opened\n * for a specific cell). `retry: false` because a `success: false` business error\n * is not worth retrying.\n */\nexport const useGradeOverrideHistory = (subsectionId?: string, userId?: string | number) => (\n useGradebookQuery({\n queryKey: gradeOverrideHistoryQueryKeys.byCell(subsectionId ?? null, userId ?? null),\n queryFn: () => getGradeOverrideHistory(subsectionId as string, userId as string | number),\n enabled: !!subsectionId && userId !== undefined && userId !== null,\n retry: false,\n })\n);\n\n/** Shape of the rejection thrown by `uploadGradeCsv` (an HTTP response, not an Error). */\ninterface CsvUploadError {\n status?: number;\n data?: { error_messages?: string[]; saved?: number; total?: number };\n}\n\n/** One grade-override entry sent to the gradebook bulk-update endpoint. */\ninterface GradeOverrideUpdate {\n grade: {\n comment: string;\n earned_graded_override: string | number;\n };\n usage_id: string | null;\n user_id: number | null;\n}\n\n/**\n * useUpdateGrades()\n * Grade-override save from the edit modal. The HTTP call runs through React Query;\n * success emits the Segment `update.*` event, shows the success banner, resets to\n * the first grades page, and invalidates the grades query (so the table reflects\n * the override) and the edited cell's override history (so reopening the modal\n * shows the new entry).\n *\n * The payload is captured as mutation variables when the trigger is called (same\n * shape as the old `selectors.app.editUpdateData`): the caller closes the modal\n * right after triggering the save, and a pending mutation picks up re-rendered\n * options, so a closure over `modalState` would read the reset (empty) state.\n *\n * Returns a no-arg trigger to match the legacy `useUpdateGrades()` call site.\n */\nexport const useUpdateGrades = () => {\n const queryClient = useQueryClient();\n const courseId = useCourseId();\n const { setShowSuccess, setGradesPageEndpoint, modalState } = useGradebookUi();\n const mutation = useMutation({\n mutationKey: gradeMutationKeys.updateGrades,\n mutationFn: (updateData: GradeOverrideUpdate[]) => lms.api.updateGradebookData(courseId, updateData),\n onSuccess: ({ data }, updateData) => {\n trackGradeOverrideSucceeded(courseId, data);\n setShowSuccess(true);\n setGradesPageEndpoint(null);\n queryClient.invalidateQueries({\n queryKey: gradesQueryKeys.byCourse(courseId),\n });\n updateData.forEach(({ usage_id: usageId, user_id: userId }) => {\n queryClient.invalidateQueries({\n queryKey: gradeOverrideHistoryQueryKeys.byCell(usageId, userId),\n });\n });\n },\n onError: (error) => {\n trackGradeOverrideFailed(courseId, error);\n },\n });\n return () => {\n mutation.mutate([{\n grade: {\n comment: modalState.reasonForChange,\n earned_graded_override: modalState.adjustedGradeValue,\n },\n usage_id: modalState.updateModuleId,\n user_id: modalState.updateUserId,\n }]);\n };\n};\n\n/**\n * useSubmitImportGradesButtonData()\n * Bulk grade-override CSV upload. The upload runs through React Query and, on\n * success, invalidates the bulk-operation-history query so the new upload shows up.\n * CSV progress/results/toast are React UI state (GradebookUi context) and success/\n * failure emit the Segment `uploadOverride.*` events.\n *\n * Returns `(formData) => Promise` whose promise always resolves (errors are handled\n * inside `onError`), matching the legacy thunk so the caller's `.then(clearInput)`\n * still runs on both success and failure.\n */\nexport const useSubmitImportGradesButtonData = () => {\n const queryClient = useQueryClient();\n const courseId = useCourseId();\n const {\n setShowImportSuccessToast,\n resetCsvUpload,\n markCsvUploadSuccess,\n setCsvUploadErrors,\n } = useGradebookUi();\n const mutation = useMutation<unknown, CsvUploadError, FormData>({\n mutationKey: gradeMutationKeys.uploadGradeCsv,\n mutationFn: (formData) => lms.api.uploadGradeCsv(courseId, formData),\n onMutate: () => {\n resetCsvUpload();\n },\n onSuccess: () => {\n markCsvUploadSuccess();\n setShowImportSuccessToast(true);\n trackUploadOverrideSucceeded(courseId);\n queryClient.invalidateQueries({\n queryKey: bulkOperationHistoryQueryKeys.byCourse(courseId),\n });\n },\n onError: (error) => {\n trackUploadOverrideFailed(courseId, error);\n if (error?.status === 200 && error?.data?.error_messages?.length) {\n const { error_messages: errorMessages } = error.data;\n setCsvUploadErrors(errorMessages);\n } else {\n setCsvUploadErrors(['Unknown error.']);\n }\n },\n });\n return (formData: FormData) => mutation.mutateAsync(formData).catch(() => {});\n};\n"]}
|
|
1
|
+
{"version":3,"file":"apiHook.js","sourceRoot":"","sources":["../../../../src/components/GradesView/data/apiHook.ts"],"names":[],"mappings":"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","sourcesContent":["import { useMemo } from 'react';\nimport { useIntl } from '@openedx/frontend-base';\nimport { useMutation, useQueryClient } from '@tanstack/react-query';\n\nimport lms from '@src/data/services/lms';\nimport { sortAlphaAsc } from '@src/data/formatUtils';\nimport { filtersSnapshot } from '@src/data/filtersSnapshot';\nimport { useGradebookUi } from '@src/data/gradebookUiContext';\nimport { useCourseIdWithGate } from '@src/data/apiHook';\nimport { useCourseId } from '@src/data/courseIdContext';\nimport { useGradebookQuery } from '@src/data/query';\nimport {\n trackGradesDisplayed,\n trackGradeOverrideSucceeded,\n trackGradeOverrideFailed,\n trackUploadOverrideSucceeded,\n trackUploadOverrideFailed,\n} from '@src/data/services/segment/events';\n// Cross-feature invalidation: a successful CSV upload refreshes the bulk-operation\n// history owned by the BulkManagementHistoryView feature.\nimport { bulkOperationHistoryQueryKeys } from '@src/components/BulkManagementHistoryView/data/queryKeys';\n\nimport messages from '../messages';\n\nimport { getGradeOverrideHistory, getGrades, getGradesPage } from './api';\nimport { gradeMutationKeys, gradeOverrideHistoryQueryKeys, gradesQueryKeys } from './queryKeys';\nimport { buildGradesFetchParams } from './utils';\n\n/**\n * Builds the `getGrades` params from the live `filtersSnapshot`. Reads at fetch\n * time (not from the query key) so imperative refetches pick up the latest\n * filter values — the FiltersProvider keeps `filtersSnapshot` in sync.\n */\nconst buildGradebookDataParams = () => buildGradesFetchParams(filtersSnapshot);\n\n/**\n * useGrades()\n * Single-responsibility query for the main gradebook rows. Caller supplies\n * `courseId`, the current pagination cursor (`gradesPageEndpoint` from\n * `GradebookUi`, or `null` for the base/unpaged page), and decides when it may\n * run via `enabled`. The base grades key is a prefix of the paged key, so\n * invalidating `byCourse(courseId)` still refetches any page. The first\n * (unpaged) page reads the current filter params at fetch time (so an\n * imperative `invalidateQueries` on filter apply / search refetches with the\n * latest values); a non-null cursor fetches that opaque prev/next URL —\n * replacing the legacy `fetchPrevNextGrades` thunk.\n */\nexport const useGrades = (\n courseId: string,\n gradesPageEndpoint: string | null,\n { enabled = true }: { enabled?: boolean } = {},\n) => useGradebookQuery({\n queryKey: [...gradesQueryKeys.byCourse(courseId), gradesPageEndpoint ?? 'base'],\n queryFn: async () => {\n const data = gradesPageEndpoint\n ? await getGradesPage(gradesPageEndpoint)\n : await getGrades({ courseId, ...buildGradebookDataParams() });\n // Mirrors the legacy `receivedGrades` redux-beacon trigger: one event per\n // actual fetch, with the filters it ran under and the resulting cursors.\n trackGradesDisplayed(courseId, {\n assignmentType: filtersSnapshot.assignmentType,\n cohort: filtersSnapshot.cohort,\n track: filtersSnapshot.track,\n prev: data.previous,\n next: data.next,\n });\n return data;\n },\n enabled: !!courseId && enabled,\n});\n\n/** One subsection cell within a learner's grade row. */\nexport interface SubsectionBreakdown {\n label: string;\n category?: string;\n subsection_name?: string;\n module_id?: string;\n score_possible?: number;\n score_earned?: number;\n percent?: number;\n attempted?: boolean;\n [key: string]: unknown;\n}\n\n/** One learner's grade row from the gradebook response. */\nexport interface GradeEntry {\n user_id?: number;\n username?: string;\n external_user_key?: string;\n email?: string;\n percent?: number;\n section_breakdown: SubsectionBreakdown[];\n [key: string]: unknown;\n}\n\n/** Shaped grades read-model derived from the `useGrades` query. */\nexport interface GradesData {\n results: GradeEntry[];\n prevPage: string | null;\n nextPage: string | null;\n totalUsersCount: number;\n filteredUsersCount: number;\n isFetching: boolean;\n isError: boolean;\n isSuccess: boolean;\n}\n\n/**\n * useGradesData()\n * Shapes the `useGrades` query into the read-model the grades table / counts /\n * pagination consume — the React Query equivalent of the Redux `grades` slice\n * fields that `useGradebookData` used to mirror. Results are sorted by username\n * (matching the legacy `fetching.received` handling).\n */\nexport const useGradesData = (): GradesData => {\n const { courseId, enabled } = useCourseIdWithGate();\n const { gradesPageEndpoint } = useGradebookUi();\n const query = useGrades(courseId, gradesPageEndpoint, { enabled });\n const data = query.data as {\n results?: GradeEntry[];\n previous?: string | null;\n next?: string | null;\n total_users_count?: number;\n filtered_users_count?: number;\n } | undefined;\n const results = useMemo(\n () => [...(data?.results ?? [])].sort(sortAlphaAsc),\n [data],\n );\n return {\n results,\n prevPage: data?.previous ?? null,\n nextPage: data?.next ?? null,\n totalUsersCount: data?.total_users_count ?? 0,\n filteredUsersCount: data?.filtered_users_count ?? 0,\n isFetching: query.isFetching,\n isError: query.isError,\n isSuccess: query.isSuccess,\n };\n};\n\n/**\n * useGradeOverrideHistory(subsectionId, userId)\n * Grade-override history for a single learner/subsection, used by the edit modal.\n * Enabled only once both identifiers are present (i.e. the modal has been opened\n * for a specific cell). `retry: false` because a `success: false` business error\n * is not worth retrying.\n */\nexport const useGradeOverrideHistory = (subsectionId?: string, userId?: string | number) => (\n useGradebookQuery({\n queryKey: gradeOverrideHistoryQueryKeys.byCell(subsectionId ?? null, userId ?? null),\n queryFn: () => getGradeOverrideHistory(subsectionId as string, userId as string | number),\n enabled: !!subsectionId && userId !== undefined && userId !== null,\n retry: false,\n })\n);\n\n/** Shape of the rejection thrown by `uploadGradeCsv` (an HTTP response, not an Error). */\ninterface CsvUploadError {\n status?: number;\n data?: { error_messages?: string[]; saved?: number; total?: number };\n}\n\n/** One grade-override entry sent to the gradebook bulk-update endpoint. */\ninterface GradeOverrideUpdate {\n grade: {\n comment: string;\n earned_graded_override: string | number;\n };\n usage_id: string | null;\n user_id: number | null;\n}\n\n/**\n * useUpdateGrades()\n * Grade-override save from the edit modal. The HTTP call runs through React Query;\n * success emits the Segment `update.*` event, shows the success banner, resets to\n * the first grades page, and invalidates the grades query (so the table reflects\n * the override) and the edited cell's override history (so reopening the modal\n * shows the new entry).\n *\n * The payload is captured as mutation variables when the trigger is called (same\n * shape as the old `selectors.app.editUpdateData`): the caller closes the modal\n * right after triggering the save, and a pending mutation picks up re-rendered\n * options, so a closure over `modalState` would read the reset (empty) state.\n *\n * Returns a no-arg trigger to match the legacy `useUpdateGrades()` call site.\n */\nexport const useUpdateGrades = () => {\n const queryClient = useQueryClient();\n const courseId = useCourseId();\n const { setShowSuccess, setGradesPageEndpoint, modalState } = useGradebookUi();\n const mutation = useMutation({\n mutationKey: gradeMutationKeys.updateGrades,\n mutationFn: (updateData: GradeOverrideUpdate[]) => lms.api.updateGradebookData(courseId, updateData),\n onSuccess: ({ data }, updateData) => {\n trackGradeOverrideSucceeded(courseId, data);\n setShowSuccess(true);\n setGradesPageEndpoint(null);\n queryClient.invalidateQueries({\n queryKey: gradesQueryKeys.byCourse(courseId),\n });\n updateData.forEach(({ usage_id: usageId, user_id: userId }) => {\n queryClient.invalidateQueries({\n queryKey: gradeOverrideHistoryQueryKeys.byCell(usageId, userId),\n });\n });\n },\n onError: (error) => {\n trackGradeOverrideFailed(courseId, error);\n },\n });\n return () => {\n mutation.mutate([{\n grade: {\n comment: modalState.reasonForChange,\n earned_graded_override: modalState.adjustedGradeValue,\n },\n usage_id: modalState.updateModuleId,\n user_id: modalState.updateUserId,\n }]);\n };\n};\n\n/**\n * useSubmitImportGradesButtonData()\n * Bulk grade-override CSV upload. The upload runs through React Query and, on\n * success, invalidates the bulk-operation-history query so the new upload shows up.\n * CSV progress/results/toast are React UI state (GradebookUi context) and success/\n * failure emit the Segment `uploadOverride.*` events.\n *\n * Returns `(formData) => Promise` whose promise always resolves (errors are handled\n * inside `onError`), matching the legacy thunk so the caller's `.then(clearInput)`\n * still runs on both success and failure.\n */\nexport const useSubmitImportGradesButtonData = () => {\n const { formatMessage } = useIntl();\n const queryClient = useQueryClient();\n const courseId = useCourseId();\n const {\n setShowImportSuccessToast,\n setShowImportErrorToast,\n resetCsvUpload,\n markCsvUploadSuccess,\n setCsvUploadErrors,\n } = useGradebookUi();\n const mutation = useMutation<unknown, CsvUploadError, FormData>({\n mutationKey: gradeMutationKeys.uploadGradeCsv,\n mutationFn: (formData) => lms.api.uploadGradeCsv(courseId, formData),\n onMutate: () => {\n resetCsvUpload();\n },\n onSuccess: () => {\n markCsvUploadSuccess();\n setShowImportSuccessToast(true);\n trackUploadOverrideSucceeded(courseId);\n queryClient.invalidateQueries({\n queryKey: bulkOperationHistoryQueryKeys.byCourse(courseId),\n });\n },\n onError: (error) => {\n trackUploadOverrideFailed(courseId, error);\n if (error?.status === 200 && error?.data?.error_messages?.length) {\n const { error_messages: errorMessages } = error.data;\n setCsvUploadErrors(errorMessages);\n } else {\n setCsvUploadErrors([formatMessage(messages.unknownImportError)]);\n }\n setShowImportErrorToast(true);\n },\n });\n return (formData: FormData) => mutation.mutateAsync(formData).catch(() => {});\n};\n"]}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
3
|
import { useIntl } from '@openedx/frontend-base';
|
|
4
|
+
import { Bubble } from '@openedx/paragon';
|
|
4
5
|
import { useFilters } from '../../data/filtersContext';
|
|
5
6
|
import BulkManagementControls from './BulkManagementControls';
|
|
6
7
|
import EditModal from './EditModal';
|
|
@@ -8,7 +9,7 @@ import FilterBadges from './FilterBadges';
|
|
|
8
9
|
import FilteredUsersLabel from './FilteredUsersLabel';
|
|
9
10
|
import FilterMenuToggle from './FilterMenuToggle';
|
|
10
11
|
import GradebookTable from './GradebookTable';
|
|
11
|
-
import
|
|
12
|
+
import ImportResultToast from './ImportResultToast';
|
|
12
13
|
import InterventionsReport from './InterventionsReport';
|
|
13
14
|
import PageButtons from './PageButtons';
|
|
14
15
|
import ScoreViewInput from './ScoreViewInput';
|
|
@@ -26,7 +27,7 @@ export const GradesView = ({ updateQueryParams }) => {
|
|
|
26
27
|
updateQueryParams(filterNames.reduce((obj, filterName) => (Object.assign(Object.assign({}, obj), { [filterName]: false })), {}));
|
|
27
28
|
fetchGrades();
|
|
28
29
|
};
|
|
29
|
-
return (_jsxs(_Fragment, { children: [_jsx(SpinnerIcon, {}), _jsx(InterventionsReport, {}), _jsx("h3", { className: "
|
|
30
|
+
return (_jsxs(_Fragment, { children: [_jsx(SpinnerIcon, {}), _jsx(InterventionsReport, {}), _jsxs("div", { className: "mb-3", children: [_jsxs("div", { className: "d-flex align-items-center mb-2", children: [_jsx(Bubble, { className: "mr-2", children: "1" }), _jsx("h3", { className: "h4 text-primary-700 my-0", children: formatMessage(messages.filterStepHeading) })] }), _jsxs("div", { className: "d-flex justify-content-between flex-wrap", children: [_jsx(FilterMenuToggle, {}), _jsx(SearchControls, {})] }), _jsx(FilterBadges, { handleClose: handleFilterBadgeClose }), _jsx(StatusAlerts, {}), _jsxs("div", { className: "d-flex align-items-center mb-2", children: [_jsx(Bubble, { className: "mr-2", children: "2" }), _jsx("h3", { className: "h4 text-primary-700 my-0", children: formatMessage(messages.gradebookStepHeading) })] }), _jsxs("div", { className: "d-flex justify-content-between align-items-center mb-2", children: [_jsx(ScoreViewInput, {}), _jsx(BulkManagementControls, {})] })] }), _jsx(FilteredUsersLabel, {}), _jsx(GradebookTable, {}), _jsx(PageButtons, {}), _jsxs("p", { children: ["* ", formatMessage(messages.mastersHint)] }), _jsx(EditModal, {}), _jsx(ImportResultToast, {})] }));
|
|
30
31
|
};
|
|
31
32
|
GradesView.propTypes = {
|
|
32
33
|
updateQueryParams: PropTypes.func.isRequired,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/components/GradesView/index.jsx"],"names":[],"mappings":";AAAA,OAAO,SAAS,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/components/GradesView/index.jsx"],"names":[],"mappings":"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","sourcesContent":["import PropTypes from 'prop-types';\nimport { useIntl } from '@openedx/frontend-base';\nimport { Bubble } from '@openedx/paragon';\n\nimport { useFilters } from '@src/data/filtersContext';\n\nimport BulkManagementControls from './BulkManagementControls';\nimport EditModal from './EditModal';\nimport FilterBadges from './FilterBadges';\nimport FilteredUsersLabel from './FilteredUsersLabel';\nimport FilterMenuToggle from './FilterMenuToggle';\nimport GradebookTable from './GradebookTable';\nimport ImportResultToast from './ImportResultToast';\nimport InterventionsReport from './InterventionsReport';\nimport PageButtons from './PageButtons';\nimport ScoreViewInput from './ScoreViewInput';\nimport SearchControls from './SearchControls';\nimport SpinnerIcon from './SpinnerIcon';\nimport StatusAlerts from './StatusAlerts';\nimport { useRefetchGrades } from './data/hooks';\nimport messages from './messages';\n\nexport const GradesView = ({ updateQueryParams }) => {\n const { formatMessage } = useIntl();\n const { resetFilters: resetContextFilters } = useFilters();\n const fetchGrades = useRefetchGrades();\n\n const handleFilterBadgeClose = (filterNames) => () => {\n resetContextFilters(filterNames);\n updateQueryParams(filterNames.reduce(\n (obj, filterName) => ({ ...obj, [filterName]: false }),\n {},\n ));\n fetchGrades();\n };\n\n return (\n <>\n <SpinnerIcon />\n\n <InterventionsReport />\n\n <div className=\"mb-3\">\n <div className=\"d-flex align-items-center mb-2\">\n <Bubble className=\"mr-2\">1</Bubble>\n <h3 className=\"h4 text-primary-700 my-0\">\n {formatMessage(messages.filterStepHeading)}\n </h3>\n </div>\n <div className=\"d-flex justify-content-between flex-wrap\">\n <FilterMenuToggle />\n <SearchControls />\n </div>\n\n <FilterBadges handleClose={handleFilterBadgeClose} />\n <StatusAlerts />\n\n <div className=\"d-flex align-items-center mb-2\">\n <Bubble className=\"mr-2\">2</Bubble>\n <h3 className=\"h4 text-primary-700 my-0\">\n {formatMessage(messages.gradebookStepHeading)}\n </h3>\n </div>\n\n <div className=\"d-flex justify-content-between align-items-center mb-2\">\n <ScoreViewInput />\n <BulkManagementControls />\n </div>\n </div>\n\n <FilteredUsersLabel />\n\n <GradebookTable />\n\n <PageButtons />\n <p>* {formatMessage(messages.mastersHint)}</p>\n <EditModal />\n\n <ImportResultToast />\n </>\n );\n};\n\nGradesView.propTypes = {\n updateQueryParams: PropTypes.func.isRequired,\n};\n\nexport default GradesView;\n"]}
|
|
@@ -13,7 +13,7 @@ declare namespace messages {
|
|
|
13
13
|
let description_1: string;
|
|
14
14
|
export { description_1 as description };
|
|
15
15
|
}
|
|
16
|
-
namespace
|
|
16
|
+
namespace unknownImportError {
|
|
17
17
|
let id_2: string;
|
|
18
18
|
export { id_2 as id };
|
|
19
19
|
let defaultMessage_2: string;
|
|
@@ -21,4 +21,12 @@ declare namespace messages {
|
|
|
21
21
|
let description_2: string;
|
|
22
22
|
export { description_2 as description };
|
|
23
23
|
}
|
|
24
|
+
namespace mastersHint {
|
|
25
|
+
let id_3: string;
|
|
26
|
+
export { id_3 as id };
|
|
27
|
+
let defaultMessage_3: string;
|
|
28
|
+
export { defaultMessage_3 as defaultMessage };
|
|
29
|
+
let description_3: string;
|
|
30
|
+
export { description_3 as description };
|
|
31
|
+
}
|
|
24
32
|
}
|
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
import { defineMessages } from '@openedx/frontend-base';
|
|
2
2
|
const messages = defineMessages({
|
|
3
3
|
filterStepHeading: {
|
|
4
|
-
id: 'gradebook.GradesView.
|
|
5
|
-
defaultMessage: '
|
|
4
|
+
id: 'gradebook.GradesView.filterHeadingV2',
|
|
5
|
+
defaultMessage: 'Filter the Grade Report',
|
|
6
6
|
description: 'Filter controls container heading string',
|
|
7
7
|
},
|
|
8
8
|
gradebookStepHeading: {
|
|
9
|
-
id: 'gradebook.GradesView.
|
|
10
|
-
defaultMessage: '
|
|
11
|
-
description: '
|
|
9
|
+
id: 'gradebook.GradesView.gradebookStepHeadingV2',
|
|
10
|
+
defaultMessage: 'View or Modify Individual Grades',
|
|
11
|
+
description: 'Gradebook table container heading string',
|
|
12
|
+
},
|
|
13
|
+
unknownImportError: {
|
|
14
|
+
id: 'gradebook.GradesView.unknownImportError',
|
|
15
|
+
defaultMessage: 'Unknown error.',
|
|
16
|
+
description: 'Shown when a grade import fails without the server saying why',
|
|
12
17
|
},
|
|
13
18
|
mastersHint: {
|
|
14
19
|
id: 'gradebook.GradesView.mastersHint',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"messages.js","sourceRoot":"","sources":["../../../src/components/GradesView/messages.js"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAExD,MAAM,QAAQ,GAAG,cAAc,CAAC;IAC9B,iBAAiB,EAAE;QACjB,EAAE,EAAE,
|
|
1
|
+
{"version":3,"file":"messages.js","sourceRoot":"","sources":["../../../src/components/GradesView/messages.js"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAExD,MAAM,QAAQ,GAAG,cAAc,CAAC;IAC9B,iBAAiB,EAAE;QACjB,EAAE,EAAE,sCAAsC;QAC1C,cAAc,EAAE,yBAAyB;QACzC,WAAW,EAAE,0CAA0C;KACxD;IACD,oBAAoB,EAAE;QACpB,EAAE,EAAE,6CAA6C;QACjD,cAAc,EAAE,kCAAkC;QAClD,WAAW,EAAE,0CAA0C;KACxD;IACD,kBAAkB,EAAE;QAClB,EAAE,EAAE,yCAAyC;QAC7C,cAAc,EAAE,gBAAgB;QAChC,WAAW,EAAE,+DAA+D;KAC7E;IACD,WAAW,EAAE;QACX,EAAE,EAAE,kCAAkC;QACtC,cAAc,EAAE,mDAAmD;QACnE,WAAW,EAAE,iDAAiD;KAC/D;CACF,CAAC,CAAC;AAEH,eAAe,QAAQ,CAAC","sourcesContent":["import { defineMessages } from '@openedx/frontend-base';\n\nconst messages = defineMessages({\n filterStepHeading: {\n id: 'gradebook.GradesView.filterHeadingV2',\n defaultMessage: 'Filter the Grade Report',\n description: 'Filter controls container heading string',\n },\n gradebookStepHeading: {\n id: 'gradebook.GradesView.gradebookStepHeadingV2',\n defaultMessage: 'View or Modify Individual Grades',\n description: 'Gradebook table container heading string',\n },\n unknownImportError: {\n id: 'gradebook.GradesView.unknownImportError',\n defaultMessage: 'Unknown error.',\n description: 'Shown when a grade import fails without the server saying why',\n },\n mastersHint: {\n id: 'gradebook.GradesView.mastersHint',\n defaultMessage: \"available for learners in the Master's track only\",\n description: 'Masters feature availability hint on Grades Tab',\n },\n});\n\nexport default messages;\n"]}
|
|
@@ -41,6 +41,9 @@ export interface GradebookUiContextValue {
|
|
|
41
41
|
/** Toast shown after a successful bulk grade CSV import. */
|
|
42
42
|
showImportSuccessToast: boolean;
|
|
43
43
|
setShowImportSuccessToast: (value: boolean) => void;
|
|
44
|
+
/** Toast shown after a bulk grade CSV import that failed or applied nothing. */
|
|
45
|
+
showImportErrorToast: boolean;
|
|
46
|
+
setShowImportErrorToast: (value: boolean) => void;
|
|
44
47
|
/** Active top-level tab: views.grades | views.bulkManagementHistory. */
|
|
45
48
|
activeView: string;
|
|
46
49
|
setActiveView: (value: string) => void;
|
|
@@ -19,6 +19,7 @@ export const GradebookUiProvider = ({ children }) => {
|
|
|
19
19
|
const [gradeFormat, setGradeFormat] = useState('percent');
|
|
20
20
|
const [showSuccess, setShowSuccess] = useState(false);
|
|
21
21
|
const [showImportSuccessToast, setShowImportSuccessToast] = useState(false);
|
|
22
|
+
const [showImportErrorToast, setShowImportErrorToast] = useState(false);
|
|
22
23
|
const [activeView, setActiveView] = useState(views.grades);
|
|
23
24
|
// Filter sidebar animation state. This mirrors the old Redux `filterMenu` thunks:
|
|
24
25
|
// `toggle` flips `open` one frame after starting the transition, and the sidebar's
|
|
@@ -82,6 +83,8 @@ export const GradebookUiProvider = ({ children }) => {
|
|
|
82
83
|
const resetCsvUpload = useCallback(() => {
|
|
83
84
|
setCsvUploadSuccess(false);
|
|
84
85
|
setCsvUploadErrorMessages([]);
|
|
86
|
+
setShowImportSuccessToast(false);
|
|
87
|
+
setShowImportErrorToast(false);
|
|
85
88
|
}, []);
|
|
86
89
|
const markCsvUploadSuccess = useCallback(() => {
|
|
87
90
|
setCsvUploadSuccess(true);
|
|
@@ -98,6 +101,8 @@ export const GradebookUiProvider = ({ children }) => {
|
|
|
98
101
|
setShowSuccess,
|
|
99
102
|
showImportSuccessToast,
|
|
100
103
|
setShowImportSuccessToast,
|
|
104
|
+
showImportErrorToast,
|
|
105
|
+
setShowImportErrorToast,
|
|
101
106
|
activeView,
|
|
102
107
|
setActiveView,
|
|
103
108
|
filterMenuOpen,
|
|
@@ -117,7 +122,7 @@ export const GradebookUiProvider = ({ children }) => {
|
|
|
117
122
|
markCsvUploadSuccess,
|
|
118
123
|
setCsvUploadErrors,
|
|
119
124
|
}), [
|
|
120
|
-
gradeFormat, showSuccess, showImportSuccessToast, activeView,
|
|
125
|
+
gradeFormat, showSuccess, showImportSuccessToast, showImportErrorToast, activeView,
|
|
121
126
|
filterMenuOpen, filterMenuTransitioning,
|
|
122
127
|
toggleFilterMenu, closeFilterMenu, handleFilterMenuTransitionEnd,
|
|
123
128
|
modalState, setModalState, setModalStateFromTable, closeModal,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gradebookUiContext.js","sourceRoot":"","sources":["../../src/data/gradebookUiContext.tsx"],"names":[],"mappings":"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","sourcesContent":["import React, {\n createContext, useCallback, useContext, useEffect, useMemo, useRef, useState,\n} from 'react';\n\nimport { views, modalFieldKeys } from '@src/data/constants/app';\nimport { formatDateForDisplay } from '@src/data/formatUtils';\n\n/** Edit-grade modal state (was the Redux `app.modalState` slice). */\nexport interface ModalState {\n open: boolean;\n adjustedGradePossible: string | number;\n adjustedGradeValue: string | number;\n assignmentName: string;\n reasonForChange: string;\n todaysDate: string;\n updateModuleId: string | null;\n updateUserId: number | null;\n updateUserName: string | null;\n}\n\n/** Payload for opening the modal from a grade-table cell. */\nexport interface ModalStateFromTablePayload {\n subsection: {\n attempted?: boolean;\n score_possible?: number;\n subsection_name?: string;\n module_id?: string;\n };\n userEntry: {\n user_id?: number;\n username?: string;\n };\n}\n\n// Mirrors the Redux `app` reducer's `initialState.modalState`.\nconst initialModalState: ModalState = {\n open: false,\n adjustedGradePossible: '',\n adjustedGradeValue: 0,\n assignmentName: '',\n reasonForChange: '',\n todaysDate: '',\n updateModuleId: null,\n updateUserId: null,\n updateUserName: null,\n};\n\n/**\n * Client-side gradebook **UI** state, held in React (Context + useState).\n *\n * Replaces the non-filter UI bits of the old Redux `app` and `grades` slices:\n * grade format toggle, success banner, import toast, active view, filter-menu\n * open/transition, modal state, pagination cursor, and CSV-upload feedback.\n */\nexport interface GradebookUiContextValue {\n /** Grade display format: 'percent' | 'absolute'. */\n gradeFormat: string;\n setGradeFormat: (value: string) => void;\n /** Success banner shown after a grade override is saved. */\n showSuccess: boolean;\n setShowSuccess: (value: boolean) => void;\n /** Toast shown after a successful bulk grade CSV import. */\n showImportSuccessToast: boolean;\n setShowImportSuccessToast: (value: boolean) => void;\n /** Active top-level tab: views.grades | views.bulkManagementHistory. */\n activeView: string;\n setActiveView: (value: string) => void;\n /**\n * Filter sidebar open/close state, with the same two-phase transition the old\n * Redux `app.filterMenu` slice modeled (`open` + `transitioning`). Consumers\n * derive `isClosed`/`isOpening` from these two.\n */\n filterMenuOpen: boolean;\n filterMenuTransitioning: boolean;\n /** Toggle the sidebar (starts the transition, flips `open` on the next frame). */\n toggleFilterMenu: () => void;\n /** Close the sidebar if it is currently open (no-op otherwise). */\n closeFilterMenu: () => void;\n /** onTransitionEnd handler for the sidebar; ends the transition phase. */\n handleFilterMenuTransitionEnd: (event: React.TransitionEvent) => void;\n /** Edit-grade modal state (was Redux `app.modalState`). */\n modalState: ModalState;\n /** Merge a partial update into modalState (filtered to the known modal fields). */\n setModalState: (partial: Partial<ModalState>) => void;\n /** Open the modal seeded from a grade-table cell (subsection + user entry). */\n setModalStateFromTable: (payload: ModalStateFromTablePayload) => void;\n /** Reset the modal to its initial (closed) state. */\n closeModal: () => void;\n /**\n * Current grades pagination cursor: the opaque prev/next URL from the last\n * grades response, or null for the first (unpaged) page. Held here so the\n * `useGrades` query can key/refetch off it; filter changes reset it to null.\n */\n gradesPageEndpoint: string | null;\n setGradesPageEndpoint: (endpoint: string | null) => void;\n /**\n * Bulk grade CSV-upload result feedback shown by `BulkManagementAlerts` (was the\n * Redux `grades.bulkManagement` `uploadSuccess`/`errorMessages`).\n */\n csvUploadSuccess: boolean;\n csvUploadErrorMessages: string[];\n /** Reset the CSV-upload feedback (upload started). */\n resetCsvUpload: () => void;\n /** Mark the CSV upload succeeded. */\n markCsvUploadSuccess: () => void;\n /** Record CSV-upload error messages. */\n setCsvUploadErrors: (messages: string[]) => void;\n}\n\nconst GradebookUiContext = createContext<GradebookUiContextValue | undefined>(undefined);\n\ninterface GradebookUiProviderProps {\n children: React.ReactNode;\n}\n\nexport const GradebookUiProvider = ({ children }: GradebookUiProviderProps) => {\n const [gradeFormat, setGradeFormat] = useState<string>('percent');\n const [showSuccess, setShowSuccess] = useState<boolean>(false);\n const [showImportSuccessToast, setShowImportSuccessToast] = useState<boolean>(false);\n const [activeView, setActiveView] = useState<string>(views.grades);\n\n // Filter sidebar animation state. This mirrors the old Redux `filterMenu` thunks:\n // `toggle` flips `open` one frame after starting the transition, and the sidebar's\n // onTransitionEnd ends the transition. `close` reads the latest `open` via a ref\n // so it can be a stable callback.\n const [filterMenuOpen, setFilterMenuOpen] = useState<boolean>(false);\n const [filterMenuTransitioning, setFilterMenuTransitioning] = useState<boolean>(false);\n const filterMenuOpenRef = useRef<boolean>(filterMenuOpen);\n useEffect(() => {\n filterMenuOpenRef.current = filterMenuOpen;\n }, [filterMenuOpen]);\n\n const toggleFilterMenu = useCallback(() => {\n setFilterMenuTransitioning(true);\n window.requestAnimationFrame(() => {\n window.setTimeout(() => {\n setFilterMenuOpen((open) => !open);\n });\n });\n }, []);\n\n const closeFilterMenu = useCallback(() => {\n if (filterMenuOpenRef.current) {\n toggleFilterMenu();\n }\n }, [toggleFilterMenu]);\n\n const handleFilterMenuTransitionEnd = useCallback((event: React.TransitionEvent) => {\n if (event.currentTarget === event.target) {\n setFilterMenuTransitioning(false);\n }\n }, []);\n\n // Edit-grade modal state. These ops are a faithful port of the Redux `app`\n // reducer cases: `setModalState` merges a field-filtered partial, `closeModal`\n // resets to the initial state, and `setModalStateFromTable` opens the modal\n // seeded from the clicked grade cell.\n const [modalState, setModalStateValue] = useState<ModalState>(initialModalState);\n\n const setModalState = useCallback((partial: Partial<ModalState>) => {\n const filtered = (Object.keys(modalFieldKeys) as (keyof ModalState)[]).reduce(\n (obj, key) => (partial[key] !== undefined ? { ...obj, [key]: partial[key] } : obj),\n {} as Partial<ModalState>,\n );\n setModalStateValue((prev) => ({ ...prev, ...filtered }));\n }, []);\n\n const setModalStateFromTable = useCallback(\n ({ subsection, userEntry }: ModalStateFromTablePayload) => {\n setModalStateValue({\n open: true,\n adjustedGradeValue: '',\n reasonForChange: '',\n todaysDate: formatDateForDisplay(new Date()),\n adjustedGradePossible: subsection.attempted ? (subsection.score_possible ?? '') : '',\n assignmentName: `${subsection.subsection_name}`,\n updateModuleId: subsection.module_id ?? null,\n updateUserId: userEntry.user_id ?? null,\n updateUserName: userEntry.username ?? null,\n });\n },\n [],\n );\n\n const closeModal = useCallback(() => {\n setModalStateValue(initialModalState);\n }, []);\n\n // Grades pagination cursor (opaque prev/next URL, or null for the first page).\n const [gradesPageEndpoint, setGradesPageEndpoint] = useState<string | null>(null);\n\n // Bulk CSV-upload result feedback (was Redux `grades.bulkManagement`).\n const [csvUploadSuccess, setCsvUploadSuccess] = useState<boolean>(false);\n const [csvUploadErrorMessages, setCsvUploadErrorMessages] = useState<string[]>([]);\n const resetCsvUpload = useCallback(() => {\n setCsvUploadSuccess(false);\n setCsvUploadErrorMessages([]);\n }, []);\n const markCsvUploadSuccess = useCallback(() => {\n setCsvUploadSuccess(true);\n setCsvUploadErrorMessages([]);\n }, []);\n const setCsvUploadErrors = useCallback((messages: string[]) => {\n setCsvUploadSuccess(false);\n setCsvUploadErrorMessages(messages);\n }, []);\n\n const value = useMemo<GradebookUiContextValue>(\n () => ({\n gradeFormat,\n setGradeFormat,\n showSuccess,\n setShowSuccess,\n showImportSuccessToast,\n setShowImportSuccessToast,\n activeView,\n setActiveView,\n filterMenuOpen,\n filterMenuTransitioning,\n toggleFilterMenu,\n closeFilterMenu,\n handleFilterMenuTransitionEnd,\n modalState,\n setModalState,\n setModalStateFromTable,\n closeModal,\n gradesPageEndpoint,\n setGradesPageEndpoint,\n csvUploadSuccess,\n csvUploadErrorMessages,\n resetCsvUpload,\n markCsvUploadSuccess,\n setCsvUploadErrors,\n }),\n [\n gradeFormat, showSuccess, showImportSuccessToast, activeView,\n filterMenuOpen, filterMenuTransitioning,\n toggleFilterMenu, closeFilterMenu, handleFilterMenuTransitionEnd,\n modalState, setModalState, setModalStateFromTable, closeModal,\n gradesPageEndpoint,\n csvUploadSuccess, csvUploadErrorMessages,\n resetCsvUpload, markCsvUploadSuccess, setCsvUploadErrors,\n ],\n );\n\n return (\n <GradebookUiContext.Provider value={value}>\n {children}\n </GradebookUiContext.Provider>\n );\n};\n\n/**\n * useGradebookUi()\n * Accessor for the gradebook UI-state context. Throws if used outside a\n * <GradebookUiProvider>.\n */\nexport const useGradebookUi = (): GradebookUiContextValue => {\n const context = useContext(GradebookUiContext);\n if (context === undefined) {\n throw new Error('useGradebookUi must be used within a GradebookUiProvider');\n }\n return context;\n};\n\nexport default GradebookUiContext;\n"]}
|
|
1
|
+
{"version":3,"file":"gradebookUiContext.js","sourceRoot":"","sources":["../../src/data/gradebookUiContext.tsx"],"names":[],"mappings":"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","sourcesContent":["import React, {\n createContext, useCallback, useContext, useEffect, useMemo, useRef, useState,\n} from 'react';\n\nimport { views, modalFieldKeys } from '@src/data/constants/app';\nimport { formatDateForDisplay } from '@src/data/formatUtils';\n\n/** Edit-grade modal state (was the Redux `app.modalState` slice). */\nexport interface ModalState {\n open: boolean;\n adjustedGradePossible: string | number;\n adjustedGradeValue: string | number;\n assignmentName: string;\n reasonForChange: string;\n todaysDate: string;\n updateModuleId: string | null;\n updateUserId: number | null;\n updateUserName: string | null;\n}\n\n/** Payload for opening the modal from a grade-table cell. */\nexport interface ModalStateFromTablePayload {\n subsection: {\n attempted?: boolean;\n score_possible?: number;\n subsection_name?: string;\n module_id?: string;\n };\n userEntry: {\n user_id?: number;\n username?: string;\n };\n}\n\n// Mirrors the Redux `app` reducer's `initialState.modalState`.\nconst initialModalState: ModalState = {\n open: false,\n adjustedGradePossible: '',\n adjustedGradeValue: 0,\n assignmentName: '',\n reasonForChange: '',\n todaysDate: '',\n updateModuleId: null,\n updateUserId: null,\n updateUserName: null,\n};\n\n/**\n * Client-side gradebook **UI** state, held in React (Context + useState).\n *\n * Replaces the non-filter UI bits of the old Redux `app` and `grades` slices:\n * grade format toggle, success banner, import toast, active view, filter-menu\n * open/transition, modal state, pagination cursor, and CSV-upload feedback.\n */\nexport interface GradebookUiContextValue {\n /** Grade display format: 'percent' | 'absolute'. */\n gradeFormat: string;\n setGradeFormat: (value: string) => void;\n /** Success banner shown after a grade override is saved. */\n showSuccess: boolean;\n setShowSuccess: (value: boolean) => void;\n /** Toast shown after a successful bulk grade CSV import. */\n showImportSuccessToast: boolean;\n setShowImportSuccessToast: (value: boolean) => void;\n /** Toast shown after a bulk grade CSV import that failed or applied nothing. */\n showImportErrorToast: boolean;\n setShowImportErrorToast: (value: boolean) => void;\n /** Active top-level tab: views.grades | views.bulkManagementHistory. */\n activeView: string;\n setActiveView: (value: string) => void;\n /**\n * Filter sidebar open/close state, with the same two-phase transition the old\n * Redux `app.filterMenu` slice modeled (`open` + `transitioning`). Consumers\n * derive `isClosed`/`isOpening` from these two.\n */\n filterMenuOpen: boolean;\n filterMenuTransitioning: boolean;\n /** Toggle the sidebar (starts the transition, flips `open` on the next frame). */\n toggleFilterMenu: () => void;\n /** Close the sidebar if it is currently open (no-op otherwise). */\n closeFilterMenu: () => void;\n /** onTransitionEnd handler for the sidebar; ends the transition phase. */\n handleFilterMenuTransitionEnd: (event: React.TransitionEvent) => void;\n /** Edit-grade modal state (was Redux `app.modalState`). */\n modalState: ModalState;\n /** Merge a partial update into modalState (filtered to the known modal fields). */\n setModalState: (partial: Partial<ModalState>) => void;\n /** Open the modal seeded from a grade-table cell (subsection + user entry). */\n setModalStateFromTable: (payload: ModalStateFromTablePayload) => void;\n /** Reset the modal to its initial (closed) state. */\n closeModal: () => void;\n /**\n * Current grades pagination cursor: the opaque prev/next URL from the last\n * grades response, or null for the first (unpaged) page. Held here so the\n * `useGrades` query can key/refetch off it; filter changes reset it to null.\n */\n gradesPageEndpoint: string | null;\n setGradesPageEndpoint: (endpoint: string | null) => void;\n /**\n * Bulk grade CSV-upload result feedback shown by `BulkManagementAlerts` (was the\n * Redux `grades.bulkManagement` `uploadSuccess`/`errorMessages`).\n */\n csvUploadSuccess: boolean;\n csvUploadErrorMessages: string[];\n /** Reset the CSV-upload feedback (upload started). */\n resetCsvUpload: () => void;\n /** Mark the CSV upload succeeded. */\n markCsvUploadSuccess: () => void;\n /** Record CSV-upload error messages. */\n setCsvUploadErrors: (messages: string[]) => void;\n}\n\nconst GradebookUiContext = createContext<GradebookUiContextValue | undefined>(undefined);\n\ninterface GradebookUiProviderProps {\n children: React.ReactNode;\n}\n\nexport const GradebookUiProvider = ({ children }: GradebookUiProviderProps) => {\n const [gradeFormat, setGradeFormat] = useState<string>('percent');\n const [showSuccess, setShowSuccess] = useState<boolean>(false);\n const [showImportSuccessToast, setShowImportSuccessToast] = useState<boolean>(false);\n const [showImportErrorToast, setShowImportErrorToast] = useState<boolean>(false);\n const [activeView, setActiveView] = useState<string>(views.grades);\n\n // Filter sidebar animation state. This mirrors the old Redux `filterMenu` thunks:\n // `toggle` flips `open` one frame after starting the transition, and the sidebar's\n // onTransitionEnd ends the transition. `close` reads the latest `open` via a ref\n // so it can be a stable callback.\n const [filterMenuOpen, setFilterMenuOpen] = useState<boolean>(false);\n const [filterMenuTransitioning, setFilterMenuTransitioning] = useState<boolean>(false);\n const filterMenuOpenRef = useRef<boolean>(filterMenuOpen);\n useEffect(() => {\n filterMenuOpenRef.current = filterMenuOpen;\n }, [filterMenuOpen]);\n\n const toggleFilterMenu = useCallback(() => {\n setFilterMenuTransitioning(true);\n window.requestAnimationFrame(() => {\n window.setTimeout(() => {\n setFilterMenuOpen((open) => !open);\n });\n });\n }, []);\n\n const closeFilterMenu = useCallback(() => {\n if (filterMenuOpenRef.current) {\n toggleFilterMenu();\n }\n }, [toggleFilterMenu]);\n\n const handleFilterMenuTransitionEnd = useCallback((event: React.TransitionEvent) => {\n if (event.currentTarget === event.target) {\n setFilterMenuTransitioning(false);\n }\n }, []);\n\n // Edit-grade modal state. These ops are a faithful port of the Redux `app`\n // reducer cases: `setModalState` merges a field-filtered partial, `closeModal`\n // resets to the initial state, and `setModalStateFromTable` opens the modal\n // seeded from the clicked grade cell.\n const [modalState, setModalStateValue] = useState<ModalState>(initialModalState);\n\n const setModalState = useCallback((partial: Partial<ModalState>) => {\n const filtered = (Object.keys(modalFieldKeys) as (keyof ModalState)[]).reduce(\n (obj, key) => (partial[key] !== undefined ? { ...obj, [key]: partial[key] } : obj),\n {} as Partial<ModalState>,\n );\n setModalStateValue((prev) => ({ ...prev, ...filtered }));\n }, []);\n\n const setModalStateFromTable = useCallback(\n ({ subsection, userEntry }: ModalStateFromTablePayload) => {\n setModalStateValue({\n open: true,\n adjustedGradeValue: '',\n reasonForChange: '',\n todaysDate: formatDateForDisplay(new Date()),\n adjustedGradePossible: subsection.attempted ? (subsection.score_possible ?? '') : '',\n assignmentName: `${subsection.subsection_name}`,\n updateModuleId: subsection.module_id ?? null,\n updateUserId: userEntry.user_id ?? null,\n updateUserName: userEntry.username ?? null,\n });\n },\n [],\n );\n\n const closeModal = useCallback(() => {\n setModalStateValue(initialModalState);\n }, []);\n\n // Grades pagination cursor (opaque prev/next URL, or null for the first page).\n const [gradesPageEndpoint, setGradesPageEndpoint] = useState<string | null>(null);\n\n // Bulk CSV-upload result feedback (was Redux `grades.bulkManagement`).\n const [csvUploadSuccess, setCsvUploadSuccess] = useState<boolean>(false);\n const [csvUploadErrorMessages, setCsvUploadErrorMessages] = useState<string[]>([]);\n const resetCsvUpload = useCallback(() => {\n setCsvUploadSuccess(false);\n setCsvUploadErrorMessages([]);\n setShowImportSuccessToast(false);\n setShowImportErrorToast(false);\n }, []);\n const markCsvUploadSuccess = useCallback(() => {\n setCsvUploadSuccess(true);\n setCsvUploadErrorMessages([]);\n }, []);\n const setCsvUploadErrors = useCallback((messages: string[]) => {\n setCsvUploadSuccess(false);\n setCsvUploadErrorMessages(messages);\n }, []);\n\n const value = useMemo<GradebookUiContextValue>(\n () => ({\n gradeFormat,\n setGradeFormat,\n showSuccess,\n setShowSuccess,\n showImportSuccessToast,\n setShowImportSuccessToast,\n showImportErrorToast,\n setShowImportErrorToast,\n activeView,\n setActiveView,\n filterMenuOpen,\n filterMenuTransitioning,\n toggleFilterMenu,\n closeFilterMenu,\n handleFilterMenuTransitionEnd,\n modalState,\n setModalState,\n setModalStateFromTable,\n closeModal,\n gradesPageEndpoint,\n setGradesPageEndpoint,\n csvUploadSuccess,\n csvUploadErrorMessages,\n resetCsvUpload,\n markCsvUploadSuccess,\n setCsvUploadErrors,\n }),\n [\n gradeFormat, showSuccess, showImportSuccessToast, showImportErrorToast, activeView,\n filterMenuOpen, filterMenuTransitioning,\n toggleFilterMenu, closeFilterMenu, handleFilterMenuTransitionEnd,\n modalState, setModalState, setModalStateFromTable, closeModal,\n gradesPageEndpoint,\n csvUploadSuccess, csvUploadErrorMessages,\n resetCsvUpload, markCsvUploadSuccess, setCsvUploadErrors,\n ],\n );\n\n return (\n <GradebookUiContext.Provider value={value}>\n {children}\n </GradebookUiContext.Provider>\n );\n};\n\n/**\n * useGradebookUi()\n * Accessor for the gradebook UI-state context. Throws if used outside a\n * <GradebookUiProvider>.\n */\nexport const useGradebookUi = (): GradebookUiContextValue => {\n const context = useContext(GradebookUiContext);\n if (context === undefined) {\n throw new Error('useGradebookUi must be used within a GradebookUiProvider');\n }\n return context;\n};\n\nexport default GradebookUiContext;\n"]}
|
package/package.json
CHANGED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { useIntl } from '@openedx/frontend-base';
|
|
2
|
-
import { useGradebookUi } from '../../../data/gradebookUiContext';
|
|
3
|
-
import { views } from '../../../data/constants/app';
|
|
4
|
-
import messages from './messages';
|
|
5
|
-
/**
|
|
6
|
-
* <ImportSuccessToast />
|
|
7
|
-
* Toast component triggered by successful grade upload.
|
|
8
|
-
* Provides a link to view the Bulk Management History tab.
|
|
9
|
-
*/
|
|
10
|
-
export const useImportSuccessToastData = () => {
|
|
11
|
-
const { formatMessage } = useIntl();
|
|
12
|
-
const { showImportSuccessToast: show, setActiveView, setShowImportSuccessToast } = useGradebookUi();
|
|
13
|
-
const onClose = () => {
|
|
14
|
-
setShowImportSuccessToast(false);
|
|
15
|
-
};
|
|
16
|
-
const handleShowHistoryView = () => {
|
|
17
|
-
setActiveView(views.bulkManagementHistory);
|
|
18
|
-
setShowImportSuccessToast(false);
|
|
19
|
-
};
|
|
20
|
-
return {
|
|
21
|
-
action: {
|
|
22
|
-
label: formatMessage(messages.showHistoryViewBtn),
|
|
23
|
-
onClick: handleShowHistoryView,
|
|
24
|
-
},
|
|
25
|
-
onClose,
|
|
26
|
-
show,
|
|
27
|
-
description: formatMessage(messages.description),
|
|
28
|
-
};
|
|
29
|
-
};
|
|
30
|
-
export default useImportSuccessToastData;
|
|
31
|
-
//# sourceMappingURL=hooks.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"hooks.js","sourceRoot":"","sources":["../../../../src/components/GradesView/ImportSuccessToast/hooks.js"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AAEjD,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAC9D,OAAO,EAAE,KAAK,EAAE,MAAM,yBAAyB,CAAC;AAChD,OAAO,QAAQ,MAAM,YAAY,CAAC;AAElC;;;;GAIG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,GAAG,EAAE;IAC5C,MAAM,EAAE,aAAa,EAAE,GAAG,OAAO,EAAE,CAAC;IAEpC,MAAM,EAAE,sBAAsB,EAAE,IAAI,EAAE,aAAa,EAAE,yBAAyB,EAAE,GAAG,cAAc,EAAE,CAAC;IAEpG,MAAM,OAAO,GAAG,GAAG,EAAE;QACnB,yBAAyB,CAAC,KAAK,CAAC,CAAC;IACnC,CAAC,CAAC;IAEF,MAAM,qBAAqB,GAAG,GAAG,EAAE;QACjC,aAAa,CAAC,KAAK,CAAC,qBAAqB,CAAC,CAAC;QAC3C,yBAAyB,CAAC,KAAK,CAAC,CAAC;IACnC,CAAC,CAAC;IAEF,OAAO;QACL,MAAM,EAAE;YACN,KAAK,EAAE,aAAa,CAAC,QAAQ,CAAC,kBAAkB,CAAC;YACjD,OAAO,EAAE,qBAAqB;SAC/B;QACD,OAAO;QACP,IAAI;QACJ,WAAW,EAAE,aAAa,CAAC,QAAQ,CAAC,WAAW,CAAC;KACjD,CAAC;AACJ,CAAC,CAAC;AAEF,eAAe,yBAAyB,CAAC","sourcesContent":["import { useIntl } from '@openedx/frontend-base';\n\nimport { useGradebookUi } from '@src/data/gradebookUiContext';\nimport { views } from '@src/data/constants/app';\nimport messages from './messages';\n\n/**\n * <ImportSuccessToast />\n * Toast component triggered by successful grade upload.\n * Provides a link to view the Bulk Management History tab.\n */\nexport const useImportSuccessToastData = () => {\n const { formatMessage } = useIntl();\n\n const { showImportSuccessToast: show, setActiveView, setShowImportSuccessToast } = useGradebookUi();\n\n const onClose = () => {\n setShowImportSuccessToast(false);\n };\n\n const handleShowHistoryView = () => {\n setActiveView(views.bulkManagementHistory);\n setShowImportSuccessToast(false);\n };\n\n return {\n action: {\n label: formatMessage(messages.showHistoryViewBtn),\n onClick: handleShowHistoryView,\n },\n onClose,\n show,\n description: formatMessage(messages.description),\n };\n};\n\nexport default useImportSuccessToastData;\n"]}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { Toast } from '@openedx/paragon';
|
|
3
|
-
import useImportSuccessToastData from './hooks';
|
|
4
|
-
/**
|
|
5
|
-
* <ImportSuccessToast />
|
|
6
|
-
* Toast component triggered by successful grade upload.
|
|
7
|
-
* Provides a link to view the Bulk Management History tab.
|
|
8
|
-
*/
|
|
9
|
-
export const ImportSuccessToast = () => {
|
|
10
|
-
const { action, onClose, show, description, } = useImportSuccessToastData();
|
|
11
|
-
return (_jsx(Toast, { action, onClose, show, children: description }));
|
|
12
|
-
};
|
|
13
|
-
ImportSuccessToast.propTypes = {};
|
|
14
|
-
export default ImportSuccessToast;
|
|
15
|
-
//# sourceMappingURL=index.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/components/GradesView/ImportSuccessToast/index.jsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAEzC,OAAO,yBAAyB,MAAM,SAAS,CAAC;AAEhD;;;;GAIG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,GAAG,EAAE;IACrC,MAAM,EACJ,MAAM,EACN,OAAO,EACP,IAAI,EACJ,WAAW,GACZ,GAAG,yBAAyB,EAAE,CAAC;IAChC,OAAO,CACL,KAAC,KAAK,IAAO,MAAM,EAAE,OAAO,EAAE,IAAI,YAC/B,WAAW,GACN,CACT,CAAC;AACJ,CAAC,CAAC;AAEF,kBAAkB,CAAC,SAAS,GAAG,EAAE,CAAC;AAElC,eAAe,kBAAkB,CAAC","sourcesContent":["import { Toast } from '@openedx/paragon';\n\nimport useImportSuccessToastData from './hooks';\n\n/**\n * <ImportSuccessToast />\n * Toast component triggered by successful grade upload.\n * Provides a link to view the Bulk Management History tab.\n */\nexport const ImportSuccessToast = () => {\n const {\n action,\n onClose,\n show,\n description,\n } = useImportSuccessToastData();\n return (\n <Toast {...{ action, onClose, show }}>\n {description}\n </Toast>\n );\n};\n\nImportSuccessToast.propTypes = {};\n\nexport default ImportSuccessToast;\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"messages.js","sourceRoot":"","sources":["../../../../src/components/GradesView/ImportSuccessToast/messages.js"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAExD,MAAM,QAAQ,GAAG,cAAc,CAAC;IAC9B,WAAW,EAAE;QACX,EAAE,EAAE,qDAAqD;QACzD,cAAc,EAAE,wDAAwD;QACxE,WAAW,EAAE,wDAAwD;KACtE;IACD,kBAAkB,EAAE;QAClB,EAAE,EAAE,4DAA4D;QAChE,cAAc,EAAE,mBAAmB;QACnC,WAAW,EAAE,4EAA4E;KAC1F;CACF,CAAC,CAAC;AAEH,eAAe,QAAQ,CAAC","sourcesContent":["import { defineMessages } from '@openedx/frontend-base';\n\nconst messages = defineMessages({\n description: {\n id: 'gradebook.GradesView.ImportSuccessToast.description',\n defaultMessage: 'Import Successful! Grades will be updated momentarily.',\n description: 'A message congratulating a successful Import of grades',\n },\n showHistoryViewBtn: {\n id: 'gradebook.GradesView.ImportSuccessToast.showHistoryViewBtn',\n defaultMessage: 'View Activity Log',\n description: 'The text on a button that loads a view of the Bulk Management Activity Log',\n },\n});\n\nexport default messages;\n"]}
|