diff --git a/crabfit-frontend/src/components/AvailabilityEditor/AvailabilityEditor.tsx b/crabfit-frontend/src/components/AvailabilityEditor/AvailabilityEditor.tsx new file mode 100644 index 0000000..4254ab5 --- /dev/null +++ b/crabfit-frontend/src/components/AvailabilityEditor/AvailabilityEditor.tsx @@ -0,0 +1,112 @@ +import { useState, useRef, Fragment } from 'react'; +import dayjs from 'dayjs'; +import localeData from 'dayjs/plugin/localeData'; +import customParseFormat from 'dayjs/plugin/customParseFormat'; + +import { + Wrapper, + Container, + Date, + DateLabel, + DayLabel, + Spacer, + TimeLabels, + TimeLabel, + TimeSpace, +} from 'components/AvailabilityViewer/availabilityViewerStyle'; +import { Time } from './availabilityEditorStyle'; + +dayjs.extend(localeData); +dayjs.extend(customParseFormat); + +const AvailabilityEditor = ({ + dates, + times, + value = [], + onChange, + ...props +}) => { + const [selectingTimes, _setSelectingTimes] = useState([]); + const staticSelectingTimes = useRef([]); + const setSelectingTimes = newTimes => { + staticSelectingTimes.current = newTimes; + _setSelectingTimes(newTimes); + }; + + const startPos = useRef({}); + const staticMode = useRef(null); + const [mode, _setMode] = useState(staticMode.current); + const setMode = newMode => { + staticMode.current = newMode; + _setMode(newMode); + }; + + return ( + + + + {times.concat([`${parseInt(times[times.length-1].slice(0, 2))+1}00`]).map((time, i) => + + {time.slice(-2) === '00' && {dayjs().hour(time.slice(0, 2)).minute(time.slice(-2)).format('h A')}} + + )} + + {dates.map((date, x) => { + const parsedDate = dayjs(date, 'DDMMYYYY'); + const last = dates.length === x+1 || dayjs(dates[x+1], 'DDMMYYYY').diff(parsedDate, 'day') > 1; + return ( + + + {parsedDate.format('MMM D')} + {parsedDate.format('ddd')} + + {times.map((time, y) => + + {last && dates.length !== x+1 && ( + + )} + + ); + })} + + + ); +}; + +export default AvailabilityEditor; diff --git a/crabfit-frontend/src/components/AvailabilityEditor/availabilityEditorStyle.ts b/crabfit-frontend/src/components/AvailabilityEditor/availabilityEditorStyle.ts new file mode 100644 index 0000000..0046eb1 --- /dev/null +++ b/crabfit-frontend/src/components/AvailabilityEditor/availabilityEditorStyle.ts @@ -0,0 +1,21 @@ +import styled from '@emotion/styled'; + +export const Time = styled.div` + height: 10px; + border-left: 1px solid ${props => props.theme.primaryDark}; + touch-action: none; + + ${props => props.time.slice(-2) === '00' && ` + border-top: 1px solid ${props.theme.primaryDark}; + `} + ${props => props.time.slice(-2) === '30' && ` + border-top: 1px dotted ${props.theme.primaryDark}; + `} + + ${props => (props.selected || (props.mode === 'add' && props.selecting)) && ` + background-color: ${props.theme.primary}; + `}; + ${props => props.mode === 'remove' && props.selecting && ` + background-color: initial; + `}; +`; diff --git a/crabfit-frontend/src/components/AvailabilityViewer/AvailabilityViewer.tsx b/crabfit-frontend/src/components/AvailabilityViewer/AvailabilityViewer.tsx index 31a6e9b..24d67e6 100644 --- a/crabfit-frontend/src/components/AvailabilityViewer/AvailabilityViewer.tsx +++ b/crabfit-frontend/src/components/AvailabilityViewer/AvailabilityViewer.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { useState, Fragment } from 'react'; import dayjs from 'dayjs'; import localeData from 'dayjs/plugin/localeData'; import customParseFormat from 'dayjs/plugin/customParseFormat'; @@ -15,6 +15,9 @@ import { TooltipTitle, TooltipDate, TooltipContent, + TimeLabels, + TimeLabel, + TimeSpace, } from './availabilityViewerStyle'; dayjs.extend(localeData); @@ -31,12 +34,19 @@ const AvailabilityViewer = ({ return ( + + {times.concat([`${parseInt(times[times.length-1].slice(0, 2))+1}00`]).map((time, i) => + + {time.slice(-2) === '00' && {dayjs().hour(time.slice(0, 2)).minute(time.slice(-2)).format('h A')}} + + )} + {dates.map((date, i) => { const parsedDate = dayjs(date, 'DDMMYYYY'); const last = dates.length === i+1 || dayjs(dates[i+1], 'DDMMYYYY').diff(parsedDate, 'day') > 1; return ( - <> - + + {parsedDate.format('MMM D')} {parsedDate.format('ddd')} @@ -53,8 +63,8 @@ const AvailabilityViewer = ({ onMouseEnter={(e) => { const cellBox = e.currentTarget.getBoundingClientRect(); setTooltip({ - x: Math.round(cellBox.x + cellBox.width), - y: Math.round(cellBox.y + cellBox.height), + x: Math.round(cellBox.x + cellBox.width/2), + y: Math.round(cellBox.y + cellBox.height)+6, available: `${peopleHere.length} / ${people.length} available`, date: parsedDate.hour(time.slice(0, 2)).minute(time.slice(-2)).format('h:mma ddd, D MMM YYYY'), people: peopleHere.join(', '), @@ -70,7 +80,7 @@ const AvailabilityViewer = ({ {last && dates.length !== i+1 && ( )} - + ); })} diff --git a/crabfit-frontend/src/components/AvailabilityViewer/availabilityViewerStyle.ts b/crabfit-frontend/src/components/AvailabilityViewer/availabilityViewerStyle.ts index da02c15..15e9103 100644 --- a/crabfit-frontend/src/components/AvailabilityViewer/availabilityViewerStyle.ts +++ b/crabfit-frontend/src/components/AvailabilityViewer/availabilityViewerStyle.ts @@ -9,8 +9,12 @@ export const Container = styled.div` display: inline-flex; box-sizing: border-box; min-width: 100%; - align-items: flex-start; + align-items: flex-end; padding: 0 calc(calc(100% - 600px) / 2); + + @media (max-width: 660px) { + padding: 0 30px; + } `; export const Date = styled.div` @@ -19,6 +23,7 @@ export const Date = styled.div` flex-direction: column; width: 60px; min-width: 60px; + margin-bottom: 10px; & .time:last-of-type { border-bottom: 1px solid ${props => props.theme.primaryDark}; @@ -63,13 +68,15 @@ export const Spacer = styled.div` export const Tooltip = styled.div` position: fixed; - top: ${props => props.y+6}px; - left: ${props => props.x+6}px; + 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}; + background-color: ${props => props.theme.background}99; max-width: 200px; + pointer-events: none; `; export const TooltipTitle = styled.span` @@ -89,3 +96,33 @@ export const TooltipContent = styled.span` font-size: 13px; display: block; `; + +export const TimeLabels = styled.div` + flex-shrink: 0; + display: flex; + flex-direction: column; + width: 40px; + padding-right: 6px; +`; + +export const TimeSpace = styled.div` + height: 10px; + position: relative; + + ${props => props.time.slice(-2) === '00' && ` + border-top: 1px solid transparent; + `} + ${props => props.time.slice(-2) === '30' && ` + border-top: 1px dotted transparent; + `} +`; + +export const TimeLabel = styled.label` + display: block; + position: absolute; + top: -.7em; + font-size: 12px; + text-align: right; + user-select: none; + width: 100%; +`; diff --git a/crabfit-frontend/src/components/Donate/Donate.tsx b/crabfit-frontend/src/components/Donate/Donate.tsx index 6555e62..4c28fc6 100644 --- a/crabfit-frontend/src/components/Donate/Donate.tsx +++ b/crabfit-frontend/src/components/Donate/Donate.tsx @@ -6,6 +6,7 @@ const Donate = () => ( diff --git a/crabfit-frontend/src/components/index.ts b/crabfit-frontend/src/components/index.ts index 707a37f..5b31173 100644 --- a/crabfit-frontend/src/components/index.ts +++ b/crabfit-frontend/src/components/index.ts @@ -6,6 +6,7 @@ export { default as TimeRangeField } from './TimeRangeField/TimeRangeField'; export { default as Button } from './Button/Button'; export { default as Legend } from './Legend/Legend'; export { default as AvailabilityViewer } from './AvailabilityViewer/AvailabilityViewer'; +export { default as AvailabilityEditor } from './AvailabilityEditor/AvailabilityEditor'; export { default as Center } from './Center/Center'; export { default as Donate } from './Donate/Donate'; diff --git a/crabfit-frontend/src/pages/Event/Event.tsx b/crabfit-frontend/src/pages/Event/Event.tsx index 71f18ef..7a52090 100644 --- a/crabfit-frontend/src/pages/Event/Event.tsx +++ b/crabfit-frontend/src/pages/Event/Event.tsx @@ -10,6 +10,7 @@ import { Button, Legend, AvailabilityViewer, + AvailabilityEditor, } from 'components'; import { @@ -33,7 +34,11 @@ const Event = (props) => { const { register, handleSubmit } = useForm(); const id = props.match.params.id; const [timezone, setTimezone] = useState(Intl.DateTimeFormat().resolvedOptions().timeZone); - const [tab, setTab] = useState('group'); + const [user, setUser] = useState({ + name: 'Benji', + availability: [], + }); + const [tab, setTab] = useState(user ? 'you' : 'group'); const onSubmit = data => console.log('submit', data); @@ -49,36 +54,40 @@ const Event = (props) => { Event name ({id}) https://page.url - Copy the link to this page, or share via Email or Facebook. + Copy the link to this page, or share via Email or Facebook. -

Sign in to add your availability

- - + {!user && ( + <> +

Sign in to add your availability

+ + - + - - - These details are only for this event. Use a password to prevent others from changing your availability. + +
+ These details are only for this event. Use a password to prevent others from changing your availability. + + )} { id="timezone" inline value={timezone} + onChange={value => setTimezone(value)} options={timezones} />
@@ -97,13 +107,13 @@ const Event = (props) => { href="#you" onClick={e => { e.preventDefault(); - if (false) { + if (user) { setTab('you'); } }} selected={tab === 'you'} - disabled={true} - title={true ? 'Login to set your availability' : ''} + disabled={!user} + title={user ? '' : 'Login to set your availability'} >Your availability { {tab === 'group' ? (
- -
Hover and click the calendar below to see who is available
+ +
Hover or tap the calendar below to see who is available
{ '1400-08032021', '1430-08032021', ], - }]} + },{ + name: 'Phoebe', + availability: [ + '1100-07032021', + '1115-07032021', + '1130-07032021', + '1145-07032021', + '1200-07032021', + '1215-07032021', + '1230-07032021', + '1245-07032021', + '1300-07032021', + '1315-07032021', + '1330-07032021', + '1345-07032021', + '1400-07032021', + ], + },user]} />
) : ( @@ -183,6 +177,12 @@ const Event = (props) => {
Click and drag the calendar below to set your availabilities
+ setUser({ ...user, availability })} + /> )} diff --git a/crabfit-frontend/src/pages/Event/eventStyle.ts b/crabfit-frontend/src/pages/Event/eventStyle.ts index 0723f4f..62a6ede 100644 --- a/crabfit-frontend/src/pages/Event/eventStyle.ts +++ b/crabfit-frontend/src/pages/Event/eventStyle.ts @@ -8,7 +8,7 @@ export const StyledMain = styled.div` export const Footer = styled.footer` width: 600px; - margin: 20px auto; + margin: 50px auto 20px; max-width: calc(100% - 60px); display: flex; align-items: center; diff --git a/crabfit-frontend/src/theme/index.ts b/crabfit-frontend/src/theme/index.ts index a35a3c0..29f9f72 100644 --- a/crabfit-frontend/src/theme/index.ts +++ b/crabfit-frontend/src/theme/index.ts @@ -10,7 +10,7 @@ const theme = { }, dark: { mode: 'dark', - background: '#111', + background: '#111111', text: '#DDDDDD', primary: '#F79E00', primaryDark: '#F4BB60',