diff --git a/frontend/src/app/how-to/page.tsx b/frontend/src/app/how-to/page.tsx index 36c3774..e7e7f48 100644 --- a/frontend/src/app/how-to/page.tsx +++ b/frontend/src/app/how-to/page.tsx @@ -1,7 +1,7 @@ import { Trans } from 'react-i18next/TransWithoutContext' import { Metadata } from 'next' import Link from 'next/link' -import { range } from '@giraugh/tools' +import { range, rotateArray } from '@giraugh/tools' import AvailabilityViewer from '/src/components/AvailabilityViewer/AvailabilityViewer' import Button from '/src/components/Button/Button' @@ -12,6 +12,7 @@ import Section from '/src/components/Section/Section' import TimeRangeField from '/src/components/TimeRangeField/TimeRangeField' import Video from '/src/components/Video/Video' import { useTranslation } from '/src/i18n/server' +import { getWeekdayNames } from '/src/utils' import styles from './page.module.scss' @@ -42,7 +43,7 @@ const Page = async () => {

___

{t('help:p4')}

-
{['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'].map(d => {d})}
+
{rotateArray(getWeekdayNames(i18n.language, 'short')).map(d => {d})}
{range(11, 17).map(d => {d})}

{t('help:p5')}

@@ -61,6 +62,17 @@ const Page = async () => {

{t('help:p8')}

{t('help:p9')}

{t('help:p10')}

+
diff --git a/frontend/src/components/AvailabilityViewer/AvailabilityViewer.module.scss b/frontend/src/components/AvailabilityViewer/AvailabilityViewer.module.scss index af16d5e..be75eec 100644 --- a/frontend/src/components/AvailabilityViewer/AvailabilityViewer.module.scss +++ b/frontend/src/components/AvailabilityViewer/AvailabilityViewer.module.scss @@ -167,3 +167,45 @@ width: 12px; flex-shrink: 0; } + +.tooltip { + position: absolute; + transform: translateX(-50%); + border: 1px solid var(--text); + border-radius: 3px; + padding: 4px 8px; + background-color: var(--background); + max-width: 200px; + pointer-events: none; + z-index: 100; + user-select: none; + + h3 { + font-size: 15px; + margin: 0; + font-weight: 700; + } + & > span { + font-size: 13px; + display: block; + opacity: .8; + font-weight: 600; + } + & > div { + font-size: 13px; + padding: 4px 0; + + span { + display: inline-block; + margin: 2px; + padding: 1px 4px; + border: 1px solid var(--primary); + border-radius: 3px; + + &[data-disabled=true] { + opacity: .5; + border-color: var(--text); + } + } + } +} diff --git a/frontend/src/components/AvailabilityViewer/AvailabilityViewer.styles.js b/frontend/src/components/AvailabilityViewer/AvailabilityViewer.styles.js deleted file mode 100644 index 89801a2..0000000 --- a/frontend/src/components/AvailabilityViewer/AvailabilityViewer.styles.js +++ /dev/null @@ -1,47 +0,0 @@ -import { styled } from 'goober' - -export const Tooltip = styled('div')` - position: absolute; - top: ${props => props.$y}px; - left: ${props => props.$x}px; - transform: translateX(-50%); - border: 1px solid var(--text); - border-radius: 3px; - padding: 4px 8px; - background-color: var(--background); - max-width: 200px; - pointer-events: none; - z-index: 100; - user-select: none; -` - -export const TooltipTitle = styled('span')` - font-size: 15px; - display: block; - font-weight: 700; -` - -export const TooltipDate = styled('span')` - font-size: 13px; - display: block; - opacity: .8; - font-weight: 600; -` - -export const TooltipContent = styled('div')` - font-size: 13px; - padding: 4px 0; -` - -export const TooltipPerson = styled('span')` - display: inline-block; - margin: 2px; - padding: 1px 4px; - border: 1px solid var(--primary); - border-radius: 3px; - - ${props => props.disabled && ` - opacity: .5; - border-color: var(--text); - `} -` diff --git a/frontend/src/components/AvailabilityViewer/AvailabilityViewer.tsx b/frontend/src/components/AvailabilityViewer/AvailabilityViewer.tsx index 3b9e261..13157b8 100644 --- a/frontend/src/components/AvailabilityViewer/AvailabilityViewer.tsx +++ b/frontend/src/components/AvailabilityViewer/AvailabilityViewer.tsx @@ -1,6 +1,6 @@ 'use client' -import { Fragment, useEffect, useMemo, useState } from 'react' +import { Fragment, useEffect, useMemo, useRef, useState } from 'react' import { Temporal } from '@js-temporal/polyfill' import { createPalette } from 'hue-map' @@ -23,15 +23,21 @@ interface AvailabilityViewerProps { const AvailabilityViewer = ({ times, timezone, people }: AvailabilityViewerProps) => { const { t, i18n } = useTranslation('event') - // const [tooltip, setTooltip] = useState(null) const timeFormat = useStore(useSettingsStore, state => state.timeFormat) const highlight = useStore(useSettingsStore, state => state.highlight) const colormap = useStore(useSettingsStore, state => state.colormap) const [filteredPeople, setFilteredPeople] = useState(people.map(p => p.name)) - // const [tempFocus, setTempFocus] = useState(null) - // const [focusCount, setFocusCount] = useState(null) + const [tempFocus, setTempFocus] = useState() + const [focusCount, setFocusCount] = useState() - // const wrapper = useRef() + const wrapperRef = useRef(null) + const [tooltip, setTooltip] = useState<{ + x: number + y: number + available: string + date: string + people: string[] + }>() // Calculate rows and columns const [dates, rows, columns] = useMemo(() => { @@ -56,7 +62,7 @@ const AvailabilityViewer = ({ times, timezone, people }: AvailabilityViewerProps useEffect(() => { setPalette(createPalette({ map: colormap !== 'crabfit' ? colormap : [[0, [247, 158, 0, 0]], [1, [247, 158, 0, 255]]], - steps: (max - min) + 1, + steps: Math.max((max - min) + 1, 2), }).format()) }, [min, max, colormap]) @@ -94,35 +100,37 @@ const AvailabilityViewer = ({ times, timezone, people }: AvailabilityViewerProps } const date = column.toZonedDateTime({ timeZone: timezone, plainTime: row }) - const peopleHere = availabilities.find(a => a.date.equals(date))?.people ?? [] + let peopleHere = availabilities.find(a => a.date.equals(date))?.people ?? [] + if (tempFocus) { + peopleHere = peopleHere.filter(p => p === tempFocus) + } return
0 && styles.highlight, + (focusCount === undefined || focusCount === peopleHere.length) && highlight && (peopleHere.length === max || tempFocus) && peopleHere.length > 0 && styles.highlight, )} style={{ - backgroundColor: palette[peopleHere.length], + backgroundColor: (focusCount === undefined || focusCount === peopleHere.length) ? palette[tempFocus && peopleHere.length ? max : peopleHere.length] : 'transparent', ...date.minute !== 0 && date.minute !== 30 && { borderTopColor: 'transparent' }, ...date.minute === 30 && { borderTopStyle: 'dotted' }, }} aria-label={peopleHere.join(', ')} - // onMouseEnter={e => { - // const cellBox = e.currentTarget.getBoundingClientRect() - // const wrapperBox = wrapper?.current?.getBoundingClientRect() ?? { x: 0, y: 0 } - // const timeText = timeFormat === '12h' ? `h${locales[locale]?.separator ?? ':'}mma` : `HH${locales[locale]?.separator ?? ':'}mm` - // 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('event:available')}`, - // date: parsedDate.hour(time.slice(0, 2)).minute(time.slice(2, 4)).format(isSpecificDates ? `${timeText} ddd, D MMM YYYY` : `${timeText} ddd`), - // people: peopleHere, - // }) - // }} - // onMouseLeave={() => { - // setTooltip(null) - // }} + 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: isSpecificDates + ? date.toLocaleString(i18n.language, { dateStyle: 'long', timeStyle: 'short', hour12: timeFormat === '12h' }) + : `${date.toLocaleString(i18n.language, { timeStyle: 'short', hour12: timeFormat === '12h' })}, ${date.toLocaleString(i18n.language, { weekday: 'long' })}`, + people: peopleHere, + }) + }} + onMouseLeave={() => setTooltip(undefined)} /> })}
@@ -140,6 +148,11 @@ const AvailabilityViewer = ({ times, timezone, people }: AvailabilityViewerProps t, timeFormat, palette, + tempFocus, + focusCount, + filteredPeople, + i18n.language, + timezone, ]) return <> @@ -149,7 +162,7 @@ const AvailabilityViewer = ({ times, timezone, people }: AvailabilityViewerProps max={max} total={filteredPeople.length} palette={palette} - onSegmentFocus={console.log} + onSegmentFocus={setFocusCount} /> {t('group.info1')} @@ -165,21 +178,16 @@ const AvailabilityViewer = ({ times, timezone, people }: AvailabilityViewerProps filteredPeople.includes(person.name) && styles.personSelected, )} key={person.name} - // onClick={() => { - // setTempFocus(null) - // if (filteredPeople.includes(person.name)) { - // if (!touched) { - // setTouched(true) - // setFilteredPeople([person.name]) - // } else { - // setFilteredPeople(filteredPeople.filter(n => n !== person.name)) - // } - // } else { - // setFilteredPeople([...filteredPeople, person.name]) - // } - // }} - // onMouseOver={() => setTempFocus(person.name)} - // onMouseOut={() => setTempFocus(null)} + onClick={() => { + setTempFocus(undefined) + if (filteredPeople.includes(person.name)) { + setFilteredPeople(filteredPeople.filter(n => n !== person.name)) + } else { + setFilteredPeople([...filteredPeople, person.name]) + } + }} + onMouseOver={() => setTempFocus(person.name)} + onMouseOut={() => setTempFocus(undefined)} title={Temporal.Instant.fromEpochSeconds(person.created_at).until(Temporal.Now.instant()).toLocaleString()} >{person.name} )} @@ -187,29 +195,23 @@ const AvailabilityViewer = ({ times, timezone, people }: AvailabilityViewerProps } -
+
{heatmap} - {/* {tooltip && ( - - {tooltip.available} - {tooltip.date} - {!!filteredPeople.length && ( - - {tooltip.people.map(person => - {person} - )} - {filteredPeople.filter(p => !tooltip.people.includes(p)).map(person => - {person} - )} - + {tooltip &&
+

{tooltip.available}

+ {tooltip.date} + {!!filteredPeople.length &&
+ {tooltip.people.map(person => {person})} + {filteredPeople.filter(p => !tooltip.people.includes(p)).map(person => + {person} )} - - )} */} +
} +
}
diff --git a/frontend/src/components/Legend/Legend.tsx b/frontend/src/components/Legend/Legend.tsx index 2b4692e..6063413 100644 --- a/frontend/src/components/Legend/Legend.tsx +++ b/frontend/src/components/Legend/Legend.tsx @@ -15,7 +15,7 @@ interface LegendProps { const Legend = ({ min, max, total, palette, onSegmentFocus }: LegendProps) => { const { t } = useTranslation('event') const highlight = useStore(useSettingsStore, state => state.highlight) - const setHighlight = useStore(useSettingsStore, state => state.setHighlight) + const setHighlight = useSettingsStore(state => state.setHighlight) return
diff --git a/frontend/src/pages-old/Help/Help.jsx b/frontend/src/pages-old/Help/Help.jsx deleted file mode 100644 index 198fef4..0000000 --- a/frontend/src/pages-old/Help/Help.jsx +++ /dev/null @@ -1,101 +0,0 @@ -import { useEffect, useState } from 'react' -import { Link, useNavigate } from 'react-router-dom' -import { useTranslation, Trans } from 'react-i18next' - -import { Button, Center, Footer, AvailabilityViewer, Logo } from '/src/components' - -import { StyledMain, AboutSection, P, VideoWrapper, VideoLink } from '../Home/Home.styles' - -import { Step, FakeCalendar, FakeTimeRange, ButtonArea } from './Help.styles' - -import video_thumb from '/src/res/video_thumb.jpg' - -const Help = () => { - const navigate = useNavigate() - const { t } = useTranslation(['common', 'help']) - const [videoPlay, setVideoPlay] = useState(false) - - useEffect(() => { - document.title = t('help:name') - }, [t]) - - return <> - - - - - -

{t('help:name')}

- {videoPlay ? ( - - - - ) : ( - { - e.preventDefault() - setVideoPlay(true) - }} - > - {t('common:video.button')} - {t('common:video.button')} - - )} -

{t('help:p1')}

-

{t('help:p2')}

- - {t('help:s1')} -

Use the form at crab.fit to make a new event. You only need to put in the rough time period for when your event occurs here, not your availability.

-

{t('help:p4')}

- -
SunMonTueWedThuFriSat
-
11121314151617
-
-

{t('help:p5')}

- -
-
-
- - {t('help:s2')} -

{t('help:p6')}

-

{t('help:p7')}

- - - {t('help:s3')} -

{t('help:p8')}

-

{t('help:p9')}

-

{t('help:p10')}

- -
- - - - -
-
-
-
- -