Skip to content

Commit f0eb088

Browse files
authored
[MDS-6845] [Core] Permit-Search-Indicate-Multiple-Mine-Relationships (PR #2) (#3924)
* Update Permit Overview, so that associated mines are now shown and able to be navigated to. Updated backend model to support the new UI, updated unit tests for new code * Switched to using CSS classes for styling, instead of linline
1 parent 53f0fcd commit f0eb088

9 files changed

Lines changed: 144 additions & 30 deletions

File tree

services/common/src/components/common/CommonPageHeader.spec.tsx

Lines changed: 42 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,34 +10,56 @@ const initialState = {
1010
[MINES]: MOCK.MINES,
1111
};
1212

13+
const defaultProps = {
14+
entityType: "Llama",
15+
entityLabel: "George",
16+
mineGuid: MOCK.MINES.mineIds[0],
17+
current_permittee: "Permit Holder",
18+
breadCrumbs: [
19+
{ route: "https://example.com", text: "All Llamas" },
20+
{ route: "https://example.com/specific", text: "Specific Llamas" },
21+
],
22+
tabProps: {
23+
items: [{ key: "overview", label: "Overview", children: <div>Overview Content</div> }],
24+
defaultActiveKey: "overview",
25+
},
26+
};
27+
1328
describe("CommonPageHeader", () => {
1429
it("renders properly", () => {
1530
const { container } = render(
1631
<ReduxWrapper initialState={initialState}>
1732
<BrowserRouter>
18-
<CommonPageHeader
19-
entityType="Llama"
20-
entityLabel="George"
21-
mineGuid={MOCK.MINES.mineIds[0]}
22-
current_permittee="Permit Holder"
23-
breadCrumbs={[
24-
{ route: "https://example.com", text: "All Llamas" },
25-
{ route: "https://example.com/specific", text: "Specific Llamas" },
26-
]}
27-
tabProps={{
28-
items: [
29-
{
30-
key: "overview",
31-
label: "Overview",
32-
children: <div>Overview Content</div>,
33-
},
34-
],
35-
defaultActiveKey: "overview",
36-
}}
37-
/>
33+
<CommonPageHeader {...defaultProps} />
3834
</BrowserRouter>
3935
</ReduxWrapper>
4036
);
4137
expect(container).toMatchSnapshot();
4238
});
39+
40+
it("shows the additional mines button when additionalMines are provided", () => {
41+
const additionalMines = [
42+
{ mine_guid: "abc-123", mine_name: "Mine Two" },
43+
{ mine_guid: "def-456", mine_name: "Mine Three" },
44+
];
45+
const { getByRole } = render(
46+
<ReduxWrapper initialState={initialState}>
47+
<BrowserRouter>
48+
<CommonPageHeader {...defaultProps} additionalMines={additionalMines} />
49+
</BrowserRouter>
50+
</ReduxWrapper>
51+
);
52+
expect(getByRole("button", { name: /show 2 more associated mines/i })).toBeInTheDocument();
53+
});
54+
55+
it("does not show the additional mines button when no additionalMines are provided", () => {
56+
const { queryByRole } = render(
57+
<ReduxWrapper initialState={initialState}>
58+
<BrowserRouter>
59+
<CommonPageHeader {...defaultProps} />
60+
</BrowserRouter>
61+
</ReduxWrapper>
62+
);
63+
expect(queryByRole("button", { name: /more associated mines/i })).not.toBeInTheDocument();
64+
});
4365
});

services/common/src/components/common/CommonPageHeader.tsx

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React, { FC, ReactNode, useEffect } from "react";
22
import { useDispatch, useSelector } from "react-redux";
3-
import { Col, Row, Tabs, TabsProps, Typography } from "antd";
3+
import { Col, Popover, Row, Tabs, TabsProps, Typography } from "antd";
44
import { Link } from "react-router-dom";
55
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
66
import CoreTag from "./CoreTag";
@@ -26,6 +26,7 @@ interface CommonPageHeaderProps {
2626
tabProps?: TabsProps;
2727
pageContent?: ReactNode;
2828
extraElement?: ReactNode;
29+
additionalMines?: { mine_guid: string; mine_name: string }[];
2930
}
3031

3132
const { Title, Text } = Typography;
@@ -39,6 +40,7 @@ const CommonPageHeader: FC<CommonPageHeaderProps> = ({
3940
tabProps,
4041
pageContent,
4142
extraElement,
43+
additionalMines,
4244
}) => {
4345
const mine = useSelector(getMineById(mineGuid));
4446
const dispatch = useDispatch();
@@ -84,6 +86,45 @@ const CommonPageHeader: FC<CommonPageHeaderProps> = ({
8486
icon={<FontAwesomeIcon icon={faLocationDot} />}
8587
text={mine?.mine_name}
8688
link={GLOBAL_ROUTES?.MINE_DASHBOARD.dynamicRoute(mineGuid)}
89+
suffix={
90+
additionalMines?.length > 0 ? (
91+
<Popover
92+
title="Associated Mines"
93+
overlayStyle={{ maxWidth: 360 }}
94+
overlayInnerStyle={{
95+
border: "1px solid #d9d9d9",
96+
borderRadius: 8,
97+
boxShadow: "0 6px 16px rgba(0,0,0,0.15)",
98+
}}
99+
content={
100+
<div>
101+
<Typography.Text type="secondary" className="tag-more-popover__description">
102+
This permit applies to multiple mine sites due to historical amendments or operational grouping.
103+
</Typography.Text>
104+
<div className="tag-more-popover__mines">
105+
{additionalMines.map((m) => (
106+
<div key={m.mine_guid} className="tag-more-popover__mine">
107+
<CoreTag
108+
icon={<FontAwesomeIcon icon={faLocationDot} />}
109+
text={m.mine_name}
110+
link={GLOBAL_ROUTES?.MINE_DASHBOARD.dynamicRoute(m.mine_guid)}
111+
/>
112+
</div>
113+
))}
114+
</div>
115+
</div>
116+
}
117+
>
118+
<button
119+
type="button"
120+
aria-label={`Show ${additionalMines.length} more associated mines`}
121+
className="tag-more-btn"
122+
>
123+
+ {additionalMines.length} More
124+
</button>
125+
</Popover>
126+
) : undefined
127+
}
87128
/>
88129
</Col>
89130
{current_permittee && (

services/common/src/components/common/CoreTag.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@ interface TagProps {
66
text: string;
77
icon: ReactNode;
88
link?: string;
9+
suffix?: ReactNode;
910
}
1011

11-
const CoreTag: FC<TagProps> = ({ text, icon, link }) => {
12+
const CoreTag: FC<TagProps> = ({ text, icon, link, suffix }) => {
1213
const getText = () => {
1314
return link ? (
1415
<Link style={{ textDecoration: "none", color: "inherit" }} to={link}>
@@ -23,6 +24,7 @@ const CoreTag: FC<TagProps> = ({ text, icon, link }) => {
2324
<Row justify="space-between" align="middle" className="tag">
2425
{icon}
2526
<Typography.Text className="margin-medium--left">{getText()}</Typography.Text>
27+
{suffix && <span className="margin-small--left">{suffix}</span>}
2628
</Row>
2729
);
2830
};

services/common/src/components/permits/ViewPermit.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,9 @@ const ViewPermit: FC = () => {
303303
<ActionMenuButton actions={headerActions} />
304304
) : null;
305305

306+
// filter out the mine already shown in the primary CoreTag (the one from the URL context)
307+
const additionalMines = permit?.mine?.filter((m) => m.mine_guid !== id) ?? [];
308+
306309
return (
307310
<div className="fixed-tabs-container permit-tabs-container">
308311
<CommonPageHeader
@@ -312,6 +315,7 @@ const ViewPermit: FC = () => {
312315
current_permittee={permit?.current_permittee ?? ""}
313316
breadCrumbs={[{ route: GLOBAL_ROUTES.MINE_PERMITS.dynamicRoute(id), text: "All Permits" }]}
314317
extraElement={headerActionComponent}
318+
additionalMines={additionalMines}
315319
tabProps={{
316320
items: tabItems,
317321
defaultActiveKey: activeTab,

services/common/src/interfaces/permits/permit.interface.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export interface IPermit {
2424
site_properties: IMineType;
2525
permit_prefix: string;
2626
mine_guid?: string;
27+
mine?: { mine_guid: string; mine_name: string; mine_no?: string }[];
2728
status_changed_timestamp?: string;
2829
update_user: string;
2930
update_timestamp: string;

services/core-api/app/api/mines/response_models.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,7 @@ def format(self, value):
364364
'exemption_fee_status_note': fields.String,
365365
'site_properties': fields.List(fields.Nested(MINE_TYPE_MODEL)),
366366
'permit_prefix': fields.String,
367+
'mine': fields.List(fields.Nested(BASIC_MINE_LIST), attribute='_all_mines'),
367368
'status_changed_timestamp': fields.DateTime,
368369
'update_user': fields.String,
369370
'update_timestamp': fields.String,

services/core-web/src/components/search/GlobalSearch/components/SearchResultItem.tsx

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -69,22 +69,26 @@ export const SearchResultItem: React.FC<SearchResultItemProps> = ({
6969
<Popover
7070
content={
7171
<>
72-
{item.result.mines.map((mine) => (
73-
<Link
74-
key={"mine-link-" + mine.mine_guid}
75-
to={router.MINE_GENERAL.dynamicRoute(mine.mine_guid)}>
76-
{mine.mine_name}
77-
</Link>
72+
{item.result.mines.map((mine, index) => (
73+
<React.Fragment key={"mine-link-" + mine.mine_guid}>
74+
{index > 0 && ", "}
75+
<Link
76+
to={router.MINE_GENERAL.dynamicRoute(mine.mine_guid)}
77+
onClick={(e) => e.stopPropagation()}
78+
>
79+
{mine.mine_name}
80+
</Link>
81+
</React.Fragment>
7882
))}
7983
</>
8084
}
8185
>
8286
<button
8387
type="button"
8488
onClick={(e) => e.stopPropagation()}
85-
style={{ background: 'none', border: 'none', padding: 0, cursor: 'pointer', color: 'inherit', font: 'inherit' }} // Strip all styling, keep as inline text
89+
style={{ background: 'none', border: 'none', padding: 0, cursor: 'pointer', color: '#1890ff', font: 'inherit', textDecoration: 'underline' }} // Strip all styling, keep as inline text
8690
>
87-
Associated with {item.result.mines.length} Mines
91+
Associated with {item.result.mines.length} Mines
8892
</button>
8993
</Popover>
9094
)}

services/core-web/src/styles/components/Tag.scss

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,36 @@
1414
stroke: $primary-btn-color;
1515
color: $primary-btn-color;
1616
}
17+
}
18+
19+
.tag-more-btn {
20+
background: none;
21+
border: none;
22+
padding: 0;
23+
cursor: pointer;
24+
color: inherit;
25+
font: inherit;
26+
font-size: 0.85em;
27+
white-space: nowrap;
28+
text-decoration: underline;
29+
}
30+
31+
.tag-more-popover {
32+
&__description {
33+
display: block;
34+
font-size: 0.85em;
35+
font-style: italic;
36+
margin-bottom: 12px;
37+
}
38+
39+
&__mines {
40+
display: flex;
41+
flex-direction: column;
42+
gap: 8px;
43+
align-items: flex-start;
44+
}
45+
46+
&__mine {
47+
display: inline-block;
48+
}
1749
}

services/core-web/src/tests/components/common/CoreTag.spec.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,11 @@ describe("CoreTag", () => {
88
const { container } = render(<CoreTag icon={<CompanyIcon />} text="test" />);
99
expect(container.firstChild).toMatchSnapshot();
1010
});
11+
12+
it("renders a suffix when provided", () => {
13+
const { getByText } = render(
14+
<CoreTag icon={<CompanyIcon />} text="test" suffix={<span>extra content</span>} />
15+
);
16+
expect(getByText("extra content")).toBeInTheDocument();
17+
});
1118
});

0 commit comments

Comments
 (0)