Skip to content
This repository has been archived by the owner on Nov 14, 2023. It is now read-only.

Commit

Permalink
Filter (#104)
Browse files Browse the repository at this point in the history
* Add filter with tests

* Fix filter and dynamic import the filter

* Add functionality for favorites

* Update tests

* Remove commented code
  • Loading branch information
tanettrimas authored Aug 1, 2023
1 parent 99390b6 commit 8a3debc
Show file tree
Hide file tree
Showing 14 changed files with 5,314 additions and 1,823 deletions.
21 changes: 19 additions & 2 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,21 @@
{
"extends": ["eslint:recommended", "next/core-web-vitals", "prettier"],
"rules": {}
"plugins": [
"@typescript-eslint"
],
"parser": "@typescript-eslint/parser",
"env": {
"jest": true
},
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"next/core-web-vitals",
"prettier"
],
"rules": {
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": [
"error"
]
}
}
66 changes: 66 additions & 0 deletions app/program/FilteredSessions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
'use client'

import { Session } from '@/app/program/program'
import { filter, useFilter } from '@/components/filter/filter'
import { SimpleTalk } from '@/app/program/SimpleTalk'
import { ProgramFilter } from '@/app/program/ProgramFilter'
import { useMemo } from 'react'
import styles from '@/app/program/page.module.css'

interface FilteredSessionProps {
sessions: Session[]
}

const getStatCount = (format: Session['format']) => (session: Session) => session.format === format

export default function FilteredSessions({ sessions }: FilteredSessionProps) {
const { filterState, updateFilter } = useFilter()
const filteredSessions = filter(sessions, filterState)

const stats = useMemo(() => {
return {
all: filteredSessions.length,
presentation: filteredSessions.filter(getStatCount('presentation')).length,
lightningTalks: filteredSessions.filter(getStatCount('lightning-talk')).length,
favorites: filteredSessions.filter(
(session) => filterState.favorites?.includes(session.sessionId),
).length,
}
}, [filterState.favorites, filteredSessions])

const toggleFavorite = (session: Session, isFavorite: boolean) => {
if (isFavorite) {
return updateFilter({
favorites: filterState.favorites?.filter((favorite) => favorite !== session.sessionId),
})
}
return updateFilter({ favorites: [...(filterState.favorites ?? []), session.sessionId] })
}

return (
<div>
<ProgramFilter filter={filterState} onFilterChange={updateFilter} statistics={stats} />
{filteredSessions
.filter(
(session) =>
!filterState.format ||
session.format === filterState.format ||
filterState.favorites?.includes(session.sessionId),
)
.map((session) => {
const isFavorite = filterState.favorites?.includes(session.sessionId) ?? false
return (
<SimpleTalk key={session.sessionId} session={session}>
<button
aria-label={!isFavorite ? 'Add to favorites' : 'Remove from favorites'}
className={styles.favorite}
onClick={() => toggleFavorite(session, isFavorite)}
>
{isFavorite ? '💔' : '❤️'}
</button>
</SimpleTalk>
)
})}
</div>
)
}
59 changes: 59 additions & 0 deletions app/program/ProgramFilter.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
.filter_container {
container-type: inline-size;
}

.filter {
background-color: var(--dark-blue-background);
padding: 1rem;
display: grid;
gap: 1rem;
grid-template-areas:
"day"
"language"
"format"
"clear-filter";
}

.filter p {
font-weight: bold;
color: var(--big-text-color);
}

.day {
grid-area: day;
}

.language {
grid-area: language;
}

.format {
grid-area: format;
}

.clear_filter {
grid-area: clear-filter;
}

.button {
font-family: inherit;
padding: 0.5rem 2rem;
cursor: pointer;
border: none;
margin: 0.2rem;
font-size: 1.2em;
font-weight: 700;
}

.button[data-active=true] {
background-color: white;
}

@container (min-width: 500px) {
.filter {
grid-template-areas:
"day language"
"format format"
"clear-filter clear-filter";
}
}
119 changes: 119 additions & 0 deletions app/program/ProgramFilter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import { Filter } from '@/components/filter/filter'
import styles from './ProgramFilter.module.css'

// weekdays
const WEDNESDAY = 3
const THURSDAY = 4

interface Props {
filter: Filter
onFilterChange: (filter: Filter) => void
statistics: {all: number, presentation: number, lightningTalks: number, favorites: number}
}

export const ProgramFilter = ({ onFilterChange, filter, statistics = { presentation: 0, favorites: 0, lightningTalks: 0, all: 0} }: Props) => {
const clearFilter = () =>
onFilterChange({
format: undefined,
language: undefined,
weekday: undefined,
})

return (
<section className={styles.filter_container}>
<div className={styles.filter}>
<div className={styles.day}>
<p>Day</p>
<button
disabled
className={`${styles.button} is-dark-blue button`}
data-active={filter.weekday === undefined}
onClick={() => onFilterChange({ weekday: undefined })}
>
Both
</button>
<button
disabled
className={`${styles.button} is-dark-blue button`}
data-active={filter.weekday === WEDNESDAY}
onClick={() => onFilterChange({ weekday: WEDNESDAY })}
>
Wednesday
</button>
<button
disabled
className={`${styles.button} is-dark-blue button`}
data-active={filter.weekday === THURSDAY}
onClick={() => onFilterChange({ weekday: THURSDAY })}
>
Thursday
</button>
</div>
<div className={styles.language}>
<p>Language</p>
<button
className={`${styles.button} is-dark-blue button`}
data-active={filter.language === undefined}
onClick={() => onFilterChange({ language: undefined })}
>
Both
</button>
<button
className={`${styles.button} is-dark-blue button`}
data-active={filter.language === 'no'}
onClick={() => onFilterChange({ language: 'no' })}
>
Norwegian
</button>
<button
className={`${styles.button} is-dark-blue button`}
data-active={filter.language === 'en'}
onClick={() => onFilterChange({ language: 'en' })}
>
English
</button>
</div>
<div className={styles.format}>
<p>Format</p>
<button
className={`${styles.button} is-dark-blue button`}
data-active={filter.format === undefined}
onClick={() => onFilterChange({ format: undefined })}
>
All ({statistics.all})
</button>
<button
className={`${styles.button} is-dark-blue button`}
data-active={filter.format === 'presentation'}
onClick={() => onFilterChange({ format: 'presentation' })}
>
Presentation ({statistics.presentation})
</button>
<button
className={`${styles.button} is-dark-blue button`}
data-active={filter.format === 'lightning-talk'}
onClick={() => onFilterChange({ format: 'lightning-talk' })}
>
Lightning talks ({statistics.lightningTalks})
</button>
<button
className={`${styles.button} is-dark-blue button`}
data-active={filter.format === 'favorites'}
onClick={() =>
onFilterChange({
format: 'favorites',
})
}
>
My favorites ({statistics.favorites})
</button>
</div>
<div className={styles.clear_filter}>
<button className={`${styles.button} is-dark-blue button`} onClick={clearFilter}>
Clear filters
</button>
</div>
</div>
</section>
)
}
31 changes: 18 additions & 13 deletions app/program/SimpleTalk.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,28 @@ import styles from './page.module.css'
import { formatter, prettyFormat, prettyLanguage } from './utils'
import { Session } from '@/app/program/program'
import Link from 'next/link'
import { ReactNode } from 'react'

export const SimpleTalk = ({
session: { endTime, format, language, room, speakers, startTime, title, id, length },
children,
}: {
session: Session
children?: ReactNode
}) => (
<Link href={`/program/${id}`} className={styles.session_link}>
<li className={styles.session}>
<h3 className={styles.session_title}>{title}</h3>
<p>Speakers: {speakers.map((speaker) => speaker.name).join(', ')}</p>
<p>Type: {prettyFormat(format)}</p>
<p>Language: {prettyLanguage(language)}</p>
<p>Length: {length} minutes</p>
{startTime && <p>{formatter.format(new Date(startTime))}</p>}
{endTime && <p>{formatter.format(new Date(endTime))}</p>}
{room && <p>{room}</p>}
</li>
</Link>
<div className={styles.session}>
<Link href={`/program/${id}`} className={styles.session_link}>
<li className={styles.session_item}>
<h3 className={styles.session_title}>{title}</h3>
<p>Speakers: {speakers.map((speaker) => speaker.name).join(', ')}</p>
<p>Type: {prettyFormat(format)}</p>
<p>Language: {prettyLanguage(language)}</p>
<p>Length: {length} minutes</p>
{startTime && <p>{formatter.format(new Date(startTime))}</p>}
{endTime && <p>{formatter.format(new Date(endTime))}</p>}
{room && <p>{room}</p>}
</li>
</Link>
{children}
</div>
)

26 changes: 20 additions & 6 deletions app/program/page.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,43 @@

.session {
display: flex;
flex-direction: column;
flex-direction: row;
justify-content: space-between;
gap: 1rem;
margin: 2rem;
padding: 2rem;
box-shadow: 0 0.5em 1em -0.125em rgba(10, 10, 10, 0.5), 0 0px 0 1px rgba(10, 10, 10, 0.05);
background-color: var(--dark-blue-background);

}

.session_item {
list-style: none;
white-space: pre-line;
}

.session_link, .session_link:hover {
color: var(--normal-text-color)
.session_item p {
margin: 0.5rem 0;
}

.session button {
align-self: flex-start;
.session_link, .session_link:hover {
color: var(--normal-text-color)
}

.session_title {
font-weight: bold;
color: var(--big-text-color)
color: var(--big-text-color);
margin-bottom: 1rem;
}

.content {
padding: 2rem 0;
}

.favorite {
align-self: center;
font-size: xx-large;
background-color: var(--big-text-color);
border-radius: 10%;
border: none;
}
14 changes: 5 additions & 9 deletions app/program/page.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,18 @@
import { Session, SessionFormat } from './program'
import styles from './page.module.css'
import { SessionGroup } from './SessionGroup'
import { fetchProgram } from '@/app/program/fetchProgram'
import dynamic from "next/dynamic";

const FilteredSessions = dynamic(() => import('./FilteredSessions'), { ssr: false })

export default async function ProgramPage() {
const data = await fetchProgram()

const sessionsGroupedByType = data.sessions.reduce((groups, session) => {
groups[session.format] = groups[session.format] ?? []
groups[session.format].push(session)
return groups
}, {} as Record<SessionFormat, Session[]>)
const sessions = data.sessions.filter(session => session.format !== "workshop")

return (
<article>
<h1 className={styles.program_title}>JavaZone Program 2023</h1>
<SessionGroup group={'presentation'} sessions={sessionsGroupedByType['presentation']} />
<SessionGroup group={'lightning-talk'} sessions={sessionsGroupedByType['lightning-talk']} />
<FilteredSessions sessions={sessions} />
</article>
)
}
Loading

1 comment on commit 8a3debc

@vercel
Copy link

@vercel vercel bot commented on 8a3debc Aug 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.