338 lines
10 KiB
JavaScript
338 lines
10 KiB
JavaScript
import { CalendarMonth } from '@mui/icons-material'
|
|
import { Avatar, Box, Chip, Grid, Typography } from '@mui/joy'
|
|
import moment from 'moment'
|
|
import { useEffect, useState } from 'react'
|
|
import { useNavigate } from 'react-router-dom'
|
|
import { useCircleMembers, useUserProfile } from '../../queries/UserQueries'
|
|
import { getPriorityColor, TASK_COLOR } from '../../utils/Colors'
|
|
|
|
import CalendarMonthly from './CalendarMonthly'
|
|
|
|
const getAssigneeColor = (assignee, userProfile) => {
|
|
return assignee === userProfile.id
|
|
? TASK_COLOR.ASSIGNED_TO_ME
|
|
: TASK_COLOR.ASSIGNED_TO_OTHER
|
|
}
|
|
const CalendarCard = ({ chores }) => {
|
|
const { data: userProfile } = useUserProfile()
|
|
|
|
const [selectedDate, setSeletedDate] = useState(null)
|
|
const Navigate = useNavigate()
|
|
|
|
// Fetch circle members data to get assignee names
|
|
const { data: circleMembersData } = useCircleMembers()
|
|
const circleMembers = circleMembersData?.res || []
|
|
|
|
// Auto-update selected calendar date when day changes
|
|
useEffect(() => {
|
|
if (!selectedDate) {
|
|
return
|
|
}
|
|
|
|
const now = new Date()
|
|
const tomorrow = new Date(now)
|
|
tomorrow.setDate(tomorrow.getDate() + 1)
|
|
tomorrow.setHours(0, 0, 0, 0) // Set to start of tomorrow
|
|
|
|
const msUntilTomorrow = tomorrow.getTime() - now.getTime()
|
|
|
|
const timeout = setTimeout(() => {
|
|
const today = new Date()
|
|
const yesterday = new Date(today)
|
|
yesterday.setDate(yesterday.getDate() - 1)
|
|
|
|
// Check if selected date is now yesterday and update to today
|
|
if (selectedDate.toDateString() === yesterday.toDateString()) {
|
|
setSeletedDate(today)
|
|
}
|
|
}, msUntilTomorrow)
|
|
|
|
return () => clearTimeout(timeout)
|
|
}, [selectedDate])
|
|
|
|
// Helper function to get assignee display name
|
|
const getAssigneeName = assignedTo => {
|
|
if (assignedTo === userProfile.id) {
|
|
return userProfile.displayName
|
|
}
|
|
const assignee = circleMembers.find(member => member.userId === assignedTo)
|
|
return assignee ? `${assignee.displayName}` : 'Assigned to other'
|
|
}
|
|
|
|
return (
|
|
<div
|
|
style={{
|
|
width: '100%',
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
}}
|
|
>
|
|
{/* Calendar Header */}
|
|
<Box
|
|
sx={{
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'flex-start',
|
|
gap: 1,
|
|
maxWidth: '250px',
|
|
mb: 2,
|
|
}}
|
|
>
|
|
<CalendarMonth />
|
|
<Typography level='title-md'>Calendar Overview</Typography>
|
|
</Box>
|
|
|
|
<div>
|
|
<CalendarMonthly
|
|
// className={styled.reactCalendar}
|
|
chores={chores}
|
|
onDateChange={date => {
|
|
setSeletedDate(date)
|
|
}}
|
|
/>
|
|
</div>
|
|
|
|
{!selectedDate && (
|
|
<Grid
|
|
container
|
|
ml={3}
|
|
mt={1}
|
|
// start from left:
|
|
sx={{
|
|
width: '100%',
|
|
display: 'flex',
|
|
// alignItems: 'center',
|
|
justifyContent: 'start',
|
|
}}
|
|
>
|
|
{/* Show legend with priority colors */}
|
|
{(() => {
|
|
const priorityLevels = new Set(
|
|
chores.map(chore => chore.priority).filter(p => p !== undefined),
|
|
)
|
|
const legendItems = []
|
|
|
|
// Add priority levels that exist in the chores
|
|
if (priorityLevels.has(1)) {
|
|
legendItems.push({
|
|
name: 'High Priority',
|
|
color: TASK_COLOR.PRIORITY_1,
|
|
})
|
|
}
|
|
if (priorityLevels.has(2)) {
|
|
legendItems.push({
|
|
name: 'Medium Priority',
|
|
color: TASK_COLOR.PRIORITY_2,
|
|
})
|
|
}
|
|
if (priorityLevels.has(3)) {
|
|
legendItems.push({
|
|
name: 'Low Priority',
|
|
color: TASK_COLOR.PRIORITY_3,
|
|
})
|
|
}
|
|
if (priorityLevels.has(4)) {
|
|
legendItems.push({
|
|
name: 'Lowest Priority',
|
|
color: TASK_COLOR.PRIORITY_4,
|
|
})
|
|
}
|
|
if (
|
|
chores.some(
|
|
chore =>
|
|
chore.priority === undefined || chore.priority === null,
|
|
)
|
|
) {
|
|
legendItems.push({
|
|
name: 'No Priority',
|
|
color: TASK_COLOR.NO_PRIORITY,
|
|
})
|
|
}
|
|
|
|
return legendItems.map((item, index) => (
|
|
<Grid
|
|
key={index}
|
|
xs={6}
|
|
sx={{
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'start',
|
|
}}
|
|
>
|
|
<Box
|
|
sx={{
|
|
display: 'inline-block',
|
|
width: 5,
|
|
height: 5,
|
|
borderRadius: '50%',
|
|
backgroundColor: item.color,
|
|
}}
|
|
/>
|
|
<Typography level='body-xs' ml={0.5}>
|
|
{item.name}
|
|
</Typography>
|
|
</Grid>
|
|
))
|
|
})()}
|
|
</Grid>
|
|
)}
|
|
{selectedDate && (
|
|
<Box
|
|
sx={{
|
|
mt: 2,
|
|
width: '100%',
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
gap: 2,
|
|
}}
|
|
>
|
|
<Box
|
|
sx={{
|
|
display: 'flex',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
width: '100%',
|
|
}}
|
|
>
|
|
<Typography level='title-md'>
|
|
{moment(selectedDate).format('MMMM D, YYYY')}
|
|
</Typography>
|
|
<Chip variant='soft' color='primary' size='md'>
|
|
{(() => {
|
|
const count = chores.filter(chore => {
|
|
const choreDate = new Date(
|
|
chore.nextDueDate,
|
|
).toLocaleDateString()
|
|
const selectedLocalDate = selectedDate.toLocaleDateString()
|
|
return choreDate === selectedLocalDate
|
|
}).length
|
|
return `${count} Tasks`
|
|
})()}
|
|
</Chip>
|
|
</Box>
|
|
|
|
<Box
|
|
sx={{
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
gap: 1,
|
|
// maxHeight: 'calc(100vh - 500px)',
|
|
overflowY: 'auto',
|
|
maxHeight: '170px',
|
|
|
|
p: 1,
|
|
}}
|
|
>
|
|
{chores
|
|
.filter(chore => {
|
|
const choreDate = new Date(
|
|
chore.nextDueDate,
|
|
).toLocaleDateString()
|
|
const selectedLocalDate = selectedDate.toLocaleDateString()
|
|
return choreDate === selectedLocalDate
|
|
})
|
|
.sort((a, b) => moment(a.nextDueDate).diff(moment(b.nextDueDate)))
|
|
.map((chore, idx) => (
|
|
<Box
|
|
key={idx}
|
|
onClick={() => {
|
|
Navigate('/chores/' + chore.id)
|
|
}}
|
|
sx={{
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
gap: 0.25,
|
|
cursor: 'pointer',
|
|
position: 'relative',
|
|
pl: '16px',
|
|
py: 0.75,
|
|
transition: 'all 0.2s ease-in-out',
|
|
borderRadius: 'sm',
|
|
'&:hover': {
|
|
// transform: 'translateX(4px)',
|
|
bgcolor: 'background.level1',
|
|
},
|
|
'&::before': {
|
|
content: '""',
|
|
position: 'absolute',
|
|
left: 0,
|
|
top: 0,
|
|
bottom: 0,
|
|
width: '3px',
|
|
backgroundColor: getPriorityColor(chore.priority),
|
|
borderRadius: '2px',
|
|
},
|
|
}}
|
|
>
|
|
<Typography
|
|
level='body-sm'
|
|
sx={{
|
|
flex: 1,
|
|
overflow: 'hidden',
|
|
textOverflow: 'ellipsis',
|
|
whiteSpace: 'nowrap',
|
|
color: 'text.primary',
|
|
}}
|
|
>
|
|
{chore.name}
|
|
</Typography>
|
|
<Box
|
|
sx={{
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
gap: 1,
|
|
}}
|
|
>
|
|
<Typography
|
|
level='body-xs'
|
|
sx={{
|
|
color: 'neutral.500',
|
|
}}
|
|
>
|
|
{moment(chore.nextDueDate).format('h:mm A')}
|
|
</Typography>
|
|
{/* <Typography
|
|
level='body-xs'
|
|
sx={{
|
|
color: getAssigneeColor(chore.assignedTo, userProfile),
|
|
}}
|
|
>
|
|
{getAssigneeName(chore.assignedTo)}
|
|
</Typography> */}
|
|
<Chip
|
|
variant='soft'
|
|
color='neutral'
|
|
size='sm'
|
|
startDecorator={
|
|
<Avatar
|
|
src={
|
|
circleMembers.find(
|
|
member => member.userId === chore.assignedTo,
|
|
)?.image
|
|
}
|
|
/>
|
|
}
|
|
sx={{
|
|
backgroundColor: getAssigneeColor(
|
|
chore.assignedTo,
|
|
userProfile,
|
|
),
|
|
color: 'white',
|
|
}}
|
|
>
|
|
{getAssigneeName(chore.assignedTo)}
|
|
</Chip>
|
|
</Box>
|
|
</Box>
|
|
))}
|
|
</Box>
|
|
</Box>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default CalendarCard
|