Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mapswipe changes on staging #6675

Merged
merged 10 commits into from
Jan 7, 2025
5 changes: 2 additions & 3 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,10 @@ jobs:
- env
- run:
name: Run yarn test
no_output_timeout: 20m
command: |
cd ${CIRCLE_WORKING_DIRECTORY}/frontend/
CI=true yarn test -w 3 --silent
CI=true yarn test -w 1 --silent
CI=true GENERATE_SOURCEMAP=false yarn build

backend-code-check-PEP8:
Expand Down Expand Up @@ -133,7 +134,6 @@ jobs:
--output text)
- run:
name: Make Database Backup
no_output_timeout: 15m
command: |
aws rds wait db-instance-available \
--db-instance-identifier ${RDS_ID}
Expand Down Expand Up @@ -211,7 +211,6 @@ jobs:
/tmp/tasking-manager.cfn.json > "$tmpfile" && mv "$tmpfile" $CIRCLE_WORKING_DIRECTORY/cfn-config-<< parameters.stack_name >>.json
- run:
name: Deploy to << parameters.stack_name >>
no_output_timeout: 15m
command: |
export NODE_PATH=/usr/local/share/.config/yarn/global/node_modules/
validate-template $CIRCLE_WORKING_DIRECTORY/scripts/aws/cloudformation/tasking-manager.template.js
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/assets/styles/_extra.scss
Original file line number Diff line number Diff line change
Expand Up @@ -611,8 +611,8 @@ a[href="https://www.mapbox.com/map-feedback/"]
color: rgba($blue-dark, 0.9);
}

.gap-0\.625 {
gap: 0.75rem;
.gap-0\.5 {
gap: 0.5rem;
}

.gap-0\.75 {
Expand Down
29 changes: 11 additions & 18 deletions frontend/src/components/partnerMapswipeStats/contributionsGrid.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import CalendarHeatmap from 'react-calendar-heatmap';
import { Tooltip } from 'react-tooltip';
import { FormattedMessage, useIntl } from 'react-intl';
import { format } from 'date-fns';
import PropTypes from 'prop-types';

import messages from './messages';

const LEGEND_INDEXES = [30, 50, 70, 100];

const Legend = () => {
const legendFontStyle = 'ph2 f7 blue-grey ttc';

Expand All @@ -25,27 +27,13 @@ const Legend = () => {
);
};

export const ContributionsGrid = ({ contributionsByDate = [] }) => {
export const ContributionsGrid = ({ contributionsByDate = [], startDate, endDate }) => {
const gridData = contributionsByDate.map((contribution) => ({
date: contribution.taskDate,
count: contribution.totalcontributions,
}));
const intl = useIntl();

const getDate = (isEndDate = false) => {
const today = new Date();
const currentYear = today.getFullYear();

const formatDate = (date) => {
const offset = date.getTimezoneOffset();
return new Date(date.getTime() - offset * 60 * 1000);
};

return !isEndDate
? formatDate(new Date(currentYear - 1, 11, 31))
: formatDate(new Date(currentYear, 11, 31));
};

const countValues = gridData.map((contribution) => contribution.count);
const maxValue = Math.max(...countValues);

Expand All @@ -69,16 +57,19 @@ export const ContributionsGrid = ({ contributionsByDate = [] }) => {
}
};

const endDateYear = endDate.split('-')[0];
const formattedEndDate = format(new Date(endDateYear, 11, 31), 'yyyy-MM-dd');

return (
<div>
<h3 className="f2 fw6 ttu barlow-condensed blue-dark mt0 pt2 mb4">
<h3 className="f2 fw6 ttu barlow-condensed blue-dark mt0 mb4">
<FormattedMessage {...messages.contributions} />
</h3>

<div className="bg-white pv4 pr4 shadow-6" style={{ fontSize: '1rem' }}>
<CalendarHeatmap
startDate={getDate()}
endDate={getDate(true)}
startDate={startDate}
endDate={formattedEndDate}
values={gridData}
classForValue={(value) => {
if (!value) return 'fill-tan';
Expand Down Expand Up @@ -114,4 +105,6 @@ ContributionsGrid.propTypes = {
totalcontributions: PropTypes.number,
}),
),
startDate: PropTypes.instanceOf(Date),
endDate: PropTypes.instanceOf(Date),
};
91 changes: 91 additions & 0 deletions frontend/src/components/partnerMapswipeStats/dateFilter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { useEffect } from 'react';
import { FormattedMessage } from 'react-intl';
import { format, parse } from 'date-fns';
import DatePicker from 'react-datepicker';
import 'react-datepicker/dist/react-datepicker.css';

import { CalendarIcon } from '../svgIcons';
import messages from './messages';

const today = new Date();
const currentYear = today.getFullYear();
const dateFormat = 'yyyy-MM-dd';

const initialState = {
fromDate: format(new Date(currentYear, 0, 1), dateFormat),
toDate: format(today, dateFormat),
};

export const DateFilter = ({ isLoading, filters, setFilters }) => {
// set initial date filter state
useEffect(() => {
setFilters((prev) => ({
...prev,
...initialState,
}));
}, [setFilters]);

const handleDateSelect = (key, selectedDate) => {
let { fromDate, toDate } = filters;
const selectedDateValue = new Date(selectedDate).valueOf();
const toDateValue = new Date(toDate).valueOf();
const fromDateValue = new Date(fromDate).valueOf();
// adjust from and to date based on greater/lesser value
if (key === 'fromDate' && selectedDateValue > toDateValue) {
fromDate = toDate;
toDate = selectedDate;
} else if (key === 'toDate' && selectedDateValue < fromDateValue) {
toDate = fromDate;
fromDate = selectedDate;
} else if (key === 'toDate') {
toDate = selectedDate;
} else {
fromDate = selectedDate;
}
// set filters state
setFilters((prev) => ({
...prev,
fromDate,
toDate,
}));
};

if (isLoading) return <></>;

return (
<div className="w-100 mt4 flex justify-end">
<div className="flex flex-column items-end gap-0.5">
<div className="flex items-center">
<CalendarIcon className="blue-grey dib w1 pr2 v-mid" />
<DatePicker
selected={filters.fromDate ? parse(filters.fromDate, dateFormat, new Date()) : null}
onChange={(date) => {
handleDateSelect('fromDate', format(date, dateFormat));
}}
dateFormat={dateFormat}
className="w4 pv2 ph1 ba b--grey-light"
showYearDropdown
scrollableYearDropdown
/>

<span className="ph3">to</span>

<CalendarIcon className="blue-grey dib w1 pr2 v-mid" />
<DatePicker
selected={filters.toDate ? parse(filters.toDate, dateFormat, new Date()) : null}
onChange={(date) => {
handleDateSelect('toDate', format(date, dateFormat));
}}
dateFormat={dateFormat}
className="w4 pv2 ph1 ba b--grey-light"
showYearDropdown
scrollableYearDropdown
/>
</div>
<span className="blue-grey f6">
<FormattedMessage {...messages.dateFilterSubText} />
</span>
</div>
</div>
);
};
8 changes: 8 additions & 0 deletions frontend/src/components/partnerMapswipeStats/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,14 @@ export default defineMessages({
id: 'management.partners.stats.mapswipe.groupMembers.error',
defaultMessage: 'Something went wrong!',
},
groupStatsboard: {
id: 'management.partners.stats.mapswipe.groupStatsboard',
defaultMessage: 'Group Statsboard',
},
dateFilterSubText: {
id: 'management.partners.stats.mapswipe.dateFilterSubText',
defaultMessage: 'All dates are calculated in UTC',
},
contributions: {
id: 'management.partners.stats.mapswipe.contributions',
defaultMessage: 'Contributions',
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/partnerMapswipeStats/overview.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export const getShortNumber = (value) => {
<span>
<FormattedNumber value={Number(shortNumberValue.substr(0, shortNumberValue.length - 1))} />
&nbsp;
<span className="fw1">{shortNumberValue.substr(-1)}</span>
<span>{shortNumberValue.substr(-1)}</span>
</span>
);
};
Expand Down
34 changes: 19 additions & 15 deletions frontend/src/views/partnersMapswipeStats.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useState } from 'react';
import { FormattedMessage } from 'react-intl';
import { useParams } from 'react-router-dom';
import ReactPlaceholder from 'react-placeholder';
Expand All @@ -9,6 +10,7 @@ import {
getShortNumber,
formatSecondsToTwoUnits,
} from '../components/partnerMapswipeStats/overview';
import { DateFilter } from '../components/partnerMapswipeStats/dateFilter';
import { GroupMembers } from '../components/partnerMapswipeStats/groupMembers';
import { ContributionsGrid } from '../components/partnerMapswipeStats/contributionsGrid';
import { ContributionsHeatmap } from '../components/partnerMapswipeStats/contributionsHeatmap';
Expand All @@ -19,6 +21,7 @@ import { SwipesByProjectType } from '../components/partnerMapswipeStats/swipesBy
import { SwipesByOrganization } from '../components/partnerMapswipeStats/swipesByOrganization';
import messages from './messages';
import { fetchLocalJSONAPI } from '../network/genericJSONRequest';

import './partnersMapswipeStats.scss';

const PagePlaceholder = () => (
Expand Down Expand Up @@ -57,23 +60,18 @@ const InfoBanner = () => {

export const PartnersMapswipeStats = () => {
const { id: partnerPermalink } = useParams();
const [filters, setFilters] = useState({}); // state for date filter
const { isLoading, isError, data, isRefetching } = useQuery({
queryKey: ['partners-mapswipe-filtered-statistics', partnerPermalink],
queryKey: [
'partners-mapswipe-filtered-statistics',
partnerPermalink,
filters.fromDate,
filters.toDate,
],
queryFn: async () => {
const today = new Date();
const currentYear = today.getFullYear();

const formatDate = (date) => {
const offset = date.getTimezoneOffset();
const adjustedDate = new Date(date.getTime() - offset * 60 * 1000);
return adjustedDate.toISOString().split('T')[0];
};

const fromDate = formatDate(new Date(currentYear, 0, 1));
const endDate = formatDate(today);

const { fromDate, toDate } = filters;
const response = await fetchLocalJSONAPI(
`partners/${partnerPermalink}/filtered-statistics/?fromDate=${fromDate}&toDate=${endDate}`,
`partners/${partnerPermalink}/filtered-statistics/?fromDate=${fromDate}&toDate=${toDate}`,
);
return response;
},
Expand Down Expand Up @@ -105,6 +103,8 @@ export const PartnersMapswipeStats = () => {
<InfoBanner />
<Overview />

<DateFilter isLoading={isLoading} filters={filters} setFilters={setFilters} />

<ReactPlaceholder customPlaceholder={<PagePlaceholder />} ready={!isLoading && !isRefetching}>
{!isLoading && isError ? (
<div className="pa3 pl0 bg-tan">
Expand All @@ -118,7 +118,11 @@ export const PartnersMapswipeStats = () => {
) : (
<>
<div className="mt3">
<ContributionsGrid contributionsByDate={data?.contributionsByDate} />
<ContributionsGrid
startDate={filters?.fromDate}
endDate={filters?.toDate}
contributionsByDate={data?.contributionsByDate}
/>
</div>

<div className="mt3">
Expand Down
16 changes: 14 additions & 2 deletions frontend/src/views/teams.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useState, useEffect } from 'react';
import { useSelector } from 'react-redux';
import { Link, useNavigate, useParams } from 'react-router-dom';
import { Link, useNavigate, useParams, useLocation } from 'react-router-dom';
import { FormattedMessage } from 'react-intl';
import { Form } from 'react-final-form';
import {
Expand Down Expand Up @@ -421,18 +421,30 @@ export function EditTeam(props) {
export function TeamDetail() {
const { id } = useParams();
useSetTitleTag(`Team #${id}`);
const location = useLocation();
const navigate = useNavigate();
const userDetails = useSelector((state) => state.auth.userDetails);
const token = useSelector((state) => state.auth.token);
const [error, loading, team] = useFetch(`teams/${id}/`);
// eslint-disable-next-line
const [projectsError, projectsLoading, projects] = useFetch(
`projects/?teamId=${id}&omitMapResults=true&projectStatuses=PUBLISHED,DRAFT,ARCHIVED`,
`projects/?teamId=${id}&omitMapResults=true&projectStatuses=PUBLISHED`,
id,
);
const [isMember, setIsMember] = useState(false);
const [managers, setManagers] = useState([]);
const [members, setMembers] = useState([]);

useEffect(() => {
if (!token) {
navigate('/login', {
state: {
from: location.pathname,
},
});
}
}, [location.pathname, navigate, token]);

useEffect(() => {
if (team && team.members) {
setManagers(filterActiveManagers(team.members));
Expand Down
Loading