Skip to content

Commit

Permalink
Activity (#922)
Browse files Browse the repository at this point in the history
* first version

* changed layout for activities on landing page

* changed limit for activities on landing page
  • Loading branch information
MadsNyl authored Nov 16, 2023
1 parent 47cde63 commit 932a6ce
Show file tree
Hide file tree
Showing 8 changed files with 290 additions and 18 deletions.
14 changes: 12 additions & 2 deletions src/components/miscellaneous/EventListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,6 @@ const EventListItem = ({ event, sx }: EventListItemProps) => {
const { observe, width } = useDimensions();
const theme = useTheme();

const getColor = () => theme.palette.colors[event.organizer?.slug.toLowerCase() === Groups.NOK.toLowerCase() ? 'nok_event' : 'other_event'];

const [height, titleFontSize, contentFontSize] = useMemo(() => {
if (width < 400) {
return [68, 18, 13];
Expand All @@ -97,6 +95,18 @@ const EventListItem = ({ event, sx }: EventListItemProps) => {
const { data: categories = [] } = useCategories();
const categoryLabel = `${event.organizer ? `${event.organizer.name} | ` : ''}${categories.find((c) => c.id === event.category)?.text || 'Laster...'}`;

const getColor = () => {
if (categories.find((c) => c.id === event.category)?.text === 'Aktivitet') {
return theme.palette.colors.activity_event;
}

if (event.organizer?.slug.toLowerCase() === Groups.NOK.toLowerCase()) {
return theme.palette.colors.nok_event;
}

return theme.palette.colors.other_event;
};

return (
<EventListItemButton
borderColor={getColor()}
Expand Down
151 changes: 151 additions & 0 deletions src/pages/Events/components/ActivitiesDefaultView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import { Button, Divider, Stack, Theme, useMediaQuery } from '@mui/material';
import { makeStyles } from 'makeStyles';
import { useCallback, useMemo, useState } from 'react';
import { useForm } from 'react-hook-form';
import { useNavigate } from 'react-router-dom';
import { argsToParams } from 'utils';

import { useEvents } from 'hooks/Event';
import { useIsAuthenticated } from 'hooks/User';
import { useAnalytics } from 'hooks/Utils';

import Bool from 'components/inputs/Bool';
import SubmitButton from 'components/inputs/SubmitButton';
import TextField from 'components/inputs/TextField';
import Expand from 'components/layout/Expand';
import Pagination from 'components/layout/Pagination';
import Paper from 'components/layout/Paper';
import EventListItem, { EventListItemLoading } from 'components/miscellaneous/EventListItem';
import NotFoundIndicator from 'components/miscellaneous/NotFoundIndicator';

const useStyles = makeStyles()((theme) => ({
grid: {
display: 'grid',
gridTemplateColumns: '3fr 1fr',
gridGap: theme.spacing(2),
alignItems: 'self-start',
paddingBottom: theme.spacing(2),

[theme.breakpoints.down('lg')]: {
gridTemplateColumns: '1fr',
},
},
list: {
display: 'grid',
gridTemplateColumns: '1fr',
gap: theme.spacing(1),
[theme.breakpoints.down('lg')]: {
order: 1,
},
},
settings: {
display: 'grid',
gridGap: theme.spacing(1),
position: 'sticky',
top: 80,

[theme.breakpoints.down('lg')]: {
order: 0,
position: 'static',
top: 0,
},
},
}));

type Filters = {
activity: boolean;
search?: string;
open_for_sign_up?: boolean;
user_favorite?: boolean;
expired: boolean;
};

const ActivitiesDefaultView = () => {
const isAuthenticated = useIsAuthenticated();
const { event } = useAnalytics();
const getInitialFilters = useCallback((): Filters => {
const params = new URLSearchParams(location.search);
const activity = true;
const expired = params.get('expired') ? Boolean(params.get('expired') === 'true') : false;
const open_for_sign_up = params.get('open_for_sign_up') ? Boolean(params.get('open_for_sign_up') === 'true') : undefined;
const user_favorite = params.get('user_favorite') ? Boolean(params.get('user_favorite') === 'true') : undefined;
const search = params.get('search') || undefined;
return { activity, expired, search, open_for_sign_up, user_favorite };
}, []);
const { classes } = useStyles();
const navigate = useNavigate();
const lgDown = useMediaQuery((theme: Theme) => theme.breakpoints.down('lg'));
const [filters, setFilters] = useState<Filters>(getInitialFilters());
const { data, error, hasNextPage, fetchNextPage, isLoading, isFetching } = useEvents(filters);
const events = useMemo(() => (data ? data.pages.map((page) => page.results).flat() : []), [data]);
const { register, control, handleSubmit, setValue, formState } = useForm<Filters>({ defaultValues: getInitialFilters() });
const isEmpty = useMemo(() => (data !== undefined ? !data.pages.some((page) => Boolean(page.results.length)) : false), [data]);

const resetFilters = () => {
setValue('search', '');
setValue('expired', false);
setValue('user_favorite', false);
setFilters({ activity: true, expired: false, open_for_sign_up: false, user_favorite: false });
navigate(`${location.pathname}${argsToParams({ expired: false })}`, { replace: true });
};

const search = (data: Filters) => {
event('search', 'events', JSON.stringify(data));
setFilters(data);
navigate(`${location.pathname}${argsToParams(data)}`, { replace: true });
!lgDown || setSearchFormExpanded((prev) => !prev);
};

const [searchFormExpanded, setSearchFormExpanded] = useState(false);

const SearchForm = () => (
<form onSubmit={handleSubmit(search)}>
<TextField disabled={isFetching} formState={formState} label='Søk' margin='none' {...register('search')} />
<Bool control={control} formState={formState} label='Tidligere' name='expired' type='switch' />
<Bool control={control} formState={formState} label='Kun med åpen påmelding' name='open_for_sign_up' type='switch' />
{isAuthenticated && <Bool control={control} formState={formState} label='Favoritter' name='user_favorite' type='switch' />}
<SubmitButton disabled={isFetching} formState={formState}>
Søk
</SubmitButton>
<Divider sx={{ my: 1 }} />
<Button color='error' fullWidth onClick={resetFilters} variant='outlined'>
Tilbakestill
</Button>
</form>
);

return (
<>
<div className={classes.grid}>
<div className={classes.list}>
{isLoading && <EventListItemLoading />}
{isEmpty && <NotFoundIndicator header='Fant ingen arrangementer' />}
{error && <Paper>{error.detail}</Paper>}
{data !== undefined && (
<Pagination fullWidth hasNextPage={hasNextPage} isLoading={isFetching} nextPage={() => fetchNextPage()}>
<Stack gap={1}>
{events.map((event) => (
<EventListItem event={event} key={event.id} />
))}
</Stack>
</Pagination>
)}
{isFetching && <EventListItemLoading />}
</div>
{lgDown ? (
<div>
<Expand expanded={searchFormExpanded} flat header='Filtrering' onChange={() => setSearchFormExpanded((prev) => !prev)}>
<SearchForm />
</Expand>
</div>
) : (
<Paper className={classes.settings}>
<SearchForm />
</Paper>
)}
</div>
</>
);
};

export default ActivitiesDefaultView;
13 changes: 8 additions & 5 deletions src/pages/Events/components/EventsDefaultView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ const EventsDefaultView = () => {
setValue('category', '');
setValue('search', '');
setValue('expired', false);
setValue('user_favorite', false);
setFilters({ expired: false, open_for_sign_up: false, user_favorite: false });
navigate(`${location.pathname}${argsToParams({ expired: false })}`, { replace: true });
};
Expand All @@ -106,11 +107,13 @@ const EventsDefaultView = () => {
<TextField disabled={isFetching} formState={formState} label='Søk' margin='none' {...register('search')} />
{Boolean(categories.length) && (
<Select control={control} formState={formState} label='Kategori' name='category'>
{categories.map((value, index) => (
<MenuItem key={index} value={value.id}>
{value.text}
</MenuItem>
))}
{categories
.filter((category) => category.text !== 'Aktivitet')
.map((value, index) => (
<MenuItem key={index} value={value.id}>
{value.text}
</MenuItem>
))}
</Select>
)}
<Bool control={control} formState={formState} label='Tidligere' name='expired' type='switch' />
Expand Down
9 changes: 8 additions & 1 deletion src/pages/Events/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import CelebrationIcon from '@mui/icons-material/Celebration';
import DateRange from '@mui/icons-material/DateRangeRounded';
import Reorder from '@mui/icons-material/ReorderRounded';
import { Collapse, Skeleton } from '@mui/material';
Expand All @@ -9,12 +10,15 @@ import Banner from 'components/layout/Banner';
import Tabs from 'components/layout/Tabs';
import Page from 'components/navigation/Page';

import ActivitiesDefaultView from './components/ActivitiesDefaultView';

const EventsCalendarView = lazy(() => import(/* webpackChunkName: "events_calendar" */ 'pages/Landing/components/EventsCalendarView'));

const Events = () => {
const listTab = { value: 'list', label: 'Liste', icon: Reorder };
const activityTab = { value: 'activity', label: 'Aktiviteter', icon: CelebrationIcon };
const calendarTab = { value: 'calendar', label: 'Kalender', icon: DateRange };
const tabs = [listTab, calendarTab];
const tabs = [listTab, activityTab, calendarTab];
const [tab, setTab] = useState(listTab.value);

return (
Expand All @@ -23,6 +27,9 @@ const Events = () => {
<Collapse in={tab === listTab.value}>
<EventsDefaultView />
</Collapse>
<Collapse in={tab === activityTab.value}>
<ActivitiesDefaultView />
</Collapse>
<Collapse in={tab === calendarTab.value} mountOnEnter>
<Suspense fallback={<Skeleton height={695} variant='rectangular' />}>
<EventsCalendarView />
Expand Down
76 changes: 76 additions & 0 deletions src/pages/Landing/components/ActivityEventsListView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { Stack, styled, Theme, Typography, useMediaQuery } from '@mui/material';
import { useCallback, useState } from 'react';

import { useEvents } from 'hooks/Event';

import EventListItem, { EventListItemLoading } from 'components/miscellaneous/EventListItem';

const Container = styled('div')(({ theme }) => ({
display: 'grid',
alignItems: 'self-start',
gridTemplateColumns: '1fr 1fr',
gap: theme.spacing(1),
}));

const Text = styled(Typography)(({ theme }) => ({
color: theme.palette.text.secondary,
p: 0.5,
}));

const NO_OF_EVENTS_TO_SHOW = 6;
const NO_OF_EVENTS_TO_SHOW_MD_DOWN = 4;

type Filters = {
activity: boolean;
};

const ActivityEventsListView = () => {
const mdDown = useMediaQuery((theme: Theme) => theme.breakpoints.down('md'));
const getInitialFilters = useCallback((): Filters => {
const activity = true;
return { activity };
}, []);
const [filters] = useState<Filters>(getInitialFilters());

const { data, isLoading } = useEvents(filters);

if (isLoading) {
return (
<Stack gap={1}>
<EventListItemLoading />
<EventListItemLoading />
<EventListItemLoading />
</Stack>
);
} else if (!data?.pages[0]?.results.length) {
return (
<Text align='center' variant='subtitle1'>
Ingen kommende arrangementer
</Text>
);
} else if (mdDown) {
return (
<Stack gap={1}>
{data?.pages[0]?.results.slice(0, NO_OF_EVENTS_TO_SHOW_MD_DOWN).map((event) => (
<EventListItem event={event} key={event.id} />
))}
</Stack>
);
}

return (
<Container>
{data?.pages[0].results.length ? (
data?.pages[0]?.results.slice(0, NO_OF_EVENTS_TO_SHOW).map((event) => <EventListItem event={event} key={event.id} />)
) : (
<Stack gap={1}>
<Text align='center' variant='subtitle1'>
Ingen kommende aktiviteter
</Text>
</Stack>
)}
</Container>
);
};

export default ActivityEventsListView;
15 changes: 14 additions & 1 deletion src/pages/Landing/components/EventsCalendarView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { ReactNode, useEffect, useMemo, useState } from 'react';
import { Category, EventList } from 'types';
import { Groups } from 'types/Enums';

import { useCategories } from 'hooks/Categories';
import { useEvents } from 'hooks/Event';
import { useAnalytics } from 'hooks/Utils';

Expand Down Expand Up @@ -40,7 +41,19 @@ const Appointment = ({ children, data }: AppointmentProps) => {
setAnchorEl(null);
};

const getColor = (event: EventList) => theme.palette.colors[event.organizer?.slug.toLowerCase() === Groups.NOK.toLowerCase() ? 'nok_event' : 'other_event'];
const { data: categories = [] } = useCategories();

const getColor = (event: EventList) => {
if (categories.find((c) => c.id === event.category)?.text === 'Aktivitet') {
return theme.palette.colors.activity_event;
}

if (event.organizer?.slug.toLowerCase() === Groups.NOK.toLowerCase()) {
return theme.palette.colors.nok_event;
}

return theme.palette.colors.other_event;
};
return (
<>
<Button onClick={handleClick} sx={{ minWidth: '40px', width: '100%', height: '100%', textAlign: 'left', textTransform: 'none' }}>
Expand Down
27 changes: 18 additions & 9 deletions src/pages/Landing/components/EventsView.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import CelebrationIcon from '@mui/icons-material/Celebration';
import DateRange from '@mui/icons-material/DateRangeRounded';
import Reorder from '@mui/icons-material/ReorderRounded';
import { Collapse, Skeleton, styled } from '@mui/material';
Expand All @@ -10,32 +11,40 @@ import EventsListView from 'pages/Landing/components/EventsListView';
import Tabs from 'components/layout/Tabs';
import { AlertOnce } from 'components/miscellaneous/UserInformation';

import ActivityEventsListView from './ActivityEventsListView';

const EventsCalendarView = lazy(() => import('pages/Landing/components/EventsCalendarView'));

const EventsView = () => {
const { data, isLoading } = useEvents();
const listTab = { value: 'list', label: 'Liste', icon: Reorder };
const calendarTab = { value: 'calendar', label: 'Kalender', icon: DateRange };
const tabs = [listTab, calendarTab];
const activityTab = { value: 'activity', label: 'Aktiviteter', icon: CelebrationIcon };
const tabs = [listTab, activityTab, calendarTab];
const [tab, setTab] = useState(listTab.value);

const ColorInfo = styled('span', { shouldForwardProp: (prop) => prop !== 'color' })<{ color: 'nok_event' | 'other_event' }>(({ theme, color }) => ({
background: theme.palette.colors[color],
borderRadius: 3,
color: theme.palette.getContrastText(theme.palette.colors.nok_event),
padding: theme.spacing(0.25, 0.25),
}));
const ColorInfo = styled('span', { shouldForwardProp: (prop) => prop !== 'color' })<{ color: 'nok_event' | 'other_event' | 'activity_event' }>(
({ theme, color }) => ({
background: theme.palette.colors[color],
borderRadius: 3,
color: theme.palette.getContrastText(theme.palette.colors.nok_event),
padding: theme.spacing(0.25, 0.25),
}),
);

return (
<>
<Tabs selected={tab} setSelected={setTab} sx={{ mx: 'auto', width: 'fit-content', mb: 1 }} tabs={tabs} />
<AlertOnce cookieKey='NewEventColors' severity='info' sx={{ mb: 1 }} variant='outlined'>
Kurs og bedpres er <ColorInfo color='nok_event'>blå</ColorInfo>, mens sosiale og andre arrangementer er{' '}
<ColorInfo color='other_event'>oransje</ColorInfo> slik at det er enkelt å se hva som er hva.
Kurs og bedpres er <ColorInfo color='nok_event'>blå</ColorInfo>, aktiviteter er <ColorInfo color='activity_event'>lilla</ColorInfo>, mens sosiale og
andre arrangementer er <ColorInfo color='other_event'>oransje</ColorInfo> slik at det er enkelt å se hva som er hva.
</AlertOnce>
<Collapse in={tab === listTab.value}>
<EventsListView events={data?.pages[0]?.results || []} isLoading={isLoading} />
</Collapse>
<Collapse in={tab === activityTab.value}>
<ActivityEventsListView />
</Collapse>
<Collapse in={tab === calendarTab.value} mountOnEnter>
<Suspense fallback={<Skeleton height={695} variant='rectangular' />}>
<EventsCalendarView />
Expand Down
Loading

0 comments on commit 932a6ce

Please sign in to comment.