diff --git a/crabfit-backend/routes/getPeople.js b/crabfit-backend/routes/getPeople.js index e157a55..716f446 100644 --- a/crabfit-backend/routes/getPeople.js +++ b/crabfit-backend/routes/getPeople.js @@ -7,6 +7,7 @@ module.exports = async (req, res) => { people = people.map(person => ({ name: person.name, availability: person.availability, + created: person.created, })); res.send({ diff --git a/crabfit-backend/routes/login.js b/crabfit-backend/routes/login.js index d8d15e8..4b6dd29 100644 --- a/crabfit-backend/routes/login.js +++ b/crabfit-backend/routes/login.js @@ -21,6 +21,7 @@ module.exports = async (req, res) => { res.send({ name: personName, availability: personResult.availability, + created: personResult.created, }); } else { res.sendStatus(404); diff --git a/crabfit-backend/swagger.yaml b/crabfit-backend/swagger.yaml index f9656a6..e64c0c9 100644 --- a/crabfit-backend/swagger.yaml +++ b/crabfit-backend/swagger.yaml @@ -36,6 +36,8 @@ definitions: type: "array" items: type: "string" + created: + type: "integer" paths: "/stats": get: diff --git a/crabfit-frontend/src/components/AvailabilityViewer/AvailabilityViewer.tsx b/crabfit-frontend/src/components/AvailabilityViewer/AvailabilityViewer.tsx index fad5a09..1d7cbf4 100644 --- a/crabfit-frontend/src/components/AvailabilityViewer/AvailabilityViewer.tsx +++ b/crabfit-frontend/src/components/AvailabilityViewer/AvailabilityViewer.tsx @@ -1,13 +1,15 @@ -import { useState, useEffect, Fragment } from 'react'; +import { useState, useEffect, useRef, Fragment } from 'react'; import dayjs from 'dayjs'; import localeData from 'dayjs/plugin/localeData'; import customParseFormat from 'dayjs/plugin/customParseFormat'; +import relativeTime from 'dayjs/plugin/relativeTime'; import { useSettingsStore } from 'stores'; import { Legend, Center } from 'components'; import { Wrapper, + ScrollWrapper, Container, Date, Times, @@ -19,6 +21,7 @@ import { TooltipTitle, TooltipDate, TooltipContent, + TooltipPerson, TimeLabels, TimeLabel, TimeSpace, @@ -29,6 +32,7 @@ import { dayjs.extend(localeData); dayjs.extend(customParseFormat); +dayjs.extend(relativeTime); const AvailabilityViewer = ({ times, @@ -47,6 +51,8 @@ const AvailabilityViewer = ({ const [tempFocus, setTempFocus] = useState(null); const [focusCount, setFocusCount] = useState(null); + const wrapper = useRef(); + useEffect(() => { setFilteredPeople(people.map(p => p.name)); setTouched(people.length <= 1); @@ -85,6 +91,7 @@ const AvailabilityViewer = ({ }} onMouseOver={() => setTempFocus(person.name)} onMouseOut={() => setTempFocus(null)} + title={person.created && dayjs.unix(person.created).fromNow()} >{person.name} )} @@ -92,82 +99,94 @@ const AvailabilityViewer = ({ )} - - - - {!!timeLabels.length && timeLabels.map((label, i) => - - {label.label?.length !== '' && {label.label}} - - )} - - {dates.map((date, i) => { - const parsedDate = isSpecificDates ? dayjs(date, 'DDMMYYYY') : dayjs().day(date); - const last = dates.length === i+1 || (isSpecificDates ? dayjs(dates[i+1], 'DDMMYYYY') : dayjs().day(dates[i+1])).diff(parsedDate, 'day') > 1; - return ( - - - {isSpecificDates && {parsedDate.format('MMM D')}} - {parsedDate.format('ddd')} + + + + + {!!timeLabels.length && timeLabels.map((label, i) => + + {label.label?.length !== '' && {label.label}} + + )} + + {dates.map((date, i) => { + const parsedDate = isSpecificDates ? dayjs(date, 'DDMMYYYY') : dayjs().day(date); + const last = dates.length === i+1 || (isSpecificDates ? dayjs(dates[i+1], 'DDMMYYYY') : dayjs().day(dates[i+1])).diff(parsedDate, 'day') > 1; + return ( + + + {isSpecificDates && {parsedDate.format('MMM D')}} + {parsedDate.format('ddd')} - - {timeLabels.map((timeLabel, i) => { - if (!timeLabel.time) return null; - if (!times.includes(`${timeLabel.time}-${date}`)) { - return ( - - ); - } - const time = `${timeLabel.time}-${date}`; - const peopleHere = tempFocus !== null - ? people.filter(person => person.availability.includes(time) && tempFocus === person.name).map(person => person.name) - : people.filter(person => person.availability.includes(time) && filteredPeople.includes(person.name)).map(person => person.name); + + {timeLabels.map((timeLabel, i) => { + if (!timeLabel.time) return null; + if (!times.includes(`${timeLabel.time}-${date}`)) { + return ( + + ); + } + const time = `${timeLabel.time}-${date}`; + const peopleHere = tempFocus !== null + ? people.filter(person => person.availability.includes(time) && tempFocus === person.name).map(person => person.name) + : people.filter(person => person.availability.includes(time) && filteredPeople.includes(person.name)).map(person => person.name); - return ( - - - {last && dates.length !== i+1 && ( - - )} - - ); - })} - - {tooltip && ( - - {tooltip.available} - {tooltip.date} - {tooltip.people} - - )} + return ( + + {last && dates.length !== i+1 && ( + + )} + + ); + })} + + {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/crabfit-frontend/src/components/AvailabilityViewer/availabilityViewerStyle.ts b/crabfit-frontend/src/components/AvailabilityViewer/availabilityViewerStyle.ts index 707488f..31d5155 100644 --- a/crabfit-frontend/src/components/AvailabilityViewer/availabilityViewerStyle.ts +++ b/crabfit-frontend/src/components/AvailabilityViewer/availabilityViewerStyle.ts @@ -1,8 +1,13 @@ import styled from '@emotion/styled'; export const Wrapper = styled.div` - overflow-x: auto; + overflow-y: visible; margin: 20px 0; + position: relative; +`; + +export const ScrollWrapper = styled.div` + overflow-x: auto; `; export const Container = styled.div` @@ -62,8 +67,8 @@ export const Time = styled.div` `} background-image: linear-gradient( - ${props => `${props.theme.primary}${Math.round(((props.peopleCount-props.minPeople)/(props.maxPeople-props.minPeople))*255).toString(16)}`}, - ${props => `${props.theme.primary}${Math.round(((props.peopleCount-props.minPeople)/(props.maxPeople-props.minPeople))*255).toString(16)}`} + ${props => `${props.theme.primary}${Math.round((props.peopleCount/props.maxPeople)*255).toString(16)}`}, + ${props => `${props.theme.primary}${Math.round((props.peopleCount/props.maxPeople)*255).toString(16)}`} ); `; @@ -73,16 +78,17 @@ export const Spacer = styled.div` `; export const Tooltip = styled.div` - position: fixed; + position: absolute; top: ${props => props.y}px; left: ${props => props.x}px; transform: translateX(-50%); border: 1px solid ${props => props.theme.text}; border-radius: 3px; padding: 4px 8px; - background-color: ${props => props.theme.background}DD; + background-color: ${props => props.theme.background}${props => props.theme.mode === 'light' ? 'EE' : 'DD'}; max-width: 200px; pointer-events: none; + z-index: 100; `; export const TooltipTitle = styled.span` @@ -94,13 +100,26 @@ export const TooltipTitle = styled.span` export const TooltipDate = styled.span` font-size: 13px; display: block; - opacity: .7; - font-weight: 700; + opacity: .8; + font-weight: 600; `; -export const TooltipContent = styled.span` +export const TooltipContent = styled.div` font-size: 13px; - display: block; + padding: 4px 0; +`; + +export const TooltipPerson = styled.span` + display: inline-block; + margin: 2px; + padding: 1px 4px; + border: 1px solid ${props => props.theme.primary}; + border-radius: 3px; + + ${props => props.disabled && ` + opacity: .5; + border-color: ${props.theme.text} + `} `; export const TimeLabels = styled.div` diff --git a/crabfit-frontend/src/components/Legend/Legend.tsx b/crabfit-frontend/src/components/Legend/Legend.tsx index 16a51e3..978d317 100644 --- a/crabfit-frontend/src/components/Legend/Legend.tsx +++ b/crabfit-frontend/src/components/Legend/Legend.tsx @@ -21,11 +21,11 @@ const Legend = ({ onSegmentFocus(null)}> - {[...Array(max-min+1).keys()].map(i => + {[...Array(max+1-min).keys()].map(i => i+min).map(i => onSegmentFocus(i+min)} + color={`${theme.primary}${Math.round((i/(max))*255).toString(16)}`} + onMouseOver={() => onSegmentFocus(i)} /> )} diff --git a/crabfit-frontend/src/pages/Event/Event.tsx b/crabfit-frontend/src/pages/Event/Event.tsx index 52ea2f1..3184ab8 100644 --- a/crabfit-frontend/src/pages/Event/Event.tsx +++ b/crabfit-frontend/src/pages/Event/Event.tsx @@ -286,7 +286,7 @@ const Event = (props) => { {(!!event || isLoading) ? ( <> {event?.name} - {event?.created && `Created ${dayjs.unix(event?.created).fromNow()}`} + {event?.created && `Created ${dayjs.unix(event?.created).fromNow()}`} navigator.clipboard?.writeText(`https://crab.fit/${id}`) .then(() => {