From 7b50f3b1d329a30933ba9cae0751ee58146e927e Mon Sep 17 00:00:00 2001 From: Oliver Date: Wed, 13 Nov 2024 06:49:28 +1100 Subject: [PATCH] [PUI] Page titles (#8467) * Add 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 --- .../src/components/nav/PageDetail.tsx | 88 ++++++++++--------- src/frontend/src/components/nav/PageTitle.tsx | 53 +++++++++++ src/frontend/src/pages/Index/Home.tsx | 3 + src/frontend/src/pages/Index/Scan.tsx | 2 + .../Index/Settings/AdminCenter/Index.tsx | 2 + .../pages/Index/Settings/SystemSettings.tsx | 2 + .../src/pages/Index/Settings/UserSettings.tsx | 40 +++++---- src/frontend/src/pages/build/BuildDetail.tsx | 2 +- src/frontend/tests/login.ts | 2 +- src/frontend/tests/pages/pui_part.spec.ts | 25 +++--- .../tests/pages/pui_sales_order.spec.ts | 2 +- src/frontend/tests/pui_basic.spec.ts | 4 +- src/frontend/tests/pui_command.spec.ts | 2 +- src/frontend/tests/pui_forms.spec.ts | 2 +- 14 files changed, 149 insertions(+), 80 deletions(-) create mode 100644 src/frontend/src/components/nav/PageTitle.tsx diff --git a/src/frontend/src/components/nav/PageDetail.tsx b/src/frontend/src/components/nav/PageDetail.tsx index 77948320d6..d61f4c6586 100644 --- a/src/frontend/src/components/nav/PageDetail.tsx +++ b/src/frontend/src/components/nav/PageDetail.tsx @@ -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,50 +52,53 @@ export function PageDetail({ ]); return ( - - {breadcrumbs && breadcrumbs.length > 0 && ( - - )} - - - - - {imageUrl && ( - - )} - - {title && {title}} - {subtitle && ( - - {icon} - - {subtitle} - - + <> + + + {breadcrumbs && breadcrumbs.length > 0 && ( + + )} + + + + + {imageUrl && ( + )} - - - - {detail} - - {badges?.map((badge, idx) => ( - {badge} - ))} - - - {actions && ( - - {actions.map((action, idx) => ( - {action} + + {title && {title}} + {subtitle && ( + + {icon} + + {subtitle} + + + )} + + + + {detail} + + {badges?.map((badge, idx) => ( + {badge} ))} - )} - - - - + + {actions && ( + + {actions.map((action, idx) => ( + {action} + ))} + + )} + + + + + ); } diff --git a/src/frontend/src/components/nav/PageTitle.tsx b/src/frontend/src/components/nav/PageTitle.tsx new file mode 100644 index 0000000000..030a417e92 --- /dev/null +++ b/src/frontend/src/components/nav/PageTitle.tsx @@ -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 {pageTitle}; +} diff --git a/src/frontend/src/pages/Index/Home.tsx b/src/frontend/src/pages/Index/Home.tsx index 2c1ff4ba2f..1dd5c4754a 100644 --- a/src/frontend/src/pages/Index/Home.tsx +++ b/src/frontend/src/pages/Index/Home.tsx @@ -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 ( <> + ); diff --git a/src/frontend/src/pages/Index/Scan.tsx b/src/frontend/src/pages/Index/Scan.tsx index 99d15b0ebc..70719e3ca1 100644 --- a/src/frontend/src/pages/Index/Scan.tsx +++ b/src/frontend/src/pages/Index/Scan.tsx @@ -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 ( <> + diff --git a/src/frontend/src/pages/Index/Settings/AdminCenter/Index.tsx b/src/frontend/src/pages/Index/Settings/AdminCenter/Index.tsx index 1fd1e900f0..2b3fe2a7cc 100644 --- a/src/frontend/src/pages/Index/Settings/AdminCenter/Index.tsx +++ b/src/frontend/src/pages/Index/Settings/AdminCenter/Index.tsx @@ -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 ( <> + {user.isStaff() ? ( + {user.isStaff() ? ( - - - + <> + + + + + + ); } diff --git a/src/frontend/src/pages/build/BuildDetail.tsx b/src/frontend/src/pages/build/BuildDetail.tsx index d307424942..1324fea172 100644 --- a/src/frontend/src/pages/build/BuildDetail.tsx +++ b/src/frontend/src/pages/build/BuildDetail.tsx @@ -528,7 +528,7 @@ export default function BuildDetail() { { diff --git a/src/frontend/tests/pages/pui_part.spec.ts b/src/frontend/tests/pages/pui_part.spec.ts index 21affbabab..9ad4f5c6ed 100644 --- a/src/frontend/tests/pages/pui_part.spec.ts +++ b/src/frontend/tests/pages/pui_part.spec.ts @@ -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(); diff --git a/src/frontend/tests/pages/pui_sales_order.spec.ts b/src/frontend/tests/pages/pui_sales_order.spec.ts index 7a26bab19a..dbe71e1090 100644 --- a/src/frontend/tests/pages/pui_sales_order.spec.ts +++ b/src/frontend/tests/pages/pui_sales_order.spec.ts @@ -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(); diff --git a/src/frontend/tests/pui_basic.spec.ts b/src/frontend/tests/pui_basic.spec.ts index e28e76bb18..c1639caa36 100644 --- a/src/frontend/tests/pui_basic.spec.ts +++ b/src/frontend/tests/pui_basic.spec.ts @@ -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/`); diff --git a/src/frontend/tests/pui_command.spec.ts b/src/frontend/tests/pui_command.spec.ts index 3b53c7bffa..8e2e2f7a98 100644 --- a/src/frontend/tests/pui_command.spec.ts +++ b/src/frontend/tests/pui_command.spec.ts @@ -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 diff --git a/src/frontend/tests/pui_forms.spec.ts b/src/frontend/tests/pui_forms.spec.ts index 222b19ef4d..68d29df3ef 100644 --- a/src/frontend/tests/pui_forms.spec.ts +++ b/src/frontend/tests/pui_forms.spec.ts @@ -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();