2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-04-28 03:26:45 +00:00

[PUI] Page titles (#8467)

* 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
This commit is contained in:
Oliver 2024-11-13 06:49:28 +11:00 committed by GitHub
parent 113b9e9fd9
commit 7b50f3b1d3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 149 additions and 80 deletions

View File

@ -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;
@ -51,6 +52,8 @@ export function PageDetail({
]);
return (
<>
<PageTitle title={title} />
<Stack gap='xs'>
{breadcrumbs && breadcrumbs.length > 0 && (
<BreadcrumbList
@ -70,7 +73,7 @@ export function PageDetail({
{subtitle && (
<Group gap='xs'>
{icon}
<Text size='md' truncate>
<Text size='sm' truncate>
{subtitle}
</Text>
</Group>
@ -96,5 +99,6 @@ export function PageDetail({
</Stack>
</Paper>
</Stack>
</>
);
}

View File

@ -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>;
}

View File

@ -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 />
</>
);

View File

@ -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';
@ -290,6 +291,7 @@ export default function Scan() {
// rendering
return (
<>
<PageTitle title={t`Barcode Scanning`} />
<Group justify='space-between'>
<Group justify='left'>
<StylishText>

View File

@ -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';
@ -244,6 +245,7 @@ export default function AdminCenter() {
return (
<>
<PageTitle title={t`Admin Center`} />
{user.isStaff() ? (
<Stack gap='xs'>
<SettingsHeader

View File

@ -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';
@ -303,6 +304,7 @@ export default function SystemSettings() {
return (
<>
<PageTitle title={t`System Settings`} />
{user.isStaff() ? (
<Stack gap='xs'>
<SettingsHeader

View File

@ -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';
@ -146,6 +147,8 @@ export default function UserSettings() {
}
return (
<>
<PageTitle title={t`User Settings`} />
<Stack gap='xs'>
<SettingsHeader
label='user'
@ -164,5 +167,6 @@ export default function UserSettings() {
id={null}
/>
</Stack>
</>
);
}

View File

@ -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}

View File

@ -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();
};
export const doLogout = async (page) => {

View File

@ -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`);
@ -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();
@ -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();
@ -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();
@ -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();
@ -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();
@ -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();

View File

@ -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();

View File

@ -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);
@ -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/`);

View File

@ -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

View File

@ -107,7 +107,7 @@ test('Forms - Supplier Validation', async ({ page, request }) => {
await page.getByLabel('text-field-name').fill(supplierName);
await page.getByRole('button', { name: 'Submit' }).click();
await page.getByText(supplierName).waitFor();
await page.getByText('A description').first().waitFor();
await page
.getByRole('link', { name: 'https://www.test-website.co.uk' })
.waitFor();