'use client' import { Fragment, useEffect, useMemo, useRef, useState } from 'react' import { Temporal } from '@js-temporal/polyfill' import Content from '/src/components/Content/Content' import Legend from '/src/components/Legend/Legend' import { PersonResponse } from '/src/config/api' import { useTranslation } from '/src/i18n/client' import { useStore } from '/src/stores' import useSettingsStore from '/src/stores/settingsStore' import { calculateAvailability, calculateTable, makeClass, relativeTimeFormat } from '/src/utils' import styles from './AvailabilityViewer.module.scss' import { usePalette } from '/hooks/usePalette' interface AvailabilityViewerProps { times: string[] timezone: string people: PersonResponse[] } const AvailabilityViewer = ({ times, timezone, people }: AvailabilityViewerProps) => { const { t, i18n } = useTranslation('event') const timeFormat = useStore(useSettingsStore, state => state.timeFormat) ?? '12h' const highlight = useStore(useSettingsStore, state => state.highlight) const [filteredPeople, setFilteredPeople] = useState(people.map(p => p.name)) const [tempFocus, setTempFocus] = useState() const [focusCount, setFocusCount] = useState() const wrapperRef = useRef(null) const [tooltip, setTooltip] = useState<{ x: number y: number available: string date: string people: string[] }>() // Calculate table const { rows, columns } = useMemo(() => calculateTable(times, i18n.language, timeFormat, timezone), [times, i18n.language, timeFormat, timezone]) // Calculate availabilities const { availabilities, min, max } = useMemo(() => calculateAvailability(times, people.filter(p => filteredPeople.includes(p.name))), [times, filteredPeople, people]) // Create the colour palette const palette = usePalette(Math.max((max - min) + 1, 2)) // Reselect everyone if the amount of people changes useEffect(() => { setFilteredPeople(people.map(p => p.name)) }, [people.length]) const heatmap = useMemo(() => columns.map((column, x) => {column ?
{column.header.dateLabel && }
{column.cells.map((cell, y) => { if (y === column.cells.length - 1) return null if (!cell) return
('greyed_times')} /> let peopleHere = availabilities.find(a => a.date === cell.serialized)?.people ?? [] if (tempFocus) { peopleHere = peopleHere.filter(p => p === tempFocus) } return
0 && styles.highlight, )} style={{ backgroundColor: (focusCount === undefined || focusCount === peopleHere.length) ? palette[tempFocus && peopleHere.length ? max : peopleHere.length] : 'transparent', ...cell.minute !== 0 && cell.minute !== 30 && { borderTopColor: 'transparent' }, ...cell.minute === 30 && { borderTopStyle: 'dotted' }, }} aria-label={peopleHere.join(', ')} onMouseEnter={e => { const cellBox = e.currentTarget.getBoundingClientRect() const wrapperBox = wrapperRef.current?.getBoundingClientRect() ?? { x: 0, y: 0 } setTooltip({ x: Math.round(cellBox.x - wrapperBox.x + cellBox.width / 2), y: Math.round(cellBox.y - wrapperBox.y + cellBox.height) + 6, available: `${peopleHere.length} / ${filteredPeople.length} ${t('available')}`, date: cell.label, people: peopleHere, }) }} onMouseLeave={() => setTooltip(undefined)} /> })}
:
} ), [ availabilities, columns, highlight, max, t, palette, tempFocus, focusCount, filteredPeople, ]) return <> {t('group.info1')} {people.length > 1 && <> {t('group.info2')}
{people.map(person => )}
}
{useMemo(() =>
{rows.map((row, i) =>
{row && }
)}
, [rows])} {heatmap}
{tooltip &&

{tooltip.available}

{tooltip.date} {!!filteredPeople.length &&
{tooltip.people.map(person => {person})} {filteredPeople.filter(p => !tooltip.people.includes(p)).map(person => {person} )}
}
}
} export default AvailabilityViewer