diff --git a/components/EventList/index.jsx b/components/EventList/index.jsx new file mode 100644 index 0000000..084f7aa --- /dev/null +++ b/components/EventList/index.jsx @@ -0,0 +1,232 @@ +import { imageUrlFor } from "@/store/sanity"; +import theme from "@/utils/theme"; +import dayjs from "dayjs"; +import Link from "next/link"; +import React from "react"; +import styled from "styled-components"; + +const EventList = props => { + const { events, venues } = props; + + const displayArena = event => { + switch (event.category) { + case "0": + return "Ekstern arena"; + break; + case "1": + return "Pride Parade"; + break; + case "2": + return "Pride Park"; + break; + case "3": + return "Pride House"; + break; + case "4": + return "Pride Art"; + break; + } + }; + + const displayEventType = event => { + switch (event.eventType) { + case "0": + return "Annet"; + break; + case "1": + return "Konsert"; + break; + case "2": + return "Debatt"; + break; + case "3": + return "Utstilling"; + break; + case "4": + return "Fest"; + break; + } + }; + + const getVenueName = reference => { + const venueData = venues.data.find(venue => venue._id === reference); + return venueData.name; + }; + + return ( + <> + {groupEventsByDay(events).map(day => { + const currentDay = dayjs(day[0].startingTime); + return ( + + +

+ {currentDay.format("dddd")}{" "} + {currentDay.format("D. MMMM")} +

+
+
+ {day.map(event => ( + + + + {event.image ? ( + + ) : ( + + )} + + + {event.title} + + {dayjs(event.startingTime).format("HH:mm")}- + {dayjs(event.endingTime).format("HH:mm")} + + + Hvor: + {displayArena(event)},{" "} + {event.location.venue + ? getVenueName(event.location.venue._ref) + : event.location.name} + + + Type: + {displayEventType(event)} + + + + + + ))} +
+
+ ); + })} + + ); +}; + +const groupEventsByDay = events => { + if (events.length === 0) { + return []; + } + + const sortedEvents = [...events]; + + sortedEvents.sort( + (a, b) => dayjs(a.startingTime).unix() - dayjs(b.startingTime).unix() + ); + + const groupedEvents = [[sortedEvents[0]]]; + + sortedEvents.slice(1).forEach(event => { + const lastGroup = groupedEvents[groupedEvents.length - 1]; + const lastEvent = lastGroup[lastGroup.length - 1]; + + const lastEventStart = dayjs(lastEvent.startingTime); + const currentEventStart = dayjs(event.startingTime); + + if (lastEventStart.format("dddd") === currentEventStart.format("dddd")) { + lastGroup.push(event); + } else { + groupedEvents.push([event]); + } + }); + return groupedEvents; +}; + +export default EventList; + +const Event = styled.div` + width: 100%; + max-width: 1000px; +`; + +const EventDay = styled.div` + background-color: ${theme.purple}; + width: 100%; + + h2 { + font-size: 25px; + font-weight: 500; + color: white; + text-transform: uppercase; + text-align: center; + } +`; + +const EventLink = styled.div` + cursor: pointer; + border-bottom: 2px solid lightgrey; + padding: 10px 0; + + &:last-child { + border-bottom: 0; + } + + & > a { + display: flex; + flex-direction: row; + align-items: center; + text-decoration: none; + + :hover { + text-decoration: underline; + } + } +`; + +const EventImage = styled.img` + width: 80px; + height: 80px; + object-fit: cover; +`; + +const EventInfo = styled.div` + display: flex; + flex-flow: row wrap; + justify-content: flex-start; + margin-left: 20px; + width: 100%; +`; + +const EventTitle = styled.div` + width: 100%; + font-size: 20px; + font-weight: 500; +`; + +const EventTime = styled.div` + font-size: 18px; + font-weight: 600; + color: ${theme.orange}; + margin-right: 10px; +`; + +const EventPlace = styled.div` + font-size: 18px; + font-weight: 300; + margin-right: 10px; +`; + +const EventType = styled.div` + font-size: 18px; + font-weight: 300; +`; + +const Descriptor = styled.span` + font-size: 18px; + font-weight: 500; +`; diff --git a/components/Filter/Selector.jsx b/components/Filter/Selector.jsx new file mode 100644 index 0000000..eb813c1 --- /dev/null +++ b/components/Filter/Selector.jsx @@ -0,0 +1,62 @@ +import React, { useCallback, useState } from "react"; +import styled from "styled-components"; + +const Selector = ({ selectors, defaultSelector, className }) => { + const [checked, setChecked] = useState(defaultSelector); + const onChange = useCallback( + (value, callback) => () => { + setChecked(value); + callback(value); + }, + [setChecked] + ); + + const inspectedChecked = selectors.some(({ value }) => value === checked) + ? checked + : defaultSelector; + + const selectorElements = selectors.map(({ name, value, callback }) => ( + + )); + + return ( + + Arena + {selectorElements} + + ); +}; + +export default Selector; + +const Container = styled.div` + width: 100%; +`; + +const SelectorTitle = styled.div` + font-weight: bold; + text-align: center; + margin-bottom: 5px; +`; + +const SelectorList = styled.div` + display: flex; + flex-wrap: wrap; + justify-content: center; + + label { + margin: 2px 8px; + } +`; + +const SelectorLabel = styled.span` + margin-left: 5px; +`; diff --git a/components/Filter/index.jsx b/components/Filter/index.jsx new file mode 100644 index 0000000..d69f372 --- /dev/null +++ b/components/Filter/index.jsx @@ -0,0 +1,17 @@ +import theme from "@/utils/theme"; +import React from "react"; +import styled from "styled-components"; +import Selector from "./Selector"; + +const Filter = ({ selector }) => ( + {selector && } +); + +export default Filter; + +const Wrapper = styled.div` + display: flex; + flex-direction: column; + padding: 10px; + border: 3px solid ${theme.purple}; +`; diff --git a/components/Filter/useURLFilter.js b/components/Filter/useURLFilter.js new file mode 100644 index 0000000..3f4e022 --- /dev/null +++ b/components/Filter/useURLFilter.js @@ -0,0 +1,63 @@ +import Router from "next/router"; +import { useMemo } from "react"; + +const toArray = val => + val === undefined ? [] : Array.isArray(val) ? val : [val]; + +export default (objects, query) => + useMemo(() => { + let filteredObjects = objects; + Object.keys(query).forEach(key => { + const filters = toArray(query[key]); + filteredObjects = + filters.length === 0 + ? objects // Return all objects if no filtes are applied + : objects.filter(obj => filters.includes(obj[key])); + }); + return filteredObjects; + }, [objects, query]); + +export const addFilter = (key, value) => { + const prevFilterList = toArray(Router.query[key]); + const newFilterList = prevFilterList.includes(value) + ? prevFilterList + : [...prevFilterList, value]; + Router.replace({ + pathname: Router.route, + query: { + ...Router.query, + [key]: newFilterList.length === 1 ? newFilterList[0] : newFilterList + } + }); +}; + +export const setFilter = (key, value) => { + Router.replace({ + pathname: Router.route, + query: { ...Router.query, [key]: value } + }); +}; + +export const removeFilter = (key, value) => { + const prevFilterList = toArray(Router.query[key]); + const newFilterList = prevFilterList.filter(v => v !== value); + Router.replace({ + pathname: Router.route, + query: { + ...Router.query, + [key]: newFilterList.length === 1 ? newFilterList[0] : newFilterList + } + }); +}; + +export const resetFilter = key => { + Router.replace({ + pathname: Router.route, + query: Object.keys(Router.query).reduce((query, k) => { + if (k !== key) { + query[k] = Router.query[k]; + } + return query; + }, {}) + }); +}; diff --git a/pages/events/index.jsx b/pages/events/index.jsx index 2f9cdd1..924e98b 100644 --- a/pages/events/index.jsx +++ b/pages/events/index.jsx @@ -1,165 +1,92 @@ +import EventList from "@/components/EventList"; +import Filter from "@/components/Filter"; +import useURLFilter, { + resetFilter, + setFilter +} from "@/components/Filter/useURLFilter"; import Sheet from "@/components/Sheet"; import { eventsActions, getEvents } from "@/store/events"; import { webResponseInitial } from "@/store/helpers"; -import { imageUrlFor } from "@/store/sanity"; import { getVenues, venuesActions } from "@/store/venues"; import theme from "@/utils/theme"; -import dayjs from "dayjs"; import NextSeo from "next-seo"; -import Link from "next/link"; -import React from "react"; +import React, { useState } from "react"; import { connect } from "react-redux"; import styled from "styled-components"; -const groupEventsByDay = events => { - if (events.length === 0) { - return []; - } - - const sortedEvents = [...events]; - - sortedEvents.sort( - (a, b) => dayjs(a.startingTime).unix() - dayjs(b.startingTime).unix() - ); - - const groupedEvents = [[sortedEvents[0]]]; - - sortedEvents.slice(1).forEach(event => { - const lastGroup = groupedEvents[groupedEvents.length - 1]; - const lastEvent = lastGroup[lastGroup.length - 1]; - - const lastEventStart = dayjs(lastEvent.startingTime); - const currentEventStart = dayjs(event.startingTime); - - if (lastEventStart.format("dddd") === currentEventStart.format("dddd")) { - lastGroup.push(event); - } else { - groupedEvents.push([event]); - } - }); - - return groupedEvents; -}; - -const displayArena = event => { - switch (event.category) { +const arenaNameMapper = arena => { + switch (arena) { case "0": return "Ekstern arena"; - break; case "1": return "Pride Parade"; - break; case "2": return "Pride Park"; - break; case "3": return "Pride House"; - break; case "4": return "Pride Art"; - break; - } -}; - -const displayEventType = event => { - switch (event.eventType) { - case "0": - return "Annet"; - break; - case "1": - return "Konsert"; - break; - case "2": - return "Debatt"; - break; - case "3": - return "Utstilling"; - break; - case "4": - return "Fest"; - break; + default: + return "Ukjent"; } }; const Events = props => { - const { events, venues } = props; + const { events, venues, query } = props; + const [visible, setVisible] = useState(false); + const filteredEvents = useURLFilter(events.data || [], query); + + const toggleFilter = () => setVisible(!visible); + const defaultSelector = query.category || "-1"; if (events.status !== "SUCCESS" || venues.status !== "SUCCESS") { // TODO: Make a better UX while loading return
Laster ...
; } - const getVenueName = reference => { - const venueData = venues.data.find(venue => venue._id === reference); - return venueData.name; - }; - return ( <> Program 2019 - {!events.data.length ?

Kommer snart!

: null} - - {groupEventsByDay(events.data).map(day => { - const currentDay = dayjs(day[0].startingTime); - return ( - - -

- {currentDay.format("dddd")}{" "} - {currentDay.format("D. MMMM")} -

-
-
- {day.map(event => ( - - - - {event.image ? ( - - ) : ( - - )} - - - {event.title} - - {dayjs(event.startingTime).format("HH:mm")}- - {dayjs(event.endingTime).format("HH:mm")} - - - Hvor: - {displayArena(event)},{" "} - {event.location.venue - ? getVenueName(event.location.venue._ref) - : event.location.name} - - - Type: - {displayEventType(event)} - - - - - - ))} -
-
- ); - })} + resetFilter("category") + }, + { + name: "Pride Parade", + value: "1", + callback: value => setFilter("category", "1") + }, + { + name: "Pride Park", + value: "2", + callback: value => setFilter("category", "2") + }, + { + name: "Pride House", + value: "3", + callback: value => setFilter("category", "3") + }, + { + name: "Pride Art", + value: "4", + callback: value => setFilter("category", "4") + } + ] + }} + /> + + {events.data.length ? ( + + ) : ( +

Kommer snart!

+ )}
{ ); }; -Events.getInitialProps = async ({ store, isServer }) => { +Events.getInitialProps = async ({ store, isServer, query }) => { if (store.getState().events.status === webResponseInitial().status) { store.dispatch(eventsActions.request()); if (isServer) { @@ -209,6 +136,8 @@ Events.getInitialProps = async ({ store, isServer }) => { } } } + + return { query }; }; const mapStateToProps = state => ({ @@ -223,85 +152,3 @@ const PageTitle = styled.h1` text-transform: uppercase; text-align: center; `; - -const Event = styled.div` - width: 100%; - max-width: 1000px; -`; - -const EventDay = styled.div` - background-color: ${theme.purple}; - width: 100%; - - h2 { - font-size: 25px; - font-weight: 500; - color: white; - text-transform: uppercase; - text-align: center; - } -`; - -const EventLink = styled.div` - cursor: pointer; - border-bottom: 2px solid lightgrey; - padding: 10px 0; - - &:last-child { - border-bottom: 0; - } - - & > a { - display: flex; - flex-direction: row; - align-items: center; - text-decoration: none; - - :hover { - text-decoration: underline; - } - } -`; - -const EventImage = styled.img` - width: 80px; - height: 80px; - object-fit: cover; -`; - -const EventInfo = styled.div` - display: flex; - flex-flow: row wrap; - justify-content: flex-start; - margin-left: 20px; - width: 100%; -`; - -const EventTitle = styled.div` - width: 100%; - font-size: 20px; - font-weight: 500; -`; - -const EventTime = styled.div` - font-size: 18px; - font-weight: 600; - color: ${theme.orange}; - margin-right: 10px; -`; - -const EventPlace = styled.div` - font-size: 18px; - font-weight: 300; - margin-right: 10px; -`; - -const EventType = styled.div` - font-size: 18px; - font-weight: 300; -`; - -const Descriptor = styled.span` - font-size: 18px; - font-weight: 500; -`; diff --git a/utils/theme.js b/utils/theme.js index 993b1ba..5fd28f1 100644 --- a/utils/theme.js +++ b/utils/theme.js @@ -3,13 +3,16 @@ import { rgb } from "polished"; export default { red: rgb(164, 29, 47), orange: rgb(239, 82, 27), + lightOrange: rgb(250, 198, 173), yellow: rgb(255, 193, 1), green: rgb(65, 140, 98), + lightGreen: rgb(199, 223, 213), blue: rgb(51, 80, 185), lightBlue: rgb(167, 195, 245), purple: rgb(53, 32, 118), lightPurple: rgb(191, 180, 211), pink: rgb(227, 80, 161), + lightPink: rgb(242, 199, 225), gray: rgb(204, 204, 204), darkgray: rgb(74, 74, 74), background: rgb(241, 244, 249)