Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Tests (and adjustments)
  • Loading branch information
RoyEJohnson committed Sep 29, 2025
commit 7a910f1ffd76c1da3b414535638e2928f275e6d3
4 changes: 3 additions & 1 deletion src/app/helpers/errata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,11 @@ export function approvedStatuses(created: string, corrected: string | null) {
};
}

export type DisplayStatusValue = 'Reviewed' | 'In Review' | 'Duplicate' | 'No Correction' | 'Will Correct';

export function getDisplayStatus(data?: Errata) {
const result = {
status: 'Reviewed',
status: 'Reviewed' as DisplayStatusValue,
barStatus: ''
};

Expand Down
12 changes: 8 additions & 4 deletions src/app/pages/errata-summary/errata-summary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const radioItems = [
];

function ErrataSummary({data, book}: ErrataSummaryProps) {
const initialValue: string = window.location.hash.replace('#', '');
const initialValue = window.location.hash.replace('#', '');
const [selectedFilter, setselectedFilter] = useState<string>(initialValue);
const onChange = React.useCallback(
(newlySelectedValue: string) => {
Expand Down Expand Up @@ -54,14 +54,18 @@ function ErrataSummary({data, book}: ErrataSummaryProps) {
}

export default function ErrataSummaryLoader() {
const book: string | null = new window.URLSearchParams(window.location.search).get('book');
const slug: string = `errata/?book_title=${book}` +
const book = new window.URLSearchParams(window.location.search).get('book');

if (!book) {
return <div>No book or errata ID selected</div>;
}
const slug = `errata/?book_title=${book}` +
'&is_assessment_errata__not=Yes&archived=False&status__not=New' +
'&status__not=OpenStax%20Editorial%20Review';

return (
<main className="errata-summary page">
<LoaderPage slug={slug} Child={ErrataSummary} props={{book: book || ''}} />
<LoaderPage slug={slug} Child={ErrataSummary} props={{book}} />
</main>
);
}
52 changes: 26 additions & 26 deletions src/app/pages/errata-summary/hero/hero.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, {useState, useEffect} from 'react';
import RawHTML from '~/components/jsx-helpers/raw-html';
import {assertNotNull} from '~/helpers/data';
import LoaderPage from '~/components/jsx-helpers/loader-page';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {faInfoCircle} from '@fortawesome/free-solid-svg-icons/faInfoCircle';
Expand Down Expand Up @@ -49,37 +50,38 @@ function useBookInfo(book: string): [string, string] {
return info;
}

const middle: string = '-135';
const margin: number = 3;
const middle = -135;
const margin = 3;

function shiftIntoView(
ref: React.RefObject<HTMLDivElement>,
leftOffset: number | string,
setLeftOffset: (value: number | string) => void
leftOffset: number,
setLeftOffset: (value: number) => void
) {
if (!ref.current) {return;}
const poptipEl = assertNotNull(ref.current);

const {left, right} = ref.current.getBoundingClientRect();
const pageElement = ref.current.closest('.page');

if (!pageElement) {return;}
const {left, right} = poptipEl.getBoundingClientRect();
const pageElement = assertNotNull(poptipEl.closest('.page'));

const {right: pageRight} = pageElement.getBoundingClientRect();
const overRight = right - pageRight + margin;
const overLeft = margin - left;
const leftOffsetNum = typeof leftOffset === 'string' ? parseInt(leftOffset, 10) : leftOffset;
const rightWants = Math.min(parseInt(middle, 10), leftOffsetNum - overRight);
const leftWants = Math.max(rightWants, leftOffsetNum + overLeft);
const rightWants = Math.min(middle, leftOffset - overRight);
const leftWants = Math.max(rightWants, leftOffset + overLeft);

setLeftOffset(leftWants);
}

function usePopTipStyle(isOpen: boolean) {
const ref = React.useRef<HTMLDivElement>(null);
const [leftOffset, setLeftOffset] = React.useState<number | string>(middle);
const [leftOffset, setLeftOffset] = React.useState<number>(middle);

useEffect(
() => shiftIntoView(ref, leftOffset, setLeftOffset),
() => {
if (isOpen) {
shiftIntoView(ref, leftOffset, setLeftOffset);
}
},
[isOpen, leftOffset]
);

Expand All @@ -103,12 +105,10 @@ function PopTip({html, isOpen}: PopTipProps) {

function usePopTipState() {
const [isOpen, setIsOpen] = React.useState<boolean>(false);
const activate = React.useCallback(() => setIsOpen(true), []);
const deactivate = React.useCallback(() => setIsOpen(false), []);

return {
isOpen,
activate(): void {setIsOpen(true);},
deactivate(): void {setIsOpen(false);}
};
return {isOpen, activate, deactivate};
}

function HeroContent({data}: HeroContentProps) {
Expand Down Expand Up @@ -137,15 +137,15 @@ function HeroContent({data}: HeroContentProps) {
export default function Hero({book}: HeroProps) {
const [slug, title] = useBookInfo(book);

if (!slug) {
return null;
}
return (
<div className="hero">
<div className="text-area">
<h1>{title} Errata</h1>
<LoaderPage slug="pages/errata" Child={HeroContent} />
</div>
{slug ?
<div className="text-area">
<h1>{title} Errata</h1>
<LoaderPage slug="pages/errata" Child={HeroContent} />
</div> :
null
}
</div>
);
}
45 changes: 19 additions & 26 deletions src/app/pages/errata-summary/table/table.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, {useState} from 'react';
import {treatSpaceOrEnterAsClick} from '~/helpers/events';
import {getDisplayStatus, Errata} from '~/helpers/errata';
import {getDisplayStatus, Errata, DisplayStatusValue} from '~/helpers/errata';
import './table.scss';

type ProcessedErrataItem = {
Expand Down Expand Up @@ -114,36 +114,29 @@ const statusSortOrder: {[key: string]: number} = {
const sortFunctions = {
sortDate: (a: string, b: string): number => new Date(a).getTime() - new Date(b).getTime(),
sort: (a: string | null, b: string | null): number => {
const as = a === null ? '' : a;
const bs = b === null ? '' : b;
const as = a ?? '';
const bs = b ?? '';

return as.localeCompare(bs, 'en', {sensitivity: 'base'});
},
sortNumber: (a: number | string, b: number | string): number => Number(a) - Number(b),
sortDecision: (a: ProcessedErrataItem, b: ProcessedErrataItem): number => {
const ar = statusSortOrder[a.displayStatus.substr(0, 2)] || 6;
const br = statusSortOrder[b.displayStatus.substr(0, 2)] || 6;
sortDecision: (a: DisplayStatusValue, b: DisplayStatusValue): number => {
const ar = statusSortOrder[a.substring(0, 2)];
const br = statusSortOrder[b.substring(0, 2)];

if (ar !== br) {
return br - ar;
}
const ad = new Date(a.modified).valueOf();
const bd = new Date(b.modified).valueOf();

return bd - ad;
return br - ar;
}
};

function DesktopHeaderColumn({colSpec, sortController}: DesktopHeaderColumnProps): React.ReactElement {
const {sortKey, sortDir, setSortFn, setSortDir, setSortKey} = sortController;
const sortable = 'sortFn' in colSpec;
const onClick = (): void => {
const onClick = () => {
if (sortKey === colSpec.id) {
setSortDir(-sortDir);
} else {
if (sortable) {
setSortFn(colSpec.sortFn);
}
// @ts-expect-error onClick is only used when sortable
setSortFn(colSpec.sortFn);
setSortKey(colSpec.id);
setSortDir(1);
}
Expand Down Expand Up @@ -189,14 +182,14 @@ function DesktopDataColumn({colSpec, entry}: DesktopDataColumnProps): React.Reac
<div className={colSpec.cssClass}>
{
colSpec.id === 'id' ?
<a href={entry[colSpec.id as keyof ProcessedErrataItem] as string}>
{entry[colSpec.id as keyof ProcessedErrataItem] || ''}
<a href={entry[colSpec.id]}>
{entry[colSpec.id]}
</a> :
<React.Fragment>
{entry[colSpec.id as keyof ProcessedErrataItem]}{' '}
{entry[colSpec.id]}{' '}
{
colSpec.id === 'displayStatus' &&
entry[colSpec.id as keyof ProcessedErrataItem] === 'No Correction' &&
entry[colSpec.id] === 'No Correction' &&
<a href={`/errata/${entry.id}`}>Details</a>
}
</React.Fragment>
Expand Down Expand Up @@ -228,10 +221,10 @@ function DesktopTable({data}: DesktopTableProps): React.ReactElement {
const sortController = useSortController();
const {sortFn, sortKey, sortDir} = sortController;

const sortedData = [...data] as Array<string & ProcessedErrataItem>;
const sortedData = [...data];

sortedData.sort((a, b) =>
// @ts-expect-error sortKey and sortFn are guaranteed compatible
// @ts-expect-error sortKey is guaranteed compatible with sortFn
sortFunctions[sortFn](a[sortKey], b[sortKey])
);
if (sortDir < 0) {
Expand All @@ -258,8 +251,8 @@ function MobileRow({entry, label, columnId}: MobileRowProps): React.ReactElement
<div>
{
columnId === 'id' ?
<a href={entry[columnId as keyof ProcessedErrataItem] as string}>
{entry[columnId as keyof ProcessedErrataItem] || ''}
<a href={entry[columnId]}>
{entry[columnId]}
</a> :
(entry[columnId as keyof ProcessedErrataItem] || '')
}
Expand Down Expand Up @@ -297,7 +290,7 @@ function MobileTables({data}: MobileTablesProps): React.ReactElement {
}

function matchesFilter(filter: string, item: ProcessedErrataItem): boolean {
const status = item.displayStatus;
const status = item.displayStatus as string;

switch (filter) {
case '':
Expand Down
2 changes: 1 addition & 1 deletion test/src/components/shell.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ describe('shell', () => {
));

render(AppElement);
await screen.findByRole('radio', {name: 'In Review'});
await screen.findByText('No book or errata ID selected');
});
it('routes "details" paths (top level routes to Subjects)', async () => {
BrowserRouter.mockImplementationOnce(({children}) => (
Expand Down
8 changes: 4 additions & 4 deletions test/src/data/errata-book.js
Original file line number Diff line number Diff line change
Expand Up @@ -447,7 +447,7 @@ export default [
"short_detail": "The solution for the question: \"June needs 48 gallons of punch for a party and ...",
"resolution_date": "2017-09-06",
"resolution_notes": "Revise the solution to exercise 202 as follows:\r\n\r\n\"The smaller cooler holds 8 gallons and the bigger cooler holds 40 gallons.\"",
"error_type": "Incorrect calculation or solution",
"error_type": "Other",
"error_type_other": null,
"resource": "Student solution manual",
"resource_other": null,
Expand Down Expand Up @@ -1169,8 +1169,8 @@ export default [
"resolution_notes": "Revise Example 5.57 as follows:\r\n\r\n\"Christy sells her photographs at a booth at a street fair. At the start of the day, she wants to have at least 25 photos to display at her booth...\"",
"error_type": "Typo",
"error_type_other": null,
"resource": "Textbook",
"resource_other": null,
"resource": "Other",
"resource_other": "Another",
"submitted_by_account_id": 147546,
"file_1": null,
"file_2": null
Expand All @@ -1193,7 +1193,7 @@ export default [
"resolution_notes": "Revise Exercise 220 as follows:\r\n\r\n\"Darrin is hanging 200 feet of Christmas garland on the three sides of fencing that enclose his rectangular front yard. The length, the side along the house, is five feet less than three times the width. Find the length and width of the fencing.\"",
"error_type": "Typo",
"error_type_other": null,
"resource": "Textbook",
"resource": "Other",
"resource_other": null,
"submitted_by_account_id": 147546,
"file_1": null,
Expand Down
Loading