Skip to content

Commit

Permalink
feat: Add enroll-by date to the assignment table (#1248)
Browse files Browse the repository at this point in the history
* feat: Add enroll-by date to the assignment table

* feat: adds internationlization on static text

* chore: center column and cells

* chore: UI feedback to match parity

* chore: Update tests

* chore: cleanup

* chore: add test

* chore: PR feedback

* chore: keep datatable component headers arch in parity

* chore: Hardcode apostrophe and more doublequote fixes

* chore: PR feedback 2
  • Loading branch information
brobro10000 authored Jun 12, 2024
1 parent e9b406f commit 2a326e7
Show file tree
Hide file tree
Showing 26 changed files with 292 additions and 75 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -93,26 +93,26 @@ const expiryThresholds = {
30: ({ intl, date }) => ({
notificationTemplate: {
title: intl.formatMessage({
id: 'adminPostal.learnerCredit.expiresInThirtyDaysNotification.title',
id: 'adminPortal.learnerCredit.expiresInThirtyDaysNotification.title',
defaultMessage: 'Your Learner Credit plan expires in less than 30 days',
description: 'Title for the notification that the Learner Credit plan is expiring in less than 30 days.',
}),
variant: 'danger',
message: intl.formatMessage({
id: 'adminPostal.learnerCredit.expiresInThirtyDaysNotification.message',
defaultMessage: 'When your plan expires you will lose access to administrative functions and the remaining balance of your plan{apostrophe}s budget(s) will be unusable. Contact support today to renew your plan.',
id: 'adminPortal.learnerCredit.expiresInThirtyDaysNotification.message',
defaultMessage: "When your plan expires you will lose access to administrative functions and the remaining balance of your plan's budget(s) will be unusable. Contact support today to renew your plan.",
description: 'Message for the notification that the Learner Credit plan is expiring in less than 30 days.',
}, { aposrophe: "'" }),
}),
},
modalTemplate: {
title: intl.formatMessage({
id: 'adminPostal.learnerCredit.expiresInThirtyDaysModal.title',
id: 'adminPortal.learnerCredit.expiresInThirtyDaysModal.title',
defaultMessage: 'Your Learner Credit plan expires in less than 30 days',
description: 'Title for the modal that the Learner Credit plan is expiring in less than 30 days.',
}),
message: parse(sanitizeHTML(
intl.formatMessage({
id: 'adminPostal.learnerCredit.expiresInThirtyDaysModal.message',
id: 'adminPortal.learnerCredit.expiresInThirtyDaysModal.message',
defaultMessage: 'Your Learner Credit plan expires {date}. Contact support today to renew your plan and keep people learning.',
description: 'Message for the modal that the Learner Credit plan is expiring in less than 30 days.',
}, { date }),
Expand Down Expand Up @@ -161,10 +161,10 @@ const expiryThresholds = {
message: parse(sanitizeHTML(
intl.formatMessage({
id: 'adminPortal.learnerCreditPlan.expiredModal.message',
defaultMessage: `Your Learner Credit plan expired on {date}. You no longer have access to administrative functions and the remaining balance of your plan{apostrophe}s budget(s) are no longer available to spend.
defaultMessage: `Your Learner Credit plan expired on {date}. You no longer have access to administrative functions and the remaining balance of your plan's budget(s) are no longer available to spend.
Please contact your representative if you have any questions or concerns.`,
description: 'Message for the modal that the Learner Credit plan has expired.',
}, { date, apostrophe: "'" }),
}, { date }),
)),
},
variant: PLAN_EXPIRY_VARIANTS.expired,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,12 +95,8 @@ const DeleteArchivedCoursesDialogs = ({
<p>
<FormattedMessage
id="highlights.highlights.tab.archived.courses.modal.message"
defaultMessage="Deleting the archived courses in this highlight will remove it from your learners{apostrophe} {doubleQoute}Find a Course{doubleQoute}. This action is permanent and cannot be undone."
defaultMessage={"Deleting the archived courses in this highlight will remove it from your learners' \"Find a Course\". This action is permanent and cannot be undone."}
description="Message shown in the modal to delete archived courses."
values={{
apostrophe: "'",
doubleQoute: '"',
}}
/>
</p>
</ModalDialog.Body>
Expand Down
6 changes: 1 addition & 5 deletions src/components/ContentHighlights/DeleteHighlightSet.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -172,12 +172,8 @@ const DeleteHighlightSet = ({ enterpriseId, enterpriseSlug }) => {
<p>
<FormattedMessage
id="highlights.modal.delete.highlight.confirmation.message"
defaultMessage="Deleting this highlight will remove it from your learners{apostrophe} {doubleQoute}Find a Course{doubleQoute}. This action is permanent and cannot be undone."
defaultMessage={"Deleting this highlight will remove it from your learners' \"Find a Course\". This action is permanent and cannot be undone."}
description="Confirmation message shown when deleting a highlight."
values={{
apostrophe: "'",
doubleQoute: '"',
}}
/>
</p>
</AlertModal>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,8 @@ const HighlightStepperTitle = () => (
<strong>
<FormattedMessage
id="highlights.new.highlights.stepper.stepper.step.pro.tip.text.create.title"
defaultMessage="Pro tip: we recommend naming your highlight collection to reflect skills
it aims to develop, or to draw the attention of specific groups it targets.
For example, {doubleQoute}Recommended for Marketing{doubleQoute}
or {doubleQoute}Develop Leadership Skills{doubleQoute}."
defaultMessage={'Pro tip: we recommend naming your highlight collection to reflect skills it aims to develop, or to draw the attention of specific groups it targets. For example, "Recommended for Marketing" or "Develop Leadership Skills".'}
description="Create title pro tip message shown to administrators during creation of new content highlights"
values={{
doubleQoute: '"',
}}
/>
</strong>
</p>
Expand Down
6 changes: 2 additions & 4 deletions src/components/NotFoundPage/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,15 @@ export const NotFound = () => (
<p className="lead">
<FormattedMessage
id="admin.portal.not.found.page.message"
defaultMessage="Oops, sorry we can{apostrophe}t find that page!"
defaultMessage="Oops, sorry we can't find that page!"
description="The message displayed on the 404 page"
values={{ apostrophe: "'" }}
/>
</p>
<p>
<FormattedMessage
id="admin.portal.not.found.page.message2"
defaultMessage="Either something went wrong or the page doesn{apostrophe}t exist anymore."
defaultMessage="Either something went wrong or the page doesn't exist anymore."
description="The message displayed on the 404 page"
values={{ apostrophe: "'" }}
/>
</p>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import PropTypes from 'prop-types';
import {
Icon, IconButtonWithTooltip, Stack,
} from '@openedx/paragon';
import { Warning } from '@openedx/paragon/icons';
import { defineMessages, useIntl } from '@edx/frontend-platform/i18n';
import { ENROLL_BY_DATE_DAYS_THRESHOLD, formatDate } from './data';
import { isTodayWithinDateThreshold } from '../../utils';

const messages = defineMessages({
tooltipContent: {
id: 'lcm.budget.detail.page.assignments.table.columns.enroll-by-date.data.tooltip-content',
defaultMessage: 'Enrollment deadline approaching',
description: 'On hover tool tip message for an upcoming enrollment date',
},
});

const ExpiringIconButtonWithTooltip = () => {
const intl = useIntl();
return (
<IconButtonWithTooltip
variant="warning"
tooltipContent={intl.formatMessage(messages.tooltipContent)}
tooltipPlacement="left"
src={Warning}
iconAs={Icon}
size="inline"
data-testid="upcoming-allocation-expiration-tooltip"
/>
);
};

const AssignmentEnrollByDateCell = ({ row }) => {
const { original: { earliestPossibleExpiration: { date } } } = row;

const formattedEnrollByDate = formatDate(date);
const isAssignmentExpiringSoon = isTodayWithinDateThreshold({
days: ENROLL_BY_DATE_DAYS_THRESHOLD,
date,
});

return (
<Stack direction="horizontal" gap={1}>
{isAssignmentExpiringSoon && <ExpiringIconButtonWithTooltip />}
<div>
{formattedEnrollByDate}
</div>
</Stack>
);
};

AssignmentEnrollByDateCell.propTypes = {
row: PropTypes.shape({
original: PropTypes.shape({
earliestPossibleExpiration: PropTypes.shape({
date: PropTypes.string.isRequired,
}).isRequired,
}).isRequired,
}).isRequired,
};

export default AssignmentEnrollByDateCell;
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React from 'react';
import {
Stack,
Icon,
IconButtonWithTooltip,
} from '@openedx/paragon';
import { InfoOutline } from '@openedx/paragon/icons';
import { defineMessages, useIntl } from '@edx/frontend-platform/i18n';

const messages = defineMessages({
header: {
id: 'lcm.budget.detail.page.assignments.table.columns.enroll-by-date.header',
defaultMessage: 'Enroll-by date',
description: 'Column header for the enroll-by date column in the assignments table',
},
headerTooltipContent: {
id: 'lcm.budget.detail.page.assignments.table.columns.enroll-by-date.header.tooltip-content',
defaultMessage: 'Failure to enroll by midnight of enrollment deadline date will release funds back into the budget',
description: 'On hover tool tip message for the enroll-by date column',
},
});

const AssignmentEnrollByDateHeader = () => {
const intl = useIntl();
return (
<Stack gap={1} direction="horizontal">
<span data-testid="assignments-table-enroll-by-column-header">
{intl.formatMessage(messages.header)}
</span>
<IconButtonWithTooltip
tooltipContent={intl.formatMessage(messages.headerTooltipContent)}
tooltipPlacement="right"
src={InfoOutline}
iconAs={Icon}
size="inline"
data-testid="enroll-by-date-tooltip"
/>
</Stack>
);
};

export default AssignmentEnrollByDateHeader;
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import AssignmentTableCancelAction from './AssignmentTableCancel';
import { DEFAULT_PAGE, PAGE_SIZE, formatPrice } from './data';
import AssignmentRecentActionTableCell from './AssignmentRecentActionTableCell';
import AssignmentsTableRefreshAction from './AssignmentsTableRefreshAction';
import AssignmentEnrollByDateCell from './AssignmentEnrollByDateCell';
import AssignmentEnrollByDateHeader from './AssignmentEnrollByDateHeader';

const FilterStatus = (rest) => <DataTable.FilterStatus showFilteredFields={false} {...rest} />;

Expand Down Expand Up @@ -111,6 +113,13 @@ const BudgetAssignmentsTable = ({
Cell: AssignmentRecentActionTableCell,
disableFilters: true,
},
{
Header: AssignmentEnrollByDateHeader,
accessor: 'earliestPossibleExpiration',
Cell: AssignmentEnrollByDateCell,
disableFilters: true,
disableSortBy: true,
},
]}
additionalColumns={[
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,15 @@ import { FormattedMessage } from '@edx/frontend-platform/i18n';
import { configuration } from '../../config';
import { BudgetDetailPageContext } from './BudgetDetailPageWrapper';
import {
useBudgetId, useSubsidyAccessPolicy, useEnterpriseCustomer, useEnterpriseGroup,
useBudgetId,
useSubsidyAccessPolicy,
useEnterpriseCustomer,
useEnterpriseGroup,
isLmsBudget,
} from './data';
import EVENT_NAMES from '../../eventTracking';
import { LEARNER_CREDIT_ROUTE } from './constants';
import { BUDGET_STATUSES } from '../EnterpriseApp/data/constants';
import isLmsBudget from './utils';
import BudgetDetail from './BudgetDetail';

const BudgetActions = ({
Expand Down Expand Up @@ -111,9 +114,8 @@ const BudgetActions = ({
<p>
<FormattedMessage
id="lcm.budget.detail.page.overview.budget.actions.people.access.edx"
defaultMessage="People who have received access to discover edX content in your integrated learning platform can spend from this budget{apostrophe}s available balance to enroll."
defaultMessage="People who have received access to discover edX content in your integrated learning platform can spend from this budget's available balance to enroll."
description="Description which tells that people can spend from the budget's available balance to enroll"
values={{ apostrophe: "'" }}
/>
</p>
<Link to={`/${enterpriseSlug}/admin/settings/access`}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,12 @@ import {
import { GroupAdd, Groups, ManageAccounts } from '@openedx/paragon/icons';

import { useIntl } from '@edx/frontend-platform/i18n';
import { formatDate, useEnterpriseCustomer, useEnterpriseGroup } from './data';
import isLmsBudget from './utils';
import {
formatDate,
useEnterpriseCustomer,
useEnterpriseGroup,
isLmsBudget,
} from './data';

const BudgetStatusSubtitle = ({
badgeVariant, status, isAssignable, term, date, policy, enterpriseUUID,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,8 @@ const CancelAssignmentModal = ({
<p>
<FormattedMessage
id="lcm.budget.detail.page.catalog.tab.cancel.assignment.modal.body2"
defaultMessage="The learner will be notified that you have canceled the assignment. The funds associated with this course assignment will move from {doubleQoute}assigned{doubleQoute} back to {doubleQoute}available{doubleQoute}."
defaultMessage={'The learner will be notified that you have canceled the assignment. The funds associated with this course assignment will move from "assigned" back to "available".'}
description="Body text for the cancel assignment modal which informs the user that the learner will be notified that the assignment has been canceled."
values={{ doubleQoute: '"' }}
/>
</p>
</ModalDialog.Body>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,8 @@ const RemindAssignmentModal = ({
<p>
<FormattedMessage
id="lcm.budget.detail.page.catalog.tab.remind.assignment.modal.body2"
defaultMessage="When your learner completes enrollment, the associated {doubleQoute}assigned{doubleQoute} funds will be marked as {doubleQoute}spent{doubleQoute}."
defaultMessage={'When your learner completes enrollment, the associated "assigned" funds will be marked as "spent".'}
description="Text2 for the body of the remind assignment modal"
values={{ doubleQoute: '"' }}
/>
</p>
</ModalDialog.Body>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,24 +78,15 @@ const AssignmentAllocationHelpCollapsibles = ({ enterpriseId, course }) => (
<li>
<FormattedMessage
id="lcm.budget.detailsPage.catalog.tab.course.card.total.assignment.cost"
defaultMessage="The total assignment cost will be earmarked as {doubleQoute}assigned{doubleQoute} funds in your
Learner Credit budget so you can{apostrophe}t overspend."
defaultMessage={"The total assignment cost will be earmarked as \"assigned\" funds in your Learner Credit budget so you can't overspend."}
description="A step which explains that the total assignment cost will be earmarked as 'assigned' funds in your Learner Credit budget"
values={{
apostrophe: "'",
doubleQoute: '"',
}}
/>
</li>
<li>
<FormattedMessage
id="lcm.budget.detail.page.catalog.tab.course.card.course.cost.will.convert"
defaultMessage="The course cost will automatically convert from {doubleQoute}assigned{doubleQoute} to {doubleQoute}spent{doubleQoute} funds
when your learners complete registration."
defaultMessage={'The course cost will automatically convert from "assigned" to "spent" funds when your learners complete registration.'}
description="A step which explains that the course cost will automatically convert from 'assigned' to 'spent' funds when learners complete registration"
values={{
doubleQoute: '"',
}}
/>
</li>
</ul>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,8 @@ const AssignmentModalSummaryEmptyState = () => (
<div className="h4 mb-0">
<FormattedMessage
id="lcm.budget.detail.page.catalog.tab.course.card.entered.no.learners"
defaultMessage="You haven{apostrophe}t entered any learners yet."
defaultMessage="You haven't entered any learners yet."
description="Error header when no learners have been entered in the assignment modal"
values={{ apostrophe: "'" }}
/>
</div>
<span className="small">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,8 @@ const AssignmentModalSummaryErrorState = () => (
<div className="h4 mb-0">
<FormattedMessage
id="lcm.budget.detail.page.catalog.tab.assign.course.section.error.header"
defaultMessage="Learners can{apostrophe}t be assigned as entered."
defaultMessage="Learners can't be assigned as entered."
description="Error message header when course assignment fails due to invalid learner emails."
values={{ apostrophe: "'" }}
/>
</div>
<span className="small">
Expand Down
3 changes: 3 additions & 0 deletions src/components/learner-credit-management/data/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ export const ASSIGNMENT_STATUS_MODAL_MAX_WIDTH = 480;

export const MEMBERS_TABLE_PAGE_SIZE = 10;

// Enroll-by date warning message threshold by days
export const ENROLL_BY_DATE_DAYS_THRESHOLD = 10;

// Query Key factory for the learner credit management module, intended to be used with `@tanstack/react-query`.
// Inspired by https://tkdodo.eu/blog/effective-react-query-keys#use-query-key-factories.
export const learnerCreditManagementQueryKeys = {
Expand Down
5 changes: 5 additions & 0 deletions src/components/learner-credit-management/data/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -545,3 +545,8 @@ export const getTranslatedBudgetTerm = (intl, term) => {
return '';
}
};

export const isLmsBudget = (
activeIntegrationsLength,
isUniversalGroup,
) => activeIntegrationsLength > 0 && isUniversalGroup;
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import {
import { InfoOutline } from '@openedx/paragon/icons';

const MemberEnrollmentsTableColumnHeader = () => (
<Stack gap={0} direction="horizontal">
<span>
<p data-testid="members-table-enrollments-column-header" className="mb-0 mr-1">Enrollments</p>
<Stack gap={1} direction="horizontal">
<span data-testid="members-table-enrollments-column-header">
Enrollments
</span>
<OverlayTrigger
key="enrollments-column-tooltip"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import {
import { InfoOutline } from '@openedx/paragon/icons';

const MemberStatusTableColumnHeader = () => (
<Stack gap={0} direction="horizontal">
<span>
<p data-testid="members-table-status-column-header" className="mb-0 mr-1">Status</p>
<Stack gap={1} direction="horizontal">
<span data-testid="members-table-status-column-header">
Status
</span>
<OverlayTrigger
key="status-column-tooltip"
Expand Down
Loading

0 comments on commit 2a326e7

Please sign in to comment.