diff --git a/app/src/components/layout/header/BaseHeader.tsx b/app/src/components/layout/header/BaseHeader.tsx index 177762a4..6add9e96 100644 --- a/app/src/components/layout/header/BaseHeader.tsx +++ b/app/src/components/layout/header/BaseHeader.tsx @@ -44,8 +44,8 @@ const BaseHeader = (props: IBaseHeader) => { = () => { {'Government - - BioHub + + BioHub BC = () => { }}> - + ); @@ -223,7 +223,7 @@ const Header: React.FC = () => { { py: 4, pb: 0 }}> - + Submissions diff --git a/app/src/features/admin/dashboard/components/ReviewedSubmissionsTable.tsx b/app/src/features/admin/dashboard/components/ReviewedSubmissionsTable.tsx index abb6bd62..d3ca0bb3 100644 --- a/app/src/features/admin/dashboard/components/ReviewedSubmissionsTable.tsx +++ b/app/src/features/admin/dashboard/components/ReviewedSubmissionsTable.tsx @@ -90,22 +90,25 @@ const ReviewedSubmissionsTable = () => { return ( <> - + {`${submissionRecords.length} ${p( submissionRecords.length, 'record' )} found`} - - + + + + {submissionRecords.map((submissionRecord) => { return ( diff --git a/app/src/features/admin/dashboard/components/UnreviewedSubmissionsTable.tsx b/app/src/features/admin/dashboard/components/UnreviewedSubmissionsTable.tsx index 8bec5304..d3281869 100644 --- a/app/src/features/admin/dashboard/components/UnreviewedSubmissionsTable.tsx +++ b/app/src/features/admin/dashboard/components/UnreviewedSubmissionsTable.tsx @@ -83,17 +83,20 @@ const UnreviewedSubmissionsTable = () => { return ( <> - + {`${submissionRecords.length} ${p( submissionRecords.length, 'record' )} found`} - - + + + + {submissionRecords.map((submissionRecord) => { return ( diff --git a/app/src/features/submissions/components/SubmissionHeaderSecurityStatus.tsx b/app/src/features/submissions/components/SubmissionHeaderSecurityStatus.tsx index 23105a4d..f02c697b 100644 --- a/app/src/features/submissions/components/SubmissionHeaderSecurityStatus.tsx +++ b/app/src/features/submissions/components/SubmissionHeaderSecurityStatus.tsx @@ -20,34 +20,67 @@ const SubmissionHeaderSecurityStatus = (props: ISubmissionHeaderSecurityStatusPr switch (submission.security) { case SECURITY_APPLIED_STATUS.SECURED: { securityStatus = ( - <> + - + Secured - + ); break; } case SECURITY_APPLIED_STATUS.PARTIALLY_SECURED: { securityStatus = ( - <> + - + Partially Secured - + ); break; } default: { securityStatus = ( - <> + - + Unsecured - + ); break; } @@ -57,56 +90,48 @@ const SubmissionHeaderSecurityStatus = (props: ISubmissionHeaderSecurityStatusPr } sx={{ textTransform: 'uppercase' }} title="Open access to all records"> - + {securityStatus} {submission.publish_timestamp ? ( - - - Published: + + + Published - {getFormattedDate(DATE_FORMAT.ShortDateFormat, submission.publish_timestamp as string)} + ({getFormattedDate(DATE_FORMAT.ShortDateFormat, submission.publish_timestamp as string)}) ) : submission.security_review_timestamp ? ( - - - Completed Review: + + + Review Complete - {getFormattedDate(DATE_FORMAT.ShortDateFormat, submission.security_review_timestamp as string)} + ({getFormattedDate(DATE_FORMAT.ShortDateFormat, submission.security_review_timestamp as string)}) ) : ( - - - Pending Security Review + + + Pending Review )} - - - Submitted: + + + Date Submitted - {getFormattedDate(DATE_FORMAT.ShortDateFormat, submission.create_date as string)} + ({getFormattedDate(DATE_FORMAT.ShortDateFormat, submission.create_date as string)}) diff --git a/app/src/features/submissions/list/SortMenuItem.tsx b/app/src/features/submissions/list/SortMenuItem.tsx deleted file mode 100644 index a5ab699f..00000000 --- a/app/src/features/submissions/list/SortMenuItem.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { mdiSortAlphabeticalAscending, mdiSortAlphabeticalDescending } from '@mdi/js'; -import Icon from '@mdi/react'; -import MenuItem from '@mui/material/MenuItem'; -import useTheme from '@mui/system/useTheme'; -import { FuseResult } from 'fuse.js'; -import { SubmissionRecord } from 'interfaces/useSubmissionsApi.interface'; -import sortBy from 'lodash-es/sortBy'; -import { useState } from 'react'; -import { SortSubmission } from './SubmissionsListSortMenu'; - -interface ISortMenuItemProps { - submissions: TSubmission[]; - handleSort: (data: TSubmission[]) => void; - sortKey: keyof SubmissionRecord; - name: string; -} - -const SortMenuItem = (props: ISortMenuItemProps) => { - const { submissions, handleSort, sortKey, name } = props; - - const theme = useTheme(); - const [sortAscending, setSortAscending] = useState(true); - - /** - * sorts by property - * - * @param {keyof SubmissionRecord} sortKey - property to sort datasets by - */ - const sort = (sortKey: keyof SubmissionRecord, ascending: boolean) => { - const getSortKey = (submission: SortSubmission) => - // uncertain why this needs to be cast to FuseResult when checking for item property? - 'item' in submission ? (submission as FuseResult).item[sortKey] : submission[sortKey]; - const sortedData: TSubmission[] = ascending - ? sortBy(submissions, getSortKey) - : sortBy(submissions, getSortKey).reverse(); - handleSort(sortedData); - }; - return ( - { - const toggleSort = !sortAscending; - sort(sortKey, toggleSort); - setSortAscending(toggleSort); - }} - dense> - - {name} - - ); -}; - -export default SortMenuItem; diff --git a/app/src/features/submissions/list/SubmissionsListPage.tsx b/app/src/features/submissions/list/SubmissionsListPage.tsx index 2fd86613..0ca7dbc5 100644 --- a/app/src/features/submissions/list/SubmissionsListPage.tsx +++ b/app/src/features/submissions/list/SubmissionsListPage.tsx @@ -86,13 +86,16 @@ const SubmissionsListPage = () => { fuzzyData.length, 'record' )} found`} - { - handleFuzzyData(data); - }} - /> + + { + handleFuzzyData(data); + }} + apiSortSync={{ key: 'publish_timestamp', sort: 'asc' }} + /> + submissions={mockSubmissions} handleSubmissions={mockHandleSubmissions} sortMenuItems={menuItems} + apiSortSync={{ key: 'name', sort: 'asc' }} /> ); diff --git a/app/src/features/submissions/list/SubmissionsListSortMenu.tsx b/app/src/features/submissions/list/SubmissionsListSortMenu.tsx index 467e8eaf..a4763586 100644 --- a/app/src/features/submissions/list/SubmissionsListSortMenu.tsx +++ b/app/src/features/submissions/list/SubmissionsListSortMenu.tsx @@ -1,19 +1,57 @@ -import { mdiChevronDown } from '@mdi/js'; +import { mdiChevronDown, mdiSortAscending, mdiSortDescending, mdiSortReverseVariant } from '@mdi/js'; import Icon from '@mdi/react'; +import { MenuItem, useTheme } from '@mui/material'; import Button from '@mui/material/Button'; import Menu from '@mui/material/Menu'; -import Stack from '@mui/material/Stack'; import { FuseResult } from 'fuse.js'; import { SubmissionRecord } from 'interfaces/useSubmissionsApi.interface'; +import sortBy from 'lodash-es/sortBy'; import React, { useState } from 'react'; -import SortMenuItem from './SortMenuItem'; +import { objectKeys } from 'utils/Utils'; export type SortSubmission = FuseResult | SubmissionRecord; +type SortProp = { key: keyof SubmissionRecord; sort: 'asc' | 'desc' }; + export interface ISubmissionsListSortMenuProps { + /** + * Submissions to sort + * + * @type {TSubmission[]} + * @memberof ISubmissionsListSortMenuProps + */ submissions: TSubmission[]; + + /** + * Callback fired after submissions are sorted + * + * @param {TSubmission[]} - data + * @returns {void} + * @memberof ISubmissionsListSortMenuProps + */ handleSubmissions: (data: TSubmission[]) => void; + + /** + * Sort menu items + * The object values will be the labels for the menu items + * + * @type {Partial>} + * @example: { name: 'Name', publish_timestamp: 'Publish Date' } + * @memberof ISubmissionsListSortMenuProps + */ sortMenuItems: Partial>; + + /** + * Manually syncs the default sort with what api responds with + * + * Note: This does not sort the submissions, only sets the default selected sort of the menu. + * Open to suggestions on a better way to implement this + * + * @type {SortProp} + * @example: { key: 'publish_timetamp', sort: 'desc' } + * @memberof ISubmissionsListSortMenuProps + */ + apiSortSync?: SortProp; } /** @@ -26,9 +64,12 @@ export interface ISubmissionsListSortMenuProps { const SubmissionsListSortMenu = ( props: ISubmissionsListSortMenuProps ) => { - const { submissions, sortMenuItems, handleSubmissions } = props; + const { submissions, sortMenuItems, handleSubmissions, apiSortSync } = props; + + const theme = useTheme(); const [anchorEl, setAnchorEl] = useState(null); + const [sortProp, setSortProp] = useState(apiSortSync); const open = Boolean(anchorEl); @@ -40,13 +81,50 @@ const SubmissionsListSortMenu = ( setAnchorEl(null); }; - const handleSort = (submissions: TSubmission[]) => { + /** + * sorts submissions by property + * + * @param {SortProp} _sortProp - property and direction of sort + */ + const sortSubmissions = (_sortProp: SortProp) => { + const getSortKey = (submission: SortSubmission) => + // uncertain why this needs to be cast to FuseResult when checking for item property? + 'item' in submission + ? (submission as FuseResult).item[_sortProp.key] + : submission[_sortProp.key]; + + const sortedData: TSubmission[] = + _sortProp.sort === 'asc' ? sortBy(submissions, getSortKey) : sortBy(submissions, getSortKey).reverse(); + + return sortedData; + }; + + const handleMenuItemClick = (sortKey: keyof SubmissionRecord) => { + const sortDirection = !sortProp || sortProp.sort === 'desc' ? 'asc' : 'desc'; + const newSortProp: SortProp = { + key: sortKey, + sort: sortDirection + }; + + const sortedSubmissions = sortSubmissions(newSortProp); + + handleSubmissions(sortedSubmissions); + setSortProp(newSortProp); handleClose(); - handleSubmissions(submissions); + }; + + const getSortIcon = (sortKey: keyof SubmissionRecord) => { + if (!sortProp || sortKey !== sortProp.key) { + return mdiSortReverseVariant; + } + if (sortProp.sort === 'asc') { + return mdiSortAscending; + } + return mdiSortDescending; }; return ( - + <> - - {Object.keys(sortMenuItems).map((key) => ( - + + {objectKeys(sortMenuItems).map((key) => ( + handleMenuItemClick(key)} dense> + + {sortMenuItems[key]} + ))} - + ); }; diff --git a/app/src/utils/Utils.ts b/app/src/utils/Utils.ts index 4cd6f521..a887189a 100644 --- a/app/src/utils/Utils.ts +++ b/app/src/utils/Utils.ts @@ -380,3 +380,14 @@ export const getFormattedIdentitySource = (identitySource: SYSTEM_IDENTITY_SOURC return null; } }; + +/** + * same implementation as Object.keys but with correct typings for interable + * + * @template Obj + * @param {Obj} obj - object to iterate through + * @returns {(keyof Obj)[]} array of object keys with correct typings ie: not string[] + */ +export const objectKeys = (obj: Obj): (keyof Obj)[] => { + return Object.keys(obj) as (keyof Obj)[]; +};