Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[MDS-6026] View Permit Conditions #3218

Merged
merged 21 commits into from
Aug 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 32 additions & 9 deletions services/common/src/components/common/ActionMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
import React, { FC, ReactNode } from "react";
import { Button, Dropdown, Modal } from "antd";
import { CaretDownOutlined } from "@ant-design/icons";
import React, { FC } from "react";
import CaretDownOutlined from "@ant-design/icons/CaretDownOutlined";
import DownOutlined from "@ant-design/icons/DownOutlined";
import { ITableAction } from "@mds/common/components/common/CoreTableCommonColumns";

interface ActionMenuProps {
record: any;
actionItems: ITableAction[];
category: string;
}

export const deleteConfirmWrapper = (recordDescription: string, onOk: () => void) => {
const title = `Confirm Deletion`;
const content = `Are you sure you want to delete this ${recordDescription}?`;
Expand Down Expand Up @@ -39,11 +34,39 @@ export const generateActionMenuItems = (actionItems: ITableAction[], record) =>
});
};

export interface IHeaderAction {
key: string;
label: string;
icon?: ReactNode;
clickFunction: () => void | Promise<void>;
}
// Looks like a button, intended for page-scope, not record-scope in the actions
export const ActionMenuButton: FC<{ buttonText?: string; actions: IHeaderAction[] }> = ({
actions,
buttonText = "Action",
}) => {
const items = generateActionMenuItems((actions as unknown) as ITableAction[], null);

return (
<Dropdown menu={{ items }} placement="bottomLeft">
<Button type="ghost" className="actions-dropdown-button">
{buttonText}
<DownOutlined />
</Button>
</Dropdown>
);
};

interface ActionMenuProps {
record: any;
actionItems: ITableAction[];
category: string;
}
const ActionMenu: FC<ActionMenuProps> = ({ record, actionItems, category }) => {
const items = generateActionMenuItems(actionItems, record);
return (
<Dropdown menu={{ items }} placement="bottomLeft">
<Button type="text" className="permit-table-button">
<Button type="text" className="actions-dropdown-button">
Actions
<CaretDownOutlined alt={`${category} Actions`} />
</Button>
Expand Down
48 changes: 48 additions & 0 deletions services/common/src/components/common/CoreButton.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React from "react";
import { render } from "@testing-library/react";
import FileOutlined from "@ant-design/icons/FileOutlined";
import CoreButton, { CoreButtonProps } from "./CoreButton";

const propsArray: CoreButtonProps[] = [
{
type: "default",
ghost: true,
className: "test-class ",
icon: <FileOutlined />,
htmlType: "reset",
},
{
type: "primary",
children: (
<>
<span>Text with icon</span>
<FileOutlined />
</>
),
},
{ type: "ghost", htmlType: "submit", size: "large", loading: { delay: 10 } },
{ type: "dashed", size: "small", children: <FileOutlined /> },
{ type: "link", style: { color: "green" }, href: "https://www.example.com", target: "blank" },
{ type: "text", disabled: true, shape: "round", prefixCls: "test-prefix" },
{ type: "tertiary", ghost: true, children: <div>button label</div> },
{ type: "filled-tertiary", loading: true, block: true, danger: true },
];

describe("Buttons", () => {
it("renders all buttons properly", () => {
const { container } = render(
<>
{propsArray.map(({ children, ...props }) => {
return children ? (
<CoreButton {...props} key={props.type} id={props.type}>
{children}
</CoreButton>
) : (
<CoreButton {...props} key={props.type} id={props.type} />
);
})}
</>
);
expect(container).toMatchSnapshot();
});
});
33 changes: 33 additions & 0 deletions services/common/src/components/common/CoreButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React, { FC } from "react";
import { Button, ButtonProps } from "antd";
import { ButtonType } from "antd/lib/button";

// DO NOT put antd button types in here: default, primary, ghost, dashed, link, text
declare const AdditionalTypes: ["tertiary", "filled-tertiary"];
// Additional doesn't exist at compile-time, I have not found a way around this. :(
// for now the two arrays should be duplicated.
const additionalTypes = ["tertiary", "filled-tertiary"];

type CoreCustomType = typeof AdditionalTypes[number];

// this one is the default because it's not used anywhere in the system,
// therefore it can be styled freely
const defaultButtonType = "dashed";

export interface CoreButtonProps extends Omit<ButtonProps, "type"> {
type: ButtonType | CoreCustomType;
}

const CoreButton: FC<CoreButtonProps> = ({ type, className, children, ...props }) => {
const isAntdType = additionalTypes.includes(type);
const buttonType = isAntdType ? defaultButtonType : (type as ButtonType);
const buttonClassName = ["core-btn", `core-btn-${type}`, className].join(" ").trim();

return (
<Button {...props} type={buttonType} className={buttonClassName}>
{children}
</Button>
);
};

export default CoreButton;
43 changes: 43 additions & 0 deletions services/common/src/components/common/CorePageHeader.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React from "react";
import { render } from "@testing-library/react";
import CorePageHeader from "./CorePageHeader";
import { ReduxWrapper } from "@mds/common/tests/utils/ReduxWrapper";
import { MINES } from "@mds/common/constants/reducerTypes";
import * as MOCK from "@mds/common/tests/mocks/dataMocks";
import { BrowserRouter } from "react-router-dom";

const initialState = {
[MINES]: MOCK.MINES,
};

describe("CorePageHeader", () => {
it("renders properly", () => {
const { container } = render(
<ReduxWrapper initialState={initialState}>
<BrowserRouter>
<CorePageHeader
entityType="Llama"
entityLabel="George"
mineGuid={MOCK.MINES.mineIds[0]}
current_permittee="Permit Holder"
breadCrumbs={[
{ route: "https://example.com", text: "All Llamas" },
{ route: "https://example.com/specific", text: "Specific Llamas" },
]}
tabProps={{
items: [
{
key: "overview",
label: "Overview",
children: <div>Overview Content</div>,
},
],
defaultActiveKey: "overview",
}}
/>
</BrowserRouter>
</ReduxWrapper>
);
expect(container).toMatchSnapshot();
});
});
105 changes: 105 additions & 0 deletions services/common/src/components/common/CorePageHeader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import React, { FC, ReactNode, useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { Col, Row, Tabs, TabsProps, Typography } from "antd";
import { Link } from "react-router-dom";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import CoreTag from "./CoreTag";

import CompanyIcon from "@mds/common/assets/icons/CompanyIcon";
import { faLocationDot } from "@fortawesome/pro-light-svg-icons";
import { getMineById } from "@mds/common/redux/selectors/mineSelectors";
import { fetchMineRecordById } from "@mds/common/redux/actionCreators/mineActionCreator";

interface BreadCrumb {
route: string;
text: string;
}

interface CorePageHeaderProps {
entityLabel: string;
entityType: string;
mineGuid: string;
current_permittee: string; // would be ideal to get this from the mine
breadCrumbs?: BreadCrumb[];
tabProps?: TabsProps;
extraElement?: ReactNode;
}

const { Title, Text } = Typography;

const CorePageHeader: FC<CorePageHeaderProps> = ({
mineGuid,
current_permittee,
entityLabel,
entityType,
breadCrumbs,
tabProps,
extraElement,
}) => {
const mine = useSelector((state) => getMineById(state, mineGuid));
const dispatch = useDispatch();

useEffect(() => {
if (!mine) {
dispatch(fetchMineRecordById(mineGuid));
}
}, [mineGuid]);

return (
<div className="core-page">
<div className="view--header padding-lg--top padding-lg--sides core-page-header">
<Row>
<Col>
{breadCrumbs.map((crumb) => {
return (
<React.Fragment key={crumb.route}>
<Link to={crumb.route} className="faded-text">
{crumb.text}
</Link>{" "}
/{" "}
</React.Fragment>
);
})}
<Text>
{entityType} {entityLabel}
</Text>
</Col>
</Row>
<Row align="middle" gutter={16} justify="space-between">
<Col>
<Row align="middle" gutter={16}>
<Col>
<Title level={1} className="margin-none">
{entityType} {entityLabel}
</Title>
</Col>
<Col>
<CoreTag
icon={<FontAwesomeIcon icon={faLocationDot} />}
text={mine?.mine_name}
link={GLOBAL_ROUTES?.MINE_DASHBOARD.dynamicRoute(mineGuid)}
/>
</Col>
{current_permittee && (
<Col>
<CoreTag icon={<CompanyIcon />} text={current_permittee} />
</Col>
)}
</Row>
</Col>

{extraElement && <Col className="core-header-extra">{extraElement}</Col>}
</Row>
</div>
{tabProps && (
<Tabs
className="core-tabs fixed-tabs-tabs padding-md--top"
{...tabProps}
tabBarStyle={{ paddingLeft: 20, paddingRight: 20 }}
/>
)}
</div>
);
};

export default CorePageHeader;
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ export const renderActionsColumn = ({
<div>
{items.length > 0 && (
<Dropdown menu={{ items }} placement="bottomLeft" disabled={isRowSelected}>
<Button data-cy="menu-actions-button" className="actions-dropdown-button ">
<Button data-cy="menu-actions-button" className="actions-dropdown-button" type="text">
{text}
<CaretDownOutlined alt={dropdownAltText} />
</Button>
Expand Down
16 changes: 14 additions & 2 deletions services/common/src/components/common/CoreTag.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,28 @@
import React, { FC, ReactNode } from "react";
import { Row, Typography } from "antd";
import { Link } from "react-router-dom";

interface TagProps {
text: string;
icon: ReactNode;
link?: string;
}

const CoreTag: FC<TagProps> = ({ text, icon }) => {
const CoreTag: FC<TagProps> = ({ text, icon, link }) => {
const getText = () => {
return link ? (
<Link style={{ textDecoration: "none", color: "inherit" }} to={link}>
{text}
</Link>
) : (
text
);
};

return (
<Row justify="space-between" align="middle" className="tag">
{icon}
<Typography.Text className="margin-medium--left">{text}</Typography.Text>
<Typography.Text className="margin-medium--left">{getText()}</Typography.Text>
</Row>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { getSystemFlag } from "@mds/common/redux/selectors/authenticationSelecto

interface ScrollSidePageWrapperProps {
content: ReactNode;
menuProps: ScrollSideMenuProps;
menuProps?: ScrollSideMenuProps;
header: ReactNode;
headerHeight?: number;
}
Expand All @@ -25,7 +25,7 @@ const ScrollSidePageWrapper: FC<ScrollSidePageWrapperProps> = ({
const isCore = systemFlag === SystemFlagEnum.core;

const systemHeaderHeight = isCore ? coreHeaderHeight : msHeaderHeight;
const contentPaddingY = isCore ? 15 : 26;
const contentPaddingY = isCore ? 24 : 26;

const handleScroll = () => {
let isMounted = true;
Expand All @@ -47,7 +47,7 @@ const ScrollSidePageWrapper: FC<ScrollSidePageWrapperProps> = ({
handleScroll();
}, []);

const hasMenu = menuProps.menuOptions.length > 0;
const hasMenu = Boolean(menuProps);
const hasHeader = Boolean(header);

const contentClass = [hasMenu && "side-menu--content", isFixedTop && "with-fixed-top"]
Expand All @@ -73,7 +73,7 @@ const ScrollSidePageWrapper: FC<ScrollSidePageWrapperProps> = ({
className={isFixedTop ? "side-menu--fixed" : "side-menu"}
style={{ top: menuTopOffset }}
>
{/* the 15 matches the margin/padding on the menu/content. Looks nicer */}
{/* the 24 matches the margin/padding on the menu/content. Looks nicer */}
<ScrollSideMenu offsetTop={topOffset + contentPaddingY} {...menuProps} />
</div>
)}
Expand Down
Loading
Loading