Skip to content

Commit

Permalink
Merge pull request #3840 from webkom/ivarnakken/aba-428-use-data-from…
Browse files Browse the repository at this point in the history
  • Loading branch information
ivarnakken committed May 2, 2023
2 parents d0aecaa + 134dfc9 commit 316195b
Show file tree
Hide file tree
Showing 9 changed files with 188 additions and 25 deletions.
11 changes: 11 additions & 0 deletions app/actions/EventActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
eventAdministrateSchema,
followersEventSchema,
} from 'app/reducers';
import type { ID } from 'app/store/models';
import type { Thunk, Action } from 'app/types';
import createQueryString from 'app/utils/createQueryString';
import { Event } from './ActionTypes';
Expand Down Expand Up @@ -418,3 +419,13 @@ export function isUserFollowing(eventId: number): Thunk<any> {
},
});
}
export function fetchAnalytics(eventId: ID): Thunk<Promise<Action>> {
return callAPI({
types: Event.FETCH,
endpoint: `/events/${String(eventId)}/statistics/`,
method: 'GET',
meta: {
errorMessage: 'Henting av analyse feilet',
},
});
}
2 changes: 1 addition & 1 deletion app/components/Chart/PieChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const DistributionPieChart = ({
distributionData,
}: {
distributionData: DistributionDataPoint[];
chartColors: string[];
chartColors?: string[];
dataKey: string;
}) => {
return (
Expand Down
4 changes: 2 additions & 2 deletions app/reducers/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ import config from 'app/config';
import { eventSchema } from 'app/reducers';
import { mutateComments } from 'app/reducers/comments';
import { isCurrentUser as checkIfCurrentUser } from 'app/routes/users/utils';
import type { DetailedEvent } from 'app/store/models/Event';
import createEntityReducer from 'app/utils/createEntityReducer';
import joinReducers from 'app/utils/joinReducers';
import mergeObjects from 'app/utils/mergeObjects';
import { Event } from '../actions/ActionTypes';
import type { Event as EventType, PhotoConsent } from '../models';

type State = any;
const mutateEvent = produce((newState: State, action: any): void => {
Expand Down Expand Up @@ -169,7 +169,7 @@ export default createEntityReducer<'events'>({
mutate,
});

function transformEvent(event: EventType) {
function transformEvent(event: DetailedEvent) {
return {
...event,
startTime: event.startTime && moment(event.startTime).toISOString(),
Expand Down
1 change: 0 additions & 1 deletion app/routes/events/EventListRoute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { createStructuredSelector } from 'reselect';
import { fetchList } from 'app/actions/EventActions';
import { selectSortedEvents } from 'app/reducers/events';
import createQueryString from 'app/utils/createQueryString';
import loadingIndicator from 'app/utils/loadingIndicator';
import withPreparedDispatch from 'app/utils/withPreparedDispatch';
import { selectPagination } from '../../reducers/selectors';
import EventList from './components/EventList';
Expand Down
8 changes: 0 additions & 8 deletions app/routes/events/components/Event.css
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,3 @@
margin: 0 auto;
border-bottom: 1px solid #6772e585;
}

.noRegistrationsText {
text-align: center;
}

.chartContainer {
overflow: hidden;
}
6 changes: 4 additions & 2 deletions app/routes/events/components/EventAdministrate/Statistics.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import type { Event, EventRegistration, Group } from 'app/models';
import type { EventRegistration, Group } from 'app/models';
import EventAttendeeStatistics from 'app/routes/events/components/EventAttendeeStatistics';
import type { DetailedEvent } from 'app/store/models/Event';

interface Props {
committees: Group[];
revueGroups: Group[];
registered: EventRegistration[];
unregistered: EventRegistration[];
event: Event;
event: DetailedEvent;
}

const Statistics = ({
Expand All @@ -18,6 +19,7 @@ const Statistics = ({
}: Props) => {
return (
<EventAttendeeStatistics
eventId={event.id}
registrations={registered}
unregistrations={unregistered}
committeeGroupIDs={committees.map((group) => group.id)}
Expand Down
31 changes: 31 additions & 0 deletions app/routes/events/components/EventAttendeeStatistics.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
.metrics {
height: 80px;
line-height: 1.3;
margin-bottom: 1rem;
}

.metricHeader {
font-weight: 500;
text-transform: uppercase;
color: var(--secondary-font-color);
}

.metricNumber {
font-size: 2rem;
font-weight: 500;
}

.metricDivider {
width: 2px;
height: 100%;
background-color: var(--border-gray);
}

.noRegistrationsText {
text-align: center;
color: var(--secondary-font-color);
}

.chartContainer {
overflow: hidden;
}
149 changes: 138 additions & 11 deletions app/routes/events/components/EventAttendeeStatistics.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,28 @@
import moment from 'moment';
import { useEffect, useState } from 'react';
import {
AreaChart,
Area,
CartesianGrid,
Legend,
Line,
LineChart,
ResponsiveContainer,
Tooltip,
XAxis,
YAxis,
} from 'recharts';
import { fetchAnalytics } from 'app/actions/EventActions';
import Card from 'app/components/Card';
import ChartLabel from 'app/components/Chart/ChartLabel';
import DistributionPieChart from 'app/components/Chart/PieChart';
import type { DistributionDataPoint } from 'app/components/Chart/utils';
import { Flex } from 'app/components/Layout';
import type { Dateish, EventRegistration } from 'app/models';
import styles from './Event.css';
import type { Dateish } from 'app/models';
import { useAppDispatch } from 'app/store/hooks';
import type { ID } from 'app/store/models';
import type { DetailedRegistration } from 'app/store/models/Registration';
import styles from './EventAttendeeStatistics.css';

interface RegistrationDateDataPoint {
name: string;
Expand Down Expand Up @@ -135,8 +143,8 @@ const sortAttendeeStatistics = (attendeeStatistics: AttendeeStatistics) => {
};

const createAttendeeDataPoints = (
registrations: Array<EventRegistration>,
unregistrations: Array<EventRegistration>,
registrations: DetailedRegistration[],
unregistrations: DetailedRegistration[],
committeeGroupIDs: number[],
revueGroupIDs: number[]
) => {
Expand Down Expand Up @@ -216,19 +224,135 @@ const isEventFromPreviousSemester = (eventStartTime: Dateish): boolean => {
);
};

const initialMetricValue = {
visitors: { title: 'Besøkende', value: 0 },
pageviews: { title: 'Sidevisninger', value: 0 },
visitDuration: { title: 'Besøkstid', value: 0 },
};

const calculateMetrics = (data) => {
// Copy initialMetricValue to avoid mutating it
const initialValue = JSON.parse(JSON.stringify(initialMetricValue));

return data.reduce((acc, item) => {
acc.visitors.value += item.visitors;
acc.pageviews.value += item.pageviews;
acc.visitDuration.value += item.visitDuration;

return acc;
}, initialValue);
};

const Analytics = ({ eventId }: { eventId: ID }) => {
const [metrics, setMetrics] = useState<{
visitors: { title: string; value: number };
pageviews: { title: string; value: number };
visitDuration: { title: string; value: number };
}>(initialMetricValue);
const [data, setData] = useState<{ date: string; visitors: number }[]>([]);

const dispatch = useAppDispatch();

useEffect(() => {
eventId &&
dispatch(fetchAnalytics(eventId)).then((res) => {
setData(res.payload);
setMetrics(calculateMetrics(res.payload));
});
}, [eventId, dispatch]);

return (
<>
<Flex wrap gap={40} className={styles.metrics}>
{Object.values(metrics).map((metric, i) => (
<>
{i !== 0 && <div className={styles.metricDivider} />}

<Flex column justifyContent="center" key={metric.title}>
<span className={styles.metricHeader}>{metric.title}</span>
<span className={styles.metricNumber}>
{metric.value.toLocaleString('no-NO')}
</span>
</Flex>
</>
))}
</Flex>

<ResponsiveContainer width="100%" aspect={2.0 / 0.8}>
<AreaChart data={data}>
<defs>
<linearGradient id="colorView" x1="0" y1="0" x2="0" y2="1">
<stop
offset="30%"
stopColor="var(--color-blue-5)"
stopOpacity={0.4}
/>
<stop
offset="75%"
stopColor="var(--color-blue-4)"
stopOpacity={0.3}
/>
<stop
offset="95%"
stopColor="var(--color-white)"
stopOpacity={0.2}
/>
</linearGradient>
</defs>
<Tooltip
contentStyle={{
borderColor: 'var(--border-gray)',
borderRadius: 'var(--border-radius-md)',
backgroundColor: 'var(--lego-card-color)',
}}
/>
<CartesianGrid
strokeDasharray="4 4"
stroke="var(--color-blue-5)"
opacity={0.4}
/>
<XAxis
dataKey="date"
tick={{ fill: 'var(--secondary-font-color)' }}
stroke="var(--border-gray)"
/>
<YAxis
dataKey="visitors"
tick={{ fill: 'var(--secondary-font-color)' }}
stroke="var(--border-gray)"
/>
<Area
name="Besøkende"
dataKey="visitors"
type="monotone"
stroke="var(--color-blue-5)"
strokeWidth={3}
strokeOpacity={1}
fill="url(#colorView)"
/>
</AreaChart>
</ResponsiveContainer>
</>
);
};

type Props = {
eventId: ID;
registrations: DetailedRegistration[];
unregistrations: DetailedRegistration[];
committeeGroupIDs: number[];
revueGroupIDs: number[];
eventStartTime: Dateish;
};

const EventAttendeeStatistics = ({
eventId,
registrations,
unregistrations,
committeeGroupIDs,
revueGroupIDs,
eventStartTime,
}: {
registrations: Array<EventRegistration>;
unregistrations: Array<EventRegistration>;
committeeGroupIDs: number[];
revueGroupIDs: number[];
eventStartTime: Dateish;
}) => {
}: Props) => {
const {
genderDistribution,
groupDistribution,
Expand All @@ -255,6 +379,9 @@ const EventAttendeeStatistics = ({
</span>
</Card>
)}

<Analytics eventId={eventId} />

{registrations.length === 0 ? (
<p className={styles.noRegistrationsText}>Ingen er påmeldt enda.</p>
) : (
Expand Down
1 change: 1 addition & 0 deletions app/store/models/Event.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ export type DetailedEvent = Pick<
| 'youtubeUrl'
| 'useContactTracing'
| 'mazemapPoi'
| 'activationTime'
> &
ObjectPermissionsMixin;

Expand Down

0 comments on commit 316195b

Please sign in to comment.