From 4a9f2263f21f897371c3b3279baa3e42eff053d8 Mon Sep 17 00:00:00 2001 From: Mo Tarbin Date: Tue, 14 Jan 2025 02:06:40 -0500 Subject: [PATCH] Add Sidepanel component for displaying task and calendar view --- src/queries/UserQueries.jsx | 14 +- src/views/Chores/Sidepanel.jsx | 91 ++++++++++ src/views/components/Calendar.css | 79 +++++++++ src/views/components/CalendarView.jsx | 176 +++++++++++++++++++ src/views/components/IconButtonTouchable.jsx | 34 ++++ 5 files changed, 393 insertions(+), 1 deletion(-) create mode 100644 src/views/Chores/Sidepanel.jsx create mode 100644 src/views/components/Calendar.css create mode 100644 src/views/components/CalendarView.jsx create mode 100644 src/views/components/IconButtonTouchable.jsx diff --git a/src/queries/UserQueries.jsx b/src/queries/UserQueries.jsx index 30dd203..d5f2d56 100644 --- a/src/queries/UserQueries.jsx +++ b/src/queries/UserQueries.jsx @@ -1,3 +1,4 @@ +import { useState } from 'react' import { useQuery } from 'react-query' import { GetAllCircleMembers, GetAllUsers } from '../utils/Fetcher' @@ -6,5 +7,16 @@ export const useAllUsers = () => { } export const useCircleMembers = () => { - return useQuery('allCircleMembers', GetAllCircleMembers) + const [refetchKey, setRefetchKey] = useState(0) + + const { data, error, isLoading, refetch } = useQuery( + ['allCircleMembers', refetchKey], + GetAllCircleMembers, + ) + const handleRefetch = () => { + setRefetchKey(prevKey => prevKey + 1) + refetch() + } + + return { data, error, isLoading, handleRefetch } } diff --git a/src/views/Chores/Sidepanel.jsx b/src/views/Chores/Sidepanel.jsx new file mode 100644 index 0000000..e5947c0 --- /dev/null +++ b/src/views/Chores/Sidepanel.jsx @@ -0,0 +1,91 @@ +import { Box, Sheet } from '@mui/joy' +import { useMediaQuery } from '@mui/material' +import { useEffect, useState } from 'react' +import { ChoresGrouper } from '../../utils/Chores' +import CalendarView from '../components/CalendarView' + +const Sidepanel = ({ chores }) => { + const isLargeScreen = useMediaQuery(theme => theme.breakpoints.up('md')) + const [dueDatePieChartData, setDueDatePieChartData] = useState([]) + + useEffect(() => { + setDueDatePieChartData(generateChoreDuePieChartData(chores)) + }, []) + + const generateChoreDuePieChartData = chores => { + const groups = ChoresGrouper('due_date', chores) + return groups + .map(group => { + return { + label: group.name, + value: group.content.length, + color: group.color, + id: group.name, + } + }) + .filter(item => item.value > 0) + } + + if (!isLargeScreen) { + return null + } + return ( + + {/* + + + {dueDatePieChartData.map((entry, index) => ( + + ))} + + + `${label}: ${value.payload.value}`} + wrapperStyle={{ paddingTop: 0, marginTop: 0 }} // Adjust padding and margin + /> + + + */} + + + + + ) +} + +export default Sidepanel diff --git a/src/views/components/Calendar.css b/src/views/components/Calendar.css new file mode 100644 index 0000000..02c3661 --- /dev/null +++ b/src/views/components/Calendar.css @@ -0,0 +1,79 @@ +.react-calendar { + width: 100%; + max-width: 600px; + font-family: 'Roboto', sans-serif; + background-color: transparent; +} + +.react-calendar__tile { + padding: 1em; + min-height: 3rem; + text-align: center; + border-radius: 8px; + transition: background-color 0.3s ease; +} +.react-calendar__tile:enabled:hover { + /* lighten existing color: ; */ + background-color: rgba(0, 123, 255, 0.2); +} + +.react-calendar__tile--active { + background-color: #007bff !important; +} + +/* Today's tile styles */ +.react-calendar .react-calendar__tile--now { + /* More specific selector */ + /* background-color: #4ec1a2e3; */ + border: 2px dotted #4ec1a2e3; + border-radius: 8px; /* Consistent with other tiles */ + background-color: inherit; +} + +.react-calendar .react-calendar__tile--now:enabled:hover { + background-color: rgba(0, 123, 255, 0.1); +} + +/* Ensure dot container doesn't break row height */ +.dot-container { + display: flex; + justify-content: center; + align-items: center; + height: 16px; + /* if more than 4 dot make them go to the new line: */ + flex-wrap: wrap; +} + +.dot { + width: 0.3em; + height: 0.3em; + border-radius: 50%; + margin: 0 1px; +} + +/* Tooltip styles */ +.dot-container { + position: relative; +} + +.chore-tooltip { + position: absolute; + top: 100%; + left: 50%; + transform: translateX(-50%); + background-color: #333; + color: white; + padding: 5px 10px; + border-radius: 4px; + z-index: 10; + visibility: hidden; + opacity: 0; + transition: + opacity 0.2s ease, + visibility 0.2s ease; +} + +/* remove different color on weekend: */ +.react-calendar__month-view__days__day--weekend { + color: inherit; +} diff --git a/src/views/components/CalendarView.jsx b/src/views/components/CalendarView.jsx new file mode 100644 index 0000000..43e44b8 --- /dev/null +++ b/src/views/components/CalendarView.jsx @@ -0,0 +1,176 @@ +import { Box, Card, CardContent, Chip, Grid, Typography } from '@mui/joy' +import moment from 'moment' +import React, { useState } from 'react' +import Calendar from 'react-calendar' +import 'react-calendar/dist/Calendar.css' +import { useNavigate } from 'react-router-dom' +import { UserContext } from '../../contexts/UserContext' +import { getTextColorFromBackgroundColor, TASK_COLOR } from '../../utils/Colors' +import './Calendar.css' + +const getAssigneeColor = (assignee, userProfile) => { + return assignee === userProfile.id + ? TASK_COLOR.ASSIGNED_TO_ME + : TASK_COLOR.ASSIGNED_TO_OTHER +} + +const CalendarView = ({ chores }) => { + const { userProfile } = React.useContext(UserContext) + const [selectedDate, setSeletedDate] = useState(null) + const Navigate = useNavigate() + + const tileContent = ({ date, view }) => { + if (view === 'month') { + const dayChores = chores.filter( + chore => + new Date(chore.nextDueDate)?.toISOString().split('T')[0] === + date.toISOString().split('T')[0], + ) + + return ( +
+ {dayChores.map((chore, index) => { + if (index > 6) { + return null + } + + return ( + + ) + })} +
+ ) + } + return null + } + + return ( +
+ { + setSeletedDate(new Date(d)) + }} + /> + {!selectedDate && ( + + {[ + { name: 'Assigned to me', color: TASK_COLOR.ASSIGNED_TO_ME }, + { name: 'Assigned to other', color: TASK_COLOR.ASSIGNED_TO_OTHER }, + ].map((item, index) => ( + + + + {item.name} + + + ))} + + )} + {selectedDate && ( + + {chores + .filter( + chore => + new Date(chore.nextDueDate)?.toISOString().split('T')[0] === + selectedDate.toISOString().split('T')[0], + ) + .map((chore, idx) => ( + { + Navigate('/chores/' + chore.id) + }} + sx={{ + mb: 0.4, + py: 1, + px: 1, + + // backgroundColor: getAssigneeColor( + // chore.assignedTo, + // userProfile, + // ), + // everything show in one row: + }} + > + + + + {moment(chore.nextDueDate).format('hh:mm A')} + + {chore.name} + + + + ))} + + )} +
+ ) +} + +export default CalendarView diff --git a/src/views/components/IconButtonTouchable.jsx b/src/views/components/IconButtonTouchable.jsx new file mode 100644 index 0000000..c8cc4e5 --- /dev/null +++ b/src/views/components/IconButtonTouchable.jsx @@ -0,0 +1,34 @@ +import IconButton from '@mui/joy/IconButton' +import React, { useRef, useState } from 'react' + +const IconButtonTouchable = ({ onHold, onClick, ...props }) => { + const [holdTimeout, setHoldTimeout] = useState(null) + const holdRef = useRef(false) + + const handleMouseDown = () => { + holdRef.current = false + setHoldTimeout( + setTimeout(() => { + holdRef.current = true + onHold && onHold() + }, 1000), + ) + } + + const handleMouseUp = () => { + clearTimeout(holdTimeout) + if (!holdRef.current) { + onClick && onClick() + } + } + + return ( + + ) +} + +export default IconButtonTouchable