diff --git a/services/core-api/app/api/mines/response_models.py b/services/core-api/app/api/mines/response_models.py index fab9915864..9e67d53e59 100644 --- a/services/core-api/app/api/mines/response_models.py +++ b/services/core-api/app/api/mines/response_models.py @@ -111,6 +111,7 @@ def format(self, value): 'document_manager_guid': fields.String, 'document_name': fields.String, 'upload_date': fields.String, + 'update_timestamp': fields.String, 'create_user': fields.String, 'is_archived': fields.Boolean, 'archived_date': fields.String, diff --git a/services/core-web/common/components/documents/ArchiveDocumentModal.tsx b/services/core-web/common/components/documents/ArchiveDocumentModal.tsx index 97448952be..0fe5b664c6 100644 --- a/services/core-web/common/components/documents/ArchiveDocumentModal.tsx +++ b/services/core-web/common/components/documents/ArchiveDocumentModal.tsx @@ -33,7 +33,7 @@ const ArchiveDocumentModal: FC = (props: ArchiveDocum documents={props.documents} view="minimal" uploadDateIndex="upload_date" - excludedColumnKeys={["archive", "remove", "category"]} + excludedColumnKeys={["actions", "category"]} />
diff --git a/services/core-web/common/components/documents/ArchivedDocumentsSection.tsx b/services/core-web/common/components/documents/ArchivedDocumentsSection.tsx index 6c6227500a..11d9e4db7c 100644 --- a/services/core-web/common/components/documents/ArchivedDocumentsSection.tsx +++ b/services/core-web/common/components/documents/ArchivedDocumentsSection.tsx @@ -1,12 +1,12 @@ import React from "react"; import DocumentTable from "@/components/common/DocumentTable"; import { Typography } from "antd"; -import { IMineDocument } from "@mds/common"; import { DeleteOutlined } from "@ant-design/icons"; import { Feature, isFeatureEnabled } from "@mds/common"; +import { MineDocument } from "@common/models/documents/document"; interface ArchivedDocumentsSectionProps { - documents: IMineDocument; + documents: MineDocument[]; documentColumns: any; titleLevel?: 1 | 2 | 3 | 4 | 5; } @@ -16,12 +16,6 @@ const ArchivedDocumentsSection = (props: ArchivedDocumentsSectionProps) => { return <>; } - const docs = props.documents.map((d) => { - d.name = d.document_name; - - return d; - }); - return (
@@ -33,7 +27,7 @@ const ArchivedDocumentsSection = (props: ArchivedDocumentsSectionProps) => {
diff --git a/services/core-web/common/components/documents/DeleteDocumentModal.tsx b/services/core-web/common/components/documents/DeleteDocumentModal.tsx new file mode 100644 index 0000000000..1d56497316 --- /dev/null +++ b/services/core-web/common/components/documents/DeleteDocumentModal.tsx @@ -0,0 +1,51 @@ +import React, { FC } from "react"; + +import DocumentTable from "@/components/common/DocumentTable"; +import { Alert, Button, Form, Typography } from "antd"; +import { MineDocument } from "@common/models/documents/document"; + +interface DeleteDocumentModalProps { + documents: MineDocument[]; + handleSubmit(documents: MineDocument[]): Promise; + closeModal(): void; +} + +const DeleteDocumentModal: FC = (props: DeleteDocumentModalProps) => { + return ( +
props.handleSubmit(props.documents).then(props.closeModal)} + > + + + + + + You're about to delete the following file{props.documents?.length > 1 ? "s" : ""}: + + + + +
+ + +
+ + ); +}; + +export default DeleteDocumentModal; diff --git a/services/core-web/common/models/documents/document.ts b/services/core-web/common/models/documents/document.ts new file mode 100644 index 0000000000..9927d81e8c --- /dev/null +++ b/services/core-web/common/models/documents/document.ts @@ -0,0 +1,153 @@ +import { USER_ROLES } from "@mds/common"; + +export enum FileOperations { + View = "Open in document viewer", + Download = "Download file", + Replace = "Replace file", + Archive = "Archive file", + Delete = "Delete", +} + +/* +A base class for Mine Documents + +There is an issue with antd where sorting a table that has children (ie matchChildColumnsToParent) +will transform the records into type (with versions still maintaining their type) so properties accessed +by the table should be *set* to the specific object, cannot expect to be able to consistently call its methods + +include "user_roles" property in the json used in the constructor to set allowed actions based on the user +*/ +export class MineDocument { + public mine_document_guid: string; + + public mine_guid: string; + + public document_manager_guid: string; + + public document_name: string; + + public create_user: string; + + public update_user: string; + + public upload_date: string; + + public update_timestamp: string; + + public is_archived: boolean; + + public archived_by: string; + + public archived_date: string; + + public is_latest_version: boolean; + + public category?: string; + + // generated + public key: string; + + public file_type: string; + + public number_prev_versions: number; + + public versions: MineDocument[]; // all previous file versions, not including latest + + public allowed_actions: FileOperations[]; + + constructor(jsonObject: any) { + this.mine_document_guid = jsonObject.mine_document_guid; + this.mine_guid = jsonObject.mine_guid; + this.document_manager_guid = jsonObject.document_manager_guid; + this.document_name = jsonObject.document_name; + this.create_user = jsonObject.create_user; + this.update_user = jsonObject.update_user; + this.upload_date = jsonObject.upload_date; + this.update_timestamp = jsonObject.update_timestamp; + this.category = jsonObject.category; + this.is_archived = jsonObject.is_archived ?? false; + this.archived_by = jsonObject.archived_by; + this.archived_date = jsonObject.archived_date; + this.is_latest_version = jsonObject.is_latest_version ?? true; + this.setCalculatedProperties(jsonObject); + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + protected makeChild(params: any, _constructorArgs: any) { + return new MineDocument(params); + } + + protected setCalculatedProperties(jsonObject: any) { + this.key = this.is_latest_version + ? this.mine_document_guid + : jsonObject.document_manager_version_guid; + this.file_type = this.getFileType(); + + const versions = jsonObject.versions ?? []; + if (this.is_latest_version && versions.length) { + this.number_prev_versions = versions.length - 1; + this.versions = versions + .slice(1) + .map((version) => this.makeChild({ ...version, is_latest_version: false }, jsonObject)); + } else { + this.number_prev_versions = 0; + this.versions = []; + } + this.setAllowedActions(jsonObject.user_roles); + } + + public getFileType() { + const index = this.document_name.lastIndexOf("."); + return index === -1 ? null : this.document_name.substring(index).toLocaleLowerCase(); + } + + public setAllowedActions(userRoles: string[] = []) { + this.allowed_actions = this.getAllowedActions(userRoles).filter(Boolean); + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + protected getAllowedActions(_userRoles: string[] = []) { + const canModify = this.is_latest_version && !this.is_archived; + return [ + this.file_type === ".pdf" && FileOperations.View, + FileOperations.Download, + canModify && FileOperations.Replace, + canModify && FileOperations.Archive, + canModify && FileOperations.Delete, + ]; + } +} + +export class MajorMineApplicationDocument extends MineDocument { + public major_mine_application_document_type_code: string; + + public versions: MajorMineApplicationDocument[]; + + constructor(jsonObject: any) { + super(jsonObject); + this.major_mine_application_document_type_code = + jsonObject.major_mine_application_document_type_code; + } + + protected makeChild(params: any, constructorArgs: any) { + return new MajorMineApplicationDocument({ + ...params, + major_mine_application_document_type_code: + constructorArgs.major_mine_application_document_type_code, + }); + } + + public getAllowedActions(userRoles: string[] = []) { + const allowedActions = super.getAllowedActions(); + + const canModifyRoles = [ + USER_ROLES.role_edit_major_mine_applications, + USER_ROLES.role_minespace_proponent, + ]; + const canModify = userRoles.some((role) => canModifyRoles.includes(role)); + + return allowedActions.filter( + (action) => canModify || [FileOperations.View, FileOperations.Download].includes(action) + ); + } +} diff --git a/services/core-web/src/components/common/CoreTable.tsx b/services/core-web/src/components/common/CoreTable.tsx index aadb22f1ac..0873b73e4c 100644 --- a/services/core-web/src/components/common/CoreTable.tsx +++ b/services/core-web/src/components/common/CoreTable.tsx @@ -1,20 +1,20 @@ import React from "react"; import { Table, TableProps, Tooltip } from "antd"; -import { ColumnType } from "antd/es/table"; +import { ColumnsType } from "antd/es/table"; import { MinusSquareFilled, PlusSquareFilled } from "@ant-design/icons"; import { ExpandableConfig } from "antd/lib/table/interface"; interface CoreTableExpandConfig extends ExpandableConfig { - getDataSource: (record: T) => any[]; + getDataSource?: (record: T) => any[]; rowKey?: string | ((record: any) => string); recordDescription?: string; - subTableColumns?: ColumnType[]; + subTableColumns?: ColumnsType; matchChildColumnsToParent?: boolean; // and any other props from expandable https://4x.ant.design/components/table/#expandable } interface CoreTableProps extends TableProps { - columns: ColumnType[]; + columns: ColumnsType; dataSource: T[]; condition?: boolean; rowKey?: string | ((record: T) => string | number); // defaults to "key" @@ -94,29 +94,26 @@ const CoreTable = (props: CoreTableProps) => { ); }; - const expansionProps = expandProps - ? { - rowExpandable: - expandProps.rowExpandable ?? ((record) => expandProps.getDataSource(record).length > 0), - expandIcon: renderTableExpandIcon, - expandRowByClick: true, - expandedRowRender: expandProps.expandedRowRender ?? renderExpandedRow, - ...expandProps, - } - : null; - - const matchChildColumnsToParentProps = expandProps - ? { - expandIcon: renderTableExpandIcon, - indentSize: 0, - } - : null; + const getExpansionProps = () => { + if (expandProps) { + return expandProps.matchChildColumnsToParent + ? { expandIcon: renderTableExpandIcon, indentSize: 0, ...expandProps } + : { + rowExpandable: + expandProps.rowExpandable ?? + ((record) => expandProps.getDataSource(record).length > 0), + expandIcon: renderTableExpandIcon, + expandRowByClick: true, + expandedRowRender: expandProps.expandedRowRender ?? renderExpandedRow, + ...expandProps, + }; + } + return { showExpandColumn: false }; + }; return condition ? ( (props: CoreTableProps) => { ? "table-row-align-middle no-sub-table-expandable-rows fade-in" : "fade-in" } - {...tableProps} columns={columns} + {...tableProps} >
) : ( { - return ( -
- {elem} +export interface ITableAction { + key: string; + label: string; + clickFunction: (event, record) => any; + icon?: ReactNode; +} - - {record?.number_of_versions > 0 ? ( - - - } color="#5E46A1" className="file-version-amount"> - {record.number_of_versions} - - - - ) : null} - {record.showArchiveIndicator && record?.is_archived ? {"Archived"} : null} - -
- ); -}; - -export const renderDocumentLinkColumn = ( - dataIndex: string, - title = "File Name", - sortable = true, - docManGuidIndex = "document_manager_guid", - showArchiveIndicator = true -): ColumnType => { - return { - title, - dataIndex, - key: dataIndex, - render: (text = "", record: any) => { - const recordWithArchiveIndicator = { ...record, showArchiveIndicator }; - const link = ( -
- -
- ); - return documentWithTag(recordWithArchiveIndicator, link, title); - }, - ...(sortable ? { sorter: nullableStringSorter(dataIndex) } : null), - }; -}; - -export const renderTaggedColumn = ( - dataIndex: string, - title: string, - sortable = false, - placeHolder = "" +export const renderActionsColumn = ( + actions: ITableAction[], + recordActionsFilter: (record, actions) => ITableAction[], + text = "Actions", + classPrefix = "", + dropdownAltText = "Menu" ) => { - return { - title, - dataIndex, - key: dataIndex, - render: (text: any, record: any) => { - const content = ( -
- {text ?? placeHolder} -
- ); - return documentWithTag(record, content, title); - }, - ...(sortable ? { sorter: nullableStringSorter(dataIndex) } : null), - }; -}; + const labelClass = classPrefix ? `${classPrefix}-dropdown-button` : "actions-dropdown-button"; -export const documentActionOperationsColumn = () => { return { - render: (record: any) => { + key: "actions", + render: (record) => { + const filteredActions = recordActionsFilter ? recordActionsFilter(record, actions) : actions; + const items = filteredActions.map((action) => { + return { + key: action.key, + icon: action.icon, + label: ( + + ), + }; + }); + return ( - +
+ + {/* // TODO: change button classname to something generic */} + + +
); }, }; diff --git a/services/core-web/src/components/common/DocumentColumns.js b/services/core-web/src/components/common/DocumentColumns.js deleted file mode 100644 index 4fb79c3ee3..0000000000 --- a/services/core-web/src/components/common/DocumentColumns.js +++ /dev/null @@ -1,61 +0,0 @@ -import React from "react"; -import * as Strings from "@common/constants/strings"; -import { Button, Popconfirm } from "antd"; -import { TRASHCAN } from "@/constants/assets"; -import { - renderDateColumn, - renderDocumentLinkColumn, - renderTaggedColumn, - renderTextColumn, -} from "./CoreTableCommonColumns"; - -export const documentNameColumn = ( - documentNameColumnIndex = "document_name", - title = "File Name", - minimalView = false -) => { - return minimalView - ? renderTaggedColumn(documentNameColumnIndex, title) - : renderDocumentLinkColumn(documentNameColumnIndex, title, true); -}; - -export const uploadDateColumn = ( - uploadDateIndex = "upload_date", - title = "Uploaded", - sortable = true -) => { - return renderDateColumn(uploadDateIndex, title, sortable, null, Strings.EMPTY_FIELD); -}; - -export const uploadedByColumn = ( - uploadedByIndex = "update_user", - title = "Uploaded By", - sortable = true -) => { - return renderTextColumn(uploadedByIndex, title, sortable); -}; - -export const removeFunctionColumn = ( - removeFunction, - documentParent = "", - documentNameColumnIndex = "document_name" -) => { - return { - key: "remove", - render: (record) => ( -
- removeFunction(event, record.key, documentParent)} - okText="Delete" - cancelText="Cancel" - > - - -
- ), - }; -}; diff --git a/services/core-web/src/components/common/DocumentColumns.tsx b/services/core-web/src/components/common/DocumentColumns.tsx new file mode 100644 index 0000000000..65ebc20c93 --- /dev/null +++ b/services/core-web/src/components/common/DocumentColumns.tsx @@ -0,0 +1,173 @@ +import React, { ReactNode } from "react"; +import * as Strings from "@common/constants/strings"; +import { Button, Popconfirm, Tag, Tooltip } from "antd"; +import { ColumnType } from "antd/lib/table"; +import { TRASHCAN } from "@/constants/assets"; +import { renderDateColumn, renderTextColumn } from "./CoreTableCommonColumns"; +import { nullableStringSorter } from "@common/utils/helpers"; +import { ClockCircleOutlined } from "@ant-design/icons"; +import DocumentLink from "./DocumentLink"; +import { downloadFileFromDocumentManager } from "@common/utils/actionlessNetworkCalls"; +import { MineDocument } from "@common/models/documents/document"; + +const documentWithTag = ( + record: MineDocument, + elem: ReactNode, + title: string, + showVersions = true +) => { + return ( +
+ {elem} + + + {record.number_prev_versions > 0 ? ( + + + } color="#5E46A1" className="file-version-amount"> + {record.number_prev_versions} + + + + ) : null} + {record.is_archived ? {"Archived"} : null} + +
+ ); +}; + +export const renderTaggedColumn = ( + dataIndex: string, + title: string, + sortable = false, + placeHolder = "" +) => { + return { + title, + dataIndex, + key: dataIndex, + render: (text: any, record: MineDocument) => { + const content = ( +
+ {text ?? placeHolder} +
+ ); + return documentWithTag(record, content, title); + }, + ...(sortable ? { sorter: nullableStringSorter(dataIndex) } : null), + }; +}; + +export const renderDocumentLinkColumn = ( + dataIndex: string, + title = "File Name", + sortable = true, + showVersions = true, + docManGuidIndex = "document_manager_guid" +): ColumnType => { + return { + title, + dataIndex, + key: dataIndex, + render: (text = "", record: MineDocument) => { + const link = ( +
+ +
+ ); + return documentWithTag(record, link, title, showVersions); + }, + ...(sortable ? { sorter: nullableStringSorter(dataIndex) } : null), + }; +}; + +export const documentNameColumn = ( + documentNameColumnIndex = "document_name", + title = "File Name", + minimalView = false +) => { + return minimalView + ? renderTaggedColumn(documentNameColumnIndex, title) + : renderDocumentLinkColumn(documentNameColumnIndex, title, true, false); +}; + +export const documentNameColumnNew = ( + dataIndex = "document_name", + title = "File Name", + sortable = true +) => { + return { + title, + dataIndex, + key: dataIndex, + render: (text: string, record: MineDocument) => { + const docLink = downloadFileFromDocumentManager(record)}>{text}; + return documentWithTag(record, docLink, "File Name"); + }, + ...(sortable ? { sorter: nullableStringSorter(dataIndex) } : null), + }; +}; + +export const uploadDateColumn = ( + uploadDateIndex = "upload_date", + title = "Uploaded", + sortable = true +) => { + return renderDateColumn(uploadDateIndex, title, sortable, null, Strings.EMPTY_FIELD); +}; + +export const uploadedByColumn = ( + uploadedByIndex = "update_user", + title = "Uploaded By", + sortable = true +) => { + return renderTextColumn(uploadedByIndex, title, sortable); +}; + +export const removeFunctionColumn = ( + removeFunction, + documentParent = "", + documentNameColumnIndex = "document_name" +) => { + return { + key: "remove", + render: (record) => ( +
+ removeFunction(event, record.key, documentParent)} + okText="Delete" + cancelText="Cancel" + > + + +
+ ), + }; +}; diff --git a/services/core-web/src/components/common/DocumentTable.js b/services/core-web/src/components/common/DocumentTable.js deleted file mode 100644 index 2477cadef6..0000000000 --- a/services/core-web/src/components/common/DocumentTable.js +++ /dev/null @@ -1,236 +0,0 @@ -import React from "react"; -import PropTypes from "prop-types"; -import * as Strings from "@common/constants/strings"; -import CustomPropTypes from "@/customPropTypes"; -import CoreTable from "@/components/common/CoreTable"; -import { - documentNameColumn, - removeFunctionColumn, - uploadDateColumn, - uploadedByColumn, -} from "./DocumentColumns"; -import { renderTextColumn, documentActionOperationsColumn } from "./CoreTableCommonColumns"; -import { Button } from "antd"; -import { some } from "lodash"; -import { closeModal, openModal } from "@common/actions/modalActions"; -import { archiveMineDocuments } from "@common/actionCreators/mineActionCreator"; -import { connect } from "react-redux"; -import { bindActionCreators } from "redux"; -import { modalConfig } from "@/components/modalContent/config"; -import { Feature, isFeatureEnabled } from "@mds/common"; - -const propTypes = { - documents: PropTypes.arrayOf(CustomPropTypes.documentRecord), - isViewOnly: PropTypes.bool, - // eslint-disable-next-line react/no-unused-prop-types - removeDocument: PropTypes.func, - archiveMineDocuments: PropTypes.func, - archiveDocumentsArgs: PropTypes.shape({ - mineGuid: PropTypes.string, - }), - onArchivedDocuments: PropTypes.func, - canArchiveDocuments: PropTypes.bool, - excludedColumnKeys: PropTypes.arrayOf(PropTypes.string), - additionalColumnProps: PropTypes.arrayOf( - PropTypes.shape({ - key: PropTypes.string, - colProps: PropTypes.objectOf(PropTypes.string), - }) - ), - // eslint-disable-next-line react/no-unused-prop-types - documentParent: PropTypes.string, - documentColumns: PropTypes.arrayOf(PropTypes.object), - isLoaded: PropTypes.bool, - matchChildColumnsToParent: PropTypes.bool, - defaultSortKeys: PropTypes.arrayOf(PropTypes.string), - openModal: PropTypes.func.isRequired, - view: PropTypes.string.isRequired, -}; - -const defaultProps = { - documents: [], - isViewOnly: false, - removeDocument: () => {}, - excludedColumnKeys: [], - additionalColumnProps: [], - documentColumns: null, - documentParent: null, - isLoaded: false, - matchChildColumnsToParent: false, - defaultSortKeys: ["upload_date", "dated", "update_timestamp"], // keys to sort by when page loads - view: "standard", - canArchiveDocuments: false, -}; - -const renderFileType = (file) => { - const index = file.lastIndexOf("."); - return index === -1 ? null : file.substr(index); -}; - -const parseVersions = (versions, documentType) => - versions.map((version) => ({ - key: version.mine_document_version_guid, - file_location: - Strings.MAJOR_MINES_APPLICATION_DOCUMENT_TYPE_CODE_LOCATION[documentType] || - Strings.EMPTY_FIELD, - file_type: renderFileType(version.document_name) || Strings.EMPTY_FIELD, - ...version, - })); - -const transformRowData = (document) => { - const pastFiles = parseVersions(document.versions, document.major_mine_application_document_type); - const currentFile = { - key: document.key, - file_location: - Strings.MAJOR_MINES_APPLICATION_DOCUMENT_TYPE_CODE_LOCATION[ - document.major_mine_application_document_type - ] || Strings.EMPTY_FIELD, - file_type: renderFileType(document.document_name) || Strings.EMPTY_FIELD, - number_of_versions: document?.versions?.length, - ...document, - }; - - return { - ...currentFile, - children: pastFiles, - }; -}; - -const openArchiveModal = (event, props, documents) => { - event.preventDefault(); - - props.openModal({ - props: { - title: `Archive ${documents?.length > 1 ? "Multiple Files" : "File"}`, - closeModal: props.closeModal, - handleSubmit: async () => { - await props.archiveMineDocuments( - props.archiveDocumentsArgs.mineGuid, - documents.map((d) => d.mine_document_guid) - ); - if (props.onArchivedDocuments) { - props.onArchivedDocuments(documents); - } - }, - documents, - }, - content: modalConfig.ARCHIVE_DOCUMENT, - }); -}; - -export const DocumentTable = (props) => { - const isMinimalView = props.view === "minimal"; - const canDelete = !props.isViewOnly && props.removeDocument; - const canArchive = - !props.isViewOnly && - props.canArchiveDocuments && - isFeatureEnabled(Feature.MAJOR_PROJECT_ARCHIVE_FILE); - - const archiveColumn = { - key: "archive", - render: (record) => { - const button = ( - - ); - return record?.mine_document_guid ?
{button}
:
; - }, - }; - - let columns = props.matchChildColumnsToParent - ? [ - documentNameColumn("document_name", "File Name"), - renderTextColumn("file_location", "File Location", !isMinimalView), - renderTextColumn("file_type", "File Type", !isMinimalView), - uploadDateColumn("update_timestamp", "Last Modified"), - uploadedByColumn("create_user", "Created By"), - documentActionOperationsColumn(), - ] - : [ - documentNameColumn("document_name", "File Name", isMinimalView), - renderTextColumn("category", "Category", !isMinimalView), - uploadDateColumn("upload_date", "Uploaded", !isMinimalView), - uploadDateColumn("dated", "Dated", !isMinimalView), - ]; - - const currentRowData = props.matchChildColumnsToParent - ? props.documents?.map((document) => { - return transformRowData(document); - }) - : null; - - if (canDelete) { - columns.push(removeFunctionColumn(props.removeDocument, props?.documentParent)); - } - - if (canArchive) { - columns.push(archiveColumn); - } - - if (!some(props.documents, "dated")) { - columns = columns.filter((column) => column.key !== "dated"); - } - - if (props?.excludedColumnKeys?.length > 0) { - columns = columns.filter((column) => !props.excludedColumnKeys.includes(column.key)); - } - - if (props?.additionalColumnProps?.length > 0) { - // eslint-disable-next-line no-unused-expressions - props?.additionalColumnProps.forEach((addColumn) => { - const columnIndex = columns.findIndex((column) => addColumn?.key === column.key); - if (columnIndex >= 0) { - columns[columnIndex] = { ...columns[columnIndex], ...addColumn?.colProps }; - } - }); - } - - if (props.defaultSortKeys.length > 0) { - columns = columns.map((column) => { - const isDefaultSort = props.defaultSortKeys.includes(column.key); - return isDefaultSort ? { defaultSortOrder: "descend", ...column } : column; - }); - } - - const minimalProps = isMinimalView - ? { size: "small", rowClassName: "ant-table-row-minimal" } - : null; - return props.matchChildColumnsToParent ? ( - record.number_of_versions > 0, - }} - /> - ) : ( - - ); -}; - -DocumentTable.propTypes = propTypes; -DocumentTable.defaultProps = defaultProps; - -const mapDispatchToProps = (dispatch) => - bindActionCreators( - { - openModal, - closeModal, - archiveMineDocuments, - }, - dispatch - ); - -export default connect(null, mapDispatchToProps)(DocumentTable); diff --git a/services/core-web/src/components/common/DocumentTable.tsx b/services/core-web/src/components/common/DocumentTable.tsx new file mode 100644 index 0000000000..7aa8edc9e0 --- /dev/null +++ b/services/core-web/src/components/common/DocumentTable.tsx @@ -0,0 +1,267 @@ +import React from "react"; +import CoreTable from "@/components/common/CoreTable"; +import { + documentNameColumn, + documentNameColumnNew, + uploadDateColumn, + uploadedByColumn, +} from "./DocumentColumns"; +import { renderTextColumn, renderActionsColumn, ITableAction } from "./CoreTableCommonColumns"; +import { some } from "lodash"; +import { closeModal, openModal } from "@common/actions/modalActions"; +import { archiveMineDocuments } from "@common/actionCreators/mineActionCreator"; +import { connect } from "react-redux"; +import { bindActionCreators } from "redux"; +import { modalConfig } from "@/components/modalContent/config"; +import { Feature, isFeatureEnabled } from "@mds/common"; +import { SizeType } from "antd/lib/config-provider/SizeContext"; +import { ColumnType, ColumnsType } from "antd/es/table"; +import { FileOperations, MineDocument } from "@common/models/documents/document"; +import { + DeleteOutlined, + DownloadOutlined, + FileOutlined, + InboxOutlined, + SyncOutlined, +} from "@ant-design/icons"; +import { openDocument } from "../syncfusion/DocumentViewer"; +import { downloadFileFromDocumentManager } from "@common/utils/actionlessNetworkCalls"; +import { getUserAccessData } from "@common/selectors/authenticationSelectors"; + +interface DocumentTableProps { + documents: MineDocument[]; + isLoaded: boolean; + isViewOnly: boolean; + canArchiveDocuments: boolean; + showVersionHistory: boolean; + documentParent: string; + view: string; + openModal: (arg) => void; + openDocument: any; + closeModal: () => void; + removeDocument: (event, doc_guid: string, mine_guid: string) => void; + archiveMineDocuments: (mineGuid: string, mineDocumentGuids: string[]) => void; + onArchivedDocuments: (docs?: MineDocument[]) => void; + documentColumns: ColumnType[]; // any? + additionalColumns: ColumnType[]; + defaultSortKeys: string[]; + excludedColumnKeys: string[]; + additionalColumnProps: { key: string; colProps: any }[]; + fileOperationPermissionMap: { operation: FileOperations; permission: string | boolean }[]; + userRoles: string[]; +} + +// eslint-disable-next-line @typescript-eslint/no-shadow +export const DocumentTable = ({ + isViewOnly = false, + excludedColumnKeys = [], + additionalColumnProps = [], + documentColumns = null, + additionalColumns = [], + documentParent = null, + isLoaded = true, + showVersionHistory = false, + defaultSortKeys = ["upload_date", "dated", "update_timestamp"], + view = "standard", + canArchiveDocuments = false, + openModal, + closeModal, + removeDocument, + openDocument, + ...props +}: DocumentTableProps) => { + const allowedTableActions = { + [FileOperations.View]: true, + [FileOperations.Download]: true, + [FileOperations.Replace]: !isViewOnly, + [FileOperations.Archive]: + !isViewOnly && canArchiveDocuments && isFeatureEnabled(Feature.MAJOR_PROJECT_ARCHIVE_FILE), + [FileOperations.Delete]: !isViewOnly && removeDocument !== undefined, + }; + + const isMinimalView: boolean = view === "minimal"; + + const parseDocuments = (docs: any[]): MineDocument[] => { + let parsedDocs: MineDocument[]; + if (docs.length && docs[0] instanceof MineDocument) { + parsedDocs = docs; + } else { + parsedDocs = docs.map((doc) => new MineDocument(doc)); + } + return parsedDocs.map((doc) => { + doc.setAllowedActions(props.userRoles); + return doc; + }); + }; + const documents = parseDocuments(props.documents ?? []); + + const openArchiveModal = (event, docs: MineDocument[]) => { + const mineGuid = docs[0].mine_guid; + event.preventDefault(); + openModal({ + props: { + title: `Archive ${docs?.length > 1 ? "Multiple Files" : "File"}`, + closeModal: closeModal, + handleSubmit: async () => { + await props.archiveMineDocuments( + mineGuid, + docs.map((d) => d.mine_document_guid) + ); + if (props.onArchivedDocuments) { + props.onArchivedDocuments(docs); + } + }, + docs, + }, + content: modalConfig.ARCHIVE_DOCUMENT, + }); + }; + + const openDeleteModal = (event, docs: MineDocument[]) => { + event.preventDefault(); + openModal({ + props: { + title: `Delete ${docs?.length > 1 ? "Multiple Files" : "File"}`, + closeModal: closeModal, + handleSubmit: async () => { + docs.forEach((record) => removeDocument(event, record.key, documentParent)); + }, + docs, + }, + content: modalConfig.DELETE_DOCUMENT, + }); + }; + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const actions = [ + { + key: "view", + label: FileOperations.View, + icon: , + clickFunction: (_event, record: MineDocument) => + openDocument(record.document_manager_guid, record.mine_document_guid), + }, + { + key: "download", + label: FileOperations.Download, + icon: , + clickFunction: (_event, record: MineDocument) => { + return downloadFileFromDocumentManager(record); + }, + }, + { + key: "replace", + label: FileOperations.Replace, + icon: , + clickFunction: (_event, _record: MineDocument) => alert("Not implemented"), + }, + { + key: "archive", + label: FileOperations.Archive, + icon: , + clickFunction: (event, record: MineDocument) => openArchiveModal(event, [record]), + }, + { + key: "delete", + label: FileOperations.Delete, + icon: , + // PopConfirm does not work in either the function or label field here + clickFunction: (event, record: MineDocument) => openDeleteModal(event, [record]), + }, + ].filter((action) => allowedTableActions[action.label]); + + const filterActions = (record: MineDocument, tableActions: ITableAction[]) => { + const allowedDocumentActions: string[] = record.allowed_actions; + return tableActions.filter((action) => allowedDocumentActions.includes(action.label)); + }; + + // document tables that don't yet have MineRecord, actions, archive, versions functionality + const oldGetColumns = () => { + let columns = documentColumns ?? [ + documentNameColumn("document_name", "File Name", isMinimalView), + renderTextColumn("category", "Category", !isMinimalView), + uploadDateColumn("upload_date", "Uploaded", !isMinimalView), + uploadDateColumn("dated", "Dated", !isMinimalView), + ]; + if (actions.length > 0 && !columns.some((column) => column.key === "actions")) { + columns.push(renderActionsColumn(actions, filterActions)); + } + if (!some(documents, "dated")) { + columns = columns.filter((column) => column.key !== "dated"); + } + + if (excludedColumnKeys?.length > 0) { + columns = columns.filter((column) => !excludedColumnKeys.includes(column.key.toString())); + } + return columns; + }; + + const newGetColumns = (): ColumnsType => { + const columns: ColumnsType = [ + documentNameColumnNew(), + ...additionalColumns, + renderTextColumn("file_type", "File Type", !isMinimalView), + uploadDateColumn("update_timestamp", "Last Modified"), + uploadedByColumn("create_user", "Created By"), + ]; + if (actions.length) { + columns.push(renderActionsColumn(actions, filterActions)); + } + return columns; + }; + + let columns: ColumnsType = showVersionHistory ? newGetColumns() : oldGetColumns(); + + if (additionalColumnProps?.length > 0) { + additionalColumnProps.forEach((addColumn) => { + const columnIndex = columns.findIndex((column) => addColumn?.key === column.key); + if (columnIndex >= 0) { + columns[columnIndex] = { ...columns[columnIndex], ...addColumn?.colProps }; + } + }); + } + + if (defaultSortKeys.length > 0) { + columns = columns.map((column) => { + const isDefaultSort = defaultSortKeys.includes(column.key.toString()); + return isDefaultSort ? { defaultSortOrder: "descend", ...column } : column; + }); + } + + const minimalProps = isMinimalView + ? { size: "small" as SizeType, rowClassName: "ant-table-row-minimal" } + : null; + return showVersionHistory ? ( + record.number_prev_versions > 0, + }} + /> + ) : ( + + ); +}; + +const mapStateToProps = (state) => ({ + userRoles: getUserAccessData(state), +}); + +// eslint-disable-next-line @typescript-eslint/no-shadow +const mapDispatchToProps = (dispatch) => + bindActionCreators( + { + openModal, + closeModal, + archiveMineDocuments, + openDocument, + }, + dispatch + ); + +export default connect(mapStateToProps, mapDispatchToProps)(DocumentTable); diff --git a/services/core-web/src/components/mine/Projects/MajorMineApplicationTab.js b/services/core-web/src/components/mine/Projects/MajorMineApplicationTab.js index 737cb62af1..1c16f7c3a7 100644 --- a/services/core-web/src/components/mine/Projects/MajorMineApplicationTab.js +++ b/services/core-web/src/components/mine/Projects/MajorMineApplicationTab.js @@ -22,6 +22,8 @@ import { fetchMineDocuments } from "@common/actionCreators/mineActionCreator"; import { getMineDocuments } from "@common/selectors/mineSelectors"; import ArchivedDocumentsSection from "@common/components/documents/ArchivedDocumentsSection"; import { Feature, isFeatureEnabled } from "@mds/common"; +import { renderCategoryColumn } from "@/components/common/CoreTableCommonColumns"; +import { MajorMineApplicationDocument } from "@common/models/documents/document"; const propTypes = { project: CustomPropTypes.project.isRequired, @@ -136,31 +138,20 @@ export class MajorMineApplicationTab extends Component {
{titleElement} [ - { - key: doc.mine_document_guid, - mine_document_guid: doc.mine_document_guid, - document_manager_guid: doc.document_manager_guid, - document_name: doc.document_name, - category: null, - upload_date: doc.upload_date, - update_timestamp: doc.update_timestamp, - create_user: doc.create_user, - major_mine_application_document_type: doc.major_mine_application_document_type_code, - versions: doc.versions, - }, - ...docs, - ], - [] - )} + documents={sectionDocuments} canArchiveDocuments={true} - archiveDocumentsArgs={{ mineGuid: this.props.project?.mine_guid }} onArchivedDocuments={() => this.fetchData()} - excludedColumnKeys={["dated", "category", "remove"]} - additionalColumnProps={[{ key: "name", colProps: { width: "80%" } }]} + additionalColumnProps={[{ key: "document_name", colProps: { width: "80%" } }]} + additionalColumns={[ + renderCategoryColumn( + "major_mine_application_document_type_code", + "File Location", + Strings.MAJOR_MINES_APPLICATION_DOCUMENT_TYPE_CODE_LOCATION, + true + ), + ]} isLoaded={this.state.isLoaded} - matchChildColumnsToParent={true} + showVersionHistory={true} />
); @@ -179,6 +170,10 @@ export class MajorMineApplicationTab extends Component { const primaryContact = contacts?.find((c) => c.is_primary) || {}; + let documents = this.props.project.major_mine_application.documents; + + documents = documents.map((doc) => new MajorMineApplicationDocument(doc)); + return ( <>
@@ -265,27 +260,24 @@ export class MajorMineApplicationTab extends Component { {this.renderDocumentSection( "Primary Documents", "primary-documents", - this.props.project.major_mine_application?.documents?.filter( - (doc) => doc.major_mine_application_document_type_code === "PRM" - ) || [], + documents.filter((doc) => doc.major_mine_application_document_type_code === "PRM") || + [], true )}
{this.renderDocumentSection( "Spatial Components", "spatial-components", - this.props.project.major_mine_application?.documents?.filter( - (doc) => doc.major_mine_application_document_type_code === "SPT" - ) || [], + documents.filter((doc) => doc.major_mine_application_document_type_code === "SPT") || + [], true )}
{this.renderDocumentSection( "Supporting Documents", "supporting-documents", - this.props.project.major_mine_application?.documents?.filter( - (doc) => doc.major_mine_application_document_type_code === "SPR" - ) || [], + documents.filter((doc) => doc.major_mine_application_document_type_code === "SPR") || + [], true )}
diff --git a/services/core-web/src/components/mine/Projects/ProjectDocumentsTab.js b/services/core-web/src/components/mine/Projects/ProjectDocumentsTab.js index 7dbd91f634..23e5d20666 100644 --- a/services/core-web/src/components/mine/Projects/ProjectDocumentsTab.js +++ b/services/core-web/src/components/mine/Projects/ProjectDocumentsTab.js @@ -19,6 +19,7 @@ import { fetchMineDocuments } from "@common/actionCreators/mineActionCreator"; import { getMineDocuments } from "@common/selectors/mineSelectors"; import ArchivedDocumentsSection from "@common/components/documents/ArchivedDocumentsSection"; import { Feature, isFeatureEnabled } from "@mds/common"; +import { MajorMineApplicationDocument } from "@common/models/documents/document"; const propTypes = { match: PropTypes.shape({ @@ -37,10 +38,14 @@ const propTypes = { export class ProjectDocumentsTab extends Component { state = { fixedTop: false, + isLoaded: false, }; componentDidMount() { - this.handleFetchData(); + this.handleFetchData().then(() => { + this.setState({ isLoaded: true }); + }); + window.addEventListener("scroll", this.handleScroll); this.handleScroll(); } @@ -133,23 +138,14 @@ export class ProjectDocumentsTab extends Component {
{titleElement} [ - { - ...doc, - key: doc.mine_document_guid, - }, - ...docs, - ], - [] - )} - documentParent={documentParent} - removeDocument={this.handleDeleteDocument} + documents={sectionDocuments.map((doc) => new MajorMineApplicationDocument(doc))} canArchiveDocuments={true} onArchivedDocuments={() => this.handleFetchData()} - archiveDocumentsArgs={{ mineGuid: this.props?.project?.mine_guid }} - excludedColumnKeys={["dated", "category"]} additionalColumnProps={[{ key: "name", colProps: { width: "80%" } }]} + documentParent={documentParent} + removeDocument={this.handleDeleteDocument} + showVersionHistory={true} + isLoaded={this.state.isLoaded} />
); diff --git a/services/core-web/src/components/modalContent/NoticeOfDepartureModal.tsx b/services/core-web/src/components/modalContent/NoticeOfDepartureModal.tsx index ec2f85eb95..fd2750e232 100644 --- a/services/core-web/src/components/modalContent/NoticeOfDepartureModal.tsx +++ b/services/core-web/src/components/modalContent/NoticeOfDepartureModal.tsx @@ -36,11 +36,7 @@ import { } from "@mds/common"; import { getUserAccessData } from "@common/selectors/authenticationSelectors"; import CoreTable from "@/components/common/CoreTable"; -import { - renderDateColumn, - renderDocumentLinkColumn, - renderTextColumn, -} from "../common/CoreTableCommonColumns"; +import { renderDateColumn, renderTextColumn } from "../common/CoreTableCommonColumns"; import * as FORM from "@/constants/forms"; import { TRASHCAN } from "@/constants/assets"; import { NOTICE_OF_DEPARTURE_DOCUMENTS } from "@/constants/API"; @@ -48,6 +44,7 @@ import { renderConfig } from "@/components/common/config"; import FileUpload from "@/components/common/FileUpload"; import { DOCUMENT, EXCEL } from "@/constants/fileTypes"; import * as Permission from "@/constants/permissions"; +import { renderDocumentLinkColumn } from "../common/DocumentColumns"; interface renderContactsProps { fields: INoDContactInterface[]; @@ -231,7 +228,7 @@ const NoticeOfDepartureModal: React.FC & const fileColumns = (isSortable: boolean) => { return [ - renderDocumentLinkColumn("document_name", "File Name", isSortable), + renderDocumentLinkColumn("document_name", "File Name", isSortable, false), renderTextColumn("document_category", "Category", isSortable, EMPTY_FIELD), renderDateColumn("create_timestamp", "Uploaded", isSortable, null, EMPTY_FIELD), ...(disabled diff --git a/services/core-web/src/components/modalContent/config.js b/services/core-web/src/components/modalContent/config.js index 3ac5af07db..28f1b3976f 100644 --- a/services/core-web/src/components/modalContent/config.js +++ b/services/core-web/src/components/modalContent/config.js @@ -57,6 +57,7 @@ import UpdateProjectDecisionPackageDocumentModal from "./UpdateProjectDecisionPa import AddMineAlertModal from "./AddMineAlertModal"; import ViewPastMineAlertModal from "./ViewPastMineAlertModal"; import ArchiveDocumentModal from "@common/components/documents/ArchiveDocumentModal"; +import DeleteDocumentModal from "@common/components/documents/DeleteDocumentModal"; export const modalConfig = { MINE_RECORD: MineRecordModal, @@ -65,6 +66,7 @@ export const modalConfig = { ADD_QUICK_PARTY: AddQuickPartyModal, MERGE_PARTY_CONFIRMATION: MergePartyConfirmationModal, ARCHIVE_DOCUMENT: ArchiveDocumentModal, + DELETE_DOCUMENT: DeleteDocumentModal, EDIT_PARTY_RELATIONSHIP: EditPartyRelationshipModal, ADD_CONTACT: AddPartyModal, ADD_PERMIT: AddPermitModal, diff --git a/services/core-web/src/tests/components/common/__snapshots__/CoreTable.spec.js.snap b/services/core-web/src/tests/components/common/__snapshots__/CoreTable.spec.js.snap index 595c04c2cb..4bb1382f66 100644 --- a/services/core-web/src/tests/components/common/__snapshots__/CoreTable.spec.js.snap +++ b/services/core-web/src/tests/components/common/__snapshots__/CoreTable.spec.js.snap @@ -5,7 +5,11 @@ exports[`CoreTable renders properly 1`] = ` className=" core-table" columns={Array []} dataSource={Object {}} - expandable={null} + expandable={ + Object { + "showExpandColumn": false, + } + } locale={ Object { "emptyText": "No Data Yet", diff --git a/services/core-web/src/tests/components/common/__snapshots__/DocumentTable.spec.js.snap b/services/core-web/src/tests/components/common/__snapshots__/DocumentTable.spec.js.snap index b09665b8a2..4e60db2ddd 100644 --- a/services/core-web/src/tests/components/common/__snapshots__/DocumentTable.spec.js.snap +++ b/services/core-web/src/tests/components/common/__snapshots__/DocumentTable.spec.js.snap @@ -26,6 +26,10 @@ exports[`DocumentTable renders properly 1`] = ` "sorter": [Function], "title": "Uploaded", }, + Object { + "key": "actions", + "render": [Function], + }, ] } dataSource={Array []} diff --git a/services/core-web/src/tests/components/mine/Projects/__snapshots__/MajorMineApplicationTab.spec.js.snap b/services/core-web/src/tests/components/mine/Projects/__snapshots__/MajorMineApplicationTab.spec.js.snap index fc3a74f4d4..ef913432cb 100644 --- a/services/core-web/src/tests/components/mine/Projects/__snapshots__/MajorMineApplicationTab.spec.js.snap +++ b/services/core-web/src/tests/components/mine/Projects/__snapshots__/MajorMineApplicationTab.spec.js.snap @@ -222,27 +222,26 @@ exports[`ProjectDocumentsTab renders properly 1`] = ` "colProps": Object { "width": "80%", }, - "key": "name", + "key": "document_name", }, ] } - archiveDocumentsArgs={ - Object { - "mineGuid": "40fb0ca4-4dfb-4660-a184-6d031a21f3e9", - } - } - canArchiveDocuments={true} - documents={Array []} - excludedColumnKeys={ + additionalColumns={ Array [ - "dated", - "category", - "remove", + Object { + "dataIndex": "major_mine_application_document_type_code", + "key": "major_mine_application_document_type_code", + "render": [Function], + "sorter": [Function], + "title": "File Location", + }, ] } + canArchiveDocuments={true} + documents={Array []} isLoaded={false} - matchChildColumnsToParent={true} onArchivedDocuments={[Function]} + showVersionHistory={true} />

@@ -267,27 +266,26 @@ exports[`ProjectDocumentsTab renders properly 1`] = ` "colProps": Object { "width": "80%", }, - "key": "name", + "key": "document_name", }, ] } - archiveDocumentsArgs={ - Object { - "mineGuid": "40fb0ca4-4dfb-4660-a184-6d031a21f3e9", - } - } - canArchiveDocuments={true} - documents={Array []} - excludedColumnKeys={ + additionalColumns={ Array [ - "dated", - "category", - "remove", + Object { + "dataIndex": "major_mine_application_document_type_code", + "key": "major_mine_application_document_type_code", + "render": [Function], + "sorter": [Function], + "title": "File Location", + }, ] } + canArchiveDocuments={true} + documents={Array []} isLoaded={false} - matchChildColumnsToParent={true} onArchivedDocuments={[Function]} + showVersionHistory={true} />
@@ -312,27 +310,26 @@ exports[`ProjectDocumentsTab renders properly 1`] = ` "colProps": Object { "width": "80%", }, - "key": "name", + "key": "document_name", }, ] } - archiveDocumentsArgs={ - Object { - "mineGuid": "40fb0ca4-4dfb-4660-a184-6d031a21f3e9", - } - } - canArchiveDocuments={true} - documents={Array []} - excludedColumnKeys={ + additionalColumns={ Array [ - "dated", - "category", - "remove", + Object { + "dataIndex": "major_mine_application_document_type_code", + "key": "major_mine_application_document_type_code", + "render": [Function], + "sorter": [Function], + "title": "File Location", + }, ] } + canArchiveDocuments={true} + documents={Array []} isLoaded={false} - matchChildColumnsToParent={true} onArchivedDocuments={[Function]} + showVersionHistory={true} />
@@ -357,27 +354,26 @@ exports[`ProjectDocumentsTab renders properly 1`] = ` "colProps": Object { "width": "80%", }, - "key": "name", + "key": "document_name", }, ] } - archiveDocumentsArgs={ - Object { - "mineGuid": "40fb0ca4-4dfb-4660-a184-6d031a21f3e9", - } - } - canArchiveDocuments={true} - documents={Array []} - excludedColumnKeys={ + additionalColumns={ Array [ - "dated", - "category", - "remove", + Object { + "dataIndex": "major_mine_application_document_type_code", + "key": "major_mine_application_document_type_code", + "render": [Function], + "sorter": [Function], + "title": "File Location", + }, ] } + canArchiveDocuments={true} + documents={Array []} isLoaded={false} - matchChildColumnsToParent={true} onArchivedDocuments={[Function]} + showVersionHistory={true} />
diff --git a/services/core-web/src/tests/components/mine/Projects/__snapshots__/ProjectDocumentsTab.spec.js.snap b/services/core-web/src/tests/components/mine/Projects/__snapshots__/ProjectDocumentsTab.spec.js.snap index 5c06ae0b1c..0d71ef2175 100644 --- a/services/core-web/src/tests/components/mine/Projects/__snapshots__/ProjectDocumentsTab.spec.js.snap +++ b/services/core-web/src/tests/components/mine/Projects/__snapshots__/ProjectDocumentsTab.spec.js.snap @@ -68,22 +68,13 @@ exports[`ProjectDocumentsTab renders properly 1`] = ` }, ] } - archiveDocumentsArgs={ - Object { - "mineGuid": "40fb0ca4-4dfb-4660-a184-6d031a21f3e9", - } - } canArchiveDocuments={true} documentParent="project summary" documents={Array []} - excludedColumnKeys={ - Array [ - "dated", - "category", - ] - } + isLoaded={false} onArchivedDocuments={[Function]} removeDocument={[Function]} + showVersionHistory={true} />
@@ -106,22 +97,13 @@ exports[`ProjectDocumentsTab renders properly 1`] = ` }, ] } - archiveDocumentsArgs={ - Object { - "mineGuid": "40fb0ca4-4dfb-4660-a184-6d031a21f3e9", - } - } canArchiveDocuments={true} documentParent="irt" documents={Array []} - excludedColumnKeys={ - Array [ - "dated", - "category", - ] - } + isLoaded={false} onArchivedDocuments={[Function]} removeDocument={[Function]} + showVersionHistory={true} />
@@ -156,22 +138,13 @@ exports[`ProjectDocumentsTab renders properly 1`] = ` }, ] } - archiveDocumentsArgs={ - Object { - "mineGuid": "40fb0ca4-4dfb-4660-a184-6d031a21f3e9", - } - } canArchiveDocuments={true} documentParent="major mine application" documents={Array []} - excludedColumnKeys={ - Array [ - "dated", - "category", - ] - } + isLoaded={false} onArchivedDocuments={[Function]} removeDocument={[Function]} + showVersionHistory={true} />
@@ -200,22 +173,13 @@ exports[`ProjectDocumentsTab renders properly 1`] = ` }, ] } - archiveDocumentsArgs={ - Object { - "mineGuid": "40fb0ca4-4dfb-4660-a184-6d031a21f3e9", - } - } canArchiveDocuments={true} documentParent="major mine application" documents={Array []} - excludedColumnKeys={ - Array [ - "dated", - "category", - ] - } + isLoaded={false} onArchivedDocuments={[Function]} removeDocument={[Function]} + showVersionHistory={true} />
@@ -244,22 +208,13 @@ exports[`ProjectDocumentsTab renders properly 1`] = ` }, ] } - archiveDocumentsArgs={ - Object { - "mineGuid": "40fb0ca4-4dfb-4660-a184-6d031a21f3e9", - } - } canArchiveDocuments={true} documentParent="major mine application" documents={Array []} - excludedColumnKeys={ - Array [ - "dated", - "category", - ] - } + isLoaded={false} onArchivedDocuments={[Function]} removeDocument={[Function]} + showVersionHistory={true} />
diff --git a/services/core-web/src/tests/models/document.spec.ts b/services/core-web/src/tests/models/document.spec.ts new file mode 100644 index 0000000000..425b0a70af --- /dev/null +++ b/services/core-web/src/tests/models/document.spec.ts @@ -0,0 +1,128 @@ +import { USER_ROLES } from "@common/constants/environment"; +import { + FileOperations, + MajorMineApplicationDocument, + MineDocument, +} from "@common/models/documents/document"; + +// Document model testing + +const mockDocumentData = { + archived_by: null, + archived_date: null, + create_user: "user@bceid", + document_manager_guid: "74c966b8-7823-48ab-af8e-cc653c0a8965", + document_name: "pdf1.PDF", + is_archived: null, + major_mine_application_document_type_code: "PRM", + major_mine_application_id: 1, + mine_document_guid: "cb4194c0-0d5e-4288-9f32-efebb4707ee7", + mine_guid: "0d11d34b-a6b1-4d01-a146-fd11970e06e9", + update_timestamp: "2023-07-10 19:12:48.529889+00:00", + upload_date: "2023-07-10 19:12:48.529892+00:00", + versions: [ + { + create_user: "user@bceid", + document_manager_guid: "74c966b8-7823-48ab-af8e-cc653c0a8965", + document_manager_version_guid: "8bfeb983-a9cd-4f14-af29-f85ee83e1630", + document_name: "pdf1.PDF", + mine_document_guid: "cb4194c0-0d5e-4288-9f32-efebb4707ee7", + mine_document_version_guid: "427d1539-0d49-4c8f-b9c4-33bc9e5a6e67", + mine_guid: "0d11d34b-a6b1-4d01-a146-fd11970e06e9", + update_timestamp: "2023-07-10T19:12:48.529889+00:00", + upload_date: "2023-07-10 19:12:48.529892+00:00", + }, + { + create_user: "test2@idir", + document_manager_guid: "74c966b8-7823-48ab-af8e-cc653c0a8965", + document_manager_version_guid: "132de7e5-b97c-4f97-b019-81c42edc4fb9", + document_name: "pdf1.PDF", + mine_document_guid: "cb4194c0-0d5e-4288-9f32-efebb4707ee7", + mine_document_version_guid: "9db600e2-be47-4c59-bff7-999fde20365e", + mine_guid: "0d11d34b-a6b1-4d01-a146-fd11970e06e9", + update_timestamp: "2023-07-10T19:12:48.529889+00:00", + upload_date: "2023-07-10 19:12:48.529892+00:00", + }, + ], +}; + +describe("MineDocument model", () => { + it("Base document model versions", () => { + const mineDocumentRecord = new MineDocument(mockDocumentData); + + const expectedNumPreviousVersions = mockDocumentData.versions.length - 1; + expect(mineDocumentRecord.number_prev_versions).toEqual(expectedNumPreviousVersions); + expect(mineDocumentRecord.versions.length).toEqual(expectedNumPreviousVersions); + expect(typeof mineDocumentRecord).toEqual(typeof mineDocumentRecord.versions[0]); + + const latestVersionKey = mineDocumentRecord.key; + const childKeys = mineDocumentRecord.versions.map((version) => version.key); + expect(childKeys).not.toContain(latestVersionKey); + }); + it("Base document model with no previous versions", () => { + const docData = { ...mockDocumentData }; + delete docData.versions; + + const mineDocumentRecord = new MineDocument(docData); + expect(mineDocumentRecord.number_prev_versions).toEqual(0); + expect(mineDocumentRecord.is_latest_version).toEqual(true); + }); + it("Base document model with versions: methods & setters", () => { + const mineDocumentRecord = new MineDocument(mockDocumentData); + + expect(mineDocumentRecord.file_type).toEqual(".pdf"); + expect(mineDocumentRecord.is_latest_version).toEqual(true); + expect(mineDocumentRecord.is_archived).toEqual(false); + }); + it("Base document model permissions", () => { + const mineDocumentRecord = new MineDocument(mockDocumentData); + + const permissions = mineDocumentRecord.allowed_actions; + const expectedPermissions = [ + FileOperations.View, + FileOperations.Download, + FileOperations.Replace, + FileOperations.Archive, + FileOperations.Delete, + ]; + expect(permissions).toEqual(expectedPermissions); + + // previous versions can only be viewed or downloaded + const previousVersionPermissions = mineDocumentRecord.versions[0].allowed_actions; + const expectedPrevPermissions = [FileOperations.View, FileOperations.Download]; + expect(previousVersionPermissions).toEqual(expectedPrevPermissions); + }); +}); + +describe("MajorMineApplicationDocument model", () => { + it("MajorMineApplicationDocument versions", () => { + const appDocRecord = new MajorMineApplicationDocument(mockDocumentData); + const prevVersion = appDocRecord.versions[0]; + const docTypeCode = mockDocumentData.major_mine_application_document_type_code; + + expect(typeof appDocRecord).toEqual(typeof prevVersion); + expect(appDocRecord.major_mine_application_document_type_code).toEqual(docTypeCode); + expect(prevVersion.major_mine_application_document_type_code).toEqual(docTypeCode); + }); + + it("MajorMineApplicationDocument permissions", () => { + const appDocRecord = new MajorMineApplicationDocument(mockDocumentData); + + const canViewActions = [FileOperations.View, FileOperations.Download].sort(); + const canModifyActions = [ + FileOperations.Replace, + FileOperations.Archive, + FileOperations.Delete, + ...canViewActions, + ].sort(); + + appDocRecord.setAllowedActions([USER_ROLES.role_minespace_proponent]); + expect(canModifyActions).toEqual(appDocRecord.allowed_actions.sort()); + + appDocRecord.setAllowedActions([USER_ROLES.role_edit_major_mine_applications]); + expect(canModifyActions).toEqual(appDocRecord.allowed_actions.sort()); + + appDocRecord.setAllowedActions([]); + expect(canViewActions).toEqual(appDocRecord.allowed_actions.sort()); + }); +}); diff --git a/services/minespace-web/common/components/documents/ArchiveDocumentModal.tsx b/services/minespace-web/common/components/documents/ArchiveDocumentModal.tsx index 97448952be..0fe5b664c6 100644 --- a/services/minespace-web/common/components/documents/ArchiveDocumentModal.tsx +++ b/services/minespace-web/common/components/documents/ArchiveDocumentModal.tsx @@ -33,7 +33,7 @@ const ArchiveDocumentModal: FC = (props: ArchiveDocum documents={props.documents} view="minimal" uploadDateIndex="upload_date" - excludedColumnKeys={["archive", "remove", "category"]} + excludedColumnKeys={["actions", "category"]} />
diff --git a/services/minespace-web/common/components/documents/ArchivedDocumentsSection.tsx b/services/minespace-web/common/components/documents/ArchivedDocumentsSection.tsx index 6c6227500a..11d9e4db7c 100644 --- a/services/minespace-web/common/components/documents/ArchivedDocumentsSection.tsx +++ b/services/minespace-web/common/components/documents/ArchivedDocumentsSection.tsx @@ -1,12 +1,12 @@ import React from "react"; import DocumentTable from "@/components/common/DocumentTable"; import { Typography } from "antd"; -import { IMineDocument } from "@mds/common"; import { DeleteOutlined } from "@ant-design/icons"; import { Feature, isFeatureEnabled } from "@mds/common"; +import { MineDocument } from "@common/models/documents/document"; interface ArchivedDocumentsSectionProps { - documents: IMineDocument; + documents: MineDocument[]; documentColumns: any; titleLevel?: 1 | 2 | 3 | 4 | 5; } @@ -16,12 +16,6 @@ const ArchivedDocumentsSection = (props: ArchivedDocumentsSectionProps) => { return <>; } - const docs = props.documents.map((d) => { - d.name = d.document_name; - - return d; - }); - return (
@@ -33,7 +27,7 @@ const ArchivedDocumentsSection = (props: ArchivedDocumentsSectionProps) => {
diff --git a/services/minespace-web/common/components/documents/DeleteDocumentModal.tsx b/services/minespace-web/common/components/documents/DeleteDocumentModal.tsx new file mode 100644 index 0000000000..1d56497316 --- /dev/null +++ b/services/minespace-web/common/components/documents/DeleteDocumentModal.tsx @@ -0,0 +1,51 @@ +import React, { FC } from "react"; + +import DocumentTable from "@/components/common/DocumentTable"; +import { Alert, Button, Form, Typography } from "antd"; +import { MineDocument } from "@common/models/documents/document"; + +interface DeleteDocumentModalProps { + documents: MineDocument[]; + handleSubmit(documents: MineDocument[]): Promise; + closeModal(): void; +} + +const DeleteDocumentModal: FC = (props: DeleteDocumentModalProps) => { + return ( +
props.handleSubmit(props.documents).then(props.closeModal)} + > + + + + + + You're about to delete the following file{props.documents?.length > 1 ? "s" : ""}: + + + + +
+ + +
+ + ); +}; + +export default DeleteDocumentModal; diff --git a/services/minespace-web/common/models/documents/document.ts b/services/minespace-web/common/models/documents/document.ts new file mode 100644 index 0000000000..9927d81e8c --- /dev/null +++ b/services/minespace-web/common/models/documents/document.ts @@ -0,0 +1,153 @@ +import { USER_ROLES } from "@mds/common"; + +export enum FileOperations { + View = "Open in document viewer", + Download = "Download file", + Replace = "Replace file", + Archive = "Archive file", + Delete = "Delete", +} + +/* +A base class for Mine Documents + +There is an issue with antd where sorting a table that has children (ie matchChildColumnsToParent) +will transform the records into type (with versions still maintaining their type) so properties accessed +by the table should be *set* to the specific object, cannot expect to be able to consistently call its methods + +include "user_roles" property in the json used in the constructor to set allowed actions based on the user +*/ +export class MineDocument { + public mine_document_guid: string; + + public mine_guid: string; + + public document_manager_guid: string; + + public document_name: string; + + public create_user: string; + + public update_user: string; + + public upload_date: string; + + public update_timestamp: string; + + public is_archived: boolean; + + public archived_by: string; + + public archived_date: string; + + public is_latest_version: boolean; + + public category?: string; + + // generated + public key: string; + + public file_type: string; + + public number_prev_versions: number; + + public versions: MineDocument[]; // all previous file versions, not including latest + + public allowed_actions: FileOperations[]; + + constructor(jsonObject: any) { + this.mine_document_guid = jsonObject.mine_document_guid; + this.mine_guid = jsonObject.mine_guid; + this.document_manager_guid = jsonObject.document_manager_guid; + this.document_name = jsonObject.document_name; + this.create_user = jsonObject.create_user; + this.update_user = jsonObject.update_user; + this.upload_date = jsonObject.upload_date; + this.update_timestamp = jsonObject.update_timestamp; + this.category = jsonObject.category; + this.is_archived = jsonObject.is_archived ?? false; + this.archived_by = jsonObject.archived_by; + this.archived_date = jsonObject.archived_date; + this.is_latest_version = jsonObject.is_latest_version ?? true; + this.setCalculatedProperties(jsonObject); + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + protected makeChild(params: any, _constructorArgs: any) { + return new MineDocument(params); + } + + protected setCalculatedProperties(jsonObject: any) { + this.key = this.is_latest_version + ? this.mine_document_guid + : jsonObject.document_manager_version_guid; + this.file_type = this.getFileType(); + + const versions = jsonObject.versions ?? []; + if (this.is_latest_version && versions.length) { + this.number_prev_versions = versions.length - 1; + this.versions = versions + .slice(1) + .map((version) => this.makeChild({ ...version, is_latest_version: false }, jsonObject)); + } else { + this.number_prev_versions = 0; + this.versions = []; + } + this.setAllowedActions(jsonObject.user_roles); + } + + public getFileType() { + const index = this.document_name.lastIndexOf("."); + return index === -1 ? null : this.document_name.substring(index).toLocaleLowerCase(); + } + + public setAllowedActions(userRoles: string[] = []) { + this.allowed_actions = this.getAllowedActions(userRoles).filter(Boolean); + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + protected getAllowedActions(_userRoles: string[] = []) { + const canModify = this.is_latest_version && !this.is_archived; + return [ + this.file_type === ".pdf" && FileOperations.View, + FileOperations.Download, + canModify && FileOperations.Replace, + canModify && FileOperations.Archive, + canModify && FileOperations.Delete, + ]; + } +} + +export class MajorMineApplicationDocument extends MineDocument { + public major_mine_application_document_type_code: string; + + public versions: MajorMineApplicationDocument[]; + + constructor(jsonObject: any) { + super(jsonObject); + this.major_mine_application_document_type_code = + jsonObject.major_mine_application_document_type_code; + } + + protected makeChild(params: any, constructorArgs: any) { + return new MajorMineApplicationDocument({ + ...params, + major_mine_application_document_type_code: + constructorArgs.major_mine_application_document_type_code, + }); + } + + public getAllowedActions(userRoles: string[] = []) { + const allowedActions = super.getAllowedActions(); + + const canModifyRoles = [ + USER_ROLES.role_edit_major_mine_applications, + USER_ROLES.role_minespace_proponent, + ]; + const canModify = userRoles.some((role) => canModifyRoles.includes(role)); + + return allowedActions.filter( + (action) => canModify || [FileOperations.View, FileOperations.Download].includes(action) + ); + } +} diff --git a/services/minespace-web/src/components/Forms/incidents/IncidentForm.js b/services/minespace-web/src/components/Forms/incidents/IncidentForm.js index ecefdc007f..98b6b032e8 100644 --- a/services/minespace-web/src/components/Forms/incidents/IncidentForm.js +++ b/services/minespace-web/src/components/Forms/incidents/IncidentForm.js @@ -32,7 +32,11 @@ import { closeModal, openModal } from "@common/actions/modalActions"; import { INCIDENT_CONTACT_METHOD_OPTIONS } from "@mds/common"; import * as FORM from "@/constants/forms"; import DocumentTable from "@/components/common/DocumentTable"; -import { uploadDateColumn, uploadedByColumn } from "@/components/common/DocumentColumns"; +import { + documentNameColumn, + uploadDateColumn, + uploadedByColumn, +} from "@/components/common/DocumentColumns"; import { renderConfig } from "@/components/common/config"; import Callout from "@/components/common/Callout"; import customPropTypes from "@/customPropTypes"; @@ -78,10 +82,7 @@ const defaultProps = { export const INITIAL_INCIDENT_DOCUMENTS_FORM_FIELD = "initial_notification_documents"; export const FINAL_REPORT_DOCUMENTS_FORM_FIELD = "final_report_documents"; -const documentColumns = [ - uploadDateColumn("upload_date"), - uploadedByColumn("Uploaded By", "update_user"), -]; +const documentColumns = [documentNameColumn(), uploadDateColumn(), uploadedByColumn()]; const retrieveIncidentDetailsDynamicValidation = (childProps) => { const { formValues } = childProps; @@ -913,6 +914,7 @@ export const IncidentForm = (props) => { document_name: fileName, document_manager_guid, mine_incident_document_type_code: documentTypeCode, + mine_guid: formValues.mine_guid, }, ]; setUploadedFiles(updatedUploadedFiles); diff --git a/services/minespace-web/src/components/Forms/projects/informationRequirementsTable/IRTFileImport.js b/services/minespace-web/src/components/Forms/projects/informationRequirementsTable/IRTFileImport.js index a833a8f490..fcadb3fe39 100644 --- a/services/minespace-web/src/components/Forms/projects/informationRequirementsTable/IRTFileImport.js +++ b/services/minespace-web/src/components/Forms/projects/informationRequirementsTable/IRTFileImport.js @@ -21,10 +21,13 @@ import DocumentTable from "@/components/common/DocumentTable"; import customPropTypes from "@/customPropTypes"; import IRTFileUpload from "@/components/Forms/projects/informationRequirementsTable/IRTFileUpload"; import { - categoryColumn, - uploadDateTimeColumn, - importedByColumn, -} from "@/components/common/DocumentColumns"; + renderCategoryColumn, + renderDateColumn, + renderTextColumn, +} from "@/components/common/CoreTableCommonColumns"; +import { formatDateTime } from "@common/utils/helpers"; +import { documentNameColumn } from "@/components/common/DocumentColumns"; +import { MineDocument } from "@common/models/documents/document"; const propTypes = { change: PropTypes.func.isRequired, @@ -67,14 +70,24 @@ export class IRTFileImport extends Component { render() { const acceptFileTypeArray = Object.keys(this.acceptedFileTypesMap); + const documents = this.props.project?.information_requirements_table?.documents.map( + (doc) => + new MineDocument({ + ...doc, + category: doc.information_requirements_table_document_type_code, + }) + ); const documentColumns = [ - categoryColumn( - "information_requirements_table_document_type_code", + documentNameColumn(), + renderCategoryColumn( + "category", + "Category", this.props.informationRequirementsTableDocumentTypesHash ), - uploadDateTimeColumn("upload_date"), - importedByColumn("create_user"), + renderDateColumn("upload_date", "Date/Time", true, formatDateTime), + renderTextColumn("create_user", "Imported By"), ]; + return ( <> @@ -104,7 +117,7 @@ export class IRTFileImport extends Component { diff --git a/services/minespace-web/src/components/Forms/projects/majorMineApplication/MajorMineApplicationForm.js b/services/minespace-web/src/components/Forms/projects/majorMineApplication/MajorMineApplicationForm.js index eb58e1f1be..0934c0b084 100644 --- a/services/minespace-web/src/components/Forms/projects/majorMineApplication/MajorMineApplicationForm.js +++ b/services/minespace-web/src/components/Forms/projects/majorMineApplication/MajorMineApplicationForm.js @@ -18,7 +18,7 @@ import { DOCUMENT, MODERN_EXCEL, SPATIAL } from "@mds/common"; import * as routes from "@/constants/routes"; import * as FORM from "@/constants/forms"; import { renderConfig } from "@/components/common/config"; -import { uploadDateColumn } from "@/components/common/DocumentColumns"; +import { documentNameColumn, uploadDateColumn } from "@/components/common/DocumentColumns"; import DocumentTable from "@/components/common/DocumentTable"; import customPropTypes from "@/customPropTypes"; import MajorMineApplicationFileUpload from "@/components/Forms/projects/majorMineApplication/MajorMineApplicationFileUpload"; @@ -64,6 +64,7 @@ export class MajorMineApplicationForm extends Component { document_name: fileName, document_manager_guid, major_mine_application_document_type_code: documentTypeCode, + mine_guid: this.props.project?.mine_guid, }); return this.props.change( documentTypeField, @@ -104,7 +105,7 @@ export class MajorMineApplicationForm extends Component { }; render() { - const documentColumns = [uploadDateColumn("upload_date")]; + const documentColumns = [documentNameColumn(), uploadDateColumn()]; const primaryDocuments = this.uniqueDocs( this.props.primary_documents, this.props.project?.major_mine_application?.documents, @@ -120,6 +121,7 @@ export class MajorMineApplicationForm extends Component { this.props.project?.major_mine_application?.documents, "SPR" ); + return (
@@ -199,7 +201,6 @@ export class MajorMineApplicationForm extends Component { documentParent="Major Mine Application" canArchiveDocuments={true} onArchivedDocuments={() => this.props.refreshData()} - archiveDocumentsArgs={{ mineGuid: this.props.project?.mine_guid }} /> )} @@ -246,7 +247,6 @@ export class MajorMineApplicationForm extends Component { documentParent="Major Mine Application" canArchiveDocuments={true} onArchivedDocuments={() => this.props.refreshData()} - archiveDocumentsArgs={{ mineGuid: this.props.project?.mine_guid }} /> )}
@@ -300,7 +300,6 @@ export class MajorMineApplicationForm extends Component { documentColumns={documentColumns} canArchiveDocuments={true} onArchivedDocuments={() => this.props.refreshData()} - archiveDocumentsArgs={{ mineGuid: this.props.project?.mine_guid }} /> )} diff --git a/services/minespace-web/src/components/Forms/projects/majorMineApplication/MajorMineApplicationReviewSubmit.js b/services/minespace-web/src/components/Forms/projects/majorMineApplication/MajorMineApplicationReviewSubmit.js index fe83c03fa8..54cb9cb83d 100644 --- a/services/minespace-web/src/components/Forms/projects/majorMineApplication/MajorMineApplicationReviewSubmit.js +++ b/services/minespace-web/src/components/Forms/projects/majorMineApplication/MajorMineApplicationReviewSubmit.js @@ -10,12 +10,14 @@ import { fetchProjectById, } from "@common/actionCreators/projectActionCreator"; import DocumentTable from "@/components/common/DocumentTable"; -import { uploadDateColumn } from "@/components/common/DocumentColumns"; +import { documentNameColumn, uploadDateColumn } from "@/components/common/DocumentColumns"; import CustomPropTypes from "@/customPropTypes"; import MajorMineApplicationCallout from "@/components/Forms/projects/majorMineApplication/MajorMineApplicationCallout"; import { MAJOR_MINE_APPLICATION_SUBMISSION_STATUSES } from "@/components/pages/Project/MajorMineApplicationPage"; import ArchivedDocumentsSection from "@common/components/documents/ArchivedDocumentsSection"; import { getMineDocuments } from "@common/selectors/mineSelectors"; +import { MajorMineApplicationDocument } from "@common/models/documents/document"; +import { renderCategoryColumn } from "@/components/common/CoreTableCommonColumns"; const propTypes = { project: CustomPropTypes.project.isRequired, @@ -74,16 +76,30 @@ export const MajorMineApplicationReviewSubmit = (props) => { const primaryContact = contacts?.find((c) => c.is_primary) || {}; - const primaryDocuments = project.major_mine_application?.documents?.filter( + const documents = + project.major_mine_application?.documents?.map((doc) => { + return new MajorMineApplicationDocument(doc); + }) ?? []; + + const primaryDocuments = documents.filter( (d) => d.major_mine_application_document_type_code === "PRM" ); - const spatialDocuments = project.major_mine_application?.documents?.filter( + const spatialDocuments = documents.filter( (d) => d.major_mine_application_document_type_code === "SPT" ); - const supportDocuments = project.major_mine_application?.documents?.filter( + const supportDocuments = documents.filter( (d) => d.major_mine_application_document_type_code === "SPR" ); - const documentColumns = [uploadDateColumn("upload_date")]; + const documentColumns = [documentNameColumn(), uploadDateColumn()]; + + const additionalColumns = [ + renderCategoryColumn( + "major_mine_application_document_type_code", + "File Location", + Strings.MAJOR_MINES_APPLICATION_DOCUMENT_TYPE_CODE_LOCATION, + true + ), + ]; const columnStyleConfig = tabbedView ? { style: { maxWidth: "67%", margin: "0 auto" } } : {}; @@ -143,38 +159,38 @@ export const MajorMineApplicationReviewSubmit = (props) => { Primary Document props.refreshData()} - archiveDocumentsArgs={{ mineGuid: props.project?.mine_guid }} + showVersionHistory={true} + additionalColumns={additionalColumns} /> Spatial Components props.refreshData()} - archiveDocumentsArgs={{ mineGuid: props.project?.mine_guid }} + showVersionHistory={true} + additionalColumns={additionalColumns} /> Supporting Documents props.refreshData()} - archiveDocumentsArgs={{ mineGuid: props.project?.mine_guid }} + showVersionHistory={true} + additionalColumns={additionalColumns} /> new MineDocument({ ...doc, category: doc.project_summary_document_type_code }) + ) ?? []; const documentColumns = [ - categoryColumn( + documentNameColumn(), + renderCategoryColumn( "project_summary_document_type_code", + "Category", this.props.projectSummaryDocumentTypesHash ), - uploadDateColumn("upload_date"), + uploadDateColumn(), ]; return ( <> @@ -79,7 +87,7 @@ export class DocumentUpload extends Component { {this.props.isEditMode && ( diff --git a/services/minespace-web/src/components/common/CoreTable.tsx b/services/minespace-web/src/components/common/CoreTable.tsx index 71031d051d..0873b73e4c 100644 --- a/services/minespace-web/src/components/common/CoreTable.tsx +++ b/services/minespace-web/src/components/common/CoreTable.tsx @@ -1,19 +1,20 @@ import React from "react"; import { Table, TableProps, Tooltip } from "antd"; -import { ColumnType } from "antd/es/table"; +import { ColumnsType } from "antd/es/table"; import { MinusSquareFilled, PlusSquareFilled } from "@ant-design/icons"; import { ExpandableConfig } from "antd/lib/table/interface"; interface CoreTableExpandConfig extends ExpandableConfig { - getDataSource: (record: T) => any[]; + getDataSource?: (record: T) => any[]; rowKey?: string | ((record: any) => string); recordDescription?: string; - subTableColumns?: ColumnType[]; + subTableColumns?: ColumnsType; + matchChildColumnsToParent?: boolean; // and any other props from expandable https://4x.ant.design/components/table/#expandable } interface CoreTableProps extends TableProps { - columns: ColumnType[]; + columns: ColumnsType; dataSource: T[]; condition?: boolean; rowKey?: string | ((record: T) => string | number); // defaults to "key" @@ -93,26 +94,37 @@ const CoreTable = (props: CoreTableProps) => { ); }; - const expansionProps = expandProps - ? { - rowExpandable: - expandProps.rowExpandable ?? ((record) => expandProps.getDataSource(record).length > 0), - expandIcon: renderTableExpandIcon, - expandRowByClick: true, - expandedRowRender: expandProps.expandedRowRender ?? renderExpandedRow, - ...expandProps, - } - : null; + const getExpansionProps = () => { + if (expandProps) { + return expandProps.matchChildColumnsToParent + ? { expandIcon: renderTableExpandIcon, indentSize: 0, ...expandProps } + : { + rowExpandable: + expandProps.rowExpandable ?? + ((record) => expandProps.getDataSource(record).length > 0), + expandIcon: renderTableExpandIcon, + expandRowByClick: true, + expandedRowRender: expandProps.expandedRowRender ?? renderExpandedRow, + ...expandProps, + }; + } + return { showExpandColumn: false }; + }; + return condition ? (
) : ( => { +export interface ITableAction { + key: string; + label: string; + clickFunction: (event, record) => any; + icon?: ReactNode; +} + +export const renderActionsColumn = ( + actions: ITableAction[], + recordActionsFilter: (record, actions) => ITableAction[], + text = "Actions", + classPrefix = "", + dropdownAltText = "Menu" +) => { + const labelClass = classPrefix ? `${classPrefix}-dropdown-button` : "actions-dropdown-button"; + return { - title, - dataIndex, - key: dataIndex, - render: (text = "", record: any) => ( -
- -
- ), - ...(sortable ? { sorter: nullableStringSorter(dataIndex) } : null), + key: "actions", + render: (record) => { + const filteredActions = recordActionsFilter ? recordActionsFilter(record, actions) : actions; + const items = filteredActions.map((action) => { + return { + key: action.key, + icon: action.icon, + label: ( + + ), + }; + }); + + return ( +
+ + {/* // TODO: change button classname to something generic */} + + +
+ ); + }, }; }; diff --git a/services/minespace-web/src/components/common/DocumentColumns.js b/services/minespace-web/src/components/common/DocumentColumns.js deleted file mode 100644 index a18526477d..0000000000 --- a/services/minespace-web/src/components/common/DocumentColumns.js +++ /dev/null @@ -1,60 +0,0 @@ -import React from "react"; -import moment from "moment"; -import { dateSorter, formatDate, formatDateTime } from "@common/utils/helpers"; -import * as Strings from "@/constants/strings"; - -export const categoryColumn = ( - categoryDataIndex = "category", - documentCategoryOptionsHash = [] -) => { - return { - title: "Category", - key: categoryDataIndex, - dataIndex: categoryDataIndex, - render: (text) => ( -
{documentCategoryOptionsHash[text] || Strings.EMPTY_FIELD}
- ), - }; -}; - -export const uploadDateColumn = ( - uploadDateIndex = "upload_date", - title = "Uploaded", - sort = false -) => { - return { - title, - key: uploadDateIndex, - dataIndex: uploadDateIndex, - sorter: sort ? dateSorter(uploadDateIndex) : null, - render: (text) =>
{formatDate(text) || Strings.EMPTY_FIELD}
, - }; -}; - -export const uploadDateTimeColumn = (uploadDateIndex) => { - return { - title: "Date/Time", - key: uploadDateIndex, - dataIndex: uploadDateIndex, - sorter: (a, b) => (moment(a.uploadDateIndex) > moment(b.uploadDateIndex) ? -1 : 1), - render: (text) =>
{formatDateTime(text) || Strings.EMPTY_FIELD}
, - }; -}; - -export const importedByColumn = (importedByIndex) => { - return { - title: "Imported By", - key: importedByIndex, - dataIndex: importedByIndex, - render: (text) => (text ?
{text}
: null), - }; -}; - -export const uploadedByColumn = (title = "Uploaded By", uploadedByIndex = "update_user") => { - return { - title, - key: uploadedByIndex, - dataIndex: uploadedByIndex, - render: (text) => (text ?
{text}
: null), - }; -}; diff --git a/services/minespace-web/src/components/common/DocumentColumns.tsx b/services/minespace-web/src/components/common/DocumentColumns.tsx new file mode 100644 index 0000000000..e8b617a46e --- /dev/null +++ b/services/minespace-web/src/components/common/DocumentColumns.tsx @@ -0,0 +1,173 @@ +import React, { ReactNode } from "react"; +import * as Strings from "@common/constants/strings"; +import { Button, Popconfirm, Tag, Tooltip } from "antd"; +import { ColumnType } from "antd/lib/table"; +import { renderDateColumn, renderTextColumn } from "./CoreTableCommonColumns"; +import { MineDocument } from "@common/models/documents/document"; +import { nullableStringSorter } from "@common/utils/helpers"; +import { ClockCircleOutlined, DeleteOutlined } from "@ant-design/icons"; +import DocumentLink from "./DocumentLink"; +import { downloadFileFromDocumentManager } from "@common/utils/actionlessNetworkCalls"; + +const documentWithTag = ( + record: MineDocument, + elem: ReactNode, + title: string, + showVersions = true +) => { + return ( +
+ {elem} + + + {record.number_prev_versions > 0 ? ( + + + } className="file-version-amount"> + {record.number_prev_versions} + + + + ) : null} + {record.is_archived ? {"Archived"} : null} + +
+ ); +}; + +export const renderTaggedColumn = ( + dataIndex: string, + title: string, + sortable = false, + placeHolder = "" +) => { + return { + title, + dataIndex, + key: dataIndex, + render: (text: any, record: MineDocument) => { + const content = ( +
+ {text ?? placeHolder} +
+ ); + return documentWithTag(record, content, title); + }, + ...(sortable ? { sorter: nullableStringSorter(dataIndex) } : null), + }; +}; + +export const renderDocumentLinkColumn = ( + dataIndex: string, + title = "File Name", + sortable = true, + showVersions = true, + docManGuidIndex = "document_manager_guid" +): ColumnType => { + return { + title, + dataIndex, + key: dataIndex, + render: (text = "", record: MineDocument) => { + const link = ( +
+ +
+ ); + return documentWithTag(record, link, title, showVersions); + }, + ...(sortable ? { sorter: nullableStringSorter(dataIndex) } : null), + }; +}; + +export const documentNameColumn = ( + documentNameColumnIndex = "document_name", + title = "File Name", + minimalView = false +) => { + return minimalView + ? renderTaggedColumn(documentNameColumnIndex, title) + : renderDocumentLinkColumn(documentNameColumnIndex, title, true, false); +}; + +export const documentNameColumnNew = ( + dataIndex = "document_name", + title = "File Name", + sortable = true +) => { + return { + title, + dataIndex, + key: dataIndex, + render: (text: string, record: MineDocument) => { + const docLink = downloadFileFromDocumentManager(record)}>{text}; + return documentWithTag(record, docLink, "File Name"); + }, + ...(sortable ? { sorter: nullableStringSorter(dataIndex) } : null), + }; +}; + +export const uploadDateColumn = ( + uploadDateIndex = "upload_date", + title = "Uploaded", + sortable = true +) => { + return renderDateColumn(uploadDateIndex, title, sortable, null, Strings.EMPTY_FIELD); +}; + +export const uploadedByColumn = ( + uploadedByIndex = "update_user", + title = "Uploaded By", + sortable = true +) => { + return renderTextColumn(uploadedByIndex, title, sortable); +}; + +export const removeFunctionColumn = ( + removeFunction, + documentParent = "", + documentNameColumnIndex = "document_name" +) => { + return { + key: "remove", + render: (record) => ( +
+ removeFunction(event, record.key, documentParent)} + okText="Delete" + cancelText="Cancel" + > + + +
+ ), + }; +}; diff --git a/services/minespace-web/src/components/common/DocumentTable.js b/services/minespace-web/src/components/common/DocumentTable.js deleted file mode 100644 index 4ef75c9ceb..0000000000 --- a/services/minespace-web/src/components/common/DocumentTable.js +++ /dev/null @@ -1,206 +0,0 @@ -import React from "react"; -import PropTypes from "prop-types"; -import { truncateFilename } from "@common/utils/helpers"; -import { Button, Tag, Row } from "antd"; -import { bindActionCreators } from "redux"; -import { connect } from "react-redux"; -import { downloadFileFromDocumentManager } from "@common/utils/actionlessNetworkCalls"; -import CustomPropTypes from "@/customPropTypes"; -import LinkButton from "@/components/common/LinkButton"; -import DocumentLink from "@/components/common/DocumentLink"; -import CoreTable from "./CoreTable"; -import { categoryColumn, uploadDateColumn } from "./DocumentColumns"; -import { closeModal, openModal } from "@common/actions/modalActions"; -import { archiveMineDocuments } from "@common/actionCreators/mineActionCreator"; -import modalConfig from "../modalContent/config"; -import { Feature, isFeatureEnabled } from "@mds/common"; - -const propTypes = { - documents: PropTypes.arrayOf(CustomPropTypes.mineDocument), - documentParent: PropTypes.string.isRequired, - categoryDataIndex: PropTypes.string.isRequired, - uploadDateIndex: PropTypes.string.isRequired, - // eslint-disable-next-line react/no-unused-prop-types - documentCategoryOptionsHash: PropTypes.objectOf(PropTypes.string).isRequired, - // eslint-disable-next-line react/no-unused-prop-types - handleDeleteDocument: PropTypes.func, - // eslint-disable-next-line react/no-unused-prop-types - deletePayload: PropTypes.objectOf(PropTypes.string), - // eslint-disable-next-line react/no-unused-prop-types - documentColumns: PropTypes.arrayOf(PropTypes.string), - deletePermission: PropTypes.string, - view: PropTypes.string, - excludedColumnKeys: PropTypes.arrayOf(PropTypes.string), - archiveMineDocuments: PropTypes.func, - archiveDocumentsArgs: PropTypes.shape({ - mineGuid: PropTypes.string, - }), - onArchivedDocuments: PropTypes.func, - canArchiveDocuments: PropTypes.bool, -}; - -const defaultProps = { - documents: [], - handleDeleteDocument: () => {}, - deletePayload: {}, - documentColumns: [], - deletePermission: null, - canArchiveDocuments: false, -}; - -const deleteEnabledDocumentParents = [ - "Major Mine Application", - "Information Requirements Table", - "Project Description", - "Mine Incident", -]; - -const openArchiveModal = (event, props, documents) => { - event.preventDefault(); - - props.openModal({ - props: { - title: `Archive ${documents?.length > 1 ? "Multiple Files" : "File"}`, - closeModal: props.closeModal, - handleSubmit: async () => { - await props.archiveMineDocuments( - props.archiveDocumentsArgs.mineGuid, - documents.map((d) => d.mine_document_guid) - ); - if (props.onArchivedDocuments) { - props.onArchivedDocuments(documents); - } - }, - documents, - }, - content: modalConfig.ARCHIVE_DOCUMENT, - }); -}; - -const withTag = (text, elem) => { - return ( - - {elem} - - {text} - - ); -}; - -export const DocumentTable = (props) => { - let columns = [ - { - title: "File Name", - key: "document_name", - dataIndex: "document_name", - render: (text, record) => { - const fileName = ( -
- downloadFileFromDocumentManager(record)}> - {truncateFilename(text)} - -
- ); - - return record.is_archived ? withTag("Archived", fileName) : fileName; - }, - }, - ]; - - const catColumn = categoryColumn(props.categoryDataIndex, props.documentCategoryOptionsHash); - const uploadedDateColumn = uploadDateColumn(props.uploadDateIndex); - - const canDeleteDocuments = - props?.deletePayload && - props?.handleDeleteDocument && - props?.deletePermission && - deleteEnabledDocumentParents.includes(props.documentParent); - - if (canDeleteDocuments) { - columns[0] = { - title: "File Name", - key: "document_name", - dataIndex: "document_name", - render: (text, record) => { - const { mine_document_guid } = record; - const payload = { ...props.deletePayload, mineDocumentGuid: mine_document_guid }; - return ( -
- -
- ); - }, - sorter: (a, b) => (a.document_name > b.document_name ? -1 : 1), - }; - } - - const archiveColumn = { - key: "archive", - className: props.isViewOnly || !props.canArchiveDocuments ? "column-hide" : "", - render: (record) => ( -
- -
- ), - }; - - if (props.documentColumns?.length > 0) { - columns.push(...props.documentColumns); - } else { - columns.push(catColumn); - columns.push(uploadedDateColumn); - } - - if (props.canArchiveDocuments && isFeatureEnabled(Feature.MAJOR_PROJECT_ARCHIVE_FILE)) { - columns.push(archiveColumn); - } - - if (props?.excludedColumnKeys?.length) { - columns = columns.filter((column) => !props.excludedColumnKeys.includes(column.key)); - } - - return ( -
- record.mine_document_guid} - emptyText={`This ${props.documentParent} does not contain any documents.`} - dataSource={props.documents} - /> -
- ); -}; - -DocumentTable.propTypes = propTypes; -DocumentTable.defaultProps = defaultProps; - -const mapDispatchToProps = (dispatch) => - bindActionCreators( - { - openModal, - closeModal, - archiveMineDocuments, - }, - dispatch - ); - -export default connect(null, mapDispatchToProps)(DocumentTable); diff --git a/services/minespace-web/src/components/common/DocumentTable.tsx b/services/minespace-web/src/components/common/DocumentTable.tsx new file mode 100644 index 0000000000..cf1b08e80c --- /dev/null +++ b/services/minespace-web/src/components/common/DocumentTable.tsx @@ -0,0 +1,269 @@ +import React from "react"; +import CoreTable from "@/components/common/CoreTable"; +import { + documentNameColumn, + documentNameColumnNew, + uploadDateColumn, + uploadedByColumn, +} from "./DocumentColumns"; +import { renderTextColumn, renderActionsColumn, ITableAction } from "./CoreTableCommonColumns"; +import { some } from "lodash"; +import { closeModal, openModal } from "@common/actions/modalActions"; +import { archiveMineDocuments } from "@common/actionCreators/mineActionCreator"; +import { connect } from "react-redux"; +import { bindActionCreators } from "redux"; +import { modalConfig } from "@/components/modalContent/config"; +import { Feature, isFeatureEnabled } from "@mds/common"; +import { SizeType } from "antd/lib/config-provider/SizeContext"; +import { ColumnType, ColumnsType } from "antd/es/table"; +import { FileOperations, MineDocument } from "@common/models/documents/document"; +import { + DeleteOutlined, + DownloadOutlined, + FileOutlined, + InboxOutlined, + SyncOutlined, +} from "@ant-design/icons"; +import { openDocument } from "../syncfusion/DocumentViewer"; +import { downloadFileFromDocumentManager } from "@common/utils/actionlessNetworkCalls"; +import { getUserInfo } from "@common/selectors/authenticationSelectors"; + +interface DocumentTableProps { + documents: MineDocument[]; + isLoaded: boolean; + isViewOnly: boolean; + canArchiveDocuments: boolean; + showVersionHistory: boolean; + documentParent: string; + view: string; + openModal: (arg) => void; + openDocument: any; + closeModal: () => void; + removeDocument: (event, doc_guid: string, mine_guid: string) => void; + archiveMineDocuments: (mineGuid: string, mineDocumentGuids: string[]) => void; + onArchivedDocuments: (docs?: MineDocument[]) => void; + documentColumns: ColumnType[]; + additionalColumns: ColumnType[]; + defaultSortKeys: string[]; + excludedColumnKeys: string[]; + additionalColumnProps: { key: string; colProps: any }[]; + fileOperationPermissionMap: { operation: FileOperations; permission: string | boolean }[]; + userInfo: any; +} + +// eslint-disable-next-line @typescript-eslint/no-shadow +export const DocumentTable = ({ + isViewOnly = false, + excludedColumnKeys = [], + additionalColumnProps = [], + documentColumns = null, + additionalColumns = [], + documentParent = null, + isLoaded = true, + showVersionHistory = false, + defaultSortKeys = ["upload_date", "dated", "update_timestamp"], + view = "standard", + canArchiveDocuments = false, + openModal, + closeModal, + removeDocument, + openDocument, + ...props +}: DocumentTableProps) => { + const allowedTableActions = { + [FileOperations.View]: true, + [FileOperations.Download]: true, + [FileOperations.Replace]: !isViewOnly, + [FileOperations.Archive]: + !isViewOnly && canArchiveDocuments && isFeatureEnabled(Feature.MAJOR_PROJECT_ARCHIVE_FILE), + [FileOperations.Delete]: !isViewOnly && removeDocument !== undefined, + }; + + const isMinimalView: boolean = view === "minimal"; + + const parseDocuments = (docs: any[]): MineDocument[] => { + let parsedDocs: MineDocument[]; + if (docs.length && docs[0] instanceof MineDocument) { + parsedDocs = docs; + } else { + parsedDocs = docs.map((doc) => new MineDocument(doc)); + } + return parsedDocs.map((doc) => { + // TODO: getUserAccessData is broken, but the correct function to use here + const { client_roles = [] } = props.userInfo; + doc.setAllowedActions(client_roles); + return doc; + }); + }; + const documents = parseDocuments(props.documents ?? []); + + const openArchiveModal = (event, docs: MineDocument[]) => { + const mineGuid = docs[0].mine_guid; + event.preventDefault(); + openModal({ + props: { + title: `Archive ${docs?.length > 1 ? "Multiple Files" : "File"}`, + closeModal: closeModal, + handleSubmit: async () => { + await props.archiveMineDocuments( + mineGuid, + docs.map((d) => d.mine_document_guid) + ); + if (props.onArchivedDocuments) { + props.onArchivedDocuments(docs); + } + }, + docs, + }, + content: modalConfig.ARCHIVE_DOCUMENT, + }); + }; + + const openDeleteModal = (event, docs: MineDocument[]) => { + event.preventDefault(); + openModal({ + props: { + title: `Delete ${docs?.length > 1 ? "Multiple Files" : "File"}`, + closeModal: closeModal, + handleSubmit: async () => { + docs.forEach((record) => removeDocument(event, record.key, documentParent)); + }, + docs, + }, + content: modalConfig.DELETE_DOCUMENT, + }); + }; + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const actions = [ + { + key: "view", + label: FileOperations.View, + icon: , + clickFunction: (_event, record: MineDocument) => + openDocument(record.document_manager_guid, record.mine_document_guid), + }, + { + key: "download", + label: FileOperations.Download, + icon: , + clickFunction: (_event, record: MineDocument) => { + return downloadFileFromDocumentManager(record); + }, + }, + { + key: "replace", + label: FileOperations.Replace, + icon: , + clickFunction: (_event, _record: MineDocument) => alert("Not implemented"), + }, + { + key: "archive", + label: FileOperations.Archive, + icon: , + clickFunction: (event, record: MineDocument) => openArchiveModal(event, [record]), + }, + { + key: "delete", + label: FileOperations.Delete, + icon: , + // PopConfirm does not work in either the function or label field here + clickFunction: (event, record: MineDocument) => openDeleteModal(event, [record]), + }, + ].filter((action) => allowedTableActions[action.label]); + + const filterActions = (record: MineDocument, tableActions: ITableAction[]) => { + const allowedDocumentActions: string[] = record.allowed_actions; + return tableActions.filter((action) => allowedDocumentActions.includes(action.label)); + }; + + // document tables that don't yet have MineRecord, actions, archive, versions functionality + const oldGetColumns = () => { + let columns = documentColumns ?? [ + documentNameColumn("document_name", "File Name", isMinimalView), + renderTextColumn("category", "Category", !isMinimalView), + uploadDateColumn("upload_date", "Uploaded", !isMinimalView), + uploadDateColumn("dated", "Dated", !isMinimalView), + ]; + if (actions.length > 0 && !columns.some((column) => column.key === "actions")) { + columns.push(renderActionsColumn(actions, filterActions)); + } + if (!some(documents, "dated")) { + columns = columns.filter((column) => column.key !== "dated"); + } + + if (excludedColumnKeys?.length > 0) { + columns = columns.filter((column) => !excludedColumnKeys.includes(column.key.toString())); + } + return columns; + }; + + const newGetColumns = (): ColumnsType => { + const columns: ColumnsType = [ + documentNameColumnNew(), + ...additionalColumns, + renderTextColumn("file_type", "File Type", !isMinimalView), + uploadDateColumn("update_timestamp", "Last Modified"), + uploadedByColumn("create_user", "Created By"), + ]; + if (actions.length) { + columns.push(renderActionsColumn(actions, filterActions)); + } + return columns; + }; + + let columns: ColumnsType = showVersionHistory ? newGetColumns() : oldGetColumns(); + + if (additionalColumnProps?.length > 0) { + additionalColumnProps.forEach((addColumn) => { + const columnIndex = columns.findIndex((column) => addColumn?.key === column.key); + if (columnIndex >= 0) { + columns[columnIndex] = { ...columns[columnIndex], ...addColumn?.colProps }; + } + }); + } + + if (defaultSortKeys.length > 0) { + columns = columns.map((column) => { + const isDefaultSort = defaultSortKeys.includes(column.key.toString()); + return isDefaultSort ? { defaultSortOrder: "descend", ...column } : column; + }); + } + + const minimalProps = isMinimalView + ? { size: "small" as SizeType, rowClassName: "ant-table-row-minimal" } + : null; + return showVersionHistory ? ( + record.number_prev_versions > 0, + }} + /> + ) : ( + + ); +}; + +const mapStateToProps = (state) => ({ + userInfo: getUserInfo(state), +}); + +// eslint-disable-next-line @typescript-eslint/no-shadow +const mapDispatchToProps = (dispatch) => + bindActionCreators( + { + openModal, + closeModal, + archiveMineDocuments, + openDocument, + }, + dispatch + ); + +export default connect(mapStateToProps, mapDispatchToProps)(DocumentTable); diff --git a/services/minespace-web/src/components/dashboard/mine/variances/VarianceDetails.js b/services/minespace-web/src/components/dashboard/mine/variances/VarianceDetails.js index a2b32c3190..a788ac3fb6 100644 --- a/services/minespace-web/src/components/dashboard/mine/variances/VarianceDetails.js +++ b/services/minespace-web/src/components/dashboard/mine/variances/VarianceDetails.js @@ -4,8 +4,10 @@ import { Descriptions } from "antd"; import { formatDate } from "@common/utils/helpers"; import CustomPropTypes from "@/customPropTypes"; import DocumentTable from "@/components/common/DocumentTable"; -import { categoryColumn, uploadDateColumn } from "@/components/common/DocumentColumns"; +import { documentNameColumn, uploadDateColumn } from "@/components/common/DocumentColumns"; import * as Strings from "@/constants/strings"; +import { renderCategoryColumn } from "@/components/common/CoreTableCommonColumns"; +import { MineDocument } from "@common/models/documents/document"; const propTypes = { variance: CustomPropTypes.variance.isRequired, @@ -16,9 +18,18 @@ const propTypes = { }; export const VarianceDetails = (props) => { + const documents = props.variance.documents.map( + (doc) => + new MineDocument({ + ...doc, + category: doc.variance_document_category_code, + upload_date: doc.created_at, + }) + ); const documentColumns = [ - categoryColumn("variance_document_category_code", props.documentCategoryOptionsHash), - uploadDateColumn("created_at"), + documentNameColumn(), + renderCategoryColumn("category", "Category", props.documentCategoryOptionsHash), + uploadDateColumn(), ]; const getActiveStatus = () => { if (props.variance.expiry_date) { @@ -61,7 +72,7 @@ export const VarianceDetails = (props) => { diff --git a/services/minespace-web/src/components/modalContent/config.js b/services/minespace-web/src/components/modalContent/config.js index 6469543020..dcf131dd1f 100644 --- a/services/minespace-web/src/components/modalContent/config.js +++ b/services/minespace-web/src/components/modalContent/config.js @@ -14,6 +14,7 @@ import ImportIRTErrorModal from "@/components/modalContent/informationRequiremen import ViewFileHistoryModal from "./informationRequirementsTable/ViewFileHistoryModal"; import UploadIncidentDocumentModal from "@/components/modalContent/incidents/UploadIncidentDocumentModal"; import ArchiveDocumentModal from "@common/components/documents/ArchiveDocumentModal"; +import DeleteDocumentModal from "@common/components/documents/DeleteDocumentModal"; export const modalConfig = { ADD_REPORT: AddReportModal, @@ -21,6 +22,7 @@ export const modalConfig = { ADD_VARIANCE: AddVarianceModal, VIEW_VARIANCE: ViewVarianceModal, ARCHIVE_DOCUMENT: ArchiveDocumentModal, + DELETE_DOCUMENT: DeleteDocumentModal, EDIT_VARIANCE: EditVarianceModal, ADD_TAILINGS: AddTailingsModal, ADD_INCIDENT: AddIncidentModal, diff --git a/services/minespace-web/src/components/modalContent/informationRequirementsTable/ViewFileHistoryModal.js b/services/minespace-web/src/components/modalContent/informationRequirementsTable/ViewFileHistoryModal.js index cb5d690a9c..f48f612188 100644 --- a/services/minespace-web/src/components/modalContent/informationRequirementsTable/ViewFileHistoryModal.js +++ b/services/minespace-web/src/components/modalContent/informationRequirementsTable/ViewFileHistoryModal.js @@ -4,10 +4,13 @@ import { Button } from "antd"; import CustomPropTypes from "@/customPropTypes"; import DocumentTable from "@/components/common/DocumentTable"; import { - categoryColumn, - uploadDateTimeColumn, - importedByColumn, -} from "@/components/common/DocumentColumns"; + renderCategoryColumn, + renderDateColumn, + renderTextColumn, +} from "@/components/common/CoreTableCommonColumns"; +import { formatDateTime } from "@common/utils/helpers"; +import { MineDocument } from "@common/models/documents/document"; +import { documentNameColumn } from "@/components/common/DocumentColumns"; const propTypes = { project: CustomPropTypes.project.isRequired, @@ -16,18 +19,20 @@ const propTypes = { }; const ViewFileHistoryModal = (props) => { + const documents = props.project?.information_requirements_table?.documents.map( + (doc) => + new MineDocument({ ...doc, category: doc.information_requirements_table_document_type_code }) + ); const documentColumns = [ - categoryColumn( - "information_requirements_table_document_type_code", - props.documentCategoryOptionsHash - ), - uploadDateTimeColumn("upload_date"), - importedByColumn("create_user"), + documentNameColumn(), + renderCategoryColumn("category", "Category", props.documentCategoryOptionsHash), + renderDateColumn("upload_date", "Date/Time", true, formatDateTime), + renderTextColumn("create_user", "Imported By"), ]; return (
diff --git a/services/minespace-web/src/components/pages/Project/DocumentsPage.js b/services/minespace-web/src/components/pages/Project/DocumentsPage.js index b32aef4e6b..2bb7913cb3 100644 --- a/services/minespace-web/src/components/pages/Project/DocumentsPage.js +++ b/services/minespace-web/src/components/pages/Project/DocumentsPage.js @@ -3,19 +3,14 @@ import { Row, Col, Typography } from "antd"; import { withRouter } from "react-router-dom"; import PropTypes from "prop-types"; import DocumentTable from "@/components/common/DocumentTable"; -import { uploadDateColumn } from "@/components/common/DocumentColumns"; const propTypes = { title: PropTypes.string.isRequired, documents: PropTypes.arrayOf(PropTypes.object).isRequired, onArchivedDocuments: PropTypes.func.isRequired, - archiveDocumentsArgs: PropTypes.shape({ - mineGuid: PropTypes.string, - }), }; export const DocumentsPage = (props) => { - const documentColumns = [uploadDateColumn("upload_date")]; return (
@@ -25,10 +20,9 @@ export const DocumentsPage = (props) => { diff --git a/services/minespace-web/src/components/pages/Project/DocumentsTab.js b/services/minespace-web/src/components/pages/Project/DocumentsTab.js index 682dfba681..a7c870fc49 100644 --- a/services/minespace-web/src/components/pages/Project/DocumentsTab.js +++ b/services/minespace-web/src/components/pages/Project/DocumentsTab.js @@ -11,7 +11,7 @@ import customPropTypes from "@/customPropTypes"; import DocumentsPage from "./DocumentsPage"; import { getMineDocuments } from "@common/selectors/mineSelectors"; import ArchivedDocumentsSection from "@common/components/documents/ArchivedDocumentsSection"; -import { uploadDateColumn } from "@/components/common/DocumentColumns"; +import { documentNameColumn, uploadDateColumn } from "@/components/common/DocumentColumns"; import { Feature, isFeatureEnabled } from "@mds/common"; const propTypes = { @@ -67,15 +67,13 @@ export class DocumentsTab extends Component { }; render() { - const documentColumns = [uploadDateColumn("upload_date")]; - + const documentColumns = [documentNameColumn(), uploadDateColumn()]; const renderAllDocuments = (docs) => (
@@ -83,7 +81,6 @@ export class DocumentsTab extends Component {
@@ -91,7 +88,6 @@ export class DocumentsTab extends Component {
diff --git a/services/minespace-web/src/styles/components/DocumentTableWithExpandedRows.scss b/services/minespace-web/src/styles/components/DocumentTableWithExpandedRows.scss new file mode 100644 index 0000000000..c8fad0df34 --- /dev/null +++ b/services/minespace-web/src/styles/components/DocumentTableWithExpandedRows.scss @@ -0,0 +1,59 @@ +.ant-table-row-level-0.no-sub-table-expandable-rows { + &:hover { + background: #BBBBBB; + } + + td:first-of-type, + .file-history-container { + display: flex; + align-items: center; + height: 100%; + } + + &>td { + border-bottom: none; + border-top: 1px solid #BBBBBB; + } + + &:nth-last-child(1)>td { + border-bottom: 1px solid #BBBBBB; + } +} + +.ant-table-row-level-1.no-sub-table-expandable-rows>td { + border: none; +} + +th.file-name-column { + text-indent: 35px; +} + +.file-name-container { + align-items: center; + flex-basis: 90%; + + @media (min-width: 769px) { + justify-content: space-between; + } + + @media (max-width: 768px) { + justify-content: flex-start; + margin-left: 0 !important; + } +} + +.file-name-text { + @media (min-width: 769px) { + width: 60%; + } + + @media (max-width: 768px) { + margin-left: 0 !important; + } +} + +.file-version-amount { + color: white; + margin-left: 5px; + background-color: $gov-blue; +} \ No newline at end of file diff --git a/services/minespace-web/src/styles/components/Tables.scss b/services/minespace-web/src/styles/components/Tables.scss index 953384d1b3..6addf7cdb4 100644 --- a/services/minespace-web/src/styles/components/Tables.scss +++ b/services/minespace-web/src/styles/components/Tables.scss @@ -15,6 +15,17 @@ display: none; } +// actions menu + +.actions-dropdown-button { + border: none; + background: transparent; +} + +.ant-dropdown-menu-item-icon { + font-size: 16px; +} + @include screen-md { .ant-table table { min-width: 100%; diff --git a/services/minespace-web/src/styles/index.scss b/services/minespace-web/src/styles/index.scss index 831470a025..72ee552729 100755 --- a/services/minespace-web/src/styles/index.scss +++ b/services/minespace-web/src/styles/index.scss @@ -35,6 +35,7 @@ @import "./components/SteppedForm.scss"; @import "./components/Tailings.scss"; @import "./components/Incidents.scss"; +@import "./components/DocumentTableWithExpandedRows.scss"; // UTILITIES - utilities and helper classes. This layer has the highest specificity. @import "./generic/helpers.scss"; diff --git a/services/minespace-web/src/tests/components/Forms/incidents/__snapshots__/IncidentForm.spec.js.snap b/services/minespace-web/src/tests/components/Forms/incidents/__snapshots__/IncidentForm.spec.js.snap index 1bcc826b86..797678a754 100644 --- a/services/minespace-web/src/tests/components/Forms/incidents/__snapshots__/IncidentForm.spec.js.snap +++ b/services/minespace-web/src/tests/components/Forms/incidents/__snapshots__/IncidentForm.spec.js.snap @@ -576,17 +576,25 @@ exports[`IncidentForm renders properly 1`] = ` { props.isViewOnly = true; props.documents = MOCK.VARIANCE.documents; props.documentCategoryOptionsHash = MOCK.VARIANCE_DOCUMENT_CATEGORY_OPTIONS_HASH; + props.userInfo = { client_roles: ["mds_minespace_proponents"] }; }; const setupDispatchProps = () => { diff --git a/services/minespace-web/src/tests/components/common/__snapshots__/DocumentTable.spec.js.snap b/services/minespace-web/src/tests/components/common/__snapshots__/DocumentTable.spec.js.snap index 5825862e60..6201aedbd4 100644 --- a/services/minespace-web/src/tests/components/common/__snapshots__/DocumentTable.spec.js.snap +++ b/services/minespace-web/src/tests/components/common/__snapshots__/DocumentTable.spec.js.snap @@ -1,44 +1,66 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`DocumentTable renders properly 1`] = ` -
- -
+ `; diff --git a/services/minespace-web/src/tests/components/dashboard/mine/variances/__snapshots__/VarianceDetails.spec.js.snap b/services/minespace-web/src/tests/components/dashboard/mine/variances/__snapshots__/VarianceDetails.spec.js.snap index e50a192e01..91ea0a8f83 100644 --- a/services/minespace-web/src/tests/components/dashboard/mine/variances/__snapshots__/VarianceDetails.spec.js.snap +++ b/services/minespace-web/src/tests/components/dashboard/mine/variances/__snapshots__/VarianceDetails.spec.js.snap @@ -32,16 +32,23 @@ exports[`VarianceDetails renders properly 1`] = ` documentColumns={ Array [ Object { - "dataIndex": "variance_document_category_code", - "key": "variance_document_category_code", + "dataIndex": "document_name", + "key": "document_name", + "render": [Function], + "sorter": [Function], + "title": "File Name", + }, + Object { + "dataIndex": "category", + "key": "category", "render": [Function], "title": "Category", }, Object { - "dataIndex": "created_at", - "key": "created_at", + "dataIndex": "upload_date", + "key": "upload_date", "render": [Function], - "sorter": null, + "sorter": [Function], "title": "Uploaded", }, ] @@ -49,12 +56,31 @@ exports[`VarianceDetails renders properly 1`] = ` documentParent="variance" documents={ Array [ - Object { - "created_at": "2019-05-02", + MineDocument { + "allowed_actions": Array [ + "Open in document viewer", + "Download file", + "Replace file", + "Archive file", + "Delete", + ], + "archived_by": undefined, + "archived_date": undefined, + "category": undefined, + "create_user": undefined, "document_manager_guid": "d7f64a25-6eaf-4bed-97fe-fd63ac347c70", "document_name": "test.pdf", + "file_type": ".pdf", + "is_archived": false, + "is_latest_version": true, + "key": "33e6b965-2402-4229-a213-23bbe7fd3e99", "mine_document_guid": "33e6b965-2402-4229-a213-23bbe7fd3e99", "mine_guid": "59e73109-48f7-4ad2-977c-3005b5bff010", + "number_prev_versions": 0, + "update_timestamp": undefined, + "update_user": undefined, + "upload_date": "2019-05-02", + "versions": Array [], }, ] } diff --git a/services/minespace-web/src/tests/components/project/DocumentsPage/__snapshots__/DocumentsPage.spec.js.snap b/services/minespace-web/src/tests/components/project/DocumentsPage/__snapshots__/DocumentsPage.spec.js.snap index 89fecba65b..443a63a2d6 100644 --- a/services/minespace-web/src/tests/components/project/DocumentsPage/__snapshots__/DocumentsPage.spec.js.snap +++ b/services/minespace-web/src/tests/components/project/DocumentsPage/__snapshots__/DocumentsPage.spec.js.snap @@ -16,17 +16,6 @@ exports[`DocumentsPage renders properly 1`] = ` > diff --git a/services/minespace-web/src/tests/components/project/DocumentsTab/__snapshots__/DocumentsTab.spec.js.snap b/services/minespace-web/src/tests/components/project/DocumentsTab/__snapshots__/DocumentsTab.spec.js.snap index 19cdacb84d..759baaddd4 100644 --- a/services/minespace-web/src/tests/components/project/DocumentsTab/__snapshots__/DocumentsTab.spec.js.snap +++ b/services/minespace-web/src/tests/components/project/DocumentsTab/__snapshots__/DocumentsTab.spec.js.snap @@ -36,11 +36,6 @@ exports[`DocumentsTab renders properly 1`] = ` id="project-description" >
@@ -48,11 +43,6 @@ exports[`DocumentsTab renders properly 1`] = ` id="information-requirements-table" >
@@ -60,22 +50,24 @@ exports[`DocumentsTab renders properly 1`] = ` id="major-mine-application" >
@@ -110,11 +97,6 @@ exports[`DocumentsTab renders properly 1`] = ` id="information-requirements-table" > @@ -122,22 +104,24 @@ exports[`DocumentsTab renders properly 1`] = ` id="major-mine-application" > @@ -172,11 +151,6 @@ exports[`DocumentsTab renders properly 1`] = ` id="information-requirements-table" > @@ -184,22 +158,24 @@ exports[`DocumentsTab renders properly 1`] = ` id="major-mine-application" > @@ -234,11 +205,6 @@ exports[`DocumentsTab renders properly 1`] = ` id="information-requirements-table" > @@ -246,22 +212,24 @@ exports[`DocumentsTab renders properly 1`] = ` id="major-mine-application" >