diff --git a/.eslintrc.js b/.eslintrc.js
index c1d2698120..ef2744dbbb 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -9,6 +9,7 @@ module.exports = {
"services/minespace-web/cypress.config.ts",
"services/minespace-web/cypress/**",
"services/core-web/webpack.config.ts",
+ "services/core-web/webpack.parts.js",
"services/core-web/cypress.config.ts",
"services/core-web/cypress/**",
],
diff --git a/services/common/src/@Types/declarations.d.ts b/services/common/src/@Types/declarations.d.ts
new file mode 100644
index 0000000000..1a3dd3c2a2
--- /dev/null
+++ b/services/common/src/@Types/declarations.d.ts
@@ -0,0 +1,4 @@
+declare module "*.svg" {
+ const content: any;
+ export default content;
+}
diff --git a/services/common/src/@Types/global.d.ts b/services/common/src/@Types/global.d.ts
index 34dbb92ef9..1869ec2b25 100644
--- a/services/common/src/@Types/global.d.ts
+++ b/services/common/src/@Types/global.d.ts
@@ -1,13 +1,16 @@
declare global {
const REQUEST_HEADER: {
- createRequestHeader: (customHeaders?: any) => {
+ createRequestHeader: (
+ customHeaders?: any
+ ) => {
headers: {
- "Access-Control-Allow-Origin": string,
- Authorization: string,
- [prop: string]: any,
- },
- },
+ "Access-Control-Allow-Origin": string;
+ Authorization: string;
+ [prop: string]: any;
+ };
+ };
};
+ const GLOBAL_ROUTES: any;
}
-export {};
\ No newline at end of file
+export {};
diff --git a/services/common/src/assets/images/small-pin-selected.svg b/services/common/src/assets/images/small-pin-selected.svg
new file mode 100644
index 0000000000..4a8a37c804
--- /dev/null
+++ b/services/common/src/assets/images/small-pin-selected.svg
@@ -0,0 +1,8 @@
+
diff --git a/services/core-web/src/components/common/ActionMenu.tsx b/services/common/src/components/common/ActionMenu.tsx
similarity index 94%
rename from services/core-web/src/components/common/ActionMenu.tsx
rename to services/common/src/components/common/ActionMenu.tsx
index 262134ed71..0a957e0d9e 100644
--- a/services/core-web/src/components/common/ActionMenu.tsx
+++ b/services/common/src/components/common/ActionMenu.tsx
@@ -1,7 +1,7 @@
import { Button, Dropdown, Modal } from "antd";
import { CaretDownOutlined } from "@ant-design/icons";
import React, { FC } from "react";
-import { ITableAction } from "@/components/common/CoreTableCommonColumns";
+import { ITableAction } from "@mds/common/components/common/CoreTableCommonColumns";
interface ActionMenuProps {
record: any;
diff --git a/services/core-web/src/components/common/CoreTableCommonColumns.tsx b/services/common/src/components/common/CoreTableCommonColumns.tsx
similarity index 94%
rename from services/core-web/src/components/common/CoreTableCommonColumns.tsx
rename to services/common/src/components/common/CoreTableCommonColumns.tsx
index bab7d916b5..720e329168 100644
--- a/services/core-web/src/components/common/CoreTableCommonColumns.tsx
+++ b/services/common/src/components/common/CoreTableCommonColumns.tsx
@@ -1,6 +1,6 @@
import React, { ReactNode } from "react";
import Highlight from "react-highlighter";
-import { dateSorter, formatDate, nullableStringSorter } from "@common/utils/helpers";
+import { dateSorter, formatDate, nullableStringSorter } from "@mds/common/redux/utils/helpers";
import { EMPTY_FIELD } from "@mds/common/constants/strings";
import { ColumnType } from "antd/lib/table";
import { Button, Dropdown } from "antd";
@@ -50,11 +50,13 @@ export const renderCategoryColumn = (
title: string,
categoryMap: any,
sortable = false,
- placeHolder = EMPTY_FIELD
+ placeHolder = EMPTY_FIELD,
+ className?: string
) => {
return {
title,
dataIndex,
+ className,
key: dataIndex,
render: (text: string) =>
{categoryMap[text] ?? placeHolder}
,
...(sortable ? { sorter: nullableStringSorter(dataIndex) } : null),
diff --git a/services/common/src/components/explosivespermits/ExplosivesPermitDiffModal.tsx b/services/common/src/components/explosivespermits/ExplosivesPermitDiffModal.tsx
new file mode 100644
index 0000000000..788292683b
--- /dev/null
+++ b/services/common/src/components/explosivespermits/ExplosivesPermitDiffModal.tsx
@@ -0,0 +1,256 @@
+import { Button, Modal, Table, Typography } from "antd";
+import React, { FC, useEffect, useState } from "react";
+import { isEqual } from "lodash";
+import { IExplosivesPermit } from "@mds/common/interfaces/permits/explosivesPermit.interface";
+
+interface ExplosivesPermitDiffModalProps {
+ explosivesPermit: IExplosivesPermit;
+ open: boolean;
+ onCancel: () => void;
+}
+
+interface IPermitDifference {
+ fieldName: string;
+ previousValue: any;
+ currentValue: any;
+}
+
+interface IPermitDifferencesByAmendment {
+ [amendmentId: string]: IPermitDifference[];
+}
+
+const ExplosivesPermitDiffModal: FC = ({
+ explosivesPermit,
+ open = false,
+ onCancel,
+}) => {
+ const [currentDiff, setCurrentDiff] = useState({});
+
+ const getPermitDifferences = (permit: IExplosivesPermit): IPermitDifferencesByAmendment => {
+ const comparablePermit = {
+ explosives_permit_amendment_id: undefined,
+ ...permit,
+ };
+
+ const permitVersions = [comparablePermit, ...permit.explosives_permit_amendments].sort(
+ (a, b) => a.explosives_permit_amendment_id - b.explosives_permit_amendment_id
+ );
+
+ const ignoredFields = [
+ "explosives_permit_amendment_id",
+ "explosives_permit_amendment_guid",
+ "issuing_inspector_party_guid",
+ "isAmendment",
+ "amendment_no",
+ ];
+
+ return permitVersions.reduce((acc, currAmendment, i) => {
+ if (i === 0) {
+ acc["0"] = [];
+ return acc;
+ }
+
+ const previousAmendment = permitVersions[i - 1];
+
+ Object.entries(currAmendment).forEach(([key, newValue]) => {
+ const oldValue = previousAmendment[key];
+
+ if (
+ (key === "detonator_magazines" || key === "explosive_magazines") &&
+ Array.isArray(newValue)
+ ) {
+ for (const [idx, newVal] of newValue.entries()) {
+ const oldVal = oldValue[idx];
+
+ if (!isEqual(newVal, oldVal)) {
+ if (!acc[currAmendment.explosives_permit_amendment_id]) {
+ acc[currAmendment.explosives_permit_amendment_id] = [];
+ }
+
+ for (const [magazineKey, magazineValue] of Object.entries(newVal)) {
+ const oldMagazineValue = oldVal?.[magazineKey];
+ const ignoredMagazineFields = [
+ "explosives_permit_amendment_magazine_id",
+ "explosives_permit_amendment_magazine_type_code",
+ "explosives_permit_magazine_id",
+ "explosives_permit_magazine_type_code",
+ ];
+
+ if (
+ magazineValue !== oldMagazineValue &&
+ !ignoredMagazineFields.includes(magazineKey)
+ ) {
+ const fieldPrefix =
+ key === "detonator_magazines" ? "Detonator Magazine" : "Explosive Magazine";
+ const diff: IPermitDifference = {
+ fieldName: `${fieldPrefix} ${idx} - ${magazineKey}`,
+ previousValue: oldMagazineValue,
+ currentValue: magazineValue,
+ };
+ acc[currAmendment.explosives_permit_amendment_id].push(diff);
+ }
+ }
+ }
+ }
+
+ return;
+ }
+
+ if (typeof newValue === "object" && newValue !== null) {
+ return;
+ }
+
+ if (!acc[currAmendment.explosives_permit_amendment_id]) {
+ acc[currAmendment.explosives_permit_amendment_id] = [];
+ }
+
+ if (newValue !== oldValue && !ignoredFields.includes(key)) {
+ const diff: IPermitDifference = {
+ fieldName: key,
+ previousValue: oldValue,
+ currentValue: newValue,
+ };
+
+ acc[currAmendment.explosives_permit_amendment_id].push(diff);
+ }
+ });
+
+ const amendmentDocuments = currAmendment.documents.map((doc) => doc.document_name);
+ if (amendmentDocuments && amendmentDocuments.length > 0) {
+ acc[currAmendment.explosives_permit_amendment_id].push({
+ fieldName: "Documents",
+ previousValue: [],
+ currentValue: amendmentDocuments,
+ });
+ }
+ return acc;
+ }, {});
+ };
+
+ useEffect(() => {
+ if (explosivesPermit) {
+ const differencesList = getPermitDifferences(explosivesPermit);
+ setCurrentDiff(differencesList);
+ }
+ }, [explosivesPermit]);
+
+ const valueOrNoData = (value: any) => {
+ if (typeof value === "boolean") {
+ return value ? "True" : "False";
+ }
+
+ return value ? value : "No Data";
+ };
+
+ const columns = [
+ {
+ title: "Notice of Work #",
+ dataIndex: "now_number",
+ key: "now_number",
+ },
+ {
+ title: "Status",
+ key: "is_closed",
+ render: (record: any) => {
+ return record.is_closed ? "Closed" : "Open";
+ },
+ },
+ {
+ title: "Amendment",
+ key: "amendment_no",
+ dataIndex: "amendment_no",
+ },
+ {
+ title: "Changes",
+ dataIndex: "differences",
+ key: "differences",
+ render: (differences: IPermitDifference[]) => {
+ return (
+
+ {differences.map((diff) => (
+
+ {diff.fieldName === "Documents" ? (
+
+
+ Files Added:
+
+ {diff.currentValue.map((file: any, index) => (
+
+ {file}
+
+ ))}
+
+ ) : (
+
+
+ {diff.fieldName}:
+
+ {diff.fieldName !== "None" && (
+
+
+ {valueOrNoData(diff.previousValue)}
+
+ {` => `}
+
+ {valueOrNoData(diff.currentValue)}
+
+
+ )}
+
+ )}
+
+ ))}
+
+ );
+ },
+ },
+ ];
+
+ const data = Object.keys(currentDiff)
+ .map((key: any, index: number) => {
+ const amendment = explosivesPermit.explosives_permit_amendments.find(
+ (amendment) => amendment.explosives_permit_amendment_id == key
+ );
+
+ const permit = key === "0" ? explosivesPermit : amendment;
+
+ return {
+ ...permit,
+ differences: currentDiff[key].length > 0 ? currentDiff[key] : [{ fieldName: "None" }],
+ };
+ })
+ .reverse();
+
+ return (
+
+ Close
+ ,
+ ]}
+ width={1000}
+ >
+ View History
+
+ You are viewing the past history of explosive storage and use permits for this permit (
+ Permit # {explosivesPermit.permit_number})
+
+
+
+ );
+};
+
+export default ExplosivesPermitDiffModal;
diff --git a/services/common/src/components/explosivespermits/ExplosivesPermitMap.tsx b/services/common/src/components/explosivespermits/ExplosivesPermitMap.tsx
new file mode 100644
index 0000000000..c7ac9b3785
--- /dev/null
+++ b/services/common/src/components/explosivespermits/ExplosivesPermitMap.tsx
@@ -0,0 +1,124 @@
+import React, { Component, FC, useEffect, useRef, useState } from "react";
+import L, { Map } from "leaflet";
+import LeafletWms from "leaflet.wms";
+
+import "leaflet.markercluster";
+import "leaflet/dist/leaflet.css";
+import "leaflet.markercluster/dist/MarkerCluster.css";
+import "leaflet.markercluster/dist/MarkerCluster.Default.css";
+
+import * as Strings from "@mds/common/constants/strings";
+import { Validate } from "@mds/common/redux/utils/Validate";
+import { SMALL_PIN_SELECTED } from "@mds/common/constants/assets";
+
+/**
+ * @class ExplosivesPermitMap.js is a Leaflet Map component.
+ */
+
+const leafletWMSTiledOptions = {
+ transparent: true,
+ tiled: true,
+ uppercase: true,
+ format: "image/png",
+ identify: false,
+};
+
+const checkValidityOfCoordinateInput = (coordinates) =>
+ coordinates.length === 2 &&
+ Validate.checkLat(coordinates[0]) &&
+ Validate.checkLon(coordinates[1]);
+
+const getMajorMinePermittedAreas = () => {
+ const majorMinesSource = LeafletWms.source(
+ "https://openmaps.gov.bc.ca/geo/pub/WHSE_MINERAL_TENURE.HSP_MJR_MINES_PERMTTD_AREAS_SP/ows",
+ { ...leafletWMSTiledOptions, identify: false }
+ );
+ return majorMinesSource.getLayer("pub:WHSE_MINERAL_TENURE.HSP_MJR_MINES_PERMTTD_AREAS_SP");
+};
+
+interface ExplosivesPermitMapProps {
+ pin: [number, number];
+}
+
+const ExplosivesPermitMap: FC = ({ pin = [] }) => {
+ const [containsPin, setContainsPin] = useState(false);
+ const mapRef = useRef