Skip to content

Commit

Permalink
[PUI] Page titles (#8467)
Browse files Browse the repository at this point in the history
* Add <PageTitle /> component

* Use useEffect to override hard-coded value

* Ensure page titles are tracked across the site

* Adjust unit tests

* Playwright test updates

* Tweak tests

* Update InvenTreeTable.tsx

Revert unused change
  • Loading branch information
SchrodingersGat authored Nov 12, 2024
1 parent 113b9e9 commit 7b50f3b
Show file tree
Hide file tree
Showing 14 changed files with 149 additions and 80 deletions.
88 changes: 46 additions & 42 deletions src/frontend/src/components/nav/PageDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Fragment, type ReactNode } from 'react';
import { ApiImage } from '../images/ApiImage';
import { StylishText } from '../items/StylishText';
import { type Breadcrumb, BreadcrumbList } from './BreadcrumbList';
import PageTitle from './PageTitle';

interface PageDetailInterface {
title?: string;
Expand Down Expand Up @@ -51,50 +52,53 @@ export function PageDetail({
]);

return (
<Stack gap='xs'>
{breadcrumbs && breadcrumbs.length > 0 && (
<BreadcrumbList
navCallback={breadcrumbAction}
breadcrumbs={breadcrumbs}
/>
)}
<Paper p='xs' radius='xs' shadow='xs'>
<Stack gap='xs'>
<Group justify='space-between' wrap='nowrap'>
<Group justify='left' wrap='nowrap'>
{imageUrl && (
<ApiImage src={imageUrl} radius='sm' mah={42} maw={42} />
)}
<Stack gap='xs'>
{title && <StylishText size='lg'>{title}</StylishText>}
{subtitle && (
<Group gap='xs'>
{icon}
<Text size='md' truncate>
{subtitle}
</Text>
</Group>
<>
<PageTitle title={title} />
<Stack gap='xs'>
{breadcrumbs && breadcrumbs.length > 0 && (
<BreadcrumbList
navCallback={breadcrumbAction}
breadcrumbs={breadcrumbs}
/>
)}
<Paper p='xs' radius='xs' shadow='xs'>
<Stack gap='xs'>
<Group justify='space-between' wrap='nowrap'>
<Group justify='left' wrap='nowrap'>
{imageUrl && (
<ApiImage src={imageUrl} radius='sm' mah={42} maw={42} />
)}
</Stack>
</Group>
<Space />
{detail}
<Group justify='right' gap='xs' wrap='nowrap'>
{badges?.map((badge, idx) => (
<Fragment key={idx}>{badge}</Fragment>
))}
</Group>
<Space />
{actions && (
<Group gap={5} justify='right'>
{actions.map((action, idx) => (
<Fragment key={idx}>{action}</Fragment>
<Stack gap='xs'>
{title && <StylishText size='lg'>{title}</StylishText>}
{subtitle && (
<Group gap='xs'>
{icon}
<Text size='sm' truncate>
{subtitle}
</Text>
</Group>
)}
</Stack>
</Group>
<Space />
{detail}
<Group justify='right' gap='xs' wrap='nowrap'>
{badges?.map((badge, idx) => (
<Fragment key={idx}>{badge}</Fragment>
))}
</Group>
)}
</Group>
</Stack>
</Paper>
</Stack>
<Space />
{actions && (
<Group gap={5} justify='right'>
{actions.map((action, idx) => (
<Fragment key={idx}>{action}</Fragment>
))}
</Group>
)}
</Group>
</Stack>
</Paper>
</Stack>
</>
);
}
53 changes: 53 additions & 0 deletions src/frontend/src/components/nav/PageTitle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { useEffect, useMemo } from 'react';
import { useGlobalSettingsState } from '../../states/SettingsState';

/**
* Component to set the page title
*/
export default function PageTitle({
title,
subtitle
}: {
title?: string;
subtitle?: string;
}) {
const globalSettings = useGlobalSettingsState();

const pageTitle = useMemo(() => {
const instanceName = globalSettings.getSetting(
'INVENTREE_INSTANCE',
'InvenTree'
);
const useInstanceName = globalSettings.isSet(
'INVENTREE_INSTANCE_TITLE',
false
);

let data = '';

if (title) {
data += title;
}

if (subtitle) {
data += ` - ${subtitle}`;
}

if (useInstanceName) {
data = `${instanceName} | ${data}`;
}

if (!data) {
// Backup: No title provided
data = instanceName;
}

return data;
}, [title, subtitle, globalSettings]);

useEffect(() => {
document.title = pageTitle;
}, [pageTitle]);

return <title>{pageTitle}</title>;
}
3 changes: 3 additions & 0 deletions src/frontend/src/pages/Index/Home.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { t } from '@lingui/macro';
import DashboardLayout from '../../components/dashboard/DashboardLayout';
import PageTitle from '../../components/nav/PageTitle';

export default function Home() {
return (
<>
<PageTitle title={t`Dashboard`} />
<DashboardLayout />
</>
);
Expand Down
2 changes: 2 additions & 0 deletions src/frontend/src/pages/Index/Scan.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import { api } from '../../App';
import { DocInfo } from '../../components/items/DocInfo';
import { StylishText } from '../../components/items/StylishText';
import { TitleWithDoc } from '../../components/items/TitleWithDoc';
import PageTitle from '../../components/nav/PageTitle';
import { RenderInstance } from '../../components/render/Instance';
import { ModelInformationDict } from '../../components/render/ModelType';
import { ApiEndpoints } from '../../enums/ApiEndpoints';
Expand Down Expand Up @@ -290,6 +291,7 @@ export default function Scan() {
// rendering
return (
<>
<PageTitle title={t`Barcode Scanning`} />
<Group justify='space-between'>
<Group justify='left'>
<StylishText>
Expand Down
2 changes: 2 additions & 0 deletions src/frontend/src/pages/Index/Settings/AdminCenter/Index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { lazy, useMemo } from 'react';

import PermissionDenied from '../../../../components/errors/PermissionDenied';
import { PlaceholderPill } from '../../../../components/items/Placeholder';
import PageTitle from '../../../../components/nav/PageTitle';
import { SettingsHeader } from '../../../../components/nav/SettingsHeader';
import type { PanelType } from '../../../../components/panels/Panel';
import { PanelGroup } from '../../../../components/panels/PanelGroup';
Expand Down Expand Up @@ -244,6 +245,7 @@ export default function AdminCenter() {

return (
<>
<PageTitle title={t`Admin Center`} />
{user.isStaff() ? (
<Stack gap='xs'>
<SettingsHeader
Expand Down
2 changes: 2 additions & 0 deletions src/frontend/src/pages/Index/Settings/SystemSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { useMemo } from 'react';

import PermissionDenied from '../../../components/errors/PermissionDenied';
import { PlaceholderPanel } from '../../../components/items/Placeholder';
import PageTitle from '../../../components/nav/PageTitle';
import { SettingsHeader } from '../../../components/nav/SettingsHeader';
import type { PanelType } from '../../../components/panels/Panel';
import { PanelGroup } from '../../../components/panels/PanelGroup';
Expand Down Expand Up @@ -303,6 +304,7 @@ export default function SystemSettings() {

return (
<>
<PageTitle title={t`System Settings`} />
{user.isStaff() ? (
<Stack gap='xs'>
<SettingsHeader
Expand Down
40 changes: 22 additions & 18 deletions src/frontend/src/pages/Index/Settings/UserSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
} from '@tabler/icons-react';
import { useMemo } from 'react';

import PageTitle from '../../../components/nav/PageTitle';
import { SettingsHeader } from '../../../components/nav/SettingsHeader';
import type { PanelType } from '../../../components/panels/Panel';
import { PanelGroup } from '../../../components/panels/PanelGroup';
Expand Down Expand Up @@ -146,23 +147,26 @@ export default function UserSettings() {
}

return (
<Stack gap='xs'>
<SettingsHeader
label='user'
title={t`Account Settings`}
subtitle={
user?.first_name && user?.last_name
? `${user?.first_name} ${user?.last_name}`
: null
}
shorthand={user?.username || ''}
/>
<PanelGroup
pageKey='user-settings'
panels={userSettingsPanels}
model='usersettings'
id={null}
/>
</Stack>
<>
<PageTitle title={t`User Settings`} />
<Stack gap='xs'>
<SettingsHeader
label='user'
title={t`Account Settings`}
subtitle={
user?.first_name && user?.last_name
? `${user?.first_name} ${user?.last_name}`
: null
}
shorthand={user?.username || ''}
/>
<PanelGroup
pageKey='user-settings'
panels={userSettingsPanels}
model='usersettings'
id={null}
/>
</Stack>
</>
);
}
2 changes: 1 addition & 1 deletion src/frontend/src/pages/build/BuildDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -528,7 +528,7 @@ export default function BuildDetail() {
<InstanceDetail status={requestStatus} loading={instanceQuery.isFetching}>
<Stack gap='xs'>
<PageDetail
title={build.reference}
title={`${t`Build Order`}: ${build.reference}`}
subtitle={build.title}
badges={buildBadges}
editAction={editBuild.open}
Expand Down
2 changes: 1 addition & 1 deletion src/frontend/tests/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export const doQuickLogin = async (
await page.goto(`${url}/login/?login=${username}&password=${password}`);
await page.waitForURL('**/platform/home');

await page.getByText(/InvenTree Demo Server/).waitFor();
await page.getByText(/InvenTree Demo Server -/).waitFor();

Check failure on line 37 in src/frontend/tests/login.ts

View workflow job for this annotation

GitHub Actions / Tests - Platform UI

[firefox] › pages/pui_part.spec.ts:60:1 › Parts - Supplier Parts

3) [firefox] › pages/pui_part.spec.ts:60:1 › Parts - Supplier Parts ────────────────────────────── Error: locator.waitFor: Test timeout of 90000ms exceeded. Call log: - waiting for getByText(/InvenTree Demo Server -/) to be visible at login.ts:37 35 | await page.waitForURL('**/platform/home'); 36 | > 37 | await page.getByText(/InvenTree Demo Server -/).waitFor(); | ^ 38 | }; 39 | 40 | export const doLogout = async (page) => { at doQuickLogin (/home/runner/work/InvenTree/InvenTree/src/frontend/tests/login.ts:37:51) at /home/runner/work/InvenTree/InvenTree/src/frontend/tests/pages/pui_part.spec.ts:61:3
};

export const doLogout = async (page) => {
Expand Down
25 changes: 12 additions & 13 deletions src/frontend/tests/pages/pui_part.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,14 +100,12 @@ test('Parts - Allocations', async ({ page }) => {
await doQuickLogin(page);

// Let's look at the allocations for a single stock item
await page.goto(`${baseUrl}/stock/item/324/`);
await page.getByRole('tab', { name: 'Allocations' }).click();

// TODO: Un-comment these lines!
// await page.goto(`${baseUrl}/stock/item/324/`);
// await page.getByRole('tab', { name: 'Allocations' }).click();

// await page.getByRole('button', { name: 'Build Order Allocations' }).waitFor();
// await page.getByRole('cell', { name: 'Making some blue chairs' }).waitFor();
// await page.getByRole('cell', { name: 'Making tables for SO 0003' }).waitFor();
await page.getByRole('button', { name: 'Build Order Allocations' }).waitFor();
await page.getByRole('cell', { name: 'Making some blue chairs' }).waitFor();
await page.getByRole('cell', { name: 'Making tables for SO 0003' }).waitFor();

// Let's look at the allocations for an entire part
await page.goto(`${baseUrl}/part/74/details`);
Expand Down Expand Up @@ -172,7 +170,8 @@ test('Parts - Pricing (Nothing, BOM)', async ({ page }) => {

// Part with no history
await page.goto(`${baseUrl}/part/82/pricing`);
await page.getByText('1551ABK').waitFor();

await page.getByText('Small plastic enclosure, black').waitFor();
await page.getByRole('tab', { name: 'Part Pricing' }).click();
await page.getByLabel('Part Pricing').getByText('Part Pricing').waitFor();
await page.getByRole('button', { name: 'Pricing Overview' }).waitFor();
Expand All @@ -183,7 +182,7 @@ test('Parts - Pricing (Nothing, BOM)', async ({ page }) => {

// Part with history
await page.goto(`${baseUrl}/part/108/pricing`);
await page.getByText('Part: Blue Chair').waitFor();
await page.getByText('A chair - with blue paint').waitFor();
await page.getByRole('tab', { name: 'Part Pricing' }).click();
await page.getByLabel('Part Pricing').getByText('Part Pricing').waitFor();
await page.getByRole('button', { name: 'Pricing Overview' }).waitFor();
Expand Down Expand Up @@ -221,7 +220,7 @@ test('Parts - Pricing (Supplier)', async ({ page }) => {

// Part
await page.goto(`${baseUrl}/part/55/pricing`);
await page.getByText('Part: C_100nF_0603').waitFor();
await page.getByText('Ceramic capacitor, 100nF in').waitFor();
await page.getByRole('tab', { name: 'Part Pricing' }).click();
await page.getByLabel('Part Pricing').getByText('Part Pricing').waitFor();
await page.getByRole('button', { name: 'Pricing Overview' }).waitFor();
Expand All @@ -247,7 +246,7 @@ test('Parts - Pricing (Variant)', async ({ page }) => {

// Part
await page.goto(`${baseUrl}/part/106/pricing`);
await page.getByText('Part: Chair').waitFor();
await page.getByText('A chair - available in multiple colors').waitFor();
await page.getByRole('tab', { name: 'Part Pricing' }).click();
await page.getByLabel('Part Pricing').getByText('Part Pricing').waitFor();
await page.getByRole('button', { name: 'Pricing Overview' }).waitFor();
Expand All @@ -273,7 +272,7 @@ test('Parts - Pricing (Internal)', async ({ page }) => {

// Part
await page.goto(`${baseUrl}/part/65/pricing`);
await page.getByText('Part: M2x4 SHCS').waitFor();
await page.getByText('Socket head cap screw, M2').waitFor();
await page.getByRole('tab', { name: 'Part Pricing' }).click();
await page.getByLabel('Part Pricing').getByText('Part Pricing').waitFor();
await page.getByRole('button', { name: 'Pricing Overview' }).waitFor();
Expand All @@ -298,7 +297,7 @@ test('Parts - Pricing (Purchase)', async ({ page }) => {

// Part
await page.goto(`${baseUrl}/part/69/pricing`);
await page.getByText('Part: 530470210').waitFor();
await page.getByText('1.25mm Pitch, PicoBlade PCB').waitFor();
await page.getByRole('tab', { name: 'Part Pricing' }).click();
await page.getByLabel('Part Pricing').getByText('Part Pricing').waitFor();
await page.getByRole('button', { name: 'Pricing Overview' }).waitFor();
Expand Down
2 changes: 1 addition & 1 deletion src/frontend/tests/pages/pui_sales_order.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ test('Sales Orders', async ({ page }) => {
await page.getByRole('cell', { name: 'SO0003' }).click();

// Order is "on hold". We will "issue" it and then place on hold again
await page.getByText('Sales Order: SO0003').waitFor();
await page.getByText('Selling stuff').first().waitFor();
await page.getByText('On Hold').first().waitFor();
await page.getByRole('button', { name: 'Issue Order' }).click();
await page.getByRole('button', { name: 'Submit' }).click();
Expand Down
4 changes: 2 additions & 2 deletions src/frontend/tests/pui_basic.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ test('Basic Login Test', async ({ page }) => {
await page.goto(baseUrl);
await page.waitForURL('**/platform');

await page.getByText('InvenTree Demo Server').waitFor();
await page.getByText('InvenTree Demo Server -').waitFor();

// Check that the username is provided
await page.getByText(user.username);
Expand Down Expand Up @@ -45,7 +45,7 @@ test('Quick Login Test', async ({ page }) => {
await page.goto(baseUrl);
await page.waitForURL('**/platform');

await page.getByText('InvenTree Demo Server').waitFor();
await page.getByText('InvenTree Demo Server - ').waitFor();

// Logout (via URL)
await page.goto(`${baseUrl}/logout/`);
Expand Down
2 changes: 1 addition & 1 deletion src/frontend/tests/pui_command.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ test('Quick Command - No Keys', async ({ page }) => {
.getByRole('button', { name: 'Dashboard Go to the InvenTree' })
.click();

await page.getByText('InvenTree Demo Server').waitFor();
await page.getByText('InvenTree Demo Server - ').waitFor();
await page.waitForURL('**/platform/home');

// Use navigation menu
Expand Down
Loading

0 comments on commit 7b50f3b

Please sign in to comment.