diff --git a/src/react/src/__tests__/components/LocationTitle.test.jsx b/src/react/src/__tests__/components/LocationTitle.test.jsx
index 1ece17077..76292a30b 100644
--- a/src/react/src/__tests__/components/LocationTitle.test.jsx
+++ b/src/react/src/__tests__/components/LocationTitle.test.jsx
@@ -3,24 +3,6 @@ import { render, screen } from '@testing-library/react';
import { MuiThemeProvider, createMuiTheme } from '@material-ui/core/styles';
import ProductionLocationDetailsTitle from '../../components/ProductionLocation/Heading/LocationTitle/LocationTitle';
-jest.mock('react-toastify', () => ({
- toast: jest.fn(),
-}));
-
-jest.mock('../../components/CopySearch', () => {
- function MockCopySearch({ children }) {
- return <>{children}>;
- }
- return MockCopySearch;
-});
-
-jest.mock('../../components/Contribute/DialogTooltip', () => {
- function MockDialogTooltip({ childComponent }) {
- return <>{childComponent}>;
- }
- return MockDialogTooltip;
-});
-
const theme = createMuiTheme();
const renderLocationTitle = (props = {}) =>
@@ -35,81 +17,30 @@ describe('ProductionLocation LocationTitle', () => {
renderLocationTitle({ data: null });
expect(screen.getByRole('heading', { level: 1 })).toBeInTheDocument();
- expect(screen.getByRole('heading', { level: 2 })).toHaveTextContent(/OS ID:/);
+ expect(screen.getByText('Location Name')).toBeInTheDocument();
});
test('renders location name from data.properties.name', () => {
const data = {
properties: {
name: 'Test Facility Name',
- os_id: 'CN2021250D1DTN7',
- },
- };
-
- renderLocationTitle({ data });
-
- expect(screen.getByRole('heading', { level: 1 })).toHaveTextContent('Test Facility Name');
- });
-
- test('renders OS ID from data.properties.os_id', () => {
- const data = {
- properties: {
- name: 'Test Facility',
- os_id: 'CN2021250D1DTN7',
- },
- };
-
- renderLocationTitle({ data });
-
- expect(screen.getByRole('heading', { level: 2 })).toHaveTextContent('OS ID: CN2021250D1DTN7');
- });
-
- test('shows Copy Link and Copy OS ID buttons when os_id is present', () => {
- const data = {
- properties: {
- name: 'Test Facility',
- os_id: 'CN2021250D1DTN7',
},
};
renderLocationTitle({ data });
- expect(screen.getByRole('button', { name: /copy link/i })).toBeInTheDocument();
- expect(screen.getByRole('button', { name: /copy os id/i })).toBeInTheDocument();
+ expect(screen.getByRole('heading', { level: 1 })).toHaveTextContent(
+ 'Test Facility Name',
+ );
});
- test('does not show copy buttons when os_id is missing', () => {
+ test('renders empty heading when data.properties.name is missing', () => {
const data = {
- properties: {
- name: 'Test Facility',
- },
- };
-
- renderLocationTitle({ data });
-
- expect(screen.queryByRole('button', { name: /copy link/i })).not.toBeInTheDocument();
- expect(screen.queryByRole('button', { name: /copy os id/i })).not.toBeInTheDocument();
- });
-
- test('does not show copy buttons when data is null', () => {
- renderLocationTitle({ data: null });
-
- expect(screen.queryByRole('button', { name: /copy link/i })).not.toBeInTheDocument();
- expect(screen.queryByRole('button', { name: /copy os id/i })).not.toBeInTheDocument();
- });
-
- test('renders info button for OS ID tooltip', () => {
- const data = {
- properties: {
- name: 'Test',
- os_id: 'US123',
- },
+ properties: {},
};
renderLocationTitle({ data });
- expect(
- screen.getByRole('button', { name: /more information about os id/i }),
- ).toBeInTheDocument();
+ expect(screen.getByRole('heading', { level: 1 })).toHaveTextContent('');
});
});
diff --git a/src/react/src/__tests__/components/OsIdBadge.test.jsx b/src/react/src/__tests__/components/OsIdBadge.test.jsx
new file mode 100644
index 000000000..c5644d407
--- /dev/null
+++ b/src/react/src/__tests__/components/OsIdBadge.test.jsx
@@ -0,0 +1,78 @@
+import React from 'react';
+import { render, screen } from '@testing-library/react';
+import { MuiThemeProvider, createMuiTheme } from '@material-ui/core/styles';
+import ProductionLocationDetailsOsIdBadge from '../../components/ProductionLocation/Heading/osIdBadge/OsIdBadge';
+
+jest.mock('react-toastify', () => ({
+ toast: jest.fn(),
+}));
+
+jest.mock('../../components/CopySearch', () => {
+ function MockCopySearch({ children }) {
+ return <>{children}>;
+ }
+ return MockCopySearch;
+});
+
+jest.mock('../../components/Contribute/DialogTooltip', () => {
+ function MockDialogTooltip({ childComponent }) {
+ return <>{childComponent}>;
+ }
+ return MockDialogTooltip;
+});
+
+const theme = createMuiTheme();
+
+const renderOsIdBadge = (props = {}) =>
+ render(
+
+
+ ,
+ );
+
+describe('ProductionLocationDetailsOsIdBadge', () => {
+ test('renders without crashing', () => {
+ renderOsIdBadge({ osId: 'CN2021250D1DTN7' });
+
+ expect(screen.getByRole('heading', { level: 2 })).toBeInTheDocument();
+ });
+
+ test('renders OS ID label and value', () => {
+ renderOsIdBadge({ osId: 'CN2021250D1DTN7' });
+
+ expect(
+ screen.getByRole('heading', { level: 2 }),
+ ).toHaveTextContent('OS ID: CN2021250D1DTN7');
+ });
+
+ test('renders info button for OS ID tooltip', () => {
+ renderOsIdBadge({ osId: 'CN2021250D1DTN7' });
+
+ expect(
+ screen.getByRole('button', {
+ name: /more information about os id/i,
+ }),
+ ).toBeInTheDocument();
+ });
+
+ test('shows Copy Link and Copy OS ID buttons when osId is present', () => {
+ renderOsIdBadge({ osId: 'CN2021250D1DTN7' });
+
+ expect(
+ screen.getByRole('button', { name: /copy link/i }),
+ ).toBeInTheDocument();
+ expect(
+ screen.getByRole('button', { name: /copy os id/i }),
+ ).toBeInTheDocument();
+ });
+
+ test('does not show copy buttons when osId is empty', () => {
+ renderOsIdBadge({ osId: '' });
+
+ expect(screen.queryByRole('button', { name: /copy link/i })).not.toBeInTheDocument();
+ expect(screen.queryByRole('button', { name: /copy os id/i })).not.toBeInTheDocument();
+ });
+});
diff --git a/src/react/src/components/ProductionLocation/Heading/ClaimFlag/styles.js b/src/react/src/components/ProductionLocation/Heading/ClaimFlag/styles.js
index 470ec98b1..e60762923 100644
--- a/src/react/src/components/ProductionLocation/Heading/ClaimFlag/styles.js
+++ b/src/react/src/components/ProductionLocation/Heading/ClaimFlag/styles.js
@@ -7,8 +7,7 @@ export default theme => {
return Object.freeze({
...typography,
root: {
- marginTop: spacing * 3,
- marginBottom: spacing * 3,
+ marginBottom: spacing * 2,
flexDirection: 'column',
padding: 0,
marginLeft: 0,
diff --git a/src/react/src/components/ProductionLocation/Heading/ClosureStatus/styles.js b/src/react/src/components/ProductionLocation/Heading/ClosureStatus/styles.js
index cd86f2b1d..383faa56a 100644
--- a/src/react/src/components/ProductionLocation/Heading/ClosureStatus/styles.js
+++ b/src/react/src/components/ProductionLocation/Heading/ClosureStatus/styles.js
@@ -5,7 +5,7 @@ export default theme =>
borderRadius: 0,
display: 'flex',
justifyContent: 'flex-start',
- marginBottom: theme.spacing.unit * 3,
+ marginBottom: theme.spacing.unit * 2,
},
contentContainer: {
width: '100%',
diff --git a/src/react/src/components/ProductionLocation/Heading/LocationTitle/LocationTitle.jsx b/src/react/src/components/ProductionLocation/Heading/LocationTitle/LocationTitle.jsx
index ccde1583d..a434ace8e 100644
--- a/src/react/src/components/ProductionLocation/Heading/LocationTitle/LocationTitle.jsx
+++ b/src/react/src/components/ProductionLocation/Heading/LocationTitle/LocationTitle.jsx
@@ -1,31 +1,19 @@
import React from 'react';
import PropTypes from 'prop-types';
import Typography from '@material-ui/core/Typography';
-import Button from '@material-ui/core/Button';
-import IconButton from '@material-ui/core/IconButton';
import { withStyles } from '@material-ui/core/styles';
-import { CopyToClipboard } from 'react-copy-to-clipboard';
-import { toast } from 'react-toastify';
import get from 'lodash/get';
-import InfoIcon from '@material-ui/icons/Info';
-
-import CopySearch from '../../../CopySearch';
-import ContentCopyIcon from '../../../ContentCopyIcon';
-import DialogTooltip from '../../../Contribute/DialogTooltip';
import productionLocationDetailsTitleStyles from './styles';
-const OS_ID_TOOLTIP_TEXT =
- 'The OS ID is a free, unique identifier automatically assigned to each production location in OS Hub. Use it to track this location across systems, share it with partners, or reference it in compliance documentation.';
-const OS_ID_LEARN_MORE_URL = 'https://info.opensupplyhub.org/resources/os-id';
-
const ProductionLocationDetailsTitle = ({ classes, data }) => {
const locationName = get(data, 'properties.name', '') || '';
- const osId = get(data, 'properties.os_id', '') || '';
return (
- {/* h1: Page title per typographyStyles */}
+
+ Location Name
+
{
>
{locationName}
-
-
-
- OS ID: {osId}
-
-
-
- Learn more →
-
-
- }
- interactive
- childComponent={
-
-
-
- }
- />
-
- {osId && (
-
-
-
-
-
-
-
-
- toast('Copied OS ID to clipboard')
- }
- >
-
-
-
-
- )}
-
);
};
diff --git a/src/react/src/components/ProductionLocation/Heading/LocationTitle/styles.js b/src/react/src/components/ProductionLocation/Heading/LocationTitle/styles.js
index ff2a31522..fd835ca72 100644
--- a/src/react/src/components/ProductionLocation/Heading/LocationTitle/styles.js
+++ b/src/react/src/components/ProductionLocation/Heading/LocationTitle/styles.js
@@ -1,14 +1,11 @@
import { getTypographyStyles } from '../../../../util/typographyStyles';
-import commonStyles from '../../commonStyles';
export default theme => {
const typography = getTypographyStyles(theme);
const spacing = theme.spacing.unit ?? 8;
return Object.freeze({
container: Object.freeze({
- ...commonStyles(theme).container,
- marginBottom: spacing * 3,
- padding: '20px 20px 20px 36px',
+ padding: '0 20px 0 0',
}),
title: Object.freeze({
...typography.formLabelTight,
@@ -17,48 +14,8 @@ export default theme => {
marginTop: 0,
marginBottom: spacing,
}),
- osIdRow: Object.freeze({
- display: 'flex',
- flexWrap: 'wrap',
- alignItems: 'center',
- gap: `${spacing}px ${spacing * 2}px`,
- }),
- osIdValueWithTooltip: Object.freeze({
- display: 'inline-flex',
- alignItems: 'center',
- gap: spacing * 0.5,
- }),
- osIdValue: Object.freeze({
- ...typography.inlineHighlight,
- fontSize: '21px',
- }),
- osIdInfoButton: Object.freeze({
- padding: spacing * 0.5,
- color: theme.palette.text.secondary,
- '&:hover': {
- color: theme.palette.text.primary,
- backgroundColor: theme.palette.action.hover,
- },
- }),
- osIdActions: Object.freeze({
- display: 'inline-flex',
- flexWrap: 'wrap',
- marginLeft: 'auto',
- }),
- copyButtonWrap: Object.freeze({
- display: 'inline-flex',
- marginLeft: spacing * 2,
- }),
- copyButtonWrapFirst: Object.freeze({
- marginLeft: 0,
- }),
- copyButton: Object.freeze({
- textTransform: 'none',
- minWidth: 'auto',
- }),
- buttonText: Object.freeze({
- marginLeft: spacing * 0.5,
- fontSize: '14px',
+ titleAccent: Object.freeze({
+ ...typography.bodyText,
}),
});
};
diff --git a/src/react/src/components/ProductionLocation/Heading/osIdBadge/OsIdBadge.jsx b/src/react/src/components/ProductionLocation/Heading/osIdBadge/OsIdBadge.jsx
new file mode 100644
index 000000000..2e26cdcf8
--- /dev/null
+++ b/src/react/src/components/ProductionLocation/Heading/osIdBadge/OsIdBadge.jsx
@@ -0,0 +1,104 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import Typography from '@material-ui/core/Typography';
+import Button from '@material-ui/core/Button';
+import IconButton from '@material-ui/core/IconButton';
+import { withStyles } from '@material-ui/core/styles';
+import { CopyToClipboard } from 'react-copy-to-clipboard';
+import { toast } from 'react-toastify';
+import InfoIcon from '@material-ui/icons/Info';
+
+import CopySearch from '../../../CopySearch';
+import ContentCopyIcon from '../../../ContentCopyIcon';
+import DialogTooltip from '../../../Contribute/DialogTooltip';
+
+import productionLocationDetailsOsIdBadgeStyles from './styles';
+import { OS_ID_TOOLTIP_TEXT, OS_ID_LEARN_MORE_URL } from './constants';
+
+const ProductionLocationDetailsOsIdBadge = ({ classes, osId }) => (
+
+
+
+ OS ID: {osId}
+
+
+
+ Learn more →
+
+
+ }
+ interactive
+ childComponent={
+
+
+
+ }
+ />
+
+ {osId && (
+
+
+ toast('Copied OS ID to clipboard')}
+ >
+
+
+
+
+
+
+
+
+
+ )}
+
+);
+
+ProductionLocationDetailsOsIdBadge.propTypes = {
+ classes: PropTypes.object.isRequired,
+ osId: PropTypes.string.isRequired,
+};
+
+export default withStyles(productionLocationDetailsOsIdBadgeStyles)(
+ ProductionLocationDetailsOsIdBadge,
+);
diff --git a/src/react/src/components/ProductionLocation/Heading/osIdBadge/constants.js b/src/react/src/components/ProductionLocation/Heading/osIdBadge/constants.js
new file mode 100644
index 000000000..9e1e21819
--- /dev/null
+++ b/src/react/src/components/ProductionLocation/Heading/osIdBadge/constants.js
@@ -0,0 +1,5 @@
+export const OS_ID_TOOLTIP_TEXT =
+ 'The OS ID is a free, unique identifier automatically assigned to each production location in OS Hub. Use it to track this location across systems, share it with partners, or reference it in compliance documentation.';
+
+export const OS_ID_LEARN_MORE_URL =
+ 'https://info.opensupplyhub.org/resources/os-id';
diff --git a/src/react/src/components/ProductionLocation/Heading/osIdBadge/styles.js b/src/react/src/components/ProductionLocation/Heading/osIdBadge/styles.js
new file mode 100644
index 000000000..1b458b732
--- /dev/null
+++ b/src/react/src/components/ProductionLocation/Heading/osIdBadge/styles.js
@@ -0,0 +1,62 @@
+import COLOURS from '../../../../util/COLOURS';
+import { getTypographyStyles } from '../../../../util/typographyStyles';
+
+export default theme => {
+ const typography = getTypographyStyles(theme);
+ const spacing = theme.spacing.unit ?? 8;
+ return Object.freeze({
+ osIdRow: Object.freeze({
+ display: 'flex',
+ flexWrap: 'wrap',
+ alignItems: 'center',
+ gap: `${spacing}px ${spacing * 2}px`,
+ padding: '12px',
+ border: `1px solid ${COLOURS.LIGHT_PURPLE_BORDER}`,
+ backgroundColor: 'rgba(128, 64, 191, 0.05)',
+ marginBottom: theme.spacing.unit * 2,
+ }),
+ osIdValueWithTooltip: Object.freeze({
+ display: 'inline-flex',
+ alignItems: 'center',
+ gap: spacing * 0.5,
+ }),
+ osIdLabel: Object.freeze({
+ fontWeight: 'bold',
+ ...typography.formLabelTight,
+ }),
+ osIdValue: Object.freeze({
+ fontWeight: 'bold',
+ fontSize: '1.75rem',
+ }),
+ osIdInfoButton: Object.freeze({
+ padding: spacing * 0.5,
+ color: theme.palette.text.secondary,
+ '&:hover': {
+ color: theme.palette.text.primary,
+ backgroundColor: theme.palette.action.hover,
+ },
+ }),
+ osIdActions: Object.freeze({
+ display: 'inline-flex',
+ flexWrap: 'wrap',
+ marginLeft: 'auto',
+ }),
+ copyButtonWrap: Object.freeze({
+ backgroundColor: COLOURS.WHITE,
+ display: 'inline-flex',
+ marginLeft: spacing * 2,
+ }),
+ copyButtonWrapFirst: Object.freeze({
+ backgroundColor: COLOURS.WHITE,
+ marginLeft: 0,
+ }),
+ copyButton: Object.freeze({
+ textTransform: 'none',
+ minWidth: 'auto',
+ }),
+ buttonText: Object.freeze({
+ marginLeft: spacing * 0.5,
+ fontSize: '14px',
+ }),
+ });
+};
diff --git a/src/react/src/components/ProductionLocation/ProductionLocationDetailsContainer/ProductionLocationDetailsContainer.jsx b/src/react/src/components/ProductionLocation/ProductionLocationDetailsContainer/ProductionLocationDetailsContainer.jsx
index bb9301483..0d5c4dca5 100644
--- a/src/react/src/components/ProductionLocation/ProductionLocationDetailsContainer/ProductionLocationDetailsContainer.jsx
+++ b/src/react/src/components/ProductionLocation/ProductionLocationDetailsContainer/ProductionLocationDetailsContainer.jsx
@@ -88,7 +88,7 @@ function ProductionLocationDetailsContainer({
}
return (
-
+
diff --git a/src/react/src/components/ProductionLocation/ProductionLocationDetailsContent/ProductionLocationDetailsContent.jsx b/src/react/src/components/ProductionLocation/ProductionLocationDetailsContent/ProductionLocationDetailsContent.jsx
index 3d491b8d2..46e59c711 100644
--- a/src/react/src/components/ProductionLocation/ProductionLocationDetailsContent/ProductionLocationDetailsContent.jsx
+++ b/src/react/src/components/ProductionLocation/ProductionLocationDetailsContent/ProductionLocationDetailsContent.jsx
@@ -1,6 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles';
+import get from 'lodash/get';
import Grid from '@material-ui/core/Grid';
import Divider from '@material-ui/core/Divider';
@@ -16,6 +17,7 @@ import DetailsMap from '../ProductionLocationDetailsMap/ProductionLocationDetail
import { facilityClaimStatusChoicesEnum } from '../../../util/constants';
import productionLocationDetailsContentStyles from './styles';
+import OsIdBadge from '../Heading/osIdBadge/OsIdBadge';
const ProductionLocationDetailsContent = ({
classes,
@@ -29,6 +31,7 @@ const ProductionLocationDetailsContent = ({
data?.properties?.claim_info?.status ===
facilityClaimStatusChoicesEnum.PENDING;
const isClaimed = !isPendingClaim && !!data?.properties?.claim_info;
+ const osId = get(data, 'properties.os_id', '') || '';
return (
@@ -40,6 +43,7 @@ const ProductionLocationDetailsContent = ({
claimInfo={data?.properties?.claim_info}
isEmbed={!!embed}
/>
+
Object.freeze({
- container: Object.freeze({}),
+ container: Object.freeze({
+ [theme.breakpoints.up('md')]: {
+ paddingLeft: '40px',
+ },
+ }),
containerItem: Object.freeze({
marginBottom: theme.spacing.unit,
}),