diff --git a/src/utils/Fetcher.jsx b/src/utils/Fetcher.jsx index afa9235..1161543 100644 --- a/src/utils/Fetcher.jsx +++ b/src/utils/Fetcher.jsx @@ -73,6 +73,16 @@ const MarkChoreComplete = (id, note, completedDate) => { body: JSON.stringify(body), }) } + +const SkipChore = id => { + return Fetch(`${API_URL}/chores/${id}/skip`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({}), + }) +} const CreateChore = chore => { return Fetch(`${API_URL}/chores/`, { method: 'POST', @@ -277,6 +287,7 @@ export { SaveChore, SaveThing, signUp, + SkipChore, UpdateThingState, UpdateUserDetails, } diff --git a/src/views/ChoreEdit/ChoreView.jsx b/src/views/ChoreEdit/ChoreView.jsx index df45aa7..172ad9b 100644 --- a/src/views/ChoreEdit/ChoreView.jsx +++ b/src/views/ChoreEdit/ChoreView.jsx @@ -3,48 +3,53 @@ import { CancelScheduleSend, Check, Checklist, - Note, + History, PeopleAlt, Person, + SwitchAccessShortcut, + Timelapse, } from '@mui/icons-material' import { Box, Button, Card, Checkbox, + Chip, Container, FormControl, Grid, Input, ListItem, ListItemContent, - ListItemDecorator, Sheet, Snackbar, styled, Typography, } from '@mui/joy' +import { Divider } from '@mui/material' import moment from 'moment' import { useEffect, useState } from 'react' -import { useParams, useSearchParams } from 'react-router-dom' +import { useNavigate, useParams, useSearchParams } from 'react-router-dom' import { GetAllUsers, GetChoreDetailById, MarkChoreComplete, + SkipChore, } from '../../utils/Fetcher' +import ConfirmationModal from '../Modals/Inputs/ConfirmationModal' const IconCard = styled('div')({ display: 'flex', alignItems: 'center', justifyContent: 'center', backgroundColor: '#f0f0f0', // Adjust the background color as needed borderRadius: '50%', - minWidth: '50px', - height: '50px', + minWidth: '40px', + height: '40px', marginRight: '16px', }) const ChoreView = () => { const [chore, setChore] = useState({}) - + const navigate = useNavigate() const [performers, setPerformers] = useState([]) const [infoCards, setInfoCards] = useState([]) const { choreId } = useParams() @@ -56,6 +61,8 @@ const ChoreView = () => { const [timeoutId, setTimeoutId] = useState(null) const [secondsLeftToCancel, setSecondsLeftToCancel] = useState(null) const [completedDate, setCompletedDate] = useState(null) + const [confirmModelConfig, setConfirmModelConfig] = useState({}) + useEffect(() => { Promise.all([ GetChoreDetailById(choreId).then(resp => { @@ -85,20 +92,20 @@ const ChoreView = () => { const generateInfoCards = chore => { const cards = [ { - icon: , - text: 'Due Date', - subtext: moment(chore.nextDueDate).format('MM/DD/YYYY hh:mm A'), - }, - { + size: 6, icon: , text: 'Assigned To', subtext: performers.find(p => p.id === chore.assignedTo)?.displayName, }, { - icon: , - text: 'Created By', - subtext: performers.find(p => p.id === chore.createdBy)?.displayName, + size: 6, + icon: , + text: 'Due Date', + subtext: chore.nextDueDate + ? moment(chore.nextDueDate).fromNow() + : 'N/A', }, + // { // icon: , // text: 'Frequency', @@ -107,35 +114,42 @@ const ChoreView = () => { // chore.frequencyType.slice(1), // }, { + size: 6, icon: , text: 'Total Completed', - subtext: `${chore.totalCompletedCount}`, + subtext: `${chore.totalCompletedCount} times`, }, - // { - // icon: , - // text: 'Last Completed', - // subtext: - // chore.lastCompletedDate && - // moment(chore.lastCompletedDate).format('MM/DD/YYYY hh:mm A'), - // }, { - icon: , + size: 6, + icon: , text: 'Last Completed', + subtext: + // chore.lastCompletedDate && + // moment(chore.lastCompletedDate).format('MM/DD/YYYY hh:mm A'), + chore.lastCompletedDate && moment(chore.lastCompletedDate).fromNow(), + }, + { + size: 6, + icon: , + text: 'Last Performer', subtext: chore.lastCompletedDate ? `${ - chore.lastCompletedDate && - moment(chore.lastCompletedDate).fromNow() - // moment(chore.lastCompletedDate).format('MM/DD/YYYY hh:mm A')) - }(${ performers.find(p => p.id === chore.lastCompletedBy)?.displayName - })` - : 'Never', + }` + : '--', }, { - icon: , - text: 'Recent Note', - subtext: chore.notes || '--', + size: 6, + icon: , + text: 'Created By', + subtext: performers.find(p => p.id === chore.createdBy)?.displayName, }, + // { + // size: 12, + // icon: , + // text: 'Recent Note', + // subtext: chore.notes || '--', + // }, ] setInfoCards(cards) } @@ -184,7 +198,16 @@ const ChoreView = () => { setTimeoutId(id) } - + const handleSkippingTask = () => { + SkipChore(choreId).then(response => { + if (response.ok) { + response.json().then(data => { + const newChore = data.res + setChore(newChore) + }) + } + }) + } return ( { justifyContent: 'space-between', }} > - + {chore.name} + } size='lg' sx={{ mb: 4 }}> + {chore.nextDueDate + ? `Due at ${moment(chore.nextDueDate).format('MM/DD/YYYY hh:mm A')}` + : 'N/A'} + + + + + Details + - - {infoCards.map((info, index) => ( - - - - - {info.icon} - + + + {infoCards.map((detail, index) => ( + + {/* divider between the list items: */} + + - - {info.text} - - - {info.subtext ? info.subtext : '--'} + + {detail.text} + + {detail.subtext ? detail.subtext : '--'} + - - - ))} - + + ))} + + + {chore.notes && ( + <> + + Previous note: + + + + {chore.notes || '--'} + + + + )} {/* */} - + + Actions + { mt: 2, }} > - Additional Notes - { - if (e.target.value.trim() === '') { - setNote(null) - return - } - setNote(e.target.value) - }} - size='md' - sx={{ - my: 1, - }} - /> + + Complete the task + - + + { + if (e.target.checked) { + setNote('') + } else { + setNote(null) + } + }} + overlay + sx={ + { + // my: 1, + } + } + label={ + + Add Additional Notes + + } + /> + + {note !== null && ( + { + if (e.target.value.trim() === '') { + setNote(null) + return + } + setNote(e.target.value) + }} + size='md' + sx={{ + mb: 1, + }} + /> + )} + + { } }} overlay - sx={{ - my: 1, - }} - label={Set completion date} + sx={ + { + // my: 1, + } + } + label={ + + Specify completion date + + } /> {completedDate !== null && ( @@ -295,22 +411,10 @@ const ChoreView = () => { }} /> )} - {completedDate === null && ( - // placeholder for the completion date with margin: - - )} - {/* */} - + or - - } - > - - Task will be marked as completed in {secondsLeftToCancel} seconds - - + + + + { + if (timeoutId) { + clearTimeout(timeoutId) + setIsPendingCompletion(false) + setTimeoutId(null) + setSecondsLeftToCancel(null) // Reset or adjust as needed + } + }} + size='lg' + variant='outlined' + color='danger' + startDecorator={} + > + Cancel + + } + > + + Task will be marked as completed in {secondsLeftToCancel} seconds + + + + ) } diff --git a/src/views/ChoreEdit/RepeatSection.jsx b/src/views/ChoreEdit/RepeatSection.jsx index 99f196f..bdf6738 100644 --- a/src/views/ChoreEdit/RepeatSection.jsx +++ b/src/views/ChoreEdit/RepeatSection.jsx @@ -509,7 +509,7 @@ const RepeatSection = ({ }} > Is this something that should be done when a thing state changes?{' '} - {!isPlusAccount(userProfile) && ( + {userProfile && !isPlusAccount(userProfile) && ( Not available in Basic Plan diff --git a/src/views/Chores/ChoreCard.jsx b/src/views/Chores/ChoreCard.jsx index 08a5406..97e407a 100644 --- a/src/views/Chores/ChoreCard.jsx +++ b/src/views/Chores/ChoreCard.jsx @@ -17,6 +17,7 @@ import { SwitchAccessShortcut, TimesOneMobiledata, Update, + ViewCarousel, Webhook, } from '@mui/icons-material' import { @@ -38,7 +39,7 @@ import moment from 'moment' import React, { useEffect } from 'react' import { useNavigate } from 'react-router-dom' import { API_URL } from '../../Config' -import { MarkChoreComplete } from '../../utils/Fetcher' +import { MarkChoreComplete, SkipChore } from '../../utils/Fetcher' import { Fetch } from '../../utils/TokenManager' import ConfirmationModal from '../Modals/Inputs/ConfirmationModal' import DateModal from '../Modals/Inputs/DateModal' @@ -107,6 +108,9 @@ const ChoreCard = ({ const handleEdit = () => { navigate(`/chores/${chore.id}/edit`) } + const handleView = () => { + navigate(`/chores/${chore.id}`) + } const handleDelete = () => { setConfirmModelConfig({ isOpen: true, @@ -521,13 +525,7 @@ const ChoreCard = ({ { - Fetch(`${API_URL}/chores/${chore.id}/skip`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({}), - }).then(response => { + SkipChore(chore.id).then(response => { if (response.ok) { response.json().then(data => { const newChore = data.res @@ -585,6 +583,10 @@ const ChoreCard = ({ Edit + + + View + Delete diff --git a/src/views/Chores/MyChores.jsx b/src/views/Chores/MyChores.jsx index cd90942..d4eed5a 100644 --- a/src/views/Chores/MyChores.jsx +++ b/src/views/Chores/MyChores.jsx @@ -3,7 +3,6 @@ import { Badge, Box, Checkbox, - CircularProgress, Container, IconButton, Input, @@ -18,8 +17,8 @@ import Fuse from 'fuse.js' import { useContext, useEffect, useRef, useState } from 'react' import { useNavigate } from 'react-router-dom' import { UserContext } from '../../contexts/UserContext' -import Logo from '../../Logo' import { GetAllUsers, GetChores, GetUserProfile } from '../../utils/Fetcher' +import LoadingComponent from '../components/Loading' import ChoreCard from './ChoreCard' const MyChores = () => { @@ -184,18 +183,7 @@ const MyChores = () => { } if (userProfile === null) { - return ( - - - - - - - - ) + return } return ( diff --git a/src/views/History/ChoreHistory.jsx b/src/views/History/ChoreHistory.jsx index b85e456..1db1f06 100644 --- a/src/views/History/ChoreHistory.jsx +++ b/src/views/History/ChoreHistory.jsx @@ -1,15 +1,13 @@ -import { Checklist, EventBusy, Timelapse } from '@mui/icons-material' +import { Checklist, EventBusy, Group, Timelapse } from '@mui/icons-material' import { Avatar, - Box, Button, - CircularProgress, + Chip, Container, Grid, List, ListItem, ListItemContent, - ListItemDecorator, Sheet, Typography, } from '@mui/joy' @@ -19,6 +17,7 @@ import { Link, useParams } from 'react-router-dom' import { API_URL } from '../../Config' import { GetAllCircleMembers } from '../../utils/Fetcher' import { Fetch } from '../../utils/TokenManager' +import LoadingComponent from '../components/Loading' import HistoryCard from './HistoryCard' const ChoreHistory = () => { @@ -92,59 +91,49 @@ const ChoreHistory = () => { const historyInfo = [ { - icon: ( - - - - ), - text: `${histories.length} completed`, - subtext: `${Object.keys(userHistories).length} users contributed`, + icon: , + text: 'Total Completed', + subtext: `${histories.length} times`, }, { - icon: ( - - - - ), - text: `Completed within ${moment - .duration(averageDelayMoment) - .humanize()}`, - subtext: `Maximum delay was ${moment - .duration(maxDelayMoment) - .humanize()}`, + icon: , + text: 'Usually Within', + subtext: moment.duration(averageDelayMoment).humanize(), }, { - icon: , - text: `${ + icon: , + text: 'Maximum Delay', + subtext: moment.duration(maxDelayMoment).humanize(), + }, + { + icon: , + text: ' Completed Most', + subtext: `${ performers.find(p => p.userId === Number(userCompletedByMost)) ?.displayName - } completed most`, - subtext: `${userHistories[userCompletedByMost]} time/s`, + } `, + }, + // contributes: + { + icon: , + text: 'Total Performers', + subtext: `${Object.keys(userHistories).length} users`, + }, + { + icon: , + text: 'Last Completed', + subtext: `${ + performers.find(p => p.userId === Number(histories[0].completedBy)) + ?.displayName + }`, }, ] - if (userCompletedByLeast !== userCompletedByMost) { - historyInfo.push({ - icon: ( - - { - performers.find(p => p.userId === userCompletedByLeast) - ?.displayName - } - - ), - text: `${ - performers.find(p => p.userId === Number(userCompletedByLeast)) - .displayName - } completed least`, - subtext: `${userHistories[userCompletedByLeast]} time/s`, - }) - } setHistoryInfo(historyInfo) } if (isLoading) { - return // Show loading indicator + return } if (!choreHistory.length) { return ( @@ -184,50 +173,43 @@ const ChoreHistory = () => { return ( - + Summary: - {/* - - - - - - - - - {choreHistory.length} completed - - - {Object.keys(userHistory).length} users contributed - - - - */} - - {historyInfo.map((info, index) => ( - - - - {info.icon} + + + {historyInfo.map((info, index) => ( + + {/* divider between the list items: */} + + - + {info.text} - - {info.subtext} - + + {info.subtext ? info.subtext : '--'} + - - - ))} - + + ))} + + + {/* User History Cards */} - + History: - + {/* Chore History List (Updated Style) */} @@ -241,7 +223,7 @@ const ChoreHistory = () => { /> ))} - + ) } diff --git a/src/views/History/HistoryCard.jsx b/src/views/History/HistoryCard.jsx index c606fbf..5e71f52 100644 --- a/src/views/History/HistoryCard.jsx +++ b/src/views/History/HistoryCard.jsx @@ -1,3 +1,4 @@ +import { CalendarViewDay, Check, Timelapse } from '@mui/icons-material' import { Avatar, Box, @@ -30,6 +31,50 @@ const HistoryCard = ({ allHistory, performers, historyEntry, index }) => { return `${timeValue} ${unit}${timeValue !== 1 ? 's' : ''}` } + + const getCompletedChip = historyEntry => { + var text = 'No Due Date' + var color = 'info' + var icon = + // if completed few hours +-6 hours + if ( + historyEntry.dueDate && + historyEntry.completedAt > historyEntry.dueDate - 1000 * 60 * 60 * 6 && + historyEntry.completedAt < historyEntry.dueDate + 1000 * 60 * 60 * 6 + ) { + text = 'On Time' + color = 'success' + icon = + } else if ( + historyEntry.dueDate && + historyEntry.completedAt < historyEntry.dueDate + ) { + text = 'On Time' + color = 'success' + icon = + } + + // if completed after due date then it's late + else if ( + historyEntry.dueDate && + historyEntry.completedAt > historyEntry.dueDate + ) { + text = 'Late' + color = 'warning' + icon = + } else { + text = 'No Due Date' + color = 'info' + icon = + } + + return ( + + {text} + + ) + } + return ( <> @@ -55,13 +100,7 @@ const HistoryCard = ({ allHistory, performers, historyEntry, index }) => { {moment(historyEntry.completedAt).format('ddd MM/DD/yyyy HH:mm')} - - - {historyEntry.dueDate && - historyEntry.completedAt > historyEntry.dueDate - ? 'Late' - : 'On Time'} - + {getCompletedChip(historyEntry)} diff --git a/src/views/Landing/DemoHistory.jsx b/src/views/Landing/DemoHistory.jsx index 2c713f1..4e0f45e 100644 --- a/src/views/Landing/DemoHistory.jsx +++ b/src/views/Landing/DemoHistory.jsx @@ -7,7 +7,7 @@ const DemoHistory = () => { { id: 32, choreId: 12, - completedAt: moment().format(), + completedAt: moment().hour(4).format(), completedBy: 1, assignedTo: 1, notes: null, @@ -25,8 +25,8 @@ const DemoHistory = () => { { id: 31, choreId: 12, - completedAt: moment().day(-10).format(), - completedBy: 1, + completedAt: moment().day(-10).hour(1).format(), + completedBy: 2, assignedTo: 1, notes: null, dueDate: moment().day(-10).format(), diff --git a/src/views/Landing/FeaturesSection.jsx b/src/views/Landing/FeaturesSection.jsx index 4133d0a..32dcbc8 100644 --- a/src/views/Landing/FeaturesSection.jsx +++ b/src/views/Landing/FeaturesSection.jsx @@ -25,44 +25,39 @@ const FeatureIcon = styled('div')({ const CardData = [ { title: 'Open Source & Transparent', - headline: 'Built for the Community', + description: - 'Donetick is a community-driven, open-source project. Contribute, customize, and make task management truly yours.', + 'Donetick is open source software. You can view, modify, and contribute to the code on GitHub.', icon: CodeRounded, }, { title: 'Circles: Your Task Hub', - headline: 'Share & Conquer Together', description: - 'Create circles for your family, friends, or team. Easily share tasks and track progress within each group.', + 'build with sharing in mind. invite other to the circle and you can assign tasks to each other. and only see the tasks the should be shared', icon: GroupRounded, }, { title: 'Track Your Progress', - headline: "See Who's Done What", description: - 'View a history of task completion for each member of your circles. Celebrate successes and stay on top of your goals.', + 'View a history of completed tasks. or use things to track simply things!', icon: HistoryRounded, }, { - title: 'Automated Chore Scheduling', - headline: 'Fully Customizable Recurring Tasks', + title: 'Automated Task Scheduling', description: - 'Set up chores to repeat daily, weekly, or monthly. Donetick will automatically assign and track each task for you.', + 'Set up Tasks to repeat daily, weekly, or monthly, or maybe specifc day in specifc months? Donetick have a flexible scheduling system', icon: AutoAwesomeMosaicOutlined, }, { title: 'Automated Task Assignment', - headline: 'Share Responsibilities Equally', description: - 'can automatically assigns tasks to each member of your circle. Randomly or based on past completion.', + 'For shared tasks, Donetick can randomly rotate assignments or choose based on last completion or least assigned.', icon: AutoAwesomeRounded, }, { title: 'Integrations & Webhooks', - headline: 'API & 3rd Party Integrations', description: - 'Connect Donetick with your favorite apps and services. Trigger tasks based on events from other platforms.', + 'Donetick can update things programmatically with API call. you can update things from other services like IFTTT, Homeassistant or even your own service', icon: Webhook, }, ] @@ -80,7 +75,7 @@ function Feature2({ icon: Icon, title, headline, description, index }) { @@ -106,7 +101,7 @@ function FeaturesSection() { - Features Overview + Why Donetick? diff --git a/src/views/Landing/Footer.jsx b/src/views/Landing/Footer.jsx new file mode 100644 index 0000000..11a011a --- /dev/null +++ b/src/views/Landing/Footer.jsx @@ -0,0 +1,127 @@ +import LogoSVG from '@/assets/logo.svg' +import { Card, Grid } from '@mui/joy' +import Box from '@mui/joy/Box' +import Link from '@mui/joy/Link' +import Typography from '@mui/joy/Typography' +import * as React from 'react' + +function Footer() { + return ( + + + +
+ logo +
+ + + Done + + tick✓ + + + + Beta + + +
+ + + Github + + + Core(Backend) + + + Frontend + + + Home Assistant Addon + + + Packages + + + + + Links + + + + Roadmap(soon) + + + Documentation(soon) + + + Changelog(soon) + + + {/* + + Others + + + Telegram Integration + + + Slash Commands + + */} +
+
+ ) +} + +export default Footer diff --git a/src/views/Landing/Landing.jsx b/src/views/Landing/Landing.jsx index a8b650d..4ca1b60 100644 --- a/src/views/Landing/Landing.jsx +++ b/src/views/Landing/Landing.jsx @@ -1,4 +1,4 @@ -import { Container, Grid } from '@mui/joy' +import { Box, Container, Grid } from '@mui/joy' import AOS from 'aos' import 'aos/dist/aos.css' import { useEffect } from 'react' @@ -8,6 +8,7 @@ import DemoHistory from './DemoHistory' import DemoMyChore from './DemoMyChore' import DemoScheduler from './DemoScheduler' import FeaturesSection from './FeaturesSection' +import Footer from './Footer' import HomeHero from './HomeHero' const Landing = () => { const Navigate = useNavigate() @@ -39,6 +40,17 @@ const Landing = () => { {/* */} + + +