Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
bce8333
First version of magic pivot
MikaelNordberg Oct 16, 2025
14c1ad8
Prettier code
MikaelNordberg Oct 16, 2025
2c942ed
Updated unit tests
MikaelNordberg Oct 16, 2025
58426d1
Content and time should be first in heading if they only have one value
MikaelNordberg Oct 17, 2025
9e15af2
Implemented magic pivot algorithm
MikaelNordberg Oct 17, 2025
003e2b0
Fixed sorting in stub
MikaelNordberg Oct 17, 2025
7f8c716
Prettier code
MikaelNordberg Oct 17, 2025
36132ae
Merge branch 'main' into feature/PXWEB2-382-magic-pivot
MikaelNordberg Oct 20, 2025
e4e5196
Magic pivot not allowed in mobile mode
MikaelNordberg Oct 20, 2025
89e1392
Changed algorithm for magic pivot
MikaelNordberg Oct 20, 2025
8157a12
Prettier code
MikaelNordberg Oct 20, 2025
1daffdb
Function to calculate number of columns in the header
MikaelNordberg Oct 20, 2025
3552b5c
Separate functions for placing single- and multivalue variables in st…
MikaelNordberg Oct 20, 2025
c644294
Added unit tests
MikaelNordberg Oct 20, 2025
27afb7d
Merged MagicPivotButton into PivotButton. Behaviour controlled by Piv…
MikaelNordberg Oct 21, 2025
cd3e043
Merge branch 'main' into feature/PXWEB2-382-magic-pivot
MikaelNordberg Oct 21, 2025
8a89a9f
Renamed Magic pivot to Auto pivot in language files
MikaelNordberg Oct 21, 2025
74517dc
Regenerated resource file for languages
MikaelNordberg Oct 21, 2025
96d277c
Updated language keys in unit tests
MikaelNordberg Oct 21, 2025
4ad2dfb
Changed PivotType from Magic to Auto
MikaelNordberg Oct 21, 2025
a8da3a9
Renamed Magic pivot to Auto pivot in TableDataProvider
MikaelNordberg Oct 21, 2025
a5c2acd
Updated unit test
MikaelNordberg Oct 21, 2025
113d910
Renamed Magic pivot to Auto pivot in utility
MikaelNordberg Oct 21, 2025
e4c16e9
Changed texts
MikaelNordberg Oct 22, 2025
d406cd5
Regenerated language resource file
MikaelNordberg Oct 22, 2025
f11570a
Fixed the screen reader message after pivot
MikaelNordberg Oct 22, 2025
8ec6a5f
Prettier code
MikaelNordberg Oct 22, 2025
0ed45d6
Auto-pivot not available on mobile
MikaelNordberg Oct 22, 2025
4ef0a25
Mock useApp in unit tests
MikaelNordberg Oct 22, 2025
7a9573e
Merge branch 'main' into feature/PXWEB2-382-magic-pivot
MikaelNordberg Oct 23, 2025
8764653
Updated description text for auto pivot
MikaelNordberg Oct 23, 2025
0280f3d
No gap between operations in list
MikaelNordberg Oct 23, 2025
3ee6bcf
Added isLoading in TableDataProvider. Used to show spinner for pivot …
MikaelNordberg Oct 23, 2025
2fbada2
Only show spinner for the button being clicked
MikaelNordberg Oct 23, 2025
4aa3c75
Prettier code
MikaelNordberg Oct 23, 2025
9a89d66
Code cleanup
MikaelNordberg Oct 23, 2025
087cdf3
Prettier code
MikaelNordberg Oct 23, 2025
ba01bc2
Merge branch 'main' into feature/PXWEB2-382-magic-pivot
MikaelNordberg Oct 24, 2025
c611c0b
Only copy metadata part of PxTable to reduce memory usage
MikaelNordberg Oct 24, 2025
2342ade
Prettier code
MikaelNordberg Oct 24, 2025
44bba05
Code refactoring
MikaelNordberg Oct 27, 2025
0a1ae7e
Prettier code
MikaelNordberg Oct 27, 2025
833a843
Merge branch 'main' into feature/PXWEB2-382-magic-pivot
MikaelNordberg Oct 27, 2025
48c5a5a
Changed algorithm + text changes
MikaelNordberg Oct 29, 2025
1627a7f
Fixed type errors in unit tests
MikaelNordberg Oct 29, 2025
532f317
Changed algorithm treshold value from 21 to 17
MikaelNordberg Oct 30, 2025
927f5c9
Merge branch 'main' into feature/PXWEB2-382-magic-pivot
MikaelNordberg Oct 30, 2025
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
7 changes: 7 additions & 0 deletions packages/pxweb2/public/locales/ar/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,16 @@
"title": "يحرر",
"customize": {
"title": "تخصيص",
"auto_pivot": {
"title": "تحسين تخطيط الجدول",
"aria_label": "تحسين تخطيط الجدول",
"description": "ينظم الصفوف والأعمدة تلقائيًا لتخطيط جدول أوضح",
"screen_reader_announcement": "تم تحسين تخطيط الجدول وتنظيمه بعد {{first_variables}} و {{last_variable}}"
},
"pivot": {
"title": "تدوير الجدول",
"aria_label": "تدوير الجدول في اتجاه عقارب الساعة",
"description": "",
"screen_reader_announcement": "جدول مدور بعد {{first_variables}} و {{last_variable}}"
},
"rearrange": {
Expand Down
7 changes: 7 additions & 0 deletions packages/pxweb2/public/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -170,9 +170,16 @@
"title": "Edit",
"customize": {
"title": "Customise",
"auto_pivot": {
"title": "Improve table layout",
"aria_label": "Improve table layout",
"description": "Organises rows and columns automatically for a clearer table layout",
"screen_reader_announcement": "Table layout improved and organised after {{first_variables}} and {{last_variable}}"
},
"pivot": {
"title": "Rotate table",
"aria_label": "Rotate table clockwise",
"description": "",
"screen_reader_announcement": "Table rotated after {{first_variables}} and {{last_variable}}"
},
"rearrange": {
Expand Down
7 changes: 7 additions & 0 deletions packages/pxweb2/public/locales/no/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -170,9 +170,16 @@
"title": "Rediger",
"customize": {
"title": "Tilpass",
"auto_pivot": {
"title": "Rydd opp i tabellen",
"aria_label": "Rydd opp i tabellen",
"description": "Gjør tabellen mer oversiktlig ved å automatisk organisere rader og kolonner",
"screen_reader_announcement": "Tabell ryddet opp og organisert etter {{first_variables}} og {{last_variable}}"
},
"pivot": {
"title": "Roter tabellen",
"aria_label": "Roter tabellen mot høyre",
"description": "",
"screen_reader_announcement": "Tabell rotert etter {{first_variables}} og {{last_variable}}"
},
"rearrange": {
Expand Down
7 changes: 7 additions & 0 deletions packages/pxweb2/public/locales/sv/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -170,9 +170,16 @@
"title": "Ändra",
"customize": {
"title": "Anpassa",
"auto_pivot": {
"title": "Ordna tabellen",
"aria_label": "Ordna tabellen",
"description": "Visa tabellen med automatiskt ordnade rader och kolumner",
"screen_reader_announcement": "Tabell ordnad efter {{first_variables}} och {{last_variable}}"
},
"pivot": {
"title": "Rotera tabellen",
"aria_label": "Rotera tabellen medsols",
"description": "",
"screen_reader_announcement": "Tabell roterat efter {{first_variables}} och {{last_variable}}"
},
"rearrange": {
Expand Down
9 changes: 8 additions & 1 deletion packages/pxweb2/src/@types/resources.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ interface Resources {
skip_to_main: 'Skip to main content';
status_messages: {
drawer_edit: 'More tools for editing the table are under construction.';
drawer_help: 'No content will be added here. The help button must be set up to link directly to your own help pages.';
drawer_help: 'The help section is under construction. It will be possible to set up links that point directly to your own help pages.';
drawer_save_api: 'Feature for API query is under construction.';
drawer_save_file: 'More file formats are in the works.';
drawer_view: 'Graph display is under construction.';
Expand Down Expand Up @@ -171,12 +171,19 @@ interface Resources {
title: 'Calculate';
};
customize: {
auto_pivot: {
aria_label: 'Auto rotate table';
description: 'Automatically organises rows and columns for a clearer table layout';
screen_reader_announcement: 'Table layout improved and organised after {{table_heading}}';
title: 'Auto rotate table';
};
change_order: {
description: 'Description text...';
title: 'Change order';
};
pivot: {
aria_label: 'Rotate table clockwise';
description: '';
screen_reader_announcement: 'Table rotated after {{first_variables}} and {{last_variable}}';
title: 'Rotate table';
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@
white-space: nowrap;
}

.operationList {
padding: 0;
margin: 0;
}

.alert {
margin-left: 8px;
margin-right: 8px;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { describe, it, expect, vi } from 'vitest';
import { describe, it, expect, vi, afterEach } from 'vitest';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import '@testing-library/jest-dom/vitest';
Expand All @@ -13,7 +13,8 @@ interface MockActionItemProps {
[key: string]: unknown;
}

const mockPivotCW = vi.fn();
import { PivotType } from '../../../context/PivotType';
const mockPivot = vi.fn();

// Mock dependencies
vi.mock('react-i18next', () => ({
Expand All @@ -24,8 +25,9 @@ vi.mock('react-i18next', () => ({

vi.mock('../../../context/useTableData', () => ({
default: () => ({
pivotCW: mockPivotCW,
pivot: mockPivot,
data: {
// Minimal shape; DrawerEdit only passes these through
stub: [{ name: 'variable1' }],
heading: [{ name: 'variable2' }],
},
Expand Down Expand Up @@ -62,12 +64,28 @@ vi.mock('@pxweb2/pxweb2-ui', () => ({
),
}));

vi.mock('../../../context/useApp', () => ({
default: () => ({ isMobile: false }),
}));

afterEach(() => {
vi.clearAllMocks();
});

describe('DrawerEdit', () => {
it('renders successfully', () => {
render(<DrawerEdit />);

expect(screen.getByTestId('content-box')).toBeInTheDocument();
expect(screen.getByTestId('action-item')).toBeInTheDocument();
// Two action buttons: auto pivot & clockwise pivot (unified PivotButton)
const buttons = screen.getAllByTestId('action-item');
expect(buttons).toHaveLength(2);
// Check labels via translation keys
expect(
screen.getByText(
'presentation_page.side_menu.edit.customize.auto_pivot.title',
),
).toBeInTheDocument();
expect(
screen.getByText(
'presentation_page.side_menu.edit.customize.pivot.title',
Expand All @@ -79,14 +97,25 @@ describe('DrawerEdit', () => {
expect(DrawerEdit.displayName).toBe('DrawerEdit');
});

it('calls pivotCW on button click', async () => {
it('calls pivot with PivotType.Clockwise on its button click', async () => {
render(<DrawerEdit />);

const button = screen.getByTestId('action-item');
const user = userEvent.setup();
await user.click(button);
const clockwiseButton = screen.getByText(
'presentation_page.side_menu.edit.customize.pivot.title',
);
await user.click(clockwiseButton);
expect(mockPivot).toHaveBeenCalledWith(PivotType.Clockwise);
expect(mockPivot).toHaveBeenCalledTimes(1);
});

expect(mockPivotCW).toHaveBeenCalledTimes(1);
expect(mockPivotCW).toHaveBeenCalledWith();
it('calls pivot with PivotType.Auto on its button click', async () => {
render(<DrawerEdit />);
const user = userEvent.setup();
const autoButton = screen.getByText(
'presentation_page.side_menu.edit.customize.auto_pivot.title',
);
await user.click(autoButton);
expect(mockPivot).toHaveBeenCalledWith(PivotType.Auto);
expect(mockPivot).toHaveBeenCalledTimes(1);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,58 +4,101 @@ import { useTranslation } from 'react-i18next';
import { ActionItem, ContentBox, Variable, Alert } from '@pxweb2/pxweb2-ui';
import useTableData from '../../../context/useTableData';
import classes from './DrawerEdit.module.scss';
import { PivotType } from '../../../context/PivotType';
import useApp from '../../../context/useApp';

interface PivotButtonProps {
readonly stub: Variable[];
readonly heading: Variable[];
readonly pivotType: PivotType;
readonly loadingPivotType: PivotType | null;
readonly setLoadingPivotType: (type: PivotType | null) => void;
}

function PivotButton({ stub, heading }: PivotButtonProps) {
function PivotButton({
stub,
heading,
pivotType,
loadingPivotType,
setLoadingPivotType,
}: PivotButtonProps) {
const { t } = useTranslation();
const pivotTableClockwise = useTableData().pivotCW;
const buildTableTitle = useTableData().buildTableTitle;
const tableData = useTableData();
const { pivot, buildTableTitle, isLoading } = tableData;

// Live region text for screen readers after activation
const [statusMessage, setStatusMessage] = useState('');
const [announceOnNextChange, setAnnounceOnNextChange] = useState(false);

const handleClick = () => {
const handleClick = async () => {
setAnnounceOnNextChange(true);
pivotTableClockwise();
setLoadingPivotType(pivotType);
await Promise.resolve(pivot(pivotType));
};

const screenReaderAnnouncementKey =
pivotType === PivotType.Auto
? 'presentation_page.side_menu.edit.customize.auto_pivot.screen_reader_announcement'
: 'presentation_page.side_menu.edit.customize.pivot.screen_reader_announcement';

// When stub/heading update after pivot, compute and announce the new screen reader message
useEffect(() => {
if (!announceOnNextChange) {
return;
}

const { firstTitlePart, lastTitlePart } = buildTableTitle(stub, heading);
const message = t(
'presentation_page.side_menu.edit.customize.pivot.screen_reader_announcement',
{
first_variables: firstTitlePart,
last_variable: lastTitlePart,
},
);
const message = t(screenReaderAnnouncementKey, '', {
first_variables: firstTitlePart,
last_variable: lastTitlePart,
});

// Clear first to ensure assistive tech re-announces even if message repeats
setStatusMessage('');
const timer = setTimeout(() => setStatusMessage(message), 0); // Force state update on different ticks
setAnnounceOnNextChange(false);

return () => clearTimeout(timer);
}, [stub, heading, announceOnNextChange, buildTableTitle, t]);
}, [
stub,
heading,
announceOnNextChange,
buildTableTitle,
t,
screenReaderAnnouncementKey,
]);

const labelKey =
pivotType === PivotType.Auto
? 'presentation_page.side_menu.edit.customize.auto_pivot.title'
: 'presentation_page.side_menu.edit.customize.pivot.title';
const ariaLabelKey =
pivotType === PivotType.Auto
? 'presentation_page.side_menu.edit.customize.auto_pivot.aria_label'
: 'presentation_page.side_menu.edit.customize.pivot.aria_label';
const descriptionKey =
pivotType === PivotType.Auto
? 'presentation_page.side_menu.edit.customize.auto_pivot.description'
: 'presentation_page.side_menu.edit.customize.pivot.description';
const iconName =
pivotType === PivotType.Auto ? 'Sparkles' : 'ArrowCirclepathClockwise';

// Hide spinner when global isLoading becomes false
useEffect(() => {
if (!isLoading) {
setLoadingPivotType(null);
}
}, [isLoading, setLoadingPivotType]);

return (
<>
<ActionItem
label={t('presentation_page.side_menu.edit.customize.pivot.title')}
ariaLabel={t(
'presentation_page.side_menu.edit.customize.pivot.aria_label',
)}
label={t(labelKey)}
ariaLabel={t(ariaLabelKey)}
description={t(descriptionKey)}
onClick={handleClick}
iconName="ArrowCirclepathClockwise"
iconName={iconName}
isLoading={loadingPivotType === pivotType && isLoading}
/>
<output aria-live="polite" aria-atomic="true" className={classes.srOnly}>
{statusMessage}
Expand All @@ -65,12 +108,35 @@ function PivotButton({ stub, heading }: PivotButtonProps) {
}

export function DrawerEdit() {
const { isMobile } = useApp();
const data = useTableData().data;
const { t } = useTranslation();
const [loadingPivotType, setLoadingPivotType] = useState<PivotType | null>(
null,
);

return (
<ContentBox title={t('presentation_page.side_menu.edit.customize.title')}>
{data && <PivotButton stub={data.stub} heading={data.heading} />}
<div className={classes.operationList}>
{data && !isMobile && (
<PivotButton
stub={data.stub}
heading={data.heading}
pivotType={PivotType.Auto}
loadingPivotType={loadingPivotType}
setLoadingPivotType={setLoadingPivotType}
/>
)}
{data && (
<PivotButton
stub={data.stub}
heading={data.heading}
pivotType={PivotType.Clockwise}
loadingPivotType={loadingPivotType}
setLoadingPivotType={setLoadingPivotType}
/>
)}
</div>
<Alert variant="info" className={classes.alert}>
{t('common.status_messages.drawer_edit')}
</Alert>
Expand Down
4 changes: 4 additions & 0 deletions packages/pxweb2/src/app/context/PivotType.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export enum PivotType {
Clockwise = 'Clockwise',
Auto = 'Auto',
}
Loading