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

Add event list/questionnaire to calendar #2186

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/features/calendar/l10n/messageIds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ export default makeMessages('feat.calendar', {
nextDay: m<{ dates: ReactElement }>('Next day {dates}'),
nextWeek: m<{ dates: ReactElement }>('Next week {dates}'),
},
openEventList: m('Make questionnaire'),
},
shortWeek: m<{ weekNumber: number }>('w {weekNumber}'),
showMore: m('Show'),
Expand Down
1 change: 1 addition & 0 deletions src/features/campaigns/l10n/messageIds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ export default makeMessages('feat.campaigns', {
allCampaigns: m('All Projects & Activities'),
archive: m('Archive'),
calendar: m('Calendar'),
eventList: m('Event List'),
insights: m('Insights'),
overview: m('Overview'),
},
Expand Down
45 changes: 44 additions & 1 deletion src/features/events/components/SelectionBar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,54 @@ import { resetSelection } from 'features/events/store';
import { RootState } from 'core/store';
import SelectionBarEllipsis from '../SelectionBarEllipsis';
import useParticipantPool from 'features/events/hooks/useParticipantPool';
import { useAppDispatch, useAppSelector } from 'core/hooks';
import {
useAppDispatch,
useAppSelector,
useNumericRouteParams,
} from 'core/hooks';

const SelectionBar = () => {
const dispatch = useAppDispatch();
const [participantsDialogOpen, setParticipantsDialogOpen] = useState(false);
const { affectedParticipantIds } = useParticipantPool();
const eventList = useAppSelector((state) => state.events.eventList);
const selectedEventIds = useAppSelector(
(state: RootState) => state.events.selectedEventIds
);
const { orgId } = useNumericRouteParams();

const handleOpenEventList = () => {
const filteredEvents = eventList.items
.filter(
(item) => item.data?.id && selectedEventIds.includes(item.data.id)
)
.map((x) => x.data);

const endDates = filteredEvents.map((x) => x?.end_time.slice(0, 10));
const startDates = filteredEvents.map((x) => x?.start_time.slice(0, 10));

const minDate = startDates.reduce((min, current) => {
const currentDate = new Date(current || '');
const minDate = new Date(min || '');
return currentDate < minDate ? current : min;
});

const maxDate = endDates.reduce((max, current) => {
const currentDate = new Date(current || '');
const maxDate = new Date(max || '');

return maxDate > currentDate ? max : current;
});

window
.open(
`/organize/${orgId}/projects/eventlist?minDate=${
minDate || ''
}&maxDate=${maxDate || ''}&ids=${selectedEventIds.join(',')}`,
'_blank'
)
?.focus();
};

const handleDeselect = () => {
dispatch(resetSelection());
Expand Down Expand Up @@ -68,6 +107,10 @@ const SelectionBar = () => {
gap={1}
justifyContent="center"
>
<Button onClick={handleOpenEventList} variant="text">
<Msg id={messageIds.selectionBar.openEventList} />
</Button>

<Badge
badgeContent={affectedParticipantIds.length}
color="primary"
Expand Down
22 changes: 22 additions & 0 deletions src/features/events/hooks/useDateRouterParam.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { useRouter } from 'next/router';
import { useMemo } from 'react';

export default function useDateRouterParam(paramName: string): Date | null {
const router = useRouter();

return useMemo(() => {
const rawValue = router.query[paramName];

if (typeof rawValue !== 'string') {
return null;
}

const date = new Date(rawValue);

if (isNaN(date.valueOf())) {
return null;
}

return date;
}, [router.query]);
}
2 changes: 2 additions & 0 deletions src/locale/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ feat:
moveMenuHeader: Move {numberOfEvents} events to
nextDay: Next day {dates}
nextWeek: Next week {dates}
openEventList: Make questionnaire
shortWeek: w {weekNumber}
showMore: Show
today: Today
Expand Down Expand Up @@ -318,6 +319,7 @@ feat:
allCampaigns: All Projects & Activities
archive: Archive
calendar: Calendar
eventList: Event List
insights: Insights
overview: Overview
linkGroup:
Expand Down
128 changes: 128 additions & 0 deletions src/pages/organize/[orgId]/projects/eventlist/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import {
Checkbox,
Table,
TableBody,
TableCell,
TableHead,
TableRow,
TextField,
} from '@mui/material';
import { Box } from '@mui/system';
import Head from 'next/head';
import { useRouter } from 'next/router';
import { FC, useMemo } from 'react';

import { useNumericRouteParams } from 'core/hooks';
import { useMessages } from 'core/i18n';
import messageIds from 'features/campaigns/l10n/messageIds';
import useEventsFromDateRange from 'features/events/hooks/useEventsFromDateRange';
import useDateRouterParam from 'features/events/hooks/useDateRouterParam';

function batchArray<T>(items: T[], batchSize: number): T[][] {
const batches: T[][] = [];

const numBatches = Math.ceil(items.length / batchSize);

for (let batchIndex = 0; batchIndex < numBatches; batchIndex++) {
batches[batchIndex] = items.slice(
batchIndex * batchSize,
Math.min(items.length, (batchIndex + 1) * batchSize)
);
}

return batches;
}

const EventList: FC<{ orgId: number }> = ({ orgId }) => {
const router = useRouter();
const messages = useMessages(messageIds);

const selectedEventIds = useMemo(() => {
const { ids } = router.query;
if (typeof ids !== 'string') {
return [];
}

const parsedIds = ids.split(',').map((x) => Number(x));

return parsedIds;
}, [router.query]);

const endDate = useDateRouterParam('maxDate') || new Date();
const startDate = useDateRouterParam('minDate') || new Date();

const filteredEvents = useEventsFromDateRange(
startDate,
endDate,
orgId
).filter((x) => selectedEventIds.includes(x.data.id));

const batches = batchArray(filteredEvents, 30);

return (
<>
<Head>
<title>{messages.layout.eventList()}</title>
</Head>

{batches.map((batch, index) => (
<Box key={index} sx={{ height: '100vh' }}>
<Box sx={{ display: 'flex', justifyContent: 'flex-end', p: 2 }}>
<TextField
disabled
InputLabelProps={{ shrink: true }}
label="Name"
value=""
variant="standard"
/>
</Box>

<Table
size="small"
sx={{
// Remove borders for all cells
'& .MuiTableCell-root': {
borderBottom: 'none',
},
}}
>
<TableHead>
<TableRow>
<TableCell sx={{ maxWidth: '15px' }} />
<TableCell sx={{ maxWidth: '35px' }}>Date</TableCell>
<TableCell>Title</TableCell>
</TableRow>
</TableHead>

<TableBody>
{batch.map((event) => (
<TableRow key={event.data.id}>
<TableCell sx={{ maxWidth: '15px' }}>
<Checkbox />
</TableCell>
<TableCell sx={{ maxWidth: '35px' }}>
{new Date(event.data.end_time).toLocaleDateString()}
</TableCell>
<TableCell>{event.data.title}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</Box>
))}

{selectedEventIds.length === 0 && <p>No events selected.</p>}
</>
);
};

const Wrapper = () => {
const { orgId } = useNumericRouteParams();
if (!orgId) {
return;
}

return <EventList orgId={orgId} />;
};

export default Wrapper;
Loading