@openedx/frontend-app-instructor-dashboard 1.0.0-alpha.33 → 1.0.0-alpha.35

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.
Files changed (53) hide show
  1. package/dist/certificates/CertificatesPage.js +34 -8
  2. package/dist/certificates/CertificatesPage.js.map +1 -1
  3. package/dist/certificates/components/CertificatesPageHeader.d.ts +2 -2
  4. package/dist/certificates/components/CertificatesPageHeader.js +3 -3
  5. package/dist/certificates/components/CertificatesPageHeader.js.map +1 -1
  6. package/dist/certificates/components/DisableCertificatesModal.js +16 -8
  7. package/dist/certificates/components/DisableCertificatesModal.js.map +1 -1
  8. package/dist/certificates/components/GrantExceptionsModal.d.ts +2 -1
  9. package/dist/certificates/components/GrantExceptionsModal.js +49 -4
  10. package/dist/certificates/components/GrantExceptionsModal.js.map +1 -1
  11. package/dist/certificates/components/InvalidateCertificateModal.js +19 -3
  12. package/dist/certificates/components/InvalidateCertificateModal.js.map +1 -1
  13. package/dist/certificates/data/api.d.ts +7 -0
  14. package/dist/certificates/data/api.js +10 -0
  15. package/dist/certificates/data/api.js.map +1 -1
  16. package/dist/certificates/data/apiHook.d.ts +10 -0
  17. package/dist/certificates/data/apiHook.js +16 -1
  18. package/dist/certificates/data/apiHook.js.map +1 -1
  19. package/dist/certificates/messages.d.ts +100 -0
  20. package/dist/certificates/messages.js +104 -4
  21. package/dist/certificates/messages.js.map +1 -1
  22. package/dist/components/SpecifyLearnerField.js +25 -5
  23. package/dist/components/SpecifyLearnerField.js.map +1 -1
  24. package/dist/components/SpecifyProblemField.js +2 -2
  25. package/dist/components/SpecifyProblemField.js.map +1 -1
  26. package/dist/components/messages.d.ts +5 -0
  27. package/dist/components/messages.js +5 -0
  28. package/dist/components/messages.js.map +1 -1
  29. package/dist/enrollments/components/UpdateBetaTesterModal.js +9 -1
  30. package/dist/enrollments/components/UpdateBetaTesterModal.js.map +1 -1
  31. package/dist/grading/components/GradingLearnerContent.js +4 -0
  32. package/dist/grading/components/GradingLearnerContent.js.map +1 -1
  33. package/dist/slots/CourseInfoSlot/CourseInfoSlot.d.ts +2 -0
  34. package/dist/slots/CourseInfoSlot/CourseInfoSlot.js +14 -0
  35. package/dist/slots/CourseInfoSlot/CourseInfoSlot.js.map +1 -0
  36. package/dist/slots.js +9 -1
  37. package/dist/slots.js.map +1 -1
  38. package/dist/specialExams/components/AllowancesList.js +9 -4
  39. package/dist/specialExams/components/AllowancesList.js.map +1 -1
  40. package/dist/specialExams/components/AttemptsList.js +9 -4
  41. package/dist/specialExams/components/AttemptsList.js.map +1 -1
  42. package/dist/specialExams/components/EditAllowanceModal.js +1 -1
  43. package/dist/specialExams/components/EditAllowanceModal.js.map +1 -1
  44. package/dist/specialExams/data/api.js +6 -0
  45. package/dist/specialExams/data/api.js.map +1 -1
  46. package/dist/specialExams/data/queryKeys.d.ts +2 -2
  47. package/dist/specialExams/data/queryKeys.js +2 -2
  48. package/dist/specialExams/data/queryKeys.js.map +1 -1
  49. package/dist/specialExams/types.d.ts +1 -0
  50. package/dist/specialExams/types.js.map +1 -1
  51. package/dist/types/index.d.ts +4 -0
  52. package/dist/types/index.js.map +1 -1
  53. package/package.json +1 -1
@@ -13,7 +13,7 @@ import InvalidateCertificateModal from '../certificates/components/InvalidateCer
13
13
  import RemoveExceptionModal from '../certificates/components/RemoveExceptionModal';
14
14
  import RemoveInvalidationModal from '../certificates/components/RemoveInvalidationModal';
15
15
  import DisableCertificatesModal from '../certificates/components/DisableCertificatesModal';
16
- import { useCertificateGenerationHistory, useGrantBulkExceptions, useInvalidateCertificate, useIssuedCertificates, useRegenerateCertificates, useRemoveException, useRemoveInvalidation, useToggleCertificateGeneration, } from '../certificates/data/apiHook';
16
+ import { useCertificateGenerationHistory, useGrantBulkExceptions, useInvalidateCertificate, useIssuedCertificates, useRegenerateCertificates, useRemoveException, useRemoveInvalidation, useToggleCertificateGeneration, useUploadBulkExceptionsCsv, } from '../certificates/data/apiHook';
17
17
  import { CertificateFilter } from '../certificates/types';
18
18
  import { CERTIFICATES_PAGE_SIZE, TAB_KEYS, MODAL_TITLES, ALERT_VARIANTS } from '../certificates/constants';
19
19
  import { getErrorMessage } from '../certificates/utils/errorHandling';
@@ -48,6 +48,7 @@ const CertificatesPage = () => {
48
48
  pageSize: CERTIFICATES_PAGE_SIZE,
49
49
  });
50
50
  const { mutate: grantExceptions, isPending: isGrantingExceptions } = useGrantBulkExceptions(courseId);
51
+ const { mutate: uploadCsvExceptions, isPending: isUploadingCsv } = useUploadBulkExceptionsCsv(courseId);
51
52
  const { mutate: invalidateCert, isPending: isInvalidating } = useInvalidateCertificate(courseId);
52
53
  const { mutate: removeExcept, isPending: isRemovingException } = useRemoveException(courseId);
53
54
  const { mutate: removeInval, isPending: isRemovingInvalidation } = useRemoveInvalidation(courseId);
@@ -60,7 +61,7 @@ const CertificatesPage = () => {
60
61
  if (data.errors && data.errors.length > 0) {
61
62
  const errorMessages = data.errors.map(err => `${err.learner}: ${err.message}`).join('\n');
62
63
  showModal({
63
- title: MODAL_TITLES.ERROR,
64
+ title: intl.formatMessage(messages.errorModalTitle),
64
65
  message: `Some exceptions failed:\n${errorMessages}`,
65
66
  variant: ALERT_VARIANTS.WARNING,
66
67
  });
@@ -71,13 +72,38 @@ const CertificatesPage = () => {
71
72
  },
72
73
  onError: (error) => {
73
74
  showModal({
74
- title: MODAL_TITLES.ERROR,
75
+ title: intl.formatMessage(messages.errorModalTitle),
75
76
  message: getErrorMessage(error, intl.formatMessage(messages.errorGrantException)),
76
77
  variant: ALERT_VARIANTS.DANGER,
77
78
  });
78
79
  },
79
80
  });
80
81
  }, [grantExceptions, showToast, showModal, intl]);
82
+ const handleUploadCsvExceptions = useCallback((file) => {
83
+ uploadCsvExceptions(file, {
84
+ onSuccess: (data) => {
85
+ setIsGrantExceptionsOpen(false);
86
+ if (data.errors && data.errors.length > 0) {
87
+ const errorMessages = data.errors.map(err => `${err.learner}: ${err.message}`).join('\n');
88
+ showModal({
89
+ title: intl.formatMessage(messages.errorModalTitle),
90
+ message: `Some exceptions failed:\n${errorMessages}`,
91
+ variant: ALERT_VARIANTS.WARNING,
92
+ });
93
+ }
94
+ if (data.success && data.success.length > 0) {
95
+ showToast(intl.formatMessage(messages.exceptionsGrantedToast, { count: data.success.length }));
96
+ }
97
+ },
98
+ onError: (error) => {
99
+ showModal({
100
+ title: intl.formatMessage(messages.errorModalTitle),
101
+ message: getErrorMessage(error, intl.formatMessage(messages.errorGrantException)),
102
+ variant: ALERT_VARIANTS.DANGER,
103
+ });
104
+ },
105
+ });
106
+ }, [uploadCsvExceptions, showToast, showModal, intl]);
81
107
  const handleInvalidateCertificate = useCallback((learners, notes) => {
82
108
  invalidateCert({ learners, notes }, {
83
109
  onSuccess: (data) => {
@@ -85,7 +111,7 @@ const CertificatesPage = () => {
85
111
  if (data.errors && data.errors.length > 0) {
86
112
  const errorMessages = data.errors.map(err => `${err.learner}: ${err.message}`).join('\n');
87
113
  showModal({
88
- title: MODAL_TITLES.ERROR,
114
+ title: intl.formatMessage(messages.errorModalTitle),
89
115
  message: `Some invalidations failed:\n${errorMessages}`,
90
116
  variant: ALERT_VARIANTS.WARNING,
91
117
  });
@@ -96,7 +122,7 @@ const CertificatesPage = () => {
96
122
  },
97
123
  onError: (error) => {
98
124
  showModal({
99
- title: MODAL_TITLES.ERROR,
125
+ title: intl.formatMessage(messages.errorModalTitle),
100
126
  message: getErrorMessage(error, intl.formatMessage(messages.errorInvalidateCertificate)),
101
127
  variant: ALERT_VARIANTS.DANGER,
102
128
  });
@@ -128,7 +154,7 @@ const CertificatesPage = () => {
128
154
  },
129
155
  onError: (error) => {
130
156
  showModal({
131
- title: MODAL_TITLES.ERROR,
157
+ title: intl.formatMessage(messages.errorModalTitle),
132
158
  message: getErrorMessage(error, intl.formatMessage(messages.errorRemoveException)),
133
159
  variant: ALERT_VARIANTS.DANGER,
134
160
  });
@@ -160,7 +186,7 @@ const CertificatesPage = () => {
160
186
  },
161
187
  onError: (error) => {
162
188
  showModal({
163
- title: MODAL_TITLES.ERROR,
189
+ title: intl.formatMessage(messages.errorModalTitle),
164
190
  message: getErrorMessage(error, intl.formatMessage(messages.errorRemoveInvalidation)),
165
191
  variant: ALERT_VARIANTS.DANGER,
166
192
  });
@@ -204,7 +230,7 @@ const CertificatesPage = () => {
204
230
  if (courseInfo && !courseInfo.certificatesEnabled) {
205
231
  return (_jsx(Container, { className: "mt-4.5 mb-4", fluid: true, children: _jsx(Alert, { variant: "warning", children: intl.formatMessage(messages.certificatesDisabledMessage) }) }));
206
232
  }
207
- return (_jsxs(Container, { className: "mt-4.5 mb-4", fluid: true, children: [_jsx(CertificatesPageHeader, { onGrantExceptions: () => setIsGrantExceptionsOpen(true), onInvalidateCertificate: () => setIsInvalidateCertificateOpen(true), onDisableCertificates: () => setIsDisableCertificatesOpen(true) }), _jsx(Card, { variant: "muted", className: "pt-3 pt-md-4 pb-4 pb-md-6 certificates-card", children: _jsxs(Tabs, { activeKey: activeTab, onSelect: (key) => setActiveTab(key || TAB_KEYS.ISSUED), className: "mx-4", variant: "button-group", children: [_jsx(Tab, { eventKey: TAB_KEYS.ISSUED, title: intl.formatMessage(messages.issuedCertificatesTab), children: _jsx(IssuedCertificatesTab, { data: (certificatesData === null || certificatesData === void 0 ? void 0 : certificatesData.results) || [], isLoading: isLoadingCertificates, itemCount: (certificatesData === null || certificatesData === void 0 ? void 0 : certificatesData.count) || 0, pageCount: (certificatesData === null || certificatesData === void 0 ? void 0 : certificatesData.numPages) || 0, search: search, onSearchChange: setSearch, filter: filter, onFilterChange: setFilter, currentPage: certificatesPage, onPageChange: setCertificatesPage, onRemoveException: handleRemoveExceptionClick, onRemoveInvalidation: handleRemoveInvalidationClick, onRegenerateCertificates: handleRegenerateCertificates }) }), _jsx(Tab, { eventKey: TAB_KEYS.HISTORY, title: intl.formatMessage(messages.generationHistoryTab), children: _jsx("div", { className: "d-flex flex-column mt-3 mt-md-4", children: _jsx(GenerationHistoryTable, { data: (historyData === null || historyData === void 0 ? void 0 : historyData.results) || [], isLoading: isLoadingHistory, itemCount: (historyData === null || historyData === void 0 ? void 0 : historyData.count) || 0, pageCount: (historyData === null || historyData === void 0 ? void 0 : historyData.numPages) || 0, currentPage: tasksPage, onPageChange: setTasksPage }) }) })] }) }), _jsx(GrantExceptionsModal, { isOpen: isGrantExceptionsOpen, onClose: () => setIsGrantExceptionsOpen(false), onSubmit: handleGrantExceptions, isSubmitting: isGrantingExceptions }), _jsx(InvalidateCertificateModal, { isOpen: isInvalidateCertificateOpen, onClose: () => setIsInvalidateCertificateOpen(false), onSubmit: handleInvalidateCertificate, isSubmitting: isInvalidating }), _jsx(RemoveExceptionModal, { isOpen: isRemoveExceptionOpen, email: selectedEmail, onClose: () => {
233
+ return (_jsxs(Container, { className: "mt-4.5 mb-4", fluid: true, children: [_jsx(CertificatesPageHeader, { onGrantExceptions: () => setIsGrantExceptionsOpen(true), onInvalidateCertificate: () => setIsInvalidateCertificateOpen(true), onStudentGeneratedCertificates: () => setIsDisableCertificatesOpen(true) }), _jsx(Card, { variant: "muted", className: "pt-3 pt-md-4 pb-4 pb-md-6 certificates-card", children: _jsxs(Tabs, { activeKey: activeTab, onSelect: (key) => setActiveTab(key || TAB_KEYS.ISSUED), className: "mx-4", variant: "button-group", children: [_jsx(Tab, { eventKey: TAB_KEYS.ISSUED, title: intl.formatMessage(messages.issuedCertificatesTab), children: _jsx(IssuedCertificatesTab, { data: (certificatesData === null || certificatesData === void 0 ? void 0 : certificatesData.results) || [], isLoading: isLoadingCertificates, itemCount: (certificatesData === null || certificatesData === void 0 ? void 0 : certificatesData.count) || 0, pageCount: (certificatesData === null || certificatesData === void 0 ? void 0 : certificatesData.numPages) || 0, search: search, onSearchChange: setSearch, filter: filter, onFilterChange: setFilter, currentPage: certificatesPage, onPageChange: setCertificatesPage, onRemoveException: handleRemoveExceptionClick, onRemoveInvalidation: handleRemoveInvalidationClick, onRegenerateCertificates: handleRegenerateCertificates }) }), _jsx(Tab, { eventKey: TAB_KEYS.HISTORY, title: intl.formatMessage(messages.generationHistoryTab), children: _jsx("div", { className: "d-flex flex-column mt-3 mt-md-4", children: _jsx(GenerationHistoryTable, { data: (historyData === null || historyData === void 0 ? void 0 : historyData.results) || [], isLoading: isLoadingHistory, itemCount: (historyData === null || historyData === void 0 ? void 0 : historyData.count) || 0, pageCount: (historyData === null || historyData === void 0 ? void 0 : historyData.numPages) || 0, currentPage: tasksPage, onPageChange: setTasksPage }) }) })] }) }), _jsx(GrantExceptionsModal, { isOpen: isGrantExceptionsOpen, onClose: () => setIsGrantExceptionsOpen(false), onSubmit: handleGrantExceptions, onUploadCsv: handleUploadCsvExceptions, isSubmitting: isGrantingExceptions || isUploadingCsv }), _jsx(InvalidateCertificateModal, { isOpen: isInvalidateCertificateOpen, onClose: () => setIsInvalidateCertificateOpen(false), onSubmit: handleInvalidateCertificate, isSubmitting: isInvalidating }), _jsx(RemoveExceptionModal, { isOpen: isRemoveExceptionOpen, email: selectedEmail, onClose: () => {
208
234
  setIsRemoveExceptionOpen(false);
209
235
  setSelectedUsername('');
210
236
  setSelectedEmail('');
@@ -1 +1 @@
1
- {"version":3,"file":"CertificatesPage.js","sourceRoot":"","sources":["../../src/certificates/CertificatesPage.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useState, useCallback } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { Card, Container, Tab, Tabs, Alert } from '@openedx/paragon';\nimport { useIntl } from '@openedx/frontend-base';\nimport { useAlert } from '@src/providers/AlertProvider';\nimport { useCourseInfo } from '@src/data/apiHook';\nimport CertificatesPageHeader from '@src/certificates/components/CertificatesPageHeader';\nimport IssuedCertificatesTab from '@src/certificates/components/IssuedCertificatesTab';\nimport GenerationHistoryTable from '@src/certificates/components/GenerationHistoryTable';\nimport GrantExceptionsModal from '@src/certificates/components/GrantExceptionsModal';\nimport InvalidateCertificateModal from '@src/certificates/components/InvalidateCertificateModal';\nimport RemoveExceptionModal from '@src/certificates/components/RemoveExceptionModal';\nimport RemoveInvalidationModal from '@src/certificates/components/RemoveInvalidationModal';\nimport DisableCertificatesModal from '@src/certificates/components/DisableCertificatesModal';\nimport {\n useCertificateGenerationHistory,\n useGrantBulkExceptions,\n useInvalidateCertificate,\n useIssuedCertificates,\n useRegenerateCertificates,\n useRemoveException,\n useRemoveInvalidation,\n useToggleCertificateGeneration,\n} from '@src/certificates/data/apiHook';\nimport { CertificateFilter } from '@src/certificates/types';\nimport { CERTIFICATES_PAGE_SIZE, TAB_KEYS, MODAL_TITLES, ALERT_VARIANTS } from '@src/certificates/constants';\nimport { getErrorMessage } from '@src/certificates/utils/errorHandling';\nimport messages from '@src/certificates/messages';\nimport './CertificatesPage.scss';\n\nconst CertificatesPage = () => {\n const intl = useIntl();\n const { courseId = '' } = useParams<{ courseId: string }>();\n const { showToast, showModal } = useAlert();\n const { data: courseInfo } = useCourseInfo(courseId);\n\n const [filter, setFilter] = useState<CertificateFilter>(CertificateFilter.ALL_LEARNERS);\n const [search, setSearch] = useState('');\n const [certificatesPage, setCertificatesPage] = useState(0);\n const [tasksPage, setTasksPage] = useState(0);\n const [activeTab, setActiveTab] = useState(TAB_KEYS.ISSUED);\n const [selectedUsername, setSelectedUsername] = useState('');\n const [selectedEmail, setSelectedEmail] = useState('');\n const [isCertificateGenerationEnabled, setIsCertificateGenerationEnabled] = useState(true);\n\n const [isGrantExceptionsOpen, setIsGrantExceptionsOpen] = useState(false);\n const [isInvalidateCertificateOpen, setIsInvalidateCertificateOpen] = useState(false);\n const [isRemoveExceptionOpen, setIsRemoveExceptionOpen] = useState(false);\n const [isRemoveInvalidationOpen, setIsRemoveInvalidationOpen] = useState(false);\n const [isDisableCertificatesOpen, setIsDisableCertificatesOpen] = useState(false);\n\n const {\n data: certificatesData,\n isLoading: isLoadingCertificates,\n } = useIssuedCertificates(courseId, {\n page: certificatesPage,\n pageSize: CERTIFICATES_PAGE_SIZE,\n filter,\n search,\n });\n\n const {\n data: historyData,\n isLoading: isLoadingHistory,\n } = useCertificateGenerationHistory(courseId, {\n page: tasksPage,\n pageSize: CERTIFICATES_PAGE_SIZE,\n });\n\n const { mutate: grantExceptions, isPending: isGrantingExceptions } = useGrantBulkExceptions(courseId);\n const { mutate: invalidateCert, isPending: isInvalidating } = useInvalidateCertificate(courseId);\n const { mutate: removeExcept, isPending: isRemovingException } = useRemoveException(courseId);\n const { mutate: removeInval, isPending: isRemovingInvalidation } = useRemoveInvalidation(courseId);\n const { mutate: toggleGeneration, isPending: isTogglingGeneration } = useToggleCertificateGeneration(courseId);\n const { mutate: regenerateCerts } = useRegenerateCertificates(courseId);\n\n const handleGrantExceptions = useCallback((learners: string[], notes: string) => {\n grantExceptions(\n { learners, notes },\n {\n onSuccess: (data) => {\n setIsGrantExceptionsOpen(false);\n if (data.errors && data.errors.length > 0) {\n const errorMessages = data.errors.map(err => `${err.learner}: ${err.message}`).join('\\n');\n showModal({\n title: MODAL_TITLES.ERROR,\n message: `Some exceptions failed:\\n${errorMessages}`,\n variant: ALERT_VARIANTS.WARNING,\n });\n }\n if (data.success && data.success.length > 0) {\n showToast(intl.formatMessage(messages.exceptionsGrantedToast, { count: data.success.length }));\n }\n },\n onError: (error) => {\n showModal({\n title: MODAL_TITLES.ERROR,\n message: getErrorMessage(error, intl.formatMessage(messages.errorGrantException)),\n variant: ALERT_VARIANTS.DANGER,\n });\n },\n },\n );\n }, [grantExceptions, showToast, showModal, intl]);\n\n const handleInvalidateCertificate = useCallback((learners: string[], notes: string) => {\n invalidateCert(\n { learners, notes },\n {\n onSuccess: (data) => {\n setIsInvalidateCertificateOpen(false);\n if (data.errors && data.errors.length > 0) {\n const errorMessages = data.errors.map(err => `${err.learner}: ${err.message}`).join('\\n');\n showModal({\n title: MODAL_TITLES.ERROR,\n message: `Some invalidations failed:\\n${errorMessages}`,\n variant: ALERT_VARIANTS.WARNING,\n });\n }\n if (data.success && data.success.length > 0) {\n showToast(intl.formatMessage(messages.certificatesInvalidatedToast, { count: data.success.length }));\n }\n },\n onError: (error) => {\n showModal({\n title: MODAL_TITLES.ERROR,\n message: getErrorMessage(error, intl.formatMessage(messages.errorInvalidateCertificate)),\n variant: ALERT_VARIANTS.DANGER,\n });\n },\n },\n );\n }, [invalidateCert, showToast, showModal, intl]);\n\n const handleRemoveExceptionClick = useCallback((username: string, email: string) => {\n setSelectedUsername(username);\n setSelectedEmail(email);\n setIsRemoveExceptionOpen(true);\n }, []);\n\n const handleRemoveExceptionConfirm = useCallback(() => {\n // Backend accepts either username or email - use whichever is available\n const identifier = selectedUsername || selectedEmail;\n\n if (!identifier) {\n showModal({\n title: MODAL_TITLES.ERROR,\n message: intl.formatMessage(messages.errorRemoveException) + ': Username or email is required',\n variant: ALERT_VARIANTS.DANGER,\n });\n return;\n }\n\n removeExcept(\n { username: identifier },\n {\n onSuccess: () => {\n setIsRemoveExceptionOpen(false);\n setSelectedUsername('');\n setSelectedEmail('');\n showToast(intl.formatMessage(messages.exceptionRemovedToast, { email: selectedEmail }));\n },\n onError: (error) => {\n showModal({\n title: MODAL_TITLES.ERROR,\n message: getErrorMessage(error, intl.formatMessage(messages.errorRemoveException)),\n variant: ALERT_VARIANTS.DANGER,\n });\n },\n },\n );\n }, [removeExcept, selectedUsername, selectedEmail, showToast, showModal, intl]);\n\n const handleRemoveInvalidationClick = useCallback((username: string, email: string) => {\n setSelectedUsername(username);\n setSelectedEmail(email);\n setIsRemoveInvalidationOpen(true);\n }, []);\n\n const handleRemoveInvalidationConfirm = useCallback(() => {\n // Backend accepts either username or email - use whichever is available\n const identifier = selectedUsername || selectedEmail;\n\n if (!identifier) {\n showModal({\n title: MODAL_TITLES.ERROR,\n message: intl.formatMessage(messages.errorRemoveInvalidation) + ': Username or email is required',\n variant: ALERT_VARIANTS.DANGER,\n });\n return;\n }\n\n removeInval(\n { username: identifier },\n {\n onSuccess: () => {\n setIsRemoveInvalidationOpen(false);\n setSelectedUsername('');\n setSelectedEmail('');\n showToast(intl.formatMessage(messages.invalidationRemovedToast, { email: selectedEmail }));\n },\n onError: (error) => {\n showModal({\n title: MODAL_TITLES.ERROR,\n message: getErrorMessage(error, intl.formatMessage(messages.errorRemoveInvalidation)),\n variant: ALERT_VARIANTS.DANGER,\n });\n },\n },\n );\n }, [removeInval, selectedUsername, selectedEmail, showToast, showModal, intl]);\n\n const handleToggleCertificateGeneration = useCallback(() => {\n const newState = !isCertificateGenerationEnabled;\n toggleGeneration(newState, {\n onSuccess: () => {\n setIsCertificateGenerationEnabled(newState);\n setIsDisableCertificatesOpen(false);\n showToast(\n newState\n ? intl.formatMessage(messages.successEnableCertificates)\n : intl.formatMessage(messages.successDisableCertificates),\n );\n },\n onError: (error) => {\n showModal({\n title: MODAL_TITLES.ERROR,\n message: getErrorMessage(error, intl.formatMessage(messages.errorToggleCertificateGeneration)),\n variant: ALERT_VARIANTS.DANGER,\n });\n },\n });\n }, [isCertificateGenerationEnabled, toggleGeneration, showToast, showModal, intl]);\n\n const handleRegenerateCertificates = useCallback(() => {\n regenerateCerts(filter, {\n onSuccess: () => {\n showToast(intl.formatMessage(messages.certificatesRegeneratedToast));\n },\n onError: (error) => {\n showModal({\n title: MODAL_TITLES.ERROR,\n message: getErrorMessage(error, intl.formatMessage(messages.errorRegenerateCertificates)),\n variant: ALERT_VARIANTS.DANGER,\n });\n },\n });\n }, [regenerateCerts, filter, showToast, showModal, intl]);\n\n // Check if certificate management is disabled\n if (courseInfo && !courseInfo.certificatesEnabled) {\n return (\n <Container className=\"mt-4.5 mb-4\" fluid>\n <Alert variant=\"warning\">\n {intl.formatMessage(messages.certificatesDisabledMessage)}\n </Alert>\n </Container>\n );\n }\n\n return (\n <Container className=\"mt-4.5 mb-4\" fluid>\n <CertificatesPageHeader\n onGrantExceptions={() => setIsGrantExceptionsOpen(true)}\n onInvalidateCertificate={() => setIsInvalidateCertificateOpen(true)}\n onDisableCertificates={() => setIsDisableCertificatesOpen(true)}\n />\n\n <Card variant=\"muted\" className=\"pt-3 pt-md-4 pb-4 pb-md-6 certificates-card\">\n <Tabs\n activeKey={activeTab}\n onSelect={(key) => setActiveTab(key || TAB_KEYS.ISSUED)}\n className=\"mx-4\"\n variant=\"button-group\"\n >\n <Tab eventKey={TAB_KEYS.ISSUED} title={intl.formatMessage(messages.issuedCertificatesTab)}>\n <IssuedCertificatesTab\n data={certificatesData?.results || []}\n isLoading={isLoadingCertificates}\n itemCount={certificatesData?.count || 0}\n pageCount={certificatesData?.numPages || 0}\n search={search}\n onSearchChange={setSearch}\n filter={filter}\n onFilterChange={setFilter}\n currentPage={certificatesPage}\n onPageChange={setCertificatesPage}\n onRemoveException={handleRemoveExceptionClick}\n onRemoveInvalidation={handleRemoveInvalidationClick}\n onRegenerateCertificates={handleRegenerateCertificates}\n />\n </Tab>\n <Tab eventKey={TAB_KEYS.HISTORY} title={intl.formatMessage(messages.generationHistoryTab)}>\n <div className=\"d-flex flex-column mt-3 mt-md-4\">\n <GenerationHistoryTable\n data={historyData?.results || []}\n isLoading={isLoadingHistory}\n itemCount={historyData?.count || 0}\n pageCount={historyData?.numPages || 0}\n currentPage={tasksPage}\n onPageChange={setTasksPage}\n />\n </div>\n </Tab>\n </Tabs>\n </Card>\n\n <GrantExceptionsModal\n isOpen={isGrantExceptionsOpen}\n onClose={() => setIsGrantExceptionsOpen(false)}\n onSubmit={handleGrantExceptions}\n isSubmitting={isGrantingExceptions}\n />\n <InvalidateCertificateModal\n isOpen={isInvalidateCertificateOpen}\n onClose={() => setIsInvalidateCertificateOpen(false)}\n onSubmit={handleInvalidateCertificate}\n isSubmitting={isInvalidating}\n />\n <RemoveExceptionModal\n isOpen={isRemoveExceptionOpen}\n email={selectedEmail}\n onClose={() => {\n setIsRemoveExceptionOpen(false);\n setSelectedUsername('');\n setSelectedEmail('');\n }}\n onConfirm={handleRemoveExceptionConfirm}\n isSubmitting={isRemovingException}\n />\n <RemoveInvalidationModal\n isOpen={isRemoveInvalidationOpen}\n email={selectedEmail}\n onClose={() => {\n setIsRemoveInvalidationOpen(false);\n setSelectedUsername('');\n setSelectedEmail('');\n }}\n onConfirm={handleRemoveInvalidationConfirm}\n isSubmitting={isRemovingInvalidation}\n />\n <DisableCertificatesModal\n isOpen={isDisableCertificatesOpen}\n isEnabled={isCertificateGenerationEnabled}\n onClose={() => setIsDisableCertificatesOpen(false)}\n onConfirm={handleToggleCertificateGeneration}\n isSubmitting={isTogglingGeneration}\n />\n </Container>\n );\n};\n\nexport default CertificatesPage;\n"]}
1
+ {"version":3,"file":"CertificatesPage.js","sourceRoot":"","sources":["../../src/certificates/CertificatesPage.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useState, useCallback } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { Card, Container, Tab, Tabs, Alert } from '@openedx/paragon';\nimport { useIntl } from '@openedx/frontend-base';\nimport { useAlert } from '@src/providers/AlertProvider';\nimport { useCourseInfo } from '@src/data/apiHook';\nimport CertificatesPageHeader from '@src/certificates/components/CertificatesPageHeader';\nimport IssuedCertificatesTab from '@src/certificates/components/IssuedCertificatesTab';\nimport GenerationHistoryTable from '@src/certificates/components/GenerationHistoryTable';\nimport GrantExceptionsModal from '@src/certificates/components/GrantExceptionsModal';\nimport InvalidateCertificateModal from '@src/certificates/components/InvalidateCertificateModal';\nimport RemoveExceptionModal from '@src/certificates/components/RemoveExceptionModal';\nimport RemoveInvalidationModal from '@src/certificates/components/RemoveInvalidationModal';\nimport DisableCertificatesModal from '@src/certificates/components/DisableCertificatesModal';\nimport {\n useCertificateGenerationHistory,\n useGrantBulkExceptions,\n useInvalidateCertificate,\n useIssuedCertificates,\n useRegenerateCertificates,\n useRemoveException,\n useRemoveInvalidation,\n useToggleCertificateGeneration,\n useUploadBulkExceptionsCsv,\n} from '@src/certificates/data/apiHook';\nimport { CertificateFilter } from '@src/certificates/types';\nimport { CERTIFICATES_PAGE_SIZE, TAB_KEYS, MODAL_TITLES, ALERT_VARIANTS } from '@src/certificates/constants';\nimport { getErrorMessage } from '@src/certificates/utils/errorHandling';\nimport messages from '@src/certificates/messages';\nimport './CertificatesPage.scss';\n\nconst CertificatesPage = () => {\n const intl = useIntl();\n const { courseId = '' } = useParams<{ courseId: string }>();\n const { showToast, showModal } = useAlert();\n const { data: courseInfo } = useCourseInfo(courseId);\n\n const [filter, setFilter] = useState<CertificateFilter>(CertificateFilter.ALL_LEARNERS);\n const [search, setSearch] = useState('');\n const [certificatesPage, setCertificatesPage] = useState(0);\n const [tasksPage, setTasksPage] = useState(0);\n const [activeTab, setActiveTab] = useState(TAB_KEYS.ISSUED);\n const [selectedUsername, setSelectedUsername] = useState('');\n const [selectedEmail, setSelectedEmail] = useState('');\n const [isCertificateGenerationEnabled, setIsCertificateGenerationEnabled] = useState(true);\n\n const [isGrantExceptionsOpen, setIsGrantExceptionsOpen] = useState(false);\n const [isInvalidateCertificateOpen, setIsInvalidateCertificateOpen] = useState(false);\n const [isRemoveExceptionOpen, setIsRemoveExceptionOpen] = useState(false);\n const [isRemoveInvalidationOpen, setIsRemoveInvalidationOpen] = useState(false);\n const [isDisableCertificatesOpen, setIsDisableCertificatesOpen] = useState(false);\n\n const {\n data: certificatesData,\n isLoading: isLoadingCertificates,\n } = useIssuedCertificates(courseId, {\n page: certificatesPage,\n pageSize: CERTIFICATES_PAGE_SIZE,\n filter,\n search,\n });\n\n const {\n data: historyData,\n isLoading: isLoadingHistory,\n } = useCertificateGenerationHistory(courseId, {\n page: tasksPage,\n pageSize: CERTIFICATES_PAGE_SIZE,\n });\n\n const { mutate: grantExceptions, isPending: isGrantingExceptions } = useGrantBulkExceptions(courseId);\n const { mutate: uploadCsvExceptions, isPending: isUploadingCsv } = useUploadBulkExceptionsCsv(courseId);\n const { mutate: invalidateCert, isPending: isInvalidating } = useInvalidateCertificate(courseId);\n const { mutate: removeExcept, isPending: isRemovingException } = useRemoveException(courseId);\n const { mutate: removeInval, isPending: isRemovingInvalidation } = useRemoveInvalidation(courseId);\n const { mutate: toggleGeneration, isPending: isTogglingGeneration } = useToggleCertificateGeneration(courseId);\n const { mutate: regenerateCerts } = useRegenerateCertificates(courseId);\n\n const handleGrantExceptions = useCallback((learners: string[], notes: string) => {\n grantExceptions(\n { learners, notes },\n {\n onSuccess: (data) => {\n setIsGrantExceptionsOpen(false);\n if (data.errors && data.errors.length > 0) {\n const errorMessages = data.errors.map(err => `${err.learner}: ${err.message}`).join('\\n');\n showModal({\n title: intl.formatMessage(messages.errorModalTitle),\n message: `Some exceptions failed:\\n${errorMessages}`,\n variant: ALERT_VARIANTS.WARNING,\n });\n }\n if (data.success && data.success.length > 0) {\n showToast(intl.formatMessage(messages.exceptionsGrantedToast, { count: data.success.length }));\n }\n },\n onError: (error) => {\n showModal({\n title: intl.formatMessage(messages.errorModalTitle),\n message: getErrorMessage(error, intl.formatMessage(messages.errorGrantException)),\n variant: ALERT_VARIANTS.DANGER,\n });\n },\n },\n );\n }, [grantExceptions, showToast, showModal, intl]);\n\n const handleUploadCsvExceptions = useCallback((file: File) => {\n uploadCsvExceptions(\n file,\n {\n onSuccess: (data) => {\n setIsGrantExceptionsOpen(false);\n if (data.errors && data.errors.length > 0) {\n const errorMessages = data.errors.map(err => `${err.learner}: ${err.message}`).join('\\n');\n showModal({\n title: intl.formatMessage(messages.errorModalTitle),\n message: `Some exceptions failed:\\n${errorMessages}`,\n variant: ALERT_VARIANTS.WARNING,\n });\n }\n if (data.success && data.success.length > 0) {\n showToast(intl.formatMessage(messages.exceptionsGrantedToast, { count: data.success.length }));\n }\n },\n onError: (error) => {\n showModal({\n title: intl.formatMessage(messages.errorModalTitle),\n message: getErrorMessage(error, intl.formatMessage(messages.errorGrantException)),\n variant: ALERT_VARIANTS.DANGER,\n });\n },\n },\n );\n }, [uploadCsvExceptions, showToast, showModal, intl]);\n\n const handleInvalidateCertificate = useCallback((learners: string[], notes: string) => {\n invalidateCert(\n { learners, notes },\n {\n onSuccess: (data) => {\n setIsInvalidateCertificateOpen(false);\n if (data.errors && data.errors.length > 0) {\n const errorMessages = data.errors.map(err => `${err.learner}: ${err.message}`).join('\\n');\n showModal({\n title: intl.formatMessage(messages.errorModalTitle),\n message: `Some invalidations failed:\\n${errorMessages}`,\n variant: ALERT_VARIANTS.WARNING,\n });\n }\n if (data.success && data.success.length > 0) {\n showToast(intl.formatMessage(messages.certificatesInvalidatedToast, { count: data.success.length }));\n }\n },\n onError: (error) => {\n showModal({\n title: intl.formatMessage(messages.errorModalTitle),\n message: getErrorMessage(error, intl.formatMessage(messages.errorInvalidateCertificate)),\n variant: ALERT_VARIANTS.DANGER,\n });\n },\n },\n );\n }, [invalidateCert, showToast, showModal, intl]);\n\n const handleRemoveExceptionClick = useCallback((username: string, email: string) => {\n setSelectedUsername(username);\n setSelectedEmail(email);\n setIsRemoveExceptionOpen(true);\n }, []);\n\n const handleRemoveExceptionConfirm = useCallback(() => {\n // Backend accepts either username or email - use whichever is available\n const identifier = selectedUsername || selectedEmail;\n\n if (!identifier) {\n showModal({\n title: MODAL_TITLES.ERROR,\n message: intl.formatMessage(messages.errorRemoveException) + ': Username or email is required',\n variant: ALERT_VARIANTS.DANGER,\n });\n return;\n }\n\n removeExcept(\n { username: identifier },\n {\n onSuccess: () => {\n setIsRemoveExceptionOpen(false);\n setSelectedUsername('');\n setSelectedEmail('');\n showToast(intl.formatMessage(messages.exceptionRemovedToast, { email: selectedEmail }));\n },\n onError: (error) => {\n showModal({\n title: intl.formatMessage(messages.errorModalTitle),\n message: getErrorMessage(error, intl.formatMessage(messages.errorRemoveException)),\n variant: ALERT_VARIANTS.DANGER,\n });\n },\n },\n );\n }, [removeExcept, selectedUsername, selectedEmail, showToast, showModal, intl]);\n\n const handleRemoveInvalidationClick = useCallback((username: string, email: string) => {\n setSelectedUsername(username);\n setSelectedEmail(email);\n setIsRemoveInvalidationOpen(true);\n }, []);\n\n const handleRemoveInvalidationConfirm = useCallback(() => {\n // Backend accepts either username or email - use whichever is available\n const identifier = selectedUsername || selectedEmail;\n\n if (!identifier) {\n showModal({\n title: MODAL_TITLES.ERROR,\n message: intl.formatMessage(messages.errorRemoveInvalidation) + ': Username or email is required',\n variant: ALERT_VARIANTS.DANGER,\n });\n return;\n }\n\n removeInval(\n { username: identifier },\n {\n onSuccess: () => {\n setIsRemoveInvalidationOpen(false);\n setSelectedUsername('');\n setSelectedEmail('');\n showToast(intl.formatMessage(messages.invalidationRemovedToast, { email: selectedEmail }));\n },\n onError: (error) => {\n showModal({\n title: intl.formatMessage(messages.errorModalTitle),\n message: getErrorMessage(error, intl.formatMessage(messages.errorRemoveInvalidation)),\n variant: ALERT_VARIANTS.DANGER,\n });\n },\n },\n );\n }, [removeInval, selectedUsername, selectedEmail, showToast, showModal, intl]);\n\n const handleToggleCertificateGeneration = useCallback(() => {\n const newState = !isCertificateGenerationEnabled;\n toggleGeneration(newState, {\n onSuccess: () => {\n setIsCertificateGenerationEnabled(newState);\n setIsDisableCertificatesOpen(false);\n showToast(\n newState\n ? intl.formatMessage(messages.successEnableCertificates)\n : intl.formatMessage(messages.successDisableCertificates),\n );\n },\n onError: (error) => {\n showModal({\n title: MODAL_TITLES.ERROR,\n message: getErrorMessage(error, intl.formatMessage(messages.errorToggleCertificateGeneration)),\n variant: ALERT_VARIANTS.DANGER,\n });\n },\n });\n }, [isCertificateGenerationEnabled, toggleGeneration, showToast, showModal, intl]);\n\n const handleRegenerateCertificates = useCallback(() => {\n regenerateCerts(filter, {\n onSuccess: () => {\n showToast(intl.formatMessage(messages.certificatesRegeneratedToast));\n },\n onError: (error) => {\n showModal({\n title: MODAL_TITLES.ERROR,\n message: getErrorMessage(error, intl.formatMessage(messages.errorRegenerateCertificates)),\n variant: ALERT_VARIANTS.DANGER,\n });\n },\n });\n }, [regenerateCerts, filter, showToast, showModal, intl]);\n\n // Check if certificate management is disabled\n if (courseInfo && !courseInfo.certificatesEnabled) {\n return (\n <Container className=\"mt-4.5 mb-4\" fluid>\n <Alert variant=\"warning\">\n {intl.formatMessage(messages.certificatesDisabledMessage)}\n </Alert>\n </Container>\n );\n }\n\n return (\n <Container className=\"mt-4.5 mb-4\" fluid>\n <CertificatesPageHeader\n onGrantExceptions={() => setIsGrantExceptionsOpen(true)}\n onInvalidateCertificate={() => setIsInvalidateCertificateOpen(true)}\n onStudentGeneratedCertificates={() => setIsDisableCertificatesOpen(true)}\n />\n\n <Card variant=\"muted\" className=\"pt-3 pt-md-4 pb-4 pb-md-6 certificates-card\">\n <Tabs\n activeKey={activeTab}\n onSelect={(key) => setActiveTab(key || TAB_KEYS.ISSUED)}\n className=\"mx-4\"\n variant=\"button-group\"\n >\n <Tab eventKey={TAB_KEYS.ISSUED} title={intl.formatMessage(messages.issuedCertificatesTab)}>\n <IssuedCertificatesTab\n data={certificatesData?.results || []}\n isLoading={isLoadingCertificates}\n itemCount={certificatesData?.count || 0}\n pageCount={certificatesData?.numPages || 0}\n search={search}\n onSearchChange={setSearch}\n filter={filter}\n onFilterChange={setFilter}\n currentPage={certificatesPage}\n onPageChange={setCertificatesPage}\n onRemoveException={handleRemoveExceptionClick}\n onRemoveInvalidation={handleRemoveInvalidationClick}\n onRegenerateCertificates={handleRegenerateCertificates}\n />\n </Tab>\n <Tab eventKey={TAB_KEYS.HISTORY} title={intl.formatMessage(messages.generationHistoryTab)}>\n <div className=\"d-flex flex-column mt-3 mt-md-4\">\n <GenerationHistoryTable\n data={historyData?.results || []}\n isLoading={isLoadingHistory}\n itemCount={historyData?.count || 0}\n pageCount={historyData?.numPages || 0}\n currentPage={tasksPage}\n onPageChange={setTasksPage}\n />\n </div>\n </Tab>\n </Tabs>\n </Card>\n\n <GrantExceptionsModal\n isOpen={isGrantExceptionsOpen}\n onClose={() => setIsGrantExceptionsOpen(false)}\n onSubmit={handleGrantExceptions}\n onUploadCsv={handleUploadCsvExceptions}\n isSubmitting={isGrantingExceptions || isUploadingCsv}\n />\n <InvalidateCertificateModal\n isOpen={isInvalidateCertificateOpen}\n onClose={() => setIsInvalidateCertificateOpen(false)}\n onSubmit={handleInvalidateCertificate}\n isSubmitting={isInvalidating}\n />\n <RemoveExceptionModal\n isOpen={isRemoveExceptionOpen}\n email={selectedEmail}\n onClose={() => {\n setIsRemoveExceptionOpen(false);\n setSelectedUsername('');\n setSelectedEmail('');\n }}\n onConfirm={handleRemoveExceptionConfirm}\n isSubmitting={isRemovingException}\n />\n <RemoveInvalidationModal\n isOpen={isRemoveInvalidationOpen}\n email={selectedEmail}\n onClose={() => {\n setIsRemoveInvalidationOpen(false);\n setSelectedUsername('');\n setSelectedEmail('');\n }}\n onConfirm={handleRemoveInvalidationConfirm}\n isSubmitting={isRemovingInvalidation}\n />\n <DisableCertificatesModal\n isOpen={isDisableCertificatesOpen}\n isEnabled={isCertificateGenerationEnabled}\n onClose={() => setIsDisableCertificatesOpen(false)}\n onConfirm={handleToggleCertificateGeneration}\n isSubmitting={isTogglingGeneration}\n />\n </Container>\n );\n};\n\nexport default CertificatesPage;\n"]}
@@ -1,7 +1,7 @@
1
1
  interface CertificatesPageHeaderProps {
2
2
  onGrantExceptions: () => void;
3
3
  onInvalidateCertificate: () => void;
4
- onDisableCertificates: () => void;
4
+ onStudentGeneratedCertificates?: () => void;
5
5
  }
6
- declare const CertificatesPageHeader: ({ onGrantExceptions, onInvalidateCertificate, onDisableCertificates, }: CertificatesPageHeaderProps) => import("react/jsx-runtime").JSX.Element;
6
+ declare const CertificatesPageHeader: ({ onGrantExceptions, onInvalidateCertificate, onStudentGeneratedCertificates, }: CertificatesPageHeaderProps) => import("react/jsx-runtime").JSX.Element;
7
7
  export default CertificatesPageHeader;
@@ -1,11 +1,11 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Button, Dropdown, IconButton, Stack } from '@openedx/paragon';
3
- import { Add, Close, MoreVert } from '@openedx/paragon/icons';
3
+ import { Add, Cancel, MoreVert } from '@openedx/paragon/icons';
4
4
  import { useIntl } from '@openedx/frontend-base';
5
5
  import messages from '../../certificates/messages';
6
- const CertificatesPageHeader = ({ onGrantExceptions, onInvalidateCertificate, onDisableCertificates, }) => {
6
+ const CertificatesPageHeader = ({ onGrantExceptions, onInvalidateCertificate, onStudentGeneratedCertificates, }) => {
7
7
  const intl = useIntl();
8
- return (_jsxs("div", { className: "d-flex flex-column flex-md-row justify-content-between align-items-start align-items-md-center mb-4 gap-3", children: [_jsx("h3", { className: "text-primary-700 mb-0", children: intl.formatMessage(messages.pageTitle) }), _jsxs(Stack, { direction: "horizontal", gap: 2, className: "flex-wrap", children: [_jsxs(Dropdown, { children: [_jsx(Dropdown.Toggle, { as: IconButton, src: MoreVert, alt: intl.formatMessage(messages.disableCertificatesButton), id: "certificates-more-menu" }), _jsx(Dropdown.Menu, { children: _jsx(Dropdown.Item, { onClick: onDisableCertificates, children: intl.formatMessage(messages.disableCertificatesButton) }) })] }), _jsx(Button, { variant: "outline-primary", iconBefore: Close, onClick: onInvalidateCertificate, className: "text-nowrap", children: intl.formatMessage(messages.invalidateCertificateButton) }), _jsx(Button, { variant: "primary", iconBefore: Add, onClick: onGrantExceptions, className: "text-nowrap", children: intl.formatMessage(messages.grantExceptionsButton) })] })] }));
8
+ return (_jsxs("div", { className: "d-flex flex-column flex-md-row justify-content-between align-items-start align-items-md-center mb-4 gap-3", children: [_jsx("h3", { className: "text-primary-700 mb-0", children: intl.formatMessage(messages.pageTitle) }), _jsxs(Stack, { direction: "horizontal", gap: 2, className: "flex-wrap", children: [_jsxs(Dropdown, { children: [_jsx(Dropdown.Toggle, { as: IconButton, src: MoreVert, alt: intl.formatMessage(messages.moreActionsButton), id: "certificates-more-menu" }), _jsx(Dropdown.Menu, { children: onStudentGeneratedCertificates && (_jsx(Dropdown.Item, { onClick: onStudentGeneratedCertificates, children: intl.formatMessage(messages.studentGeneratedCertificatesMenuItem) })) })] }), _jsx(Button, { variant: "outline-primary", iconBefore: Cancel, onClick: onInvalidateCertificate, className: "text-nowrap", children: intl.formatMessage(messages.invalidateCertificateButton) }), _jsx(Button, { variant: "primary", iconBefore: Add, onClick: onGrantExceptions, className: "text-nowrap", children: intl.formatMessage(messages.grantExceptionsButton) })] })] }));
9
9
  };
10
10
  export default CertificatesPageHeader;
11
11
  //# sourceMappingURL=CertificatesPageHeader.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"CertificatesPageHeader.js","sourceRoot":"","sources":["../../../src/certificates/components/CertificatesPageHeader.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACvE,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,QAAQ,MAAM,4BAA4B,CAAC;AAQlD,MAAM,sBAAsB,GAAG,CAAC,EAC9B,iBAAiB,EACjB,uBAAuB,EACvB,qBAAqB,GACO,EAAE,EAAE;IAChC,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC;IAEvB,OAAO,CACL,eAAK,SAAS,EAAC,2GAA2G,aACxH,aAAI,SAAS,EAAC,uBAAuB,YAAE,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,SAAS,CAAC,GAAM,EACnF,MAAC,KAAK,IAAC,SAAS,EAAC,YAAY,EAAC,GAAG,EAAE,CAAC,EAAE,SAAS,EAAC,WAAW,aACzD,MAAC,QAAQ,eACP,KAAC,QAAQ,CAAC,MAAM,IACd,EAAE,EAAE,UAAU,EACd,GAAG,EAAE,QAAQ,EACb,GAAG,EAAE,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,yBAAyB,CAAC,EAC3D,EAAE,EAAC,wBAAwB,GAC3B,EACF,KAAC,QAAQ,CAAC,IAAI,cACZ,KAAC,QAAQ,CAAC,IAAI,IAAC,OAAO,EAAE,qBAAqB,YAC1C,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,yBAAyB,CAAC,GACzC,GACF,IACP,EACX,KAAC,MAAM,IACL,OAAO,EAAC,iBAAiB,EACzB,UAAU,EAAE,KAAK,EACjB,OAAO,EAAE,uBAAuB,EAChC,SAAS,EAAC,aAAa,YAEtB,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,2BAA2B,CAAC,GAClD,EACT,KAAC,MAAM,IACL,OAAO,EAAC,SAAS,EACjB,UAAU,EAAE,GAAG,EACf,OAAO,EAAE,iBAAiB,EAC1B,SAAS,EAAC,aAAa,YAEtB,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,qBAAqB,CAAC,GAC5C,IACH,IACJ,CACP,CAAC;AACJ,CAAC,CAAC;AAEF,eAAe,sBAAsB,CAAC","sourcesContent":["import { Button, Dropdown, IconButton, Stack } from '@openedx/paragon';\nimport { Add, Close, MoreVert } from '@openedx/paragon/icons';\nimport { useIntl } from '@openedx/frontend-base';\nimport messages from '@src/certificates/messages';\n\ninterface CertificatesPageHeaderProps {\n onGrantExceptions: () => void,\n onInvalidateCertificate: () => void,\n onDisableCertificates: () => void,\n}\n\nconst CertificatesPageHeader = ({\n onGrantExceptions,\n onInvalidateCertificate,\n onDisableCertificates,\n}: CertificatesPageHeaderProps) => {\n const intl = useIntl();\n\n return (\n <div className=\"d-flex flex-column flex-md-row justify-content-between align-items-start align-items-md-center mb-4 gap-3\">\n <h3 className=\"text-primary-700 mb-0\">{intl.formatMessage(messages.pageTitle)}</h3>\n <Stack direction=\"horizontal\" gap={2} className=\"flex-wrap\">\n <Dropdown>\n <Dropdown.Toggle\n as={IconButton}\n src={MoreVert}\n alt={intl.formatMessage(messages.disableCertificatesButton)}\n id=\"certificates-more-menu\"\n />\n <Dropdown.Menu>\n <Dropdown.Item onClick={onDisableCertificates}>\n {intl.formatMessage(messages.disableCertificatesButton)}\n </Dropdown.Item>\n </Dropdown.Menu>\n </Dropdown>\n <Button\n variant=\"outline-primary\"\n iconBefore={Close}\n onClick={onInvalidateCertificate}\n className=\"text-nowrap\"\n >\n {intl.formatMessage(messages.invalidateCertificateButton)}\n </Button>\n <Button\n variant=\"primary\"\n iconBefore={Add}\n onClick={onGrantExceptions}\n className=\"text-nowrap\"\n >\n {intl.formatMessage(messages.grantExceptionsButton)}\n </Button>\n </Stack>\n </div>\n );\n};\n\nexport default CertificatesPageHeader;\n"]}
1
+ {"version":3,"file":"CertificatesPageHeader.js","sourceRoot":"","sources":["../../../src/certificates/components/CertificatesPageHeader.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACvE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,QAAQ,MAAM,4BAA4B,CAAC;AAQlD,MAAM,sBAAsB,GAAG,CAAC,EAC9B,iBAAiB,EACjB,uBAAuB,EACvB,8BAA8B,GACF,EAAE,EAAE;IAChC,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC;IAEvB,OAAO,CACL,eAAK,SAAS,EAAC,2GAA2G,aACxH,aAAI,SAAS,EAAC,uBAAuB,YAAE,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,SAAS,CAAC,GAAM,EACnF,MAAC,KAAK,IAAC,SAAS,EAAC,YAAY,EAAC,GAAG,EAAE,CAAC,EAAE,SAAS,EAAC,WAAW,aACzD,MAAC,QAAQ,eACP,KAAC,QAAQ,CAAC,MAAM,IACd,EAAE,EAAE,UAAU,EACd,GAAG,EAAE,QAAQ,EACb,GAAG,EAAE,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,iBAAiB,CAAC,EACnD,EAAE,EAAC,wBAAwB,GAC3B,EACF,KAAC,QAAQ,CAAC,IAAI,cACX,8BAA8B,IAAI,CACjC,KAAC,QAAQ,CAAC,IAAI,IAAC,OAAO,EAAE,8BAA8B,YACnD,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,oCAAoC,CAAC,GACpD,CACjB,GACa,IACP,EACX,KAAC,MAAM,IACL,OAAO,EAAC,iBAAiB,EACzB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,uBAAuB,EAChC,SAAS,EAAC,aAAa,YAEtB,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,2BAA2B,CAAC,GAClD,EACT,KAAC,MAAM,IACL,OAAO,EAAC,SAAS,EACjB,UAAU,EAAE,GAAG,EACf,OAAO,EAAE,iBAAiB,EAC1B,SAAS,EAAC,aAAa,YAEtB,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,qBAAqB,CAAC,GAC5C,IACH,IACJ,CACP,CAAC;AACJ,CAAC,CAAC;AAEF,eAAe,sBAAsB,CAAC","sourcesContent":["import { Button, Dropdown, IconButton, Stack } from '@openedx/paragon';\nimport { Add, Cancel, MoreVert } from '@openedx/paragon/icons';\nimport { useIntl } from '@openedx/frontend-base';\nimport messages from '@src/certificates/messages';\n\ninterface CertificatesPageHeaderProps {\n onGrantExceptions: () => void,\n onInvalidateCertificate: () => void,\n onStudentGeneratedCertificates?: () => void,\n}\n\nconst CertificatesPageHeader = ({\n onGrantExceptions,\n onInvalidateCertificate,\n onStudentGeneratedCertificates,\n}: CertificatesPageHeaderProps) => {\n const intl = useIntl();\n\n return (\n <div className=\"d-flex flex-column flex-md-row justify-content-between align-items-start align-items-md-center mb-4 gap-3\">\n <h3 className=\"text-primary-700 mb-0\">{intl.formatMessage(messages.pageTitle)}</h3>\n <Stack direction=\"horizontal\" gap={2} className=\"flex-wrap\">\n <Dropdown>\n <Dropdown.Toggle\n as={IconButton}\n src={MoreVert}\n alt={intl.formatMessage(messages.moreActionsButton)}\n id=\"certificates-more-menu\"\n />\n <Dropdown.Menu>\n {onStudentGeneratedCertificates && (\n <Dropdown.Item onClick={onStudentGeneratedCertificates}>\n {intl.formatMessage(messages.studentGeneratedCertificatesMenuItem)}\n </Dropdown.Item>\n )}\n </Dropdown.Menu>\n </Dropdown>\n <Button\n variant=\"outline-primary\"\n iconBefore={Cancel}\n onClick={onInvalidateCertificate}\n className=\"text-nowrap\"\n >\n {intl.formatMessage(messages.invalidateCertificateButton)}\n </Button>\n <Button\n variant=\"primary\"\n iconBefore={Add}\n onClick={onGrantExceptions}\n className=\"text-nowrap\"\n >\n {intl.formatMessage(messages.grantExceptionsButton)}\n </Button>\n </Stack>\n </div>\n );\n};\n\nexport default CertificatesPageHeader;\n"]}
@@ -1,16 +1,24 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { ActionRow, Button, ModalDialog } from '@openedx/paragon';
2
+ import { useState } from 'react';
3
+ import { ActionRow, Button, Form, ModalDialog } from '@openedx/paragon';
3
4
  import { useIntl } from '@openedx/frontend-base';
4
5
  import messages from '../../certificates/messages';
5
6
  const DisableCertificatesModal = ({ isOpen, isEnabled, onClose, onConfirm, isSubmitting, }) => {
6
7
  const intl = useIntl();
7
- const title = isEnabled
8
- ? intl.formatMessage(messages.disableCertificatesModalTitle)
9
- : intl.formatMessage(messages.enableCertificatesModalTitle);
10
- const message = isEnabled
11
- ? intl.formatMessage(messages.disableCertificatesModalMessage)
12
- : intl.formatMessage(messages.enableCertificatesModalMessage);
13
- return (_jsxs(ModalDialog, { title: title, onClose: onClose, isOpen: isOpen, size: "sm", hasCloseButton: false, isOverflowVisible: false, children: [_jsx("div", { className: "mx-4 mt-4 mb-2.5", children: _jsx("p", { children: message }) }), _jsx(ModalDialog.Footer, { children: _jsxs(ActionRow, { children: [_jsx(Button, { variant: "tertiary", onClick: onClose, disabled: isSubmitting, children: intl.formatMessage(messages.cancel) }), _jsx(Button, { variant: "primary", onClick: onConfirm, disabled: isSubmitting, children: intl.formatMessage(messages.confirm) })] }) })] }));
8
+ const [enabled, setEnabled] = useState(isEnabled);
9
+ const handleSave = () => {
10
+ if (enabled !== isEnabled) {
11
+ onConfirm();
12
+ }
13
+ else {
14
+ onClose();
15
+ }
16
+ };
17
+ const handleClose = () => {
18
+ setEnabled(isEnabled); // Reset to original value
19
+ onClose();
20
+ };
21
+ return (_jsxs(ModalDialog, { title: intl.formatMessage(messages.studentGeneratedCertificatesModalTitle), onClose: handleClose, isOpen: isOpen, size: "md", hasCloseButton: true, isOverflowVisible: false, children: [_jsx(ModalDialog.Header, { className: "border-bottom", children: _jsx(ModalDialog.Title, { children: intl.formatMessage(messages.studentGeneratedCertificatesModalTitle) }) }), _jsx(ModalDialog.Body, { className: "px-4 py-3", children: _jsxs(Form.Group, { children: [_jsx(Form.Checkbox, { checked: enabled, onChange: (e) => setEnabled(e.target.checked), children: intl.formatMessage(messages.enableStudentGeneratedCertificates) }), _jsx(Form.Text, { className: "text-muted", children: intl.formatMessage(messages.studentGeneratedCertificatesDescription) })] }) }), _jsx(ModalDialog.Footer, { children: _jsxs(ActionRow, { children: [_jsx(Button, { variant: "tertiary", onClick: handleClose, disabled: isSubmitting, children: intl.formatMessage(messages.close) }), _jsx(Button, { variant: "primary", onClick: handleSave, disabled: isSubmitting, children: intl.formatMessage(messages.save) })] }) })] }));
14
22
  };
15
23
  export default DisableCertificatesModal;
16
24
  //# sourceMappingURL=DisableCertificatesModal.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"DisableCertificatesModal.js","sourceRoot":"","sources":["../../../src/certificates/components/DisableCertificatesModal.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAClE,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,QAAQ,MAAM,4BAA4B,CAAC;AAUlD,MAAM,wBAAwB,GAAG,CAAC,EAChC,MAAM,EACN,SAAS,EACT,OAAO,EACP,SAAS,EACT,YAAY,GACkB,EAAE,EAAE;IAClC,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC;IAEvB,MAAM,KAAK,GAAG,SAAS;QACrB,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,6BAA6B,CAAC;QAC5D,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,4BAA4B,CAAC,CAAC;IAE9D,MAAM,OAAO,GAAG,SAAS;QACvB,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,+BAA+B,CAAC;QAC9D,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,8BAA8B,CAAC,CAAC;IAEhE,OAAO,CACL,MAAC,WAAW,IACV,KAAK,EAAE,KAAK,EACZ,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,MAAM,EACd,IAAI,EAAC,IAAI,EACT,cAAc,EAAE,KAAK,EACrB,iBAAiB,EAAE,KAAK,aAExB,cAAK,SAAS,EAAC,kBAAkB,YAC/B,sBAAI,OAAO,GAAK,GACZ,EACN,KAAC,WAAW,CAAC,MAAM,cACjB,MAAC,SAAS,eACR,KAAC,MAAM,IAAC,OAAO,EAAC,UAAU,EAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,YAChE,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,MAAM,CAAC,GAC7B,EACT,KAAC,MAAM,IAAC,OAAO,EAAC,SAAS,EAAC,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,YAAY,YACjE,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,OAAO,CAAC,GAC9B,IACC,GACO,IACT,CACf,CAAC;AACJ,CAAC,CAAC;AAEF,eAAe,wBAAwB,CAAC","sourcesContent":["import { ActionRow, Button, ModalDialog } from '@openedx/paragon';\nimport { useIntl } from '@openedx/frontend-base';\nimport messages from '@src/certificates/messages';\n\ninterface DisableCertificatesModalProps {\n isOpen: boolean,\n isEnabled: boolean,\n onClose: () => void,\n onConfirm: () => void,\n isSubmitting: boolean,\n}\n\nconst DisableCertificatesModal = ({\n isOpen,\n isEnabled,\n onClose,\n onConfirm,\n isSubmitting,\n}: DisableCertificatesModalProps) => {\n const intl = useIntl();\n\n const title = isEnabled\n ? intl.formatMessage(messages.disableCertificatesModalTitle)\n : intl.formatMessage(messages.enableCertificatesModalTitle);\n\n const message = isEnabled\n ? intl.formatMessage(messages.disableCertificatesModalMessage)\n : intl.formatMessage(messages.enableCertificatesModalMessage);\n\n return (\n <ModalDialog\n title={title}\n onClose={onClose}\n isOpen={isOpen}\n size=\"sm\"\n hasCloseButton={false}\n isOverflowVisible={false}\n >\n <div className=\"mx-4 mt-4 mb-2.5\">\n <p>{message}</p>\n </div>\n <ModalDialog.Footer>\n <ActionRow>\n <Button variant=\"tertiary\" onClick={onClose} disabled={isSubmitting}>\n {intl.formatMessage(messages.cancel)}\n </Button>\n <Button variant=\"primary\" onClick={onConfirm} disabled={isSubmitting}>\n {intl.formatMessage(messages.confirm)}\n </Button>\n </ActionRow>\n </ModalDialog.Footer>\n </ModalDialog>\n );\n};\n\nexport default DisableCertificatesModal;\n"]}
1
+ {"version":3,"file":"DisableCertificatesModal.js","sourceRoot":"","sources":["../../../src/certificates/components/DisableCertificatesModal.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACjC,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACxE,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,QAAQ,MAAM,4BAA4B,CAAC;AAUlD,MAAM,wBAAwB,GAAG,CAAC,EAChC,MAAM,EACN,SAAS,EACT,OAAO,EACP,SAAS,EACT,YAAY,GACkB,EAAE,EAAE;IAClC,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC;IACvB,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAC;IAElD,MAAM,UAAU,GAAG,GAAG,EAAE;QACtB,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YAC1B,SAAS,EAAE,CAAC;QACd,CAAC;aAAM,CAAC;YACN,OAAO,EAAE,CAAC;QACZ,CAAC;IACH,CAAC,CAAC;IAEF,MAAM,WAAW,GAAG,GAAG,EAAE;QACvB,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,0BAA0B;QACjD,OAAO,EAAE,CAAC;IACZ,CAAC,CAAC;IAEF,OAAO,CACL,MAAC,WAAW,IACV,KAAK,EAAE,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,sCAAsC,CAAC,EAC1E,OAAO,EAAE,WAAW,EACpB,MAAM,EAAE,MAAM,EACd,IAAI,EAAC,IAAI,EACT,cAAc,QACd,iBAAiB,EAAE,KAAK,aAExB,KAAC,WAAW,CAAC,MAAM,IAAC,SAAS,EAAC,eAAe,YAC3C,KAAC,WAAW,CAAC,KAAK,cACf,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,sCAAsC,CAAC,GAClD,GACD,EACrB,KAAC,WAAW,CAAC,IAAI,IAAC,SAAS,EAAC,WAAW,YACrC,MAAC,IAAI,CAAC,KAAK,eACT,KAAC,IAAI,CAAC,QAAQ,IACZ,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,YAE5C,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,kCAAkC,CAAC,GAClD,EAChB,KAAC,IAAI,CAAC,IAAI,IAAC,SAAS,EAAC,YAAY,YAC9B,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,uCAAuC,CAAC,GAC3D,IACD,GACI,EACnB,KAAC,WAAW,CAAC,MAAM,cACjB,MAAC,SAAS,eACR,KAAC,MAAM,IAAC,OAAO,EAAC,UAAU,EAAC,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,YAAY,YACpE,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,KAAK,CAAC,GAC5B,EACT,KAAC,MAAM,IAAC,OAAO,EAAC,SAAS,EAAC,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,YAAY,YAClE,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,IAAI,CAAC,GAC3B,IACC,GACO,IACT,CACf,CAAC;AACJ,CAAC,CAAC;AAEF,eAAe,wBAAwB,CAAC","sourcesContent":["import { useState } from 'react';\nimport { ActionRow, Button, Form, ModalDialog } from '@openedx/paragon';\nimport { useIntl } from '@openedx/frontend-base';\nimport messages from '@src/certificates/messages';\n\ninterface DisableCertificatesModalProps {\n isOpen: boolean,\n isEnabled: boolean,\n onClose: () => void,\n onConfirm: () => void,\n isSubmitting: boolean,\n}\n\nconst DisableCertificatesModal = ({\n isOpen,\n isEnabled,\n onClose,\n onConfirm,\n isSubmitting,\n}: DisableCertificatesModalProps) => {\n const intl = useIntl();\n const [enabled, setEnabled] = useState(isEnabled);\n\n const handleSave = () => {\n if (enabled !== isEnabled) {\n onConfirm();\n } else {\n onClose();\n }\n };\n\n const handleClose = () => {\n setEnabled(isEnabled); // Reset to original value\n onClose();\n };\n\n return (\n <ModalDialog\n title={intl.formatMessage(messages.studentGeneratedCertificatesModalTitle)}\n onClose={handleClose}\n isOpen={isOpen}\n size=\"md\"\n hasCloseButton\n isOverflowVisible={false}\n >\n <ModalDialog.Header className=\"border-bottom\">\n <ModalDialog.Title>\n {intl.formatMessage(messages.studentGeneratedCertificatesModalTitle)}\n </ModalDialog.Title>\n </ModalDialog.Header>\n <ModalDialog.Body className=\"px-4 py-3\">\n <Form.Group>\n <Form.Checkbox\n checked={enabled}\n onChange={(e) => setEnabled(e.target.checked)}\n >\n {intl.formatMessage(messages.enableStudentGeneratedCertificates)}\n </Form.Checkbox>\n <Form.Text className=\"text-muted\">\n {intl.formatMessage(messages.studentGeneratedCertificatesDescription)}\n </Form.Text>\n </Form.Group>\n </ModalDialog.Body>\n <ModalDialog.Footer>\n <ActionRow>\n <Button variant=\"tertiary\" onClick={handleClose} disabled={isSubmitting}>\n {intl.formatMessage(messages.close)}\n </Button>\n <Button variant=\"primary\" onClick={handleSave} disabled={isSubmitting}>\n {intl.formatMessage(messages.save)}\n </Button>\n </ActionRow>\n </ModalDialog.Footer>\n </ModalDialog>\n );\n};\n\nexport default DisableCertificatesModal;\n"]}
@@ -2,7 +2,8 @@ interface GrantExceptionsModalProps {
2
2
  isOpen: boolean;
3
3
  onClose: () => void;
4
4
  onSubmit: (learners: string[], notes: string) => void;
5
+ onUploadCsv: (file: File) => void;
5
6
  isSubmitting: boolean;
6
7
  }
7
- declare const GrantExceptionsModal: ({ isOpen, onClose, onSubmit, isSubmitting, }: GrantExceptionsModalProps) => import("react/jsx-runtime").JSX.Element;
8
+ declare const GrantExceptionsModal: ({ isOpen, onClose, onSubmit, onUploadCsv, isSubmitting, }: GrantExceptionsModalProps) => import("react/jsx-runtime").JSX.Element;
8
9
  export default GrantExceptionsModal;
@@ -1,10 +1,55 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from 'react';
2
3
  import { useIntl } from '@openedx/frontend-base';
3
- import LearnerActionModal from '../../certificates/components/LearnerActionModal';
4
+ import { ActionRow, Button, Dropzone, Form, Hyperlink, Icon, ModalDialog, OverlayTrigger, Tab, Tabs, Tooltip } from '@openedx/paragon';
5
+ import { InfoOutline } from '@openedx/paragon/icons';
4
6
  import messages from '../../certificates/messages';
5
- const GrantExceptionsModal = ({ isOpen, onClose, onSubmit, isSubmitting, }) => {
7
+ const GrantExceptionsModal = ({ isOpen, onClose, onSubmit, onUploadCsv, isSubmitting, }) => {
6
8
  const intl = useIntl();
7
- return (_jsx(LearnerActionModal, { isOpen: isOpen, onClose: onClose, onSubmit: onSubmit, isSubmitting: isSubmitting, title: intl.formatMessage(messages.grantExceptionsModalTitle), description: intl.formatMessage(messages.grantExceptionsModalDescription), learnersLabel: intl.formatMessage(messages.learnersLabel), learnersPlaceholder: intl.formatMessage(messages.learnersPlaceholder), notesLabel: intl.formatMessage(messages.notesLabel), notesPlaceholder: intl.formatMessage(messages.notesPlaceholder), submitLabel: intl.formatMessage(messages.submit), cancelLabel: intl.formatMessage(messages.cancel) }));
9
+ const [learner, setLearner] = useState('');
10
+ const [notes, setNotes] = useState('');
11
+ const [csvFileName, setCsvFileName] = useState('');
12
+ const [csvFile, setCsvFile] = useState(null);
13
+ const [activeTab, setActiveTab] = useState('single');
14
+ const handleSubmit = () => {
15
+ if (activeTab === 'single') {
16
+ const trimmedLearner = learner.trim();
17
+ if (trimmedLearner) {
18
+ onSubmit([trimmedLearner], notes);
19
+ setLearner('');
20
+ setNotes('');
21
+ }
22
+ }
23
+ else {
24
+ // Handle CSV upload
25
+ if (csvFile) {
26
+ onUploadCsv(csvFile);
27
+ setCsvFileName('');
28
+ setCsvFile(null);
29
+ }
30
+ }
31
+ };
32
+ const handleClose = () => {
33
+ setLearner('');
34
+ setNotes('');
35
+ setCsvFileName('');
36
+ setCsvFile(null);
37
+ setActiveTab('single');
38
+ onClose();
39
+ };
40
+ return (_jsxs(ModalDialog, { isOpen: isOpen, onClose: handleClose, hasCloseButton: true, title: intl.formatMessage(messages.grantExceptionsModalTitle), className: "grant-exceptions-modal", isOverflowVisible: false, children: [_jsx(ModalDialog.Header, { className: "border-bottom", children: _jsx(ModalDialog.Title, { children: intl.formatMessage(messages.grantExceptionsModalTitle) }) }), _jsxs(ModalDialog.Body, { className: "px-4 py-3", children: [_jsx("p", { className: "mb-4", children: intl.formatMessage(messages.grantExceptionsModalDescription) }), _jsxs(Tabs, { activeKey: activeTab, onSelect: (key) => setActiveTab(key), id: "grant-exceptions-tabs", className: "mb-3", children: [_jsx(Tab, { eventKey: "single", title: intl.formatMessage(messages.singleLearnerTab), children: _jsxs("div", { className: "mt-3", children: [_jsx("p", { className: "mb-3", children: intl.formatMessage(messages.individualTabDescription) }), _jsx(Form.Group, { className: "mb-3", children: _jsx(Form.Control, { type: "text", placeholder: intl.formatMessage(messages.studentEmailUsername), value: learner, onChange: (e) => setLearner(e.target.value) }) }), _jsx(Form.Group, { children: _jsx(Form.Control, { as: "textarea", rows: 3, placeholder: intl.formatMessage(messages.notesOptional), value: notes, onChange: (e) => setNotes(e.target.value) }) })] }) }), _jsx(Tab, { eventKey: "bulk", title: intl.formatMessage(messages.bulkUploadTab), children: _jsxs("div", { className: "mt-3", children: [_jsx(Form.Label, { className: "mb-3", children: intl.formatMessage(messages.csvFileLabel) }), csvFileName && (_jsxs("div", { className: "alert alert-success mb-3", role: "alert", children: [_jsx("strong", { children: "\u2713" }), " ", intl.formatMessage(messages.csvFileSelected, { fileName: csvFileName })] })), _jsx(Dropzone, { accept: { 'text/csv': ['.csv'] }, maxSize: 5 * 1024 * 1024, onProcessUpload: ({ fileData, handleError }) => {
41
+ const file = fileData.get('file') || fileData.get('files[0]') || Array.from(fileData.values()).find(value => value instanceof File);
42
+ if (file instanceof File) {
43
+ setCsvFileName(file.name);
44
+ setCsvFile(file);
45
+ }
46
+ else if (handleError) {
47
+ handleError(new Error('No file found'));
48
+ }
49
+ } }), _jsx("div", { className: "d-flex justify-content-end mt-3", children: _jsx(OverlayTrigger, { placement: "top", overlay: (_jsx(Tooltip, { id: "csv-instructions-tooltip", variant: "light", style: { maxWidth: '400px' }, children: intl.formatMessage(messages.csvInstructionsTooltip) })), children: _jsxs("span", { className: "d-inline-flex align-items-center gap-1 text-muted", style: { cursor: 'pointer' }, children: [_jsx(Icon, { src: InfoOutline, size: "sm" }), _jsx(Hyperlink, { destination: "#", onClick: (e) => {
50
+ e.preventDefault();
51
+ // TODO: Link to CSV instructions documentation
52
+ }, className: "text-muted text-decoration-none", children: intl.formatMessage(messages.csvInstructions) })] }) }) })] }) })] })] }), _jsx(ModalDialog.Footer, { children: _jsxs(ActionRow, { children: [_jsx(Button, { variant: "tertiary", onClick: handleClose, disabled: isSubmitting, children: intl.formatMessage(messages.cancel) }), _jsx(Button, { variant: "primary", onClick: handleSubmit, disabled: isSubmitting || (activeTab === 'single' ? !learner.trim() : !csvFile), children: intl.formatMessage(messages.save) })] }) })] }));
8
53
  };
9
54
  export default GrantExceptionsModal;
10
55
  //# sourceMappingURL=GrantExceptionsModal.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"GrantExceptionsModal.js","sourceRoot":"","sources":["../../../src/certificates/components/GrantExceptionsModal.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,kBAAkB,MAAM,iDAAiD,CAAC;AACjF,OAAO,QAAQ,MAAM,4BAA4B,CAAC;AASlD,MAAM,oBAAoB,GAAG,CAAC,EAC5B,MAAM,EACN,OAAO,EACP,QAAQ,EACR,YAAY,GACc,EAAE,EAAE;IAC9B,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC;IAEvB,OAAO,CACL,KAAC,kBAAkB,IACjB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,QAAQ,EAClB,YAAY,EAAE,YAAY,EAC1B,KAAK,EAAE,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,yBAAyB,CAAC,EAC7D,WAAW,EAAE,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,+BAA+B,CAAC,EACzE,aAAa,EAAE,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,aAAa,CAAC,EACzD,mBAAmB,EAAE,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,mBAAmB,CAAC,EACrE,UAAU,EAAE,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,UAAU,CAAC,EACnD,gBAAgB,EAAE,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,gBAAgB,CAAC,EAC/D,WAAW,EAAE,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,MAAM,CAAC,EAChD,WAAW,EAAE,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,MAAM,CAAC,GAChD,CACH,CAAC;AACJ,CAAC,CAAC;AAEF,eAAe,oBAAoB,CAAC","sourcesContent":["import { useIntl } from '@openedx/frontend-base';\nimport LearnerActionModal from '@src/certificates/components/LearnerActionModal';\nimport messages from '@src/certificates/messages';\n\ninterface GrantExceptionsModalProps {\n isOpen: boolean,\n onClose: () => void,\n onSubmit: (learners: string[], notes: string) => void,\n isSubmitting: boolean,\n}\n\nconst GrantExceptionsModal = ({\n isOpen,\n onClose,\n onSubmit,\n isSubmitting,\n}: GrantExceptionsModalProps) => {\n const intl = useIntl();\n\n return (\n <LearnerActionModal\n isOpen={isOpen}\n onClose={onClose}\n onSubmit={onSubmit}\n isSubmitting={isSubmitting}\n title={intl.formatMessage(messages.grantExceptionsModalTitle)}\n description={intl.formatMessage(messages.grantExceptionsModalDescription)}\n learnersLabel={intl.formatMessage(messages.learnersLabel)}\n learnersPlaceholder={intl.formatMessage(messages.learnersPlaceholder)}\n notesLabel={intl.formatMessage(messages.notesLabel)}\n notesPlaceholder={intl.formatMessage(messages.notesPlaceholder)}\n submitLabel={intl.formatMessage(messages.submit)}\n cancelLabel={intl.formatMessage(messages.cancel)}\n />\n );\n};\n\nexport default GrantExceptionsModal;\n"]}
1
+ {"version":3,"file":"GrantExceptionsModal.js","sourceRoot":"","sources":["../../../src/certificates/components/GrantExceptionsModal.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useState } from 'react';\nimport { useIntl } from '@openedx/frontend-base';\nimport { ActionRow, Button, Dropzone, Form, Hyperlink, Icon, ModalDialog, OverlayTrigger, Tab, Tabs, Tooltip } from '@openedx/paragon';\nimport { InfoOutline } from '@openedx/paragon/icons';\nimport messages from '@src/certificates/messages';\n\ninterface GrantExceptionsModalProps {\n isOpen: boolean,\n onClose: () => void,\n onSubmit: (learners: string[], notes: string) => void,\n onUploadCsv: (file: File) => void,\n isSubmitting: boolean,\n}\n\nconst GrantExceptionsModal = ({\n isOpen,\n onClose,\n onSubmit,\n onUploadCsv,\n isSubmitting,\n}: GrantExceptionsModalProps) => {\n const intl = useIntl();\n const [learner, setLearner] = useState('');\n const [notes, setNotes] = useState('');\n const [csvFileName, setCsvFileName] = useState<string>('');\n const [csvFile, setCsvFile] = useState<File | null>(null);\n const [activeTab, setActiveTab] = useState('single');\n\n const handleSubmit = () => {\n if (activeTab === 'single') {\n const trimmedLearner = learner.trim();\n if (trimmedLearner) {\n onSubmit([trimmedLearner], notes);\n setLearner('');\n setNotes('');\n }\n } else {\n // Handle CSV upload\n if (csvFile) {\n onUploadCsv(csvFile);\n setCsvFileName('');\n setCsvFile(null);\n }\n }\n };\n\n const handleClose = () => {\n setLearner('');\n setNotes('');\n setCsvFileName('');\n setCsvFile(null);\n setActiveTab('single');\n onClose();\n };\n\n return (\n <ModalDialog\n isOpen={isOpen}\n onClose={handleClose}\n hasCloseButton\n title={intl.formatMessage(messages.grantExceptionsModalTitle)}\n className=\"grant-exceptions-modal\"\n isOverflowVisible={false}\n >\n <ModalDialog.Header className=\"border-bottom\">\n <ModalDialog.Title>{intl.formatMessage(messages.grantExceptionsModalTitle)}</ModalDialog.Title>\n </ModalDialog.Header>\n <ModalDialog.Body className=\"px-4 py-3\">\n <p className=\"mb-4\">{intl.formatMessage(messages.grantExceptionsModalDescription)}</p>\n <Tabs\n activeKey={activeTab}\n onSelect={(key) => setActiveTab(key as string)}\n id=\"grant-exceptions-tabs\"\n className=\"mb-3\"\n >\n <Tab eventKey=\"single\" title={intl.formatMessage(messages.singleLearnerTab)}>\n <div className=\"mt-3\">\n <p className=\"mb-3\">{intl.formatMessage(messages.individualTabDescription)}</p>\n <Form.Group className=\"mb-3\">\n <Form.Control\n type=\"text\"\n placeholder={intl.formatMessage(messages.studentEmailUsername)}\n value={learner}\n onChange={(e) => setLearner(e.target.value)}\n />\n </Form.Group>\n <Form.Group>\n <Form.Control\n as=\"textarea\"\n rows={3}\n placeholder={intl.formatMessage(messages.notesOptional)}\n value={notes}\n onChange={(e) => setNotes(e.target.value)}\n />\n </Form.Group>\n </div>\n </Tab>\n <Tab eventKey=\"bulk\" title={intl.formatMessage(messages.bulkUploadTab)}>\n <div className=\"mt-3\">\n <Form.Label className=\"mb-3\">{intl.formatMessage(messages.csvFileLabel)}</Form.Label>\n {csvFileName && (\n <div className=\"alert alert-success mb-3\" role=\"alert\">\n <strong>✓</strong> {intl.formatMessage(messages.csvFileSelected, { fileName: csvFileName })}\n </div>\n )}\n <Dropzone\n accept={{ 'text/csv': ['.csv'] }}\n maxSize={5 * 1024 * 1024}\n onProcessUpload={({ fileData, handleError }) => {\n const file = fileData.get('file') || fileData.get('files[0]') || Array.from(fileData.values()).find(value => value instanceof File);\n\n if (file instanceof File) {\n setCsvFileName(file.name);\n setCsvFile(file);\n } else if (handleError) {\n handleError(new Error('No file found'));\n }\n }}\n />\n <div className=\"d-flex justify-content-end mt-3\">\n <OverlayTrigger\n placement=\"top\"\n overlay={(\n <Tooltip id=\"csv-instructions-tooltip\" variant=\"light\" style={{ maxWidth: '400px' }}>\n {intl.formatMessage(messages.csvInstructionsTooltip)}\n </Tooltip>\n )}\n >\n <span className=\"d-inline-flex align-items-center gap-1 text-muted\" style={{ cursor: 'pointer' }}>\n <Icon src={InfoOutline} size=\"sm\" />\n <Hyperlink\n destination=\"#\"\n onClick={(e) => {\n e.preventDefault();\n // TODO: Link to CSV instructions documentation\n }}\n className=\"text-muted text-decoration-none\"\n >\n {intl.formatMessage(messages.csvInstructions)}\n </Hyperlink>\n </span>\n </OverlayTrigger>\n </div>\n </div>\n </Tab>\n </Tabs>\n </ModalDialog.Body>\n <ModalDialog.Footer>\n <ActionRow>\n <Button variant=\"tertiary\" onClick={handleClose} disabled={isSubmitting}>\n {intl.formatMessage(messages.cancel)}\n </Button>\n <Button\n variant=\"primary\"\n onClick={handleSubmit}\n disabled={isSubmitting || (activeTab === 'single' ? !learner.trim() : !csvFile)}\n >\n {intl.formatMessage(messages.save)}\n </Button>\n </ActionRow>\n </ModalDialog.Footer>\n </ModalDialog>\n );\n};\n\nexport default GrantExceptionsModal;\n"]}
@@ -1,10 +1,26 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from 'react';
2
3
  import { useIntl } from '@openedx/frontend-base';
3
- import LearnerActionModal from '../../certificates/components/LearnerActionModal';
4
+ import { ActionRow, Button, Form, ModalDialog } from '@openedx/paragon';
4
5
  import messages from '../../certificates/messages';
5
6
  const InvalidateCertificateModal = ({ isOpen, onClose, onSubmit, isSubmitting, }) => {
6
7
  const intl = useIntl();
7
- return (_jsx(LearnerActionModal, { isOpen: isOpen, onClose: onClose, onSubmit: onSubmit, isSubmitting: isSubmitting, title: intl.formatMessage(messages.invalidateCertificateModalTitle), description: intl.formatMessage(messages.invalidateCertificateModalDescription), learnersLabel: intl.formatMessage(messages.learnersLabel), learnersPlaceholder: intl.formatMessage(messages.learnersPlaceholder), notesLabel: intl.formatMessage(messages.notesLabel), notesPlaceholder: intl.formatMessage(messages.notesPlaceholder), submitLabel: intl.formatMessage(messages.submit), cancelLabel: intl.formatMessage(messages.cancel) }));
8
+ const [learner, setLearner] = useState('');
9
+ const [notes, setNotes] = useState('');
10
+ const handleSubmit = () => {
11
+ const trimmedLearner = learner.trim();
12
+ if (trimmedLearner) {
13
+ onSubmit([trimmedLearner], notes);
14
+ setLearner('');
15
+ setNotes('');
16
+ }
17
+ };
18
+ const handleClose = () => {
19
+ setLearner('');
20
+ setNotes('');
21
+ onClose();
22
+ };
23
+ return (_jsxs(ModalDialog, { isOpen: isOpen, onClose: handleClose, hasCloseButton: true, title: intl.formatMessage(messages.invalidateCertificateModalTitle), className: "invalidate-certificate-modal", isOverflowVisible: false, children: [_jsx(ModalDialog.Header, { className: "border-bottom", children: _jsx(ModalDialog.Title, { children: intl.formatMessage(messages.invalidateCertificateModalTitle) }) }), _jsxs(ModalDialog.Body, { className: "px-3", children: [_jsx("p", { className: "mb-3", children: intl.formatMessage(messages.invalidateCertificateModalDescription) }), _jsxs(Form.Group, { className: "mb-3", children: [_jsx(Form.Label, { children: intl.formatMessage(messages.learnerLabel) }), _jsx(Form.Control, { type: "text", placeholder: intl.formatMessage(messages.learnerPlaceholder), value: learner, onChange: (e) => setLearner(e.target.value) })] }), _jsxs(Form.Group, { children: [_jsx(Form.Label, { children: intl.formatMessage(messages.notesLabel) }), _jsx(Form.Control, { as: "textarea", rows: 3, placeholder: intl.formatMessage(messages.notesPlaceholder), value: notes, onChange: (e) => setNotes(e.target.value) })] })] }), _jsx(ModalDialog.Footer, { children: _jsxs(ActionRow, { children: [_jsx(Button, { variant: "tertiary", onClick: handleClose, disabled: isSubmitting, children: intl.formatMessage(messages.cancel) }), _jsx(Button, { variant: "primary", onClick: handleSubmit, disabled: isSubmitting || !learner.trim(), children: intl.formatMessage(messages.save) })] }) })] }));
8
24
  };
9
25
  export default InvalidateCertificateModal;
10
26
  //# sourceMappingURL=InvalidateCertificateModal.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"InvalidateCertificateModal.js","sourceRoot":"","sources":["../../../src/certificates/components/InvalidateCertificateModal.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,kBAAkB,MAAM,iDAAiD,CAAC;AACjF,OAAO,QAAQ,MAAM,4BAA4B,CAAC;AASlD,MAAM,0BAA0B,GAAG,CAAC,EAClC,MAAM,EACN,OAAO,EACP,QAAQ,EACR,YAAY,GACoB,EAAE,EAAE;IACpC,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC;IAEvB,OAAO,CACL,KAAC,kBAAkB,IACjB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,QAAQ,EAClB,YAAY,EAAE,YAAY,EAC1B,KAAK,EAAE,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,+BAA+B,CAAC,EACnE,WAAW,EAAE,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,qCAAqC,CAAC,EAC/E,aAAa,EAAE,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,aAAa,CAAC,EACzD,mBAAmB,EAAE,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,mBAAmB,CAAC,EACrE,UAAU,EAAE,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,UAAU,CAAC,EACnD,gBAAgB,EAAE,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,gBAAgB,CAAC,EAC/D,WAAW,EAAE,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,MAAM,CAAC,EAChD,WAAW,EAAE,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,MAAM,CAAC,GAChD,CACH,CAAC;AACJ,CAAC,CAAC;AAEF,eAAe,0BAA0B,CAAC","sourcesContent":["import { useIntl } from '@openedx/frontend-base';\nimport LearnerActionModal from '@src/certificates/components/LearnerActionModal';\nimport messages from '@src/certificates/messages';\n\ninterface InvalidateCertificateModalProps {\n isOpen: boolean,\n onClose: () => void,\n onSubmit: (learners: string[], notes: string) => void,\n isSubmitting: boolean,\n}\n\nconst InvalidateCertificateModal = ({\n isOpen,\n onClose,\n onSubmit,\n isSubmitting,\n}: InvalidateCertificateModalProps) => {\n const intl = useIntl();\n\n return (\n <LearnerActionModal\n isOpen={isOpen}\n onClose={onClose}\n onSubmit={onSubmit}\n isSubmitting={isSubmitting}\n title={intl.formatMessage(messages.invalidateCertificateModalTitle)}\n description={intl.formatMessage(messages.invalidateCertificateModalDescription)}\n learnersLabel={intl.formatMessage(messages.learnersLabel)}\n learnersPlaceholder={intl.formatMessage(messages.learnersPlaceholder)}\n notesLabel={intl.formatMessage(messages.notesLabel)}\n notesPlaceholder={intl.formatMessage(messages.notesPlaceholder)}\n submitLabel={intl.formatMessage(messages.submit)}\n cancelLabel={intl.formatMessage(messages.cancel)}\n />\n );\n};\n\nexport default InvalidateCertificateModal;\n"]}
1
+ {"version":3,"file":"InvalidateCertificateModal.js","sourceRoot":"","sources":["../../../src/certificates/components/InvalidateCertificateModal.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useState } from 'react';\nimport { useIntl } from '@openedx/frontend-base';\nimport { ActionRow, Button, Form, ModalDialog } from '@openedx/paragon';\nimport messages from '@src/certificates/messages';\n\ninterface InvalidateCertificateModalProps {\n isOpen: boolean,\n onClose: () => void,\n onSubmit: (learners: string[], notes: string) => void,\n isSubmitting: boolean,\n}\n\nconst InvalidateCertificateModal = ({\n isOpen,\n onClose,\n onSubmit,\n isSubmitting,\n}: InvalidateCertificateModalProps) => {\n const intl = useIntl();\n const [learner, setLearner] = useState('');\n const [notes, setNotes] = useState('');\n\n const handleSubmit = () => {\n const trimmedLearner = learner.trim();\n if (trimmedLearner) {\n onSubmit([trimmedLearner], notes);\n setLearner('');\n setNotes('');\n }\n };\n\n const handleClose = () => {\n setLearner('');\n setNotes('');\n onClose();\n };\n\n return (\n <ModalDialog\n isOpen={isOpen}\n onClose={handleClose}\n hasCloseButton\n title={intl.formatMessage(messages.invalidateCertificateModalTitle)}\n className=\"invalidate-certificate-modal\"\n isOverflowVisible={false}\n >\n <ModalDialog.Header className=\"border-bottom\">\n <ModalDialog.Title>{intl.formatMessage(messages.invalidateCertificateModalTitle)}</ModalDialog.Title>\n </ModalDialog.Header>\n <ModalDialog.Body className=\"px-3\">\n <p className=\"mb-3\">{intl.formatMessage(messages.invalidateCertificateModalDescription)}</p>\n <Form.Group className=\"mb-3\">\n <Form.Label>{intl.formatMessage(messages.learnerLabel)}</Form.Label>\n <Form.Control\n type=\"text\"\n placeholder={intl.formatMessage(messages.learnerPlaceholder)}\n value={learner}\n onChange={(e) => setLearner(e.target.value)}\n />\n </Form.Group>\n <Form.Group>\n <Form.Label>{intl.formatMessage(messages.notesLabel)}</Form.Label>\n <Form.Control\n as=\"textarea\"\n rows={3}\n placeholder={intl.formatMessage(messages.notesPlaceholder)}\n value={notes}\n onChange={(e) => setNotes(e.target.value)}\n />\n </Form.Group>\n </ModalDialog.Body>\n <ModalDialog.Footer>\n <ActionRow>\n <Button variant=\"tertiary\" onClick={handleClose} disabled={isSubmitting}>\n {intl.formatMessage(messages.cancel)}\n </Button>\n <Button variant=\"primary\" onClick={handleSubmit} disabled={isSubmitting || !learner.trim()}>\n {intl.formatMessage(messages.save)}\n </Button>\n </ActionRow>\n </ModalDialog.Footer>\n </ModalDialog>\n );\n};\n\nexport default InvalidateCertificateModal;\n"]}
@@ -9,6 +9,13 @@ export declare const grantBulkExceptions: (courseId: string, request: GrantExcep
9
9
  message: string;
10
10
  }[];
11
11
  }>;
12
+ export declare const uploadBulkExceptionsCsv: (courseId: string, file: File) => Promise<{
13
+ success: string[];
14
+ errors: {
15
+ learner: string;
16
+ message: string;
17
+ }[];
18
+ }>;
12
19
  export declare const invalidateCertificate: (courseId: string, request: InvalidateCertificateRequest) => Promise<{
13
20
  success: string[];
14
21
  errors: {
@@ -36,6 +36,16 @@ export const grantBulkExceptions = (courseId, request) => __awaiter(void 0, void
36
36
  });
37
37
  return camelCaseObject(data);
38
38
  });
39
+ export const uploadBulkExceptionsCsv = (courseId, file) => __awaiter(void 0, void 0, void 0, function* () {
40
+ const formData = new FormData();
41
+ formData.append('file', file);
42
+ const { data } = yield getAuthenticatedHttpClient().post(`${getApiBaseUrl()}/api/instructor/v2/courses/${courseId}/certificates/exceptions/bulk`, formData, {
43
+ headers: {
44
+ 'Content-Type': 'multipart/form-data',
45
+ },
46
+ });
47
+ return camelCaseObject(data);
48
+ });
39
49
  export const invalidateCertificate = (courseId, request) => __awaiter(void 0, void 0, void 0, function* () {
40
50
  const { data } = yield getAuthenticatedHttpClient().post(`${getApiBaseUrl()}/api/instructor/v2/courses/${courseId}/certificates/invalidations`, {
41
51
  learners: request.learners,