Add demo scheduler component for advanced scheduling and automation
This commit is contained in:
parent
a24134f852
commit
40f1384dfc
6 changed files with 538 additions and 21 deletions
192
src/views/Landing/DemoAssignee.jsx
Normal file
192
src/views/Landing/DemoAssignee.jsx
Normal file
|
@ -0,0 +1,192 @@
|
|||
import {
|
||||
Box,
|
||||
Card,
|
||||
Checkbox,
|
||||
Grid,
|
||||
List,
|
||||
ListItem,
|
||||
Option,
|
||||
Select,
|
||||
Typography,
|
||||
} from '@mui/joy'
|
||||
import { useState } from 'react'
|
||||
const ASSIGN_STRATEGIES = [
|
||||
'random',
|
||||
'least_assigned',
|
||||
'least_completed',
|
||||
'keep_last_assigned',
|
||||
]
|
||||
const DemoAssignee = () => {
|
||||
const [assignStrategy, setAssignStrategy] = useState('random')
|
||||
const [assignees, setAssignees] = useState([
|
||||
{
|
||||
userId: 3,
|
||||
id: 3,
|
||||
displayName: 'Ryan',
|
||||
},
|
||||
])
|
||||
const [assignedTo, setAssignedTo] = useState(3)
|
||||
const performers = [
|
||||
{
|
||||
userId: 1,
|
||||
id: 1,
|
||||
displayName: 'Mo',
|
||||
},
|
||||
{
|
||||
userId: 2,
|
||||
id: 2,
|
||||
displayName: 'Jiji',
|
||||
},
|
||||
{
|
||||
userId: 3,
|
||||
id: 3,
|
||||
displayName: 'Ryan',
|
||||
},
|
||||
]
|
||||
return (
|
||||
<>
|
||||
<Grid item xs={12} sm={6} data-aos-create-chore-assignee>
|
||||
<Box
|
||||
mt={2}
|
||||
data-aos-delay={200}
|
||||
data-aos-anchor='[data-aos-create-chore-assignee]'
|
||||
data-aos='fade-right'
|
||||
>
|
||||
<Typography level='h4'>Assignees :</Typography>
|
||||
<Typography level='h5'>Who can do this chore?</Typography>
|
||||
<Card>
|
||||
<List
|
||||
orientation='horizontal'
|
||||
wrap
|
||||
sx={{
|
||||
'--List-gap': '8px',
|
||||
'--ListItem-radius': '20px',
|
||||
}}
|
||||
>
|
||||
{performers?.map(item => (
|
||||
<ListItem key={item.id}>
|
||||
<Checkbox
|
||||
// disabled={index === 0}
|
||||
checked={assignees.find(a => a.userId == item.id) != null}
|
||||
onClick={() => {
|
||||
if (assignees.find(a => a.userId == item.id)) {
|
||||
setAssignees(
|
||||
assignees.filter(i => i.userId !== item.id),
|
||||
)
|
||||
} else {
|
||||
setAssignees([...assignees, { userId: item.id }])
|
||||
}
|
||||
}}
|
||||
overlay
|
||||
disableIcon
|
||||
variant='soft'
|
||||
label={item.displayName}
|
||||
/>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
</Card>
|
||||
</Box>
|
||||
<Box
|
||||
mt={2}
|
||||
data-aos-delay={300}
|
||||
data-aos-anchor='[data-aos-create-chore-assignee]'
|
||||
data-aos='fade-right'
|
||||
>
|
||||
<Typography level='h4'>Assigned :</Typography>
|
||||
<Typography level='h5'>
|
||||
Who is assigned the next due chore?
|
||||
</Typography>
|
||||
|
||||
<Select
|
||||
placeholder={
|
||||
assignees.length === 0
|
||||
? 'No Assignees yet can perform this chore'
|
||||
: 'Select an assignee for this chore'
|
||||
}
|
||||
disabled={assignees.length === 0}
|
||||
value={assignedTo > -1 ? assignedTo : null}
|
||||
>
|
||||
{performers
|
||||
?.filter(p => assignees.find(a => a.userId == p.userId))
|
||||
.map((item, index) => (
|
||||
<Option
|
||||
value={item.id}
|
||||
key={item.displayName}
|
||||
onClick={() => {}}
|
||||
>
|
||||
{item.displayName}
|
||||
{/* <Chip size='sm' color='neutral' variant='soft'>
|
||||
</Chip> */}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</Box>
|
||||
<Box
|
||||
mt={2}
|
||||
data-aos-delay={400}
|
||||
data-aos-anchor='[data-aos-create-chore-assignee]'
|
||||
data-aos='fade-right'
|
||||
>
|
||||
<Typography level='h4'>Picking Mode :</Typography>
|
||||
<Typography level='h5'>
|
||||
How to pick the next assignee for the following chore?
|
||||
</Typography>
|
||||
|
||||
<Card>
|
||||
<List
|
||||
orientation='horizontal'
|
||||
wrap
|
||||
sx={{
|
||||
'--List-gap': '8px',
|
||||
'--ListItem-radius': '20px',
|
||||
}}
|
||||
>
|
||||
{ASSIGN_STRATEGIES.map((item, idx) => (
|
||||
<ListItem key={item}>
|
||||
<Checkbox
|
||||
// disabled={index === 0}
|
||||
checked={assignStrategy === item}
|
||||
onClick={() => setAssignStrategy(item)}
|
||||
overlay
|
||||
disableIcon
|
||||
variant='soft'
|
||||
label={item
|
||||
.split('_')
|
||||
.map(x => x.charAt(0).toUpperCase() + x.slice(1))
|
||||
.join(' ')}
|
||||
/>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
</Card>
|
||||
</Box>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6} data-aos-create-chore-section-assignee>
|
||||
<Card
|
||||
sx={{
|
||||
p: 4,
|
||||
py: 6,
|
||||
}}
|
||||
data-aos-delay={200}
|
||||
data-aos-anchor='[data-aos-create-chore-section-assignee]'
|
||||
data-aos='fade-left'
|
||||
>
|
||||
<Typography level='h3' textAlign='center' sx={{ mt: 2, mb: 4 }}>
|
||||
Flexible Task Assignment
|
||||
</Typography>
|
||||
<Typography level='body-lg' textAlign='center' sx={{ mb: 4 }}>
|
||||
Whether you’re a solo user managing personal tasks or coordinating
|
||||
chores with others, Donetick provides robust assignment options.
|
||||
Assign tasks to different people and choose specific rotation
|
||||
strategies, such as assigning tasks based on who completed the most
|
||||
or least, randomly rotating assignments, or sticking with the last
|
||||
assigned person.
|
||||
</Typography>
|
||||
</Card>
|
||||
</Grid>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default DemoAssignee
|
95
src/views/Landing/DemoHistory.jsx
Normal file
95
src/views/Landing/DemoHistory.jsx
Normal file
|
@ -0,0 +1,95 @@
|
|||
import { Box, Card, Grid, List, Typography } from '@mui/joy'
|
||||
import moment from 'moment'
|
||||
import HistoryCard from '../History/HistoryCard'
|
||||
|
||||
const DemoHistory = () => {
|
||||
const allHistory = [
|
||||
{
|
||||
id: 32,
|
||||
choreId: 12,
|
||||
completedAt: moment().format(),
|
||||
completedBy: 1,
|
||||
assignedTo: 1,
|
||||
notes: null,
|
||||
dueDate: moment().format(),
|
||||
},
|
||||
{
|
||||
id: 31,
|
||||
choreId: 12,
|
||||
completedAt: moment().day(-1).format(),
|
||||
completedBy: 1,
|
||||
assignedTo: 1,
|
||||
notes: 'Need to be replaced with a new one',
|
||||
dueDate: moment().day(-2).format(),
|
||||
},
|
||||
{
|
||||
id: 31,
|
||||
choreId: 12,
|
||||
completedAt: moment().day(-10).format(),
|
||||
completedBy: 1,
|
||||
assignedTo: 1,
|
||||
notes: null,
|
||||
dueDate: moment().day(-10).format(),
|
||||
},
|
||||
]
|
||||
const performers = [
|
||||
{
|
||||
userId: 1,
|
||||
displayName: 'Ryan',
|
||||
},
|
||||
{
|
||||
userId: 2,
|
||||
displayName: 'Sarah',
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<>
|
||||
<Grid item xs={12} sm={6} data-aos-history-list>
|
||||
<Box sx={{ borderRadius: 'sm', p: 2, boxShadow: 'md' }}>
|
||||
<List sx={{ p: 0 }}>
|
||||
{allHistory.map((historyEntry, index) => (
|
||||
<div
|
||||
data-aos-delay={100 * index + 200}
|
||||
data-aos-anchor='[data-aos-history-list]'
|
||||
data-aos='fade-right'
|
||||
key={index}
|
||||
>
|
||||
<HistoryCard
|
||||
allHistory={allHistory}
|
||||
historyEntry={historyEntry}
|
||||
key={index}
|
||||
index={index}
|
||||
performers={performers}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</List>
|
||||
</Box>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6} data-aos-history-demo-section>
|
||||
<Card
|
||||
sx={{
|
||||
p: 4,
|
||||
py: 6,
|
||||
}}
|
||||
data-aos-delay={200}
|
||||
data-aos-anchor='[data-aos-history-demo-section]'
|
||||
data-aos='fade-left'
|
||||
>
|
||||
<Typography level='h3' textAlign='center' sx={{ mt: 2, mb: 4 }}>
|
||||
History with a purpose
|
||||
</Typography>
|
||||
<Typography level='body-lg' textAlign='center' sx={{ mb: 4 }}>
|
||||
Keep track of all your chores and tasks with ease. Donetick records
|
||||
due dates, completion dates, and who completed each task. Any notes
|
||||
added to tasks are also tracked, providing a complete history for
|
||||
your reference. Stay organized and informed with detailed task
|
||||
tracking.
|
||||
</Typography>
|
||||
</Card>
|
||||
</Grid>
|
||||
</>
|
||||
)
|
||||
}
|
||||
export default DemoHistory
|
138
src/views/Landing/DemoMyChore.jsx
Normal file
138
src/views/Landing/DemoMyChore.jsx
Normal file
|
@ -0,0 +1,138 @@
|
|||
import { Card, Grid, Typography } from '@mui/joy'
|
||||
import moment from 'moment'
|
||||
import ChoreCard from '../Chores/ChoreCard'
|
||||
|
||||
const DemoMyChore = () => {
|
||||
const cards = [
|
||||
{
|
||||
id: 12,
|
||||
name: '♻️ Take out recycle ',
|
||||
frequencyType: 'days_of_the_week',
|
||||
frequency: 1,
|
||||
frequencyMetadata:
|
||||
'{"days":["thursday"],"time":"2024-07-07T22:00:00-04:00"}',
|
||||
nextDueDate: moment().add(1, 'days').hour(8).minute(0).toISOString(),
|
||||
isRolling: false,
|
||||
assignedTo: 1,
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
name: '🐜 Spray Pesticide',
|
||||
frequencyType: 'interval',
|
||||
frequency: 3,
|
||||
frequencyMetadata: '{"unit":"months"}',
|
||||
nextDueDate: moment().subtract(7, 'day').toISOString(),
|
||||
isRolling: false,
|
||||
assignedTo: 1,
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: '🍂 Gutter Cleaning',
|
||||
frequencyType: 'day_of_the_month',
|
||||
frequency: 1,
|
||||
frequencyMetadata: '{"months":["may"]}',
|
||||
nextDueDate: moment()
|
||||
.month('may')
|
||||
.year(moment().year() + 1)
|
||||
.date(1)
|
||||
.hour(17)
|
||||
.minute(0)
|
||||
.toISOString(),
|
||||
isRolling: false,
|
||||
assignedTo: 1,
|
||||
},
|
||||
// {
|
||||
// id: 10,
|
||||
// name: '💨 Air dust Synology NAS and',
|
||||
// frequencyType: 'interval',
|
||||
// frequency: 12,
|
||||
// frequencyMetadata: '{"unit":"weeks"}',
|
||||
// nextDueDate: '2024-07-24T17:18:00Z',
|
||||
// isRolling: false,
|
||||
// assignedTo: 1,
|
||||
// },
|
||||
// {
|
||||
// id: 8,
|
||||
// name: '🛁 Deep Cleaning Bathroom',
|
||||
// frequencyType: 'monthly',
|
||||
// frequency: 1,
|
||||
// frequencyMetadata: '{}',
|
||||
// nextDueDate: '2024-08-04T17:15:00Z',
|
||||
// isRolling: false,
|
||||
// assignedTo: 1,
|
||||
// },
|
||||
// {
|
||||
// id: 11,
|
||||
// name: '☴ Replace AC Air filter',
|
||||
// frequencyType: 'adaptive',
|
||||
// frequency: 1,
|
||||
// frequencyMetadata: '{"unit":"days"}',
|
||||
// nextDueDate: moment().add(120, 'days').toISOString(),
|
||||
// isRolling: false,
|
||||
// assignedTo: 1,
|
||||
// },
|
||||
// {
|
||||
// id: 6,
|
||||
// name: '🍂 Gutter Cleaning ',
|
||||
// frequencyType: 'day_of_the_month',
|
||||
// frequency: 1,
|
||||
// frequencyMetadata: '{"months":["may"]}',
|
||||
// nextDueDate: '2025-05-01T17:00:00Z',
|
||||
// isRolling: false,
|
||||
// assignedTo: 1,
|
||||
// },
|
||||
// {
|
||||
// id: 13,
|
||||
// name: '🚰 Replace Water Filter',
|
||||
// frequencyType: 'yearly',
|
||||
// frequency: 1,
|
||||
// frequencyMetadata: '{}',
|
||||
// nextDueDate: '2025-07-08T01:00:00Z',
|
||||
// isRolling: false,
|
||||
// assignedTo: 1,
|
||||
// },
|
||||
]
|
||||
|
||||
const users = [{ displayName: 'Me', id: 1 }]
|
||||
return (
|
||||
<>
|
||||
<Grid item xs={12} sm={5} data-aos-first-tasks-list>
|
||||
{cards.map((card, index) => (
|
||||
<div
|
||||
key={index}
|
||||
data-aos-delay={100 * index + 200}
|
||||
data-aos-anchor='[data-aos-first-tasks-list]'
|
||||
data-aos='fade-up'
|
||||
>
|
||||
<ChoreCard chore={card} performers={users} viewOnly={true} />
|
||||
</div>
|
||||
))}
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={7} data-aos-my-chore-demo-section>
|
||||
<Card
|
||||
sx={{
|
||||
p: 4,
|
||||
py: 6,
|
||||
}}
|
||||
data-aos-delay={200}
|
||||
data-aos-anchor='[data-aos-my-chore-demo-section]'
|
||||
data-aos='fade-left'
|
||||
>
|
||||
<Typography level='h3' textAlign='center' sx={{ mt: 2, mb: 4 }}>
|
||||
Glance at your task and chores
|
||||
</Typography>
|
||||
<Typography level='body-lg' textAlign='center' sx={{ mb: 4 }}>
|
||||
Main view prioritize tasks due today, followed by overdue ones, and
|
||||
finally, future tasks or those without due dates. With Donetick, you
|
||||
can view all the tasks you've created (whether assigned to you or
|
||||
not) as well as tasks assigned to you by others. Quickly mark them
|
||||
as done with just one click, ensuring a smooth and efficient task
|
||||
management experience.
|
||||
</Typography>
|
||||
</Card>
|
||||
</Grid>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default DemoMyChore
|
68
src/views/Landing/DemoScheduler.jsx
Normal file
68
src/views/Landing/DemoScheduler.jsx
Normal file
|
@ -0,0 +1,68 @@
|
|||
import { Box, Card, Grid, Typography } from '@mui/joy'
|
||||
import { useState } from 'react'
|
||||
import RepeatSection from '../ChoreEdit/RepeatSection'
|
||||
|
||||
const DemoScheduler = () => {
|
||||
const [assignees, setAssignees] = useState([])
|
||||
const [frequency, setFrequency] = useState(2)
|
||||
const [frequencyType, setFrequencyType] = useState('weekly')
|
||||
const [frequencyMetadata, setFrequencyMetadata] = useState({
|
||||
months: ['may', 'june', 'july'],
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
<Grid item xs={12} sm={5} data-aos-create-chore-scheduler>
|
||||
<Box
|
||||
data-aos-delay={300}
|
||||
data-aos-anchor='[data-aos-create-chore-scheduler]'
|
||||
data-aos='fade-right'
|
||||
>
|
||||
<RepeatSection
|
||||
frequency={frequency}
|
||||
onFrequencyUpdate={setFrequency}
|
||||
frequencyType={frequencyType}
|
||||
onFrequencyTypeUpdate={setFrequencyType}
|
||||
frequencyMetadata={frequencyMetadata}
|
||||
onFrequencyMetadataUpdate={setFrequencyMetadata}
|
||||
onFrequencyTimeUpdate={t => {}}
|
||||
frequencyError={null}
|
||||
allUserThings={[]}
|
||||
onTriggerUpdate={thingUpdate => {}}
|
||||
OnTriggerValidate={() => {}}
|
||||
isAttemptToSave={false}
|
||||
selectedThing={null}
|
||||
/>
|
||||
</Box>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={7} data-aos-create-chore-section-scheduler>
|
||||
<Card
|
||||
sx={{
|
||||
p: 4,
|
||||
py: 6,
|
||||
}}
|
||||
data-aos-delay={200}
|
||||
data-aos-anchor='[data-aos-create-chore-section-scheduler]'
|
||||
data-aos='fade-left'
|
||||
>
|
||||
<Typography level='h3' textAlign='center' sx={{ mt: 2, mb: 4 }}>
|
||||
Advanced Scheduling and Automation
|
||||
</Typography>
|
||||
<Typography level='body-lg' textAlign='center' sx={{ mb: 4 }}>
|
||||
Scheduling is a crucial aspect of managing tasks and chores.
|
||||
Donetick offers basic scheduling options, such as recurring tasks
|
||||
daily, weekly, or yearly, as well as more customizable schedules
|
||||
like specific days of the week or month. For those unsure of exact
|
||||
frequencies, the adaptive scheduling feature averages based on how
|
||||
often you mark a task as completed. Additionally, Donetick supports
|
||||
automation by linking tasks with triggers via API. When specific
|
||||
conditions are met, Donetick’s Things feature will automatically
|
||||
initiate the task, streamlining your workflow.
|
||||
</Typography>
|
||||
</Card>
|
||||
</Grid>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default DemoScheduler
|
|
@ -4,15 +4,18 @@ import { Button } from '@mui/joy'
|
|||
import Typography from '@mui/joy/Typography'
|
||||
import Box from '@mui/material/Box'
|
||||
import Grid from '@mui/material/Grid'
|
||||
import React from 'react'
|
||||
import React, { useEffect } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
|
||||
import Logo from '@/assets/logo.svg'
|
||||
import screenShotMyChore from '@/assets/screenshot-my-chore.png'
|
||||
import { GitHub } from '@mui/icons-material'
|
||||
import useWindowWidth from '../../hooks/useWindowWidth'
|
||||
|
||||
const HomeHero = () => {
|
||||
const navigate = useNavigate()
|
||||
const windowWidth = useWindowWidth()
|
||||
const windowThreshold = 600
|
||||
const HERO_TEXT_THAT = [
|
||||
// 'Donetick simplifies the entire process, from scheduling and reminders to automatic task assignment and progress tracking.',
|
||||
// 'Donetick is the intuitive task and chore management app designed for groups. Take charge of shared responsibilities, automate your workflow, and achieve more together.',
|
||||
|
@ -21,7 +24,7 @@ const HomeHero = () => {
|
|||
|
||||
const [heroTextIndex, setHeroTextIndex] = React.useState(0)
|
||||
|
||||
React.useEffect(() => {
|
||||
useEffect(() => {
|
||||
// const intervalId = setInterval(
|
||||
// () => setHeroTextIndex(index => index + 1),
|
||||
// 4000, // every 4 seconds
|
||||
|
@ -162,7 +165,7 @@ const HomeHero = () => {
|
|||
</Box>
|
||||
</div>
|
||||
</Grid>
|
||||
|
||||
{windowWidth > windowThreshold && (
|
||||
<Grid item xs={12} md={5}>
|
||||
<div className='flex justify-center'>
|
||||
<img
|
||||
|
@ -179,6 +182,7 @@ const HomeHero = () => {
|
|||
/>
|
||||
</div>
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
import { Container } from '@mui/joy'
|
||||
import { Container, Grid } from '@mui/joy'
|
||||
import AOS from 'aos'
|
||||
import 'aos/dist/aos.css'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import DemoAssignee from './DemoAssignee'
|
||||
import DemoHistory from './DemoHistory'
|
||||
import DemoMyChore from './DemoMyChore'
|
||||
import DemoScheduler from './DemoScheduler'
|
||||
import FeaturesSection from './FeaturesSection'
|
||||
import HomeHero from './HomeHero'
|
||||
const Landing = () => {
|
||||
|
@ -10,7 +14,6 @@ const Landing = () => {
|
|||
const getCurrentUser = () => {
|
||||
return JSON.parse(localStorage.getItem('user'))
|
||||
}
|
||||
const [users, setUsers] = useState([])
|
||||
const [currentUser, setCurrentUser] = useState(getCurrentUser())
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -22,6 +25,23 @@ const Landing = () => {
|
|||
return (
|
||||
<Container className='flex h-full items-center justify-center'>
|
||||
<HomeHero />
|
||||
<Grid
|
||||
overflow={'hidden'}
|
||||
container
|
||||
spacing={4}
|
||||
sx={{
|
||||
mt: 5,
|
||||
mb: 5,
|
||||
// align item vertically:
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<DemoMyChore />
|
||||
<DemoAssignee />
|
||||
<DemoScheduler />
|
||||
|
||||
<DemoHistory />
|
||||
</Grid>
|
||||
<FeaturesSection />
|
||||
{/* <PricingSection /> */}
|
||||
</Container>
|
||||
|
|
Loading…
Add table
Reference in a new issue