Skip to content

Commit

Permalink
Add Reef check surveys in Site page (#1064)
Browse files Browse the repository at this point in the history
* Hide previous data while loading

* Add reef check surveys in Sites survey list

* Add default value to surveyList

* Add tests

* Add react-scan for performance profiling (#1065)

* Use available width for timeline items

* Format impact count and show all rows

* Fix timeline styling issue

* Scroll to top on route change

* Update tests

* Update index.tsx

* Filter out empty rows

* Fix hasSpotter when there is topTemp only (#1068)

* Update index.tsx

* Update index.tsx

* [Snyk] Fix for 3 vulnerabilities (#1067)

The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-JS-TAR-6476909
- https://snyk.io/vuln/SNYK-JS-INFLIGHT-6095116
- https://snyk.io/vuln/SNYK-JS-AXIOS-6671926

Co-authored-by: snyk-bot <snyk-bot@snyk.io>

* Filter out empty rows for Fish and Invertebrates

* Fix timeline skeleton

* Show invertebrates header inline

---------

Co-authored-by: ericboucher <eric.p.boucher@gmail.com>
Co-authored-by: snyk-bot <snyk-bot@snyk.io>
  • Loading branch information
3 people authored Dec 20, 2024
1 parent 9ada832 commit bd00680
Show file tree
Hide file tree
Showing 18 changed files with 423 additions and 44 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import React from 'react';
import { render } from '@testing-library/react';
import { mockReefCheckSurvey } from 'mocks/mockReefCheckSurvey';
import { ReefCheckSurvey } from 'store/ReefCheckSurveys';
import { BrowserRouter } from 'react-router-dom';
import { ReefCheckSurveyCard } from '.';

describe('ReefCheckSurveyCard', () => {
function renderReefCheckSurveyCard(overrides: Partial<ReefCheckSurvey> = {}) {
return render(
<BrowserRouter>
<ReefCheckSurveyCard
survey={{ ...mockReefCheckSurvey, ...overrides }}
/>
,
</BrowserRouter>,
);
}

it('should render date', () => {
const { getByText } = renderReefCheckSurveyCard();

expect(
getByText(`Date: ${new Date(mockReefCheckSurvey.date).toLocaleString()}`),
).toBeInTheDocument();
});

it('should render user if submittedBy is present', () => {
const { getByText } = renderReefCheckSurveyCard({
submittedBy: 'Test User',
});
expect(getByText('User: Test User')).toBeInTheDocument();
});

it('should render table with correct number of rows', () => {
const { container } = renderReefCheckSurveyCard();

expect(container.querySelectorAll('mock-tablerow').length).toBe(3);
});

it('should show correct counts in headers', () => {
const { container } = renderReefCheckSurveyCard();
const headers = [
...container.querySelectorAll('mock-tablehead mock-tablecell').values(),
].map((el) => el.textContent);
expect(headers).toEqual(
expect.arrayContaining([
'FISH (2)',
'Count',
'INVERTEBRATES (2)',
'Count',
'BLEACHING AND CORAL DIDEASES',
'YES/NO',
'IMPACT',
'YES/NO',
]),
);
});

it('should display link to survey details', () => {
const { getByRole } = renderReefCheckSurveyCard();

expect(
getByRole(
(role, element) =>
role === 'link' && element?.textContent === 'VIEW DETAILS',
),
).toHaveAttribute('href', `/reef_check_survey/${mockReefCheckSurvey.id}`);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import React from 'react';
import {
Box,
Button,
createStyles,
Paper,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Theme,
Typography,
WithStyles,
withStyles,
} from '@material-ui/core';
import { groupBy, times } from 'lodash';
import { Link } from 'react-router-dom';
import cls from 'classnames';
import { reefCheckImpactRows, ReefCheckSurvey } from 'store/ReefCheckSurveys';

type ReefCheckSurveyCardIncomingProps = {
survey: ReefCheckSurvey;
};

const ReefCheckSurveyCardComponent = ({
survey,
classes,
}: ReefCheckSurveyCardProps) => {
const stats = groupBy(
// eslint-disable-next-line fp/no-mutating-methods
survey.organisms
.map((organism) => ({
...organism,
count: organism.s1 + organism.s2 + organism.s3 + organism.s4,
}))
.filter(
({ count, type }) =>
// Filter out fish and invertebrates with no count
count > 0 || type === 'Impact' || type === 'Bleaching',
)
.sort((a, b) => b.count - a.count),
({ type, organism }) => {
if (type === 'Impact') {
return reefCheckImpactRows.includes(organism) ? 'Impact' : 'Bleaching';
}
return type;
},
);

const rowCount = Math.max(
stats.Fish?.length ?? 0,
stats.Invertebrate?.length ?? 0,
stats.Bleaching?.length ?? 0,
stats.Impact?.length ?? 0,
);

return (
<Paper className={classes.paper}>
<Box display="flex" justifyContent="space-between">
<Typography>Date: {new Date(survey.date).toLocaleString()}</Typography>
{survey.submittedBy && (
<Typography>User: {survey.submittedBy}</Typography>
)}
</Box>
<TableContainer className={classes.tableRoot}>
<Table size="small">
<TableHead>
<TableRow className={classes.header}>
<TableCell className={classes.label}>
FISH ({stats.Fish?.length ?? 0})
</TableCell>
<TableCell>Count</TableCell>
<TableCell className={cls(classes.label, classes.noWrap)}>
INVERTEBRATES ({stats.Invertebrate?.length ?? 0})
</TableCell>
<TableCell>Count</TableCell>
<TableCell className={classes.label}>
BLEACHING AND CORAL DIDEASES
</TableCell>
<TableCell>YES/NO</TableCell>
<TableCell className={classes.label}>IMPACT</TableCell>
<TableCell>YES/NO</TableCell>
</TableRow>
</TableHead>
<TableBody>
{times(rowCount).map((i) => (
<TableRow key={i}>
<TableCell className={classes.label}>
{stats.Fish?.[i]?.organism}
</TableCell>
<TableCell>{stats.Fish?.[i]?.count}</TableCell>
<TableCell className={classes.label}>
{stats.Invertebrate?.[i]?.organism}
</TableCell>
<TableCell>{stats.Invertebrate?.[i]?.count}</TableCell>
<TableCell className={classes.label}>
{stats.Bleaching?.[i]?.organism}
</TableCell>
<TableCell>
{formatImpactCount(stats.Bleaching?.[i]?.count)}
</TableCell>
<TableCell className={classes.label}>
{stats.Impact?.[i]?.organism}
</TableCell>
<TableCell>
{formatImpactCount(stats.Impact?.[i]?.count)}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>

<Box marginTop={2}>
<Link to={`reef_check_survey/${survey.id}`}>
<Button size="small" variant="outlined" color="primary">
VIEW DETAILS
</Button>
</Link>
</Box>
</Paper>
);
};

const formatImpactCount = (count?: number) => {
if (count === undefined) {
return '';
}
return count > 0 ? 'YES' : 'NO';
};

const styles = (theme: Theme) =>
createStyles({
paper: {
padding: 16,
color: theme.palette.text.secondary,
maxWidth: '100%',
},
label: {
backgroundColor: '#FAFAFA',
},
tableRoot: {
maxHeight: 200,
},
header: {
'& th': {
borderBottom: '1px solid black',
},
},
noWrap: {
whiteSpace: 'nowrap',
},
});

type ReefCheckSurveyCardProps = ReefCheckSurveyCardIncomingProps &
WithStyles<typeof styles>;

export const ReefCheckSurveyCard = withStyles(styles)(
ReefCheckSurveyCardComponent,
);
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,14 @@ import classNames from 'classnames';
import grey from '@material-ui/core/colors/grey';

import { displayTimeInLocalTimezone } from 'helpers/dates';
import { times } from 'lodash';
import { SurveyListItem } from 'store/Survey/types';
import AddButton from '../AddButton';
import SurveyCard from '../SurveyCard';
import LoadingSkeleton from '../../../LoadingSkeleton';
import incomingStyles from '../styles';
import { TimelineProps } from './types';
import { ReefCheckSurveyCard } from '../ReefCheckSurveyCard';

const CONNECTOR_COLOR = grey[500];

Expand Down Expand Up @@ -48,7 +51,7 @@ const TimelineDesktop = ({
</TimelineContent>
</TimelineItem>
)}
{surveys.map((survey, index) => (
{(loading ? times(2, () => null) : surveys).map((survey, index) => (
<TimelineItem
key={survey?.id || `loading-survey-${index}`}
className={classes.timelineItem}
Expand All @@ -58,13 +61,13 @@ const TimelineDesktop = ({
className={classes.dateSkeleton}
loading={loading}
variant="text"
width="30%"
lines={1}
width={80}
>
{survey?.diveDate && (
{survey?.date && (
<Typography variant="h6" className={classes.dates}>
{displayTimeInLocalTimezone({
isoDate: survey.diveDate,
isoDate: survey.date,
format: 'LL/dd/yyyy',
displayTimezone: false,
timeZone,
Expand All @@ -79,14 +82,19 @@ const TimelineDesktop = ({
<hr className={classes.connector} />
</TimelineSeparator>
<TimelineContent className={classes.surveyCardWrapper}>
<SurveyCard
pointId={pointId}
pointName={pointName}
isAdmin={isAdmin}
siteId={siteId}
survey={survey}
loading={loading}
/>
{(survey?.type === 'survey' || loading) && (
<SurveyCard
pointId={pointId}
pointName={pointName}
isAdmin={isAdmin}
siteId={siteId}
survey={survey as SurveyListItem}
loading={loading}
/>
)}
{survey?.type === 'reefCheckSurvey' && (
<ReefCheckSurveyCard survey={survey} />
)}
</TimelineContent>
</TimelineItem>
))}
Expand All @@ -100,7 +108,7 @@ const useStyles = makeStyles((theme: Theme) => ({
alignItems: 'center',
},
timelineOppositeContent: {
flex: 0.5,
flex: '0 0 130px',
},
addNewButtonOpposite: {
padding: theme.spacing(0, 1.25),
Expand Down
29 changes: 18 additions & 11 deletions packages/website/src/common/SiteDetails/Surveys/Timeline/Tablet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ import React from 'react';
import { Grid, makeStyles, Typography } from '@material-ui/core';

import { displayTimeInLocalTimezone } from 'helpers/dates';
import { times } from 'lodash';
import AddButton from '../AddButton';
import SurveyCard from '../SurveyCard';
import incomingStyles from '../styles';
import { TimelineProps } from './types';
import LoadingSkeleton from '../../../LoadingSkeleton';
import { ReefCheckSurveyCard } from '../ReefCheckSurveyCard';

const TimelineTablet = ({
siteId,
Expand Down Expand Up @@ -34,7 +36,7 @@ const TimelineTablet = ({
{isSiteIdValid && <AddButton siteId={siteId} />}
</Grid>
)}
{surveys.map((survey, index) => (
{(loading ? times(2, () => null) : surveys).map((survey, index) => (
<Grid
key={survey?.id || `loading-survey-${index}`}
className={classes.surveyWrapper}
Expand All @@ -50,10 +52,10 @@ const TimelineTablet = ({
width="30%"
lines={1}
>
{survey?.diveDate && (
{survey?.date && (
<Typography variant="h6" className={classes.dates}>
{displayTimeInLocalTimezone({
isoDate: survey.diveDate,
isoDate: survey.date,
format: 'LL/dd/yyyy',
displayTimezone: false,
timeZone,
Expand All @@ -63,14 +65,19 @@ const TimelineTablet = ({
</LoadingSkeleton>
</Grid>
<Grid className={classes.surveyCardWrapper} container item xs={12}>
<SurveyCard
pointId={pointId}
pointName={pointName}
isAdmin={isAdmin}
siteId={siteId}
survey={survey}
loading={loading}
/>
{survey?.type === 'survey' && (
<SurveyCard
pointId={pointId}
pointName={pointName}
isAdmin={isAdmin}
siteId={siteId}
survey={survey}
loading={loading}
/>
)}
{survey?.type === 'reefCheckSurvey' && (
<ReefCheckSurveyCard survey={survey} />
)}
</Grid>
</Grid>
))}
Expand Down
Loading

0 comments on commit bd00680

Please sign in to comment.