diff --git a/app/src/components/attachments/list/AttachmentsList.tsx b/app/src/components/attachments/list/AttachmentsList.tsx index 197fe494b1..d1e89d66db 100644 --- a/app/src/components/attachments/list/AttachmentsList.tsx +++ b/app/src/components/attachments/list/AttachmentsList.tsx @@ -107,6 +107,7 @@ const AttachmentsList = columns={attachmentsListColumnDefs} rows={attachments} pageSizeOptions={pageSizeOptions} + disableRowSelectionOnClick initialState={{ pagination: { paginationModel: { diff --git a/app/src/components/data-grid/StyledDataGrid.tsx b/app/src/components/data-grid/StyledDataGrid.tsx index 566f929745..c7fddb4e62 100644 --- a/app/src/components/data-grid/StyledDataGrid.tsx +++ b/app/src/components/data-grid/StyledDataGrid.tsx @@ -43,12 +43,31 @@ export const StyledDataGrid = (props: StyledD borderBottom: 'none' } }, - '& .MuiDataGrid-columnHeader:first-of-type, .MuiDataGrid-cell:first-of-type': { - pl: 2 + // Define custom header padding for the first column vs every other column + '& .MuiDataGrid-columnHeader:first-of-type:not(.MuiDataGrid-columnHeaderCheckbox)': { + pl: 3 // Add extra padding to the first header, unless it is a checkbox header }, - '& .MuiDataGrid-columnHeader:last-of-type, .MuiDataGrid-cell:last-of-type': { - pr: 2 + '& .MuiDataGrid-columnHeader:first-of-type.MuiDataGrid-columnHeaderCheckbox': { + pl: 2 // Add extra padding to the first header when it is a checkbox header }, + '& .MuiDataGrid-columnHeader:not(:first-of-type)': { + pl: 1 // Add extra padding to every other header + }, + // Define custom cell padding for the first column vs every other column + '& .MuiDataGrid-cell:first-of-type:not(.MuiDataGrid-cellCheckbox)': { + pl: 3 // Add extra padding to the first cell, unless it is a checkbox cell + }, + '& .MuiDataGrid-cell:first-of-type.MuiDataGrid-cellCheckbox': { + pl: 2 // Add extra padding to the first cell when it is a checkbox cell + }, + '& .MuiDataGrid-cell:not(:first-of-type)': { + pl: 1 // Add extra padding to every other cell + }, + // Ensure the draggable container is at least 50px wide + '& .MuiDataGrid-columnHeaderDraggableContainer': { + minWidth: '50px' + }, + // Custom styling for cell content at different densities '&.MuiDataGrid-root--densityCompact .MuiDataGrid-cell': { py: '8px', wordWrap: 'anywhere' @@ -61,9 +80,6 @@ export const StyledDataGrid = (props: StyledD py: '22px', wordWrap: 'anywhere' }, - '& .MuiDataGrid-columnHeaderDraggableContainer': { - minWidth: '50px' - }, ...props.sx }} /> diff --git a/app/src/components/toolbar/CustomToggleButtonGroup.tsx b/app/src/components/toolbar/CustomToggleButtonGroup.tsx index c0f504971d..3ee4460524 100644 --- a/app/src/components/toolbar/CustomToggleButtonGroup.tsx +++ b/app/src/components/toolbar/CustomToggleButtonGroup.tsx @@ -3,26 +3,73 @@ import Button from '@mui/material/Button'; import ToggleButton from '@mui/material/ToggleButton'; import ToggleButtonGroup from '@mui/material/ToggleButtonGroup'; -interface CustomToggleButtonGroupProps { - views: Array<{ value: T; label: string; icon: string }>; - activeView: T; - onViewChange: (view: T) => void; +export interface ToggleButtonView { + /** + * The value of the toggle button, which will be passed to the `onViewChange` callback. + * + * @type {ViewValueType} + * @memberof ToggleButtonView + */ + value: ViewValueType; + /** + * The label to display for the toggle button. + * + * @type {string} + * @memberof ToggleButtonView + */ + label: string; + /** + * An optional start icon. + * + * @type {string} + * @memberof ToggleButtonView + */ + icon?: string; +} + +interface CustomToggleButtonGroupProps { + /** + * An array of views to display in the toggle button group. + * + * @type {ToggleButtonView[]} + * @memberof CustomToggleButtonGroupProps + */ + views: ToggleButtonView[]; + /** + * The currently active view. + * + * @type {ViewValueType} + * @memberof CustomToggleButtonGroupProps + */ + activeView: ViewValueType; + /** + * Callback fired when a toggle button is clicked. + * + * @memberof CustomToggleButtonGroupProps + */ + onViewChange: (view: ViewValueType) => void; + /** + * The orientation of the toggle button group. + * + * @type {('horizontal' | 'vertical')} + * @memberof CustomToggleButtonGroupProps + */ + orientation: 'horizontal' | 'vertical'; } /** * A custom toggle button group that allows users to select from multiple views. * - * TODO: Update all togglebuttongroups throughout the app to use this component for consistent styling - * - * @param {CustomToggleButtonGroupProps} props + * @template ViewValueType + * @param {CustomToggleButtonGroupProps} props * @return {*} */ -const CustomToggleButtonGroup = (props: CustomToggleButtonGroupProps) => { - const { views, activeView, onViewChange } = props; +const CustomToggleButtonGroup = (props: CustomToggleButtonGroupProps) => { + const { views, activeView, onViewChange, orientation } = props; return ( { if (view) { @@ -45,16 +92,15 @@ const CustomToggleButtonGroup = (props: CustomToggleButtonGrou justifyContent: 'flex-start' } }}> - {views.map((view) => ( - } - value={view.value}> - {view.label} - - ))} + {views.map((view) => { + const startIcon = (view.icon && ) || undefined; + + return ( + + {view.label} + + ); + })} ); }; diff --git a/app/src/features/admin/alert/AlertContainer.tsx b/app/src/features/admin/alert/AlertContainer.tsx index b1c9a22b71..0da1f0bbe5 100644 --- a/app/src/features/admin/alert/AlertContainer.tsx +++ b/app/src/features/admin/alert/AlertContainer.tsx @@ -4,10 +4,9 @@ import Box from '@mui/material/Box'; import Button from '@mui/material/Button'; import Divider from '@mui/material/Divider'; import Paper from '@mui/material/Paper'; -import ToggleButton from '@mui/material/ToggleButton'; -import ToggleButtonGroup from '@mui/material/ToggleButtonGroup'; import Toolbar from '@mui/material/Toolbar'; import Typography from '@mui/material/Typography'; +import CustomToggleButtonGroup from 'components/toolbar/CustomToggleButtonGroup'; import dayjs from 'dayjs'; import { useBiohubApi } from 'hooks/useBioHubApi'; import useDataLoader from 'hooks/useDataLoader'; @@ -76,38 +75,16 @@ const AlertListContainer = () => { - - view && setActiveView(view)} - exclusive - sx={{ - width: '100%', - gap: 1, - '& Button': { - py: 0.5, - px: 1.5, - border: 'none !important', - fontWeight: 700, - borderRadius: '4px !important', - fontSize: '0.875rem', - letterSpacing: '0.02rem' - } - }}> - {views.map(({ value, label, icon }) => ( - }> - {label} - - ))} - + + setActiveView(view)} + orientation="horizontal" + /> - + {/* Modals */} {alertId && modalState.edit && } diff --git a/app/src/features/admin/users/access-requests/AccessRequestContainer.tsx b/app/src/features/admin/users/access-requests/AccessRequestContainer.tsx index 154f211262..336bd4982b 100644 --- a/app/src/features/admin/users/access-requests/AccessRequestContainer.tsx +++ b/app/src/features/admin/users/access-requests/AccessRequestContainer.tsx @@ -1,13 +1,10 @@ import { mdiCancel, mdiCheck, mdiExclamationThick } from '@mdi/js'; -import Icon from '@mdi/react'; import Box from '@mui/material/Box'; -import Button from '@mui/material/Button'; import Divider from '@mui/material/Divider'; import Paper from '@mui/material/Paper'; -import ToggleButton from '@mui/material/ToggleButton'; -import ToggleButtonGroup from '@mui/material/ToggleButtonGroup/ToggleButtonGroup'; import Toolbar from '@mui/material/Toolbar'; import Typography from '@mui/material/Typography'; +import CustomToggleButtonGroup from 'components/toolbar/CustomToggleButtonGroup'; import { IGetAccessRequestsListResponse } from 'interfaces/useAdminApi.interface'; import { useState } from 'react'; import AccessRequestActionedList from './list/actioned/AccessRequestActionedList'; @@ -34,16 +31,16 @@ const AccessRequestContainer = (props: IAccessRequestContainerProps) => { const [activeView, setActiveView] = useState(AccessRequestViewEnum.PENDING); - const views = [ - { value: AccessRequestViewEnum.PENDING, label: 'Pending', icon: mdiExclamationThick }, - { value: AccessRequestViewEnum.ACTIONED, label: 'Approved', icon: mdiCheck }, - { value: AccessRequestViewEnum.REJECTED, label: 'Rejected', icon: mdiCancel } - ]; - const pendingRequests = accessRequests.filter((request) => request.status_name === 'Pending'); const actionedRequests = accessRequests.filter((request) => request.status_name === 'Actioned'); const rejectedRequests = accessRequests.filter((request) => request.status_name === 'Rejected'); + const views = [ + { value: AccessRequestViewEnum.PENDING, label: `Pending (${pendingRequests.length})`, icon: mdiExclamationThick }, + { value: AccessRequestViewEnum.ACTIONED, label: `Approved (${actionedRequests.length})`, icon: mdiCheck }, + { value: AccessRequestViewEnum.REJECTED, label: `Rejected (${rejectedRequests.length})`, icon: mdiCancel } + ]; + return ( @@ -53,58 +50,17 @@ const AccessRequestContainer = (props: IAccessRequestContainerProps) => { - { - if (!view) { - // An active view must be selected at all times - return; - } - + { setActiveView(view); }} - exclusive - sx={{ - width: '100%', - gap: 1, - '& Button': { - py: 0.5, - px: 1.5, - border: 'none !important', - fontWeight: 700, - borderRadius: '4px !important', - fontSize: '0.875rem', - letterSpacing: '0.02rem' - } - }}> - {views.map((view) => { - const getCount = () => { - switch (view.value) { - case AccessRequestViewEnum.PENDING: - return pendingRequests.length; - case AccessRequestViewEnum.ACTIONED: - return actionedRequests.length; - case AccessRequestViewEnum.REJECTED: - return rejectedRequests.length; - default: - return 0; - } - }; - return ( - }> - {view.label} ({getCount()}) - - ); - })} - + orientation="horizontal" + /> - + {activeView === AccessRequestViewEnum.PENDING && ( )} diff --git a/app/src/features/admin/users/active/ActiveUsersList.tsx b/app/src/features/admin/users/active/ActiveUsersList.tsx index 2e9073a02c..d2d988f07e 100644 --- a/app/src/features/admin/users/active/ActiveUsersList.tsx +++ b/app/src/features/admin/users/active/ActiveUsersList.tsx @@ -67,8 +67,8 @@ const ActiveUsersList = (props: IActiveUsersListProps) => { { field: 'system_user_id', headerName: 'ID', - width: 70, - minWidth: 70, + width: 85, + minWidth: 85, renderHeader: () => ( ID @@ -412,7 +412,7 @@ const ActiveUsersList = (props: IActiveUsersListProps) => { - + noRowsMessage="No Active Users" columns={activeUsersColumnDefs} diff --git a/app/src/features/admin/users/projects/UsersDetailProjects.tsx b/app/src/features/admin/users/projects/UsersDetailProjects.tsx index 47e7827515..397a80155c 100644 --- a/app/src/features/admin/users/projects/UsersDetailProjects.tsx +++ b/app/src/features/admin/users/projects/UsersDetailProjects.tsx @@ -163,7 +163,7 @@ const UsersDetailProjects: React.FC = (props) => { - + diff --git a/app/src/features/projects/view/ProjectAttachments.tsx b/app/src/features/projects/view/ProjectAttachments.tsx index 174e6924d3..480edf4579 100644 --- a/app/src/features/projects/view/ProjectAttachments.tsx +++ b/app/src/features/projects/view/ProjectAttachments.tsx @@ -1,6 +1,5 @@ import { mdiAttachment, mdiFilePdfBox, mdiTrayArrowUp } from '@mdi/js'; import Icon from '@mdi/react'; -import Box from '@mui/material/Box'; import Button from '@mui/material/Button'; import Divider from '@mui/material/Divider'; import { IReportMetaForm } from 'components/attachments/ReportMetaForm'; @@ -107,10 +106,10 @@ const ProjectAttachments = () => { )} /> - - - - + + + + ); }; diff --git a/app/src/features/standards/StandardsPage.tsx b/app/src/features/standards/StandardsPage.tsx index 9a1af27b41..1b43308f82 100644 --- a/app/src/features/standards/StandardsPage.tsx +++ b/app/src/features/standards/StandardsPage.tsx @@ -6,10 +6,10 @@ import Divider from '@mui/material/Divider'; import Paper from '@mui/material/Paper'; import Stack from '@mui/material/Stack'; import PageHeader from 'components/layout/PageHeader'; +import CustomToggleButtonGroup from 'components/toolbar/CustomToggleButtonGroup'; import { SystemAlertBanner } from 'features/alert/banner/SystemAlertBanner'; import { SystemAlertBannerEnum } from 'interfaces/useAlertApi.interface'; import { useState } from 'react'; -import { StandardsToolbar } from './components/StandardsToolbar'; import { EnvironmentStandards } from './view/environment/EnvironmentStandards'; import { MethodStandards } from './view/methods/MethodStandards'; import { SpeciesStandards } from './view/species/SpeciesStandards'; @@ -20,19 +20,13 @@ export enum StandardsPageView { ENVIRONMENT = 'ENVIRONMENT' } -export interface IStandardsPageView { - label: string; - value: StandardsPageView; - icon: string; -} - const StandardsPage = () => { - const [currentView, setCurrentView] = useState(StandardsPageView.SPECIES); + const [activeView, setActiveView] = useState(StandardsPageView.SPECIES); - const views: IStandardsPageView[] = [ - { label: 'Species', value: StandardsPageView.SPECIES, icon: mdiPaw }, - { label: 'Sampling Methods', value: StandardsPageView.METHODS, icon: mdiToolbox }, - { label: 'Environment variables', value: StandardsPageView.ENVIRONMENT, icon: mdiLeaf } + const views = [ + { value: StandardsPageView.SPECIES, label: 'Species', icon: mdiPaw }, + { value: StandardsPageView.METHODS, label: 'Sampling Methods', icon: mdiToolbox }, + { value: StandardsPageView.ENVIRONMENT, label: 'Environment variables', icon: mdiLeaf } ]; return ( @@ -43,20 +37,25 @@ const StandardsPage = () => { {/* TOOLBAR FOR SWITCHING VIEWS */} - + setActiveView(view)} + orientation="vertical" + /> {/* SPECIES STANDARDS */} - {currentView === StandardsPageView.SPECIES && } + {activeView === StandardsPageView.SPECIES && } {/* METHOD STANDARDS */} - {currentView === StandardsPageView.METHODS && } + {activeView === StandardsPageView.METHODS && } {/* ENVIRONMENT STANDARDS */} - {currentView === StandardsPageView.ENVIRONMENT && } + {activeView === StandardsPageView.ENVIRONMENT && } diff --git a/app/src/features/standards/components/StandardsToolbar.tsx b/app/src/features/standards/components/StandardsToolbar.tsx deleted file mode 100644 index 44ff76623c..0000000000 --- a/app/src/features/standards/components/StandardsToolbar.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import Icon from '@mdi/react'; -import Button from '@mui/material/Button'; -import ToggleButton from '@mui/material/ToggleButton/ToggleButton'; -import ToggleButtonGroup from '@mui/material/ToggleButtonGroup'; -import Typography from '@mui/material/Typography'; -import React, { SetStateAction } from 'react'; -import { IStandardsPageView, StandardsPageView } from '../StandardsPage'; - -interface IStandardsToolbar { - views: IStandardsPageView[]; - currentView: StandardsPageView; - setCurrentView: React.Dispatch>; -} - -/** - * Toolbar for setting the standards page view - * - * @param props - * @returns - */ -export const StandardsToolbar = (props: IStandardsToolbar) => { - const { views, currentView, setCurrentView } = props; - - return ( - <> - Data types - , view: StandardsPageView | null) => { - if (view) { - setCurrentView(view); - } - }} - exclusive - sx={{ - display: 'flex', - gap: 1, - '& Button': { - py: 1.25, - px: 2.5, - border: 'none', - borderRadius: '4px !important', - fontSize: '0.875rem', - fontWeight: 700, - letterSpacing: '0.02rem', - textAlign: 'left', - justifyContent: 'flex-start' - } - }}> - {views.map((view) => ( - } - key={view.value} - value={view.value} - color="primary"> - {view.label} - - ))} - - - ); -}; diff --git a/app/src/features/standards/view/species/SpeciesStandardsResults.tsx b/app/src/features/standards/view/species/SpeciesStandardsResults.tsx index 859ae312bb..d665721a6f 100644 --- a/app/src/features/standards/view/species/SpeciesStandardsResults.tsx +++ b/app/src/features/standards/view/species/SpeciesStandardsResults.tsx @@ -2,11 +2,16 @@ import { mdiRuler, mdiTag } from '@mdi/js'; import { Box, Divider, Stack, Typography } from '@mui/material'; import { blueGrey, grey } from '@mui/material/colors'; import ColouredRectangleChip from 'components/chips/ColouredRectangleChip'; +import CustomToggleButtonGroup from 'components/toolbar/CustomToggleButtonGroup'; import { AccordionStandardCard } from 'features/standards/view/components/AccordionStandardCard'; import { ScientificNameTypography } from 'features/surveys/animals/components/ScientificNameTypography'; import { ISpeciesStandards } from 'interfaces/useStandardsApi.interface'; import { useState } from 'react'; -import SpeciesStandardsToolbar, { SpeciesStandardsViewEnum } from './components/SpeciesStandardsToolbar'; + +enum SpeciesStandardsViewEnum { + MEASUREMENTS = 'measurements', + MARKING_BODY_LOCATIONS = 'marking_body_locations' +} interface ISpeciesStandardsResultsProps { data?: ISpeciesStandards; @@ -35,23 +40,14 @@ const SpeciesStandardsResults = (props: ISpeciesStandardsResultsProps) => { - diff --git a/app/src/features/standards/view/species/components/SpeciesStandardsToolbar.tsx b/app/src/features/standards/view/species/components/SpeciesStandardsToolbar.tsx deleted file mode 100644 index feeca668c7..0000000000 --- a/app/src/features/standards/view/species/components/SpeciesStandardsToolbar.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import Icon from '@mdi/react'; -import { Box, Button, ToggleButton, ToggleButtonGroup } from '@mui/material'; - -export enum SpeciesStandardsViewEnum { - MEASUREMENTS = 'MEASUREMENTS', - MARKING_BODY_LOCATIONS = 'MARKING BODY LOCATIONS' -} - -interface ISurveySpatialDatasetView { - label: string; - icon: string; - value: SpeciesStandardsViewEnum; - isLoading: boolean; -} - -interface ISpeciesStandardsToolbarProps { - updateDatasetView: (view: SpeciesStandardsViewEnum) => void; - views: ISurveySpatialDatasetView[]; - activeView: SpeciesStandardsViewEnum; -} - -/** - * Toolbar for handling what species standards information is displayed - * - * @return {*} - */ -const SpeciesStandardsToolbar = (props: ISpeciesStandardsToolbarProps) => { - const updateDatasetView = (_event: React.MouseEvent, view: SpeciesStandardsViewEnum) => { - if (!view) { - return; - } - - props.updateDatasetView(view); - }; - - return ( - - - {props.views.map((view) => ( - } - value={view.value}> - {view.label} - - ))} - - - ); -}; - -export default SpeciesStandardsToolbar; diff --git a/app/src/features/summary/list-data/ListDataTableContainer.tsx b/app/src/features/summary/list-data/ListDataTableContainer.tsx index 6674848b52..40cf426227 100644 --- a/app/src/features/summary/list-data/ListDataTableContainer.tsx +++ b/app/src/features/summary/list-data/ListDataTableContainer.tsx @@ -3,10 +3,9 @@ import Icon from '@mdi/react'; import Button from '@mui/material/Button'; import Divider from '@mui/material/Divider'; import Stack from '@mui/material/Stack'; -import ToggleButton from '@mui/material/ToggleButton'; -import ToggleButtonGroup from '@mui/material/ToggleButtonGroup'; import Toolbar from '@mui/material/Toolbar'; import HelpButtonDialog from 'components/buttons/HelpButtonDialog'; +import CustomToggleButtonGroup from 'components/toolbar/CustomToggleButtonGroup'; import ProjectsListContainer from 'features/summary/list-data/project/ProjectsListContainer'; import SurveysListContainer from 'features/summary/list-data/survey/SurveysListContainer'; import { useSearchParams } from 'hooks/useSearchParams'; @@ -31,16 +30,6 @@ type ListDataTableURLParams = { [SHOW_SEARCH_KEY]: SHOW_SEARCH_VALUE; }; -const buttonSx = { - py: 0.5, - px: 1.5, - border: 'none !important', - fontWeight: 700, - borderRadius: '4px !important', - fontSize: '0.875rem', - letterSpacing: '0.02rem' -}; - /** * Data table component for list data (ie: projects, surveys). * @@ -49,7 +38,9 @@ const buttonSx = { export const ListDataTableContainer = () => { const { searchParams, setSearchParams } = useSearchParams(); - const [activeView, setActiveView] = useState(searchParams.get(ACTIVE_VIEW_KEY) ?? ACTIVE_VIEW_VALUE.projects); + const [activeView, setActiveView] = useState( + (searchParams.get(ACTIVE_VIEW_KEY) as ACTIVE_VIEW_VALUE | null) ?? ACTIVE_VIEW_VALUE.projects + ); const [showSearch, setShowSearch] = useState(searchParams.get(SHOW_SEARCH_KEY) === SHOW_SEARCH_VALUE.true); const views = [ @@ -57,40 +48,18 @@ export const ListDataTableContainer = () => { { value: ACTIVE_VIEW_VALUE.surveys, label: 'surveys', icon: mdiListBoxOutline } ]; - const onChangeView = (_: React.MouseEvent, value: ACTIVE_VIEW_VALUE) => { - if (!value) { - // User has clicked the active view, do nothing - return; - } - - setSearchParams(searchParams.set(ACTIVE_VIEW_KEY, value)); - setActiveView(value); - }; - return ( <> - - {views.map((view) => ( - } - value={view.value}> - {view.label} - - ))} - + { + setSearchParams(searchParams.set(ACTIVE_VIEW_KEY, view)); + setActiveView(view); + }} + orientation="horizontal" + />