Compare commits

...

2 Commits

Author SHA1 Message Date
Vencislav Atanasov c5776739e0 Add speaker pictures 2024-09-24 02:05:34 +03:00
Vencislav Atanasov 66ac808f42 Parse dates in the API response, simplify sorting 2024-09-24 01:40:07 +03:00
9 changed files with 52 additions and 23 deletions

View File

@ -4,7 +4,6 @@ import { getSpeakerName, isTrackHidden } from './utils.js';
import { Fragment, useState } from 'react'; import { Fragment, useState } from 'react';
import useScheduleTable from '../hooks/useScheduleTable.js'; import useScheduleTable from '../hooks/useScheduleTable.js';
import Event from './Event.jsx'; import Event from './Event.jsx';
import defaultSpeaker from '../assets/default-speaker.png';
import './Schedule.scss'; import './Schedule.scss';
import { langs } from './constants.js'; import { langs } from './constants.js';
import Speaker from './Speaker.jsx'; import Speaker from './Speaker.jsx';
@ -99,13 +98,13 @@ export default function Schedule({
<div className="grid members"> <div className="grid members">
{speakers.map(speaker => <div key={speaker.id} className="col4 wmember"> {speakers.map(speaker => <div key={speaker.id} className="col4 wmember">
<a href={'#'.concat(speaker.id)}> <a href={'#'.concat(speaker.id)}>
<img width="100" height="100" src={defaultSpeaker} alt={getSpeakerName(speaker)} /> <img width="100" height="100" src={speaker.picture} alt={getSpeakerName(speaker)} />
</a> </a>
</div>)} </div>)}
</div> </div>
{speakers.map(speaker => <Fragment key={speaker.id}> {speakers.map(speaker => <Fragment key={speaker.id}>
<div className="speaker" id={'speaker-'.concat(speaker.id)}> <div className="speaker" id={'speaker-'.concat(speaker.id)}>
<img width="100" height="100" src={defaultSpeaker} alt={getSpeakerName(speaker)}/> <img width="100" height="100" src={speaker.picture} alt={getSpeakerName(speaker)}/>
<h3>{getSpeakerName(speaker)}</h3> <h3>{getSpeakerName(speaker)}</h3>
<div className="icons"> <div className="icons">
{speaker.twitter && <a href={'https://twitter.com/'.concat(speaker.twitter)}> {speaker.twitter && <a href={'https://twitter.com/'.concat(speaker.twitter)}>

View File

@ -1,9 +1,8 @@
import ScheduleLoader from './ScheduleLoader.jsx'; import ScheduleLoader from './ScheduleLoader.jsx';
import { langs } from './constants.js'; import { langs } from './constants.js';
import { useMemo, useState } from 'react'; import { useMemo, useState } from 'react';
import { dateSorter } from '../utils.js'; import { sorter } from '../utils.js';
import useConferences from '../hooks/useConferences.js'; import useConferences from '../hooks/useConferences.js';
import { getConferenceYear } from './utils.js';
export default function ScheduleChooser() { export default function ScheduleChooser() {
const { const {
@ -12,7 +11,7 @@ export default function ScheduleChooser() {
isLoading, isLoading,
} = useConferences(); } = useConferences();
const conferences = useMemo(() => Array.isArray(data) ? data.sort(dateSorter('start_date')) : data, [data]); const conferences = useMemo(() => Array.isArray(data) ? data.sort(sorter('start_date')) : data, [data]);
const [ year, setYear ] = useState(2023); const [ year, setYear ] = useState(2023);
const [ lang, setLang ] = useState('bg'); const [ lang, setLang ] = useState('bg');
@ -29,7 +28,7 @@ export default function ScheduleChooser() {
{conferences && <> {conferences && <>
<select value={year} onChange={e => setYear(parseInt(e.target.value, 10))}> <select value={year} onChange={e => setYear(parseInt(e.target.value, 10))}>
{conferences.map(conference => {conferences.map(conference =>
<option key={conference.id} value={getConferenceYear(conference)}>{conference.title}</option>)} <option key={conference.id} value={conference.start_date.getFullYear()}>{conference.title}</option>)}
</select> </select>
</>} </>}
<ScheduleLoader year={year} lang={lang} /> <ScheduleLoader year={year} lang={lang} />

View File

@ -13,10 +13,8 @@ export default function ScheduleLoader({
isLoading, isLoading,
} = useConferences(); } = useConferences();
const conferenceId = useMemo(() => data && data.filter(conference => { const conferenceId = useMemo(() => data && data.filter(conference =>
const dt = new Date(Date.parse(conference.start_date)); conference.start_date.getFullYear() === year)?.[0]?.id, [data, year]);
return dt.getFullYear() === year;
})?.[0]?.id, [data, year]);
return (<> return (<>
{isLoading && <p>Loading conferences...</p>} {isLoading && <p>Loading conferences...</p>}

View File

@ -1,5 +1,3 @@
export const getConferenceYear = conference => (new Date(conference.start_date)).getFullYear();
export const getSpeakerName = speaker => speaker.first_name.concat(' ').concat(speaker.last_name); export const getSpeakerName = speaker => speaker.first_name.concat(' ').concat(speaker.last_name);
export const isTrackHidden = track => track?.name?.en === 'Other' || track?.name?.bg === 'Други'; export const isTrackHidden = track => track?.name?.en === 'Other' || track?.name?.bg === 'Други';

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

View File

@ -1,5 +1,24 @@
import useCfpRequest from './useCfpRequest.js'; import useCfpRequest from './useCfpRequest.js';
import { useMemo } from 'react';
import { parseDateFields } from '../utils.js';
export default function useConferences() { export default function useConferences() {
return useCfpRequest('.json'); const {
data: conferences,
...restOfRequest
} = useCfpRequest('.json');
const parsedConferences = useMemo(() => (conferences ?? []).map(conference =>
parseDateFields(conference, [
'start_date',
'end_date',
'created_at',
'updated_at',
])
), [conferences]);
return {
data: parsedConferences,
...restOfRequest,
};
} }

View File

@ -4,7 +4,7 @@ import useTracks from './useTracks.js';
import useEventTypes from './useEventTypes.js'; import useEventTypes from './useEventTypes.js';
import useHalls from './useHalls.js'; import useHalls from './useHalls.js';
import useSlots from './useSlots.js'; import useSlots from './useSlots.js';
import { calculateProgress, normalizeResponse } from '../utils.js'; import { calculateProgress, normalizeResponse, parseDateFields } from '../utils.js';
import { useMemo } from 'react'; import { useMemo } from 'react';
export default function useSchedule(conferenceId) { export default function useSchedule(conferenceId) {
@ -61,7 +61,12 @@ export default function useSchedule(conferenceId) {
const slots = useMemo(() => normalizeResponse(slotsResponse, [ const slots = useMemo(() => normalizeResponse(slotsResponse, [
['hall', halls, 'hall_id'], ['hall', halls, 'hall_id'],
['event', events, 'event_id'], ['event', events, 'event_id'],
]), [slotsResponse, halls, events]); ]).map(slot =>
parseDateFields(slot, [
'starts_at',
'ends_at',
])
), [slotsResponse, halls, events]);
const { const {
isStarted: isLoading, isStarted: isLoading,

View File

@ -1,4 +1,5 @@
import { useMemo } from 'react'; import { useMemo } from 'react';
import { sorter } from '../utils.js';
export default function useScheduleTable({ export default function useScheduleTable({
eventTypeId, eventTypeId,
@ -9,10 +10,18 @@ export default function useScheduleTable({
return useMemo(() => { return useMemo(() => {
const filteredEvents = events.filter(event => eventTypeId > 0 ? event.event_type_id === eventTypeId : true); const filteredEvents = events.filter(event => eventTypeId > 0 ? event.event_type_id === eventTypeId : true);
const filteredEventIds = filteredEvents.map(event => event.id); const filteredEventIds = filteredEvents.map(event => event.id);
const filteredSlots = slots.filter(slot => filteredEventIds.includes(slot.event_id)); const filteredSlots = slots.sort(sorter('starts_at')).filter(slot => filteredEventIds.includes(slot.event_id));
const days = Array.from(new Set(filteredSlots.map(slot =>
slot.starts_at.setHours(0, 0, 0, 0)
))).map(ts => new Date(ts));
const filteredHallIds = new Set(filteredSlots.map(slot => slot.hall_id)); const filteredHallIds = new Set(filteredSlots.map(slot => slot.hall_id));
const header = halls.filter(hall => filteredHallIds.has(hall.id)); const filteredHalls = halls.filter(hall => filteredHallIds.has(hall.id));
const hallSlots = Object.fromEntries(filteredHalls.map(hall => [
hall.id,
filteredSlots.filter(slot => slot.hall_id === hall.id),
]));
const header = filteredHalls;
const rows = filteredEvents.map(event => ({ const rows = filteredEvents.map(event => ({
id: event.id, id: event.id,
cells: [{ cells: [{

View File

@ -1,13 +1,15 @@
function sorter(a, b, fieldFn) { export const sorter = field => (a, b) => {
const fieldA = fieldFn(a); const fieldA = a[field];
const fieldB = fieldFn(b); const fieldB = b[field];
return fieldA === fieldB ? 0 : ( return fieldA === fieldB ? 0 : (
fieldA < fieldB ? -1 : 1 fieldA < fieldB ? -1 : 1
); );
} };
export const dateSorter = key => (a, b) => sorter(a, b, item => Date.parse(item[key])); export const parseDateFields = (item, dateFields) => Object.fromEntries(Object.entries(item).map(([key, value]) =>
[key, dateFields.includes(key) ? new Date(value) : value]
));
export function calculateProgress(...elements) { export function calculateProgress(...elements) {
const totalCount = elements.length; const totalCount = elements.length;