diff --git a/package-lock.json b/package-lock.json index 1217ac7..7dac78c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,8 @@ "@fontsource/roboto": "^5.1.0", "@mui/icons-material": "^6.1.6", "@mui/material": "^6.1.6", + "@mui/x-date-pickers": "^7.22.1", + "dayjs": "^1.11.13", "firebase": "^11.0.1", "react": "^18.3.1", "react-calendar": "^5.1.0", @@ -2083,6 +2085,92 @@ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "license": "MIT" }, + "node_modules/@mui/x-date-pickers": { + "version": "7.22.1", + "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-7.22.1.tgz", + "integrity": "sha512-VBgicE+7PvJrdHSL6HyieHT6a/0dENH8RaMIM2VwUFrGoZzvik50WNwY5U+Hip1BwZLIEvlqtNRQIIj6kgBR6Q==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.25.7", + "@mui/utils": "^5.16.6 || ^6.0.0", + "@mui/x-internals": "7.21.0", + "@types/react-transition-group": "^4.4.11", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.9.0", + "@emotion/styled": "^11.8.1", + "@mui/material": "^5.15.14 || ^6.0.0", + "@mui/system": "^5.15.14 || ^6.0.0", + "date-fns": "^2.25.0 || ^3.2.0 || ^4.0.0", + "date-fns-jalali": "^2.13.0-0 || ^3.2.0-0", + "dayjs": "^1.10.7", + "luxon": "^3.0.2", + "moment": "^2.29.4", + "moment-hijri": "^2.1.2", + "moment-jalaali": "^0.7.4 || ^0.8.0 || ^0.9.0 || ^0.10.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "date-fns": { + "optional": true + }, + "date-fns-jalali": { + "optional": true + }, + "dayjs": { + "optional": true + }, + "luxon": { + "optional": true + }, + "moment": { + "optional": true + }, + "moment-hijri": { + "optional": true + }, + "moment-jalaali": { + "optional": true + } + } + }, + "node_modules/@mui/x-internals": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-7.21.0.tgz", + "integrity": "sha512-94YNyZ0BhK5Z+Tkr90RKf47IVCW8R/1MvdUhh6MCQg6sZa74jsX+x+gEZ4kzuCqOsuyTyxikeQ8vVuCIQiP7UQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.25.7", + "@mui/utils": "^5.16.6 || ^6.0.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0" + } + }, "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { "version": "5.1.1-v1", "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", @@ -3493,6 +3581,24 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/date-fns": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", + "license": "MIT", + "optional": true, + "peer": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, + "node_modules/dayjs": { + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", + "license": "MIT" + }, "node_modules/debug": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", diff --git a/package.json b/package.json index 9b110ea..d579c37 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,8 @@ "@fontsource/roboto": "^5.1.0", "@mui/icons-material": "^6.1.6", "@mui/material": "^6.1.6", + "@mui/x-date-pickers": "^7.22.1", + "dayjs": "^1.11.13", "firebase": "^11.0.1", "react": "^18.3.1", "react-calendar": "^5.1.0", diff --git a/src/components/Home/AddItem.jsx b/src/components/Home/AddItem.jsx index 43c4451..d80bc42 100644 --- a/src/components/Home/AddItem.jsx +++ b/src/components/Home/AddItem.jsx @@ -1,33 +1,47 @@ // @ts-check import CategoryPicker from '@/components/Home/CategoryPicker' +import { DateTimePicker } from '@/components/Home/DateTimePickers' import AddIcon from '@mui/icons-material/Add' import { IconButton, InputAdornment, TextField } from '@mui/material' import { useState } from 'react' const AddItem = ({ label, onAdd }) => { const [inputValue, setInputValue] = useState('') - const [selectedCategory, setSelectedCategory] = useState('#000000') + const [selectedCategory, setSelectedCategory] = useState('#000000') // For 'New Goal' + const [dueDate, setDueDate] = useState(null) // For 'New Task' const handleAdd = async () => { if (inputValue.trim()) { - await onAdd( - inputValue.trim(), - label === 'New Goal' ? selectedCategory : null - ) + let attributes = null + + if (label === 'New Goal') { + attributes = selectedCategory + } else if (label === 'New Task') { + attributes = dueDate + } + + await onAdd(inputValue.trim(), attributes ?? null) setInputValue('') setSelectedCategory('#000000') + setDueDate(null) } } // Conditionally render the category picker - const startAdornment = - label === 'New Goal' ? ( - - ) : null + const getStartAdornment = () => { + if (label === 'New Goal') { + return ( + + ) + } else if (label === 'New Task') { + return + } + return null + } return ( { fullWidth slotProps={{ input: { - startAdornment, + startAdornment: getStartAdornment(), endAdornment: ( setOpen((prev) => !prev) // Toggle the open state + + return ( + + + + + + + + + ) +} diff --git a/src/components/Home/MicroGoal.jsx b/src/components/Home/MicroGoal.jsx index 7f5a355..d67c14e 100644 --- a/src/components/Home/MicroGoal.jsx +++ b/src/components/Home/MicroGoal.jsx @@ -59,8 +59,8 @@ const MicroGoal = ({ microGoal, macroGoalIndex, microGoalIndex }) => { - addTask(macroGoalIndex, microGoalIndex, taskName) + onAdd={(taskName, dueDate) => + addTask(macroGoalIndex, microGoalIndex, taskName, dueDate) } /> diff --git a/src/components/Home/Task.jsx b/src/components/Home/Task.jsx index c3d3ece..066022e 100644 --- a/src/components/Home/Task.jsx +++ b/src/components/Home/Task.jsx @@ -1,5 +1,13 @@ +// @ts-check + import DeleteItem from '@/components/Home/DeleteItem' -import { Checkbox, ListItem, ListItemText } from '@mui/material' +import AccessTimeIcon from '@mui/icons-material/AccessTime' +import { Box, Checkbox, Chip, ListItem, ListItemText } from '@mui/material' +import { useTheme } from '@mui/material/styles' +import dayjs from 'dayjs' +import calendar from 'dayjs/plugin/calendar' + +dayjs.extend(calendar) const Task = ({ task, @@ -8,17 +16,32 @@ const Task = ({ microGoalIndex, taskIndex, }) => { + const renderDueDateChip = () => { + const theme = useTheme() + const isOverdue = + dayjs().isAfter(dayjs(task.due.toMillis())) && !task.completed + + return ( + } + size='small' + label={`${isOverdue ? 'Past' : 'Due'}: ${dayjs(task.due.toMillis()).calendar()}`} + sx={{ + ...(isOverdue && { + bgcolor: theme.palette.error.main, + color: theme.palette.error.contrastText, + }), + }} + /> + ) + } + return ( + {task.name} + {task.due ? renderDueDateChip() : null} + + } primaryTypographyProps={{ - sx: { - textDecoration: task.completed ? 'line-through' : 'none', - color: task.completed ? 'text.disabled' : 'text.primary', - }, + sx: task.completed ? styles.primaryTextCompleted : {}, }} />