Merge branch 'dev'
This commit is contained in:
commit
cb4fcc82f2
6 changed files with 208 additions and 10 deletions
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "fe-template",
|
"name": "donetick",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.1.63",
|
"version": "0.1.65",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
"*.{js,jsx,ts,tsx}": [
|
"*.{js,jsx,ts,tsx}": [
|
||||||
|
|
|
@ -112,6 +112,20 @@ const GetChoreHistory = choreId => {
|
||||||
headers: HEADERS(),
|
headers: HEADERS(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
const DeleteChoreHistory = (choreId, id) => {
|
||||||
|
return Fetch(`${API_URL}/chores/${choreId}/history/${id}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: HEADERS(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const UpdateChoreHistory = (choreId, id, choreHistory) => {
|
||||||
|
return Fetch(`${API_URL}/chores/${choreId}/history/${id}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: HEADERS(),
|
||||||
|
body: JSON.stringify(choreHistory),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const GetAllCircleMembers = () => {
|
const GetAllCircleMembers = () => {
|
||||||
return Fetch(`${API_URL}/circles/members`, {
|
return Fetch(`${API_URL}/circles/members`, {
|
||||||
|
@ -264,6 +278,7 @@ export {
|
||||||
CreateLongLiveToken,
|
CreateLongLiveToken,
|
||||||
CreateThing,
|
CreateThing,
|
||||||
DeleteChore,
|
DeleteChore,
|
||||||
|
DeleteChoreHistory,
|
||||||
DeleteCircleMember,
|
DeleteCircleMember,
|
||||||
DeleteLongLiveToken,
|
DeleteLongLiveToken,
|
||||||
DeleteThing,
|
DeleteThing,
|
||||||
|
@ -288,6 +303,7 @@ export {
|
||||||
SaveThing,
|
SaveThing,
|
||||||
signUp,
|
signUp,
|
||||||
SkipChore,
|
SkipChore,
|
||||||
|
UpdateChoreHistory,
|
||||||
UpdateThingState,
|
UpdateThingState,
|
||||||
UpdateUserDetails,
|
UpdateUserDetails,
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,9 +15,14 @@ import moment from 'moment'
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { Link, useParams } from 'react-router-dom'
|
import { Link, useParams } from 'react-router-dom'
|
||||||
import { API_URL } from '../../Config'
|
import { API_URL } from '../../Config'
|
||||||
import { GetAllCircleMembers } from '../../utils/Fetcher'
|
import {
|
||||||
|
DeleteChoreHistory,
|
||||||
|
GetAllCircleMembers,
|
||||||
|
UpdateChoreHistory,
|
||||||
|
} from '../../utils/Fetcher'
|
||||||
import { Fetch } from '../../utils/TokenManager'
|
import { Fetch } from '../../utils/TokenManager'
|
||||||
import LoadingComponent from '../components/Loading'
|
import LoadingComponent from '../components/Loading'
|
||||||
|
import EditHistoryModal from '../Modals/EditHistoryModal'
|
||||||
import HistoryCard from './HistoryCard'
|
import HistoryCard from './HistoryCard'
|
||||||
|
|
||||||
const ChoreHistory = () => {
|
const ChoreHistory = () => {
|
||||||
|
@ -28,6 +33,8 @@ const ChoreHistory = () => {
|
||||||
|
|
||||||
const [isLoading, setIsLoading] = useState(true) // Add loading state
|
const [isLoading, setIsLoading] = useState(true) // Add loading state
|
||||||
const { choreId } = useParams()
|
const { choreId } = useParams()
|
||||||
|
const [isEditModalOpen, setIsEditModalOpen] = useState(false)
|
||||||
|
const [editHistory, setEditHistory] = useState({})
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setIsLoading(true) // Start loading
|
setIsLoading(true) // Start loading
|
||||||
|
@ -63,12 +70,12 @@ const ChoreHistory = () => {
|
||||||
|
|
||||||
const averageDelay =
|
const averageDelay =
|
||||||
histories.reduce((acc, chore) => {
|
histories.reduce((acc, chore) => {
|
||||||
if (chore.dueDate) {
|
if (chore.dueDate && chore.completedAt) {
|
||||||
// Only consider chores with a due date
|
// Only consider chores with a due date
|
||||||
return acc + moment(chore.completedAt).diff(chore.dueDate, 'hours')
|
return acc + moment(chore.completedAt).diff(chore.dueDate, 'hours')
|
||||||
}
|
}
|
||||||
return acc
|
return acc
|
||||||
}, 0) / histories.length
|
}, 0) / histories.filter(chore => chore.dueDate).length
|
||||||
const averageDelayMoment = moment.duration(averageDelay, 'hours')
|
const averageDelayMoment = moment.duration(averageDelay, 'hours')
|
||||||
const maximumDelay = histories.reduce((acc, chore) => {
|
const maximumDelay = histories.reduce((acc, chore) => {
|
||||||
if (chore.dueDate) {
|
if (chore.dueDate) {
|
||||||
|
@ -215,6 +222,10 @@ const ChoreHistory = () => {
|
||||||
<List sx={{ p: 0 }}>
|
<List sx={{ p: 0 }}>
|
||||||
{choreHistory.map((historyEntry, index) => (
|
{choreHistory.map((historyEntry, index) => (
|
||||||
<HistoryCard
|
<HistoryCard
|
||||||
|
onClick={() => {
|
||||||
|
setIsEditModalOpen(true)
|
||||||
|
setEditHistory(historyEntry)
|
||||||
|
}}
|
||||||
historyEntry={historyEntry}
|
historyEntry={historyEntry}
|
||||||
performers={performers}
|
performers={performers}
|
||||||
allHistory={choreHistory}
|
allHistory={choreHistory}
|
||||||
|
@ -224,6 +235,46 @@ const ChoreHistory = () => {
|
||||||
))}
|
))}
|
||||||
</List>
|
</List>
|
||||||
</Sheet>
|
</Sheet>
|
||||||
|
<EditHistoryModal
|
||||||
|
config={{
|
||||||
|
isOpen: isEditModalOpen,
|
||||||
|
onClose: () => {
|
||||||
|
setIsEditModalOpen(false)
|
||||||
|
},
|
||||||
|
onSave: updated => {
|
||||||
|
UpdateChoreHistory(choreId, editHistory.id, {
|
||||||
|
completedAt: updated.completedAt,
|
||||||
|
dueDate: updated.dueDate,
|
||||||
|
notes: updated.notes,
|
||||||
|
}).then(res => {
|
||||||
|
if (!res.ok) {
|
||||||
|
console.error('Failed to update chore history:', res)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const newRecord = res.json().then(data => {
|
||||||
|
const newRecord = data.res
|
||||||
|
const newHistory = choreHistory.map(record =>
|
||||||
|
record.id === newRecord.id ? newRecord : record,
|
||||||
|
)
|
||||||
|
setChoresHistory(newHistory)
|
||||||
|
setEditHistory(newRecord)
|
||||||
|
setIsEditModalOpen(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
onDelete: () => {
|
||||||
|
DeleteChoreHistory(choreId, editHistory.id).then(() => {
|
||||||
|
const newHistory = choreHistory.filter(
|
||||||
|
record => record.id !== editHistory.id,
|
||||||
|
)
|
||||||
|
setChoresHistory(newHistory)
|
||||||
|
setIsEditModalOpen(false)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
historyRecord={editHistory}
|
||||||
|
/>
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,13 @@ import {
|
||||||
} from '@mui/joy'
|
} from '@mui/joy'
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
|
|
||||||
const HistoryCard = ({ allHistory, performers, historyEntry, index }) => {
|
const HistoryCard = ({
|
||||||
|
allHistory,
|
||||||
|
performers,
|
||||||
|
historyEntry,
|
||||||
|
index,
|
||||||
|
onClick,
|
||||||
|
}) => {
|
||||||
function formatTimeDifference(startDate, endDate) {
|
function formatTimeDifference(startDate, endDate) {
|
||||||
const diffInMinutes = moment(startDate).diff(endDate, 'minutes')
|
const diffInMinutes = moment(startDate).diff(endDate, 'minutes')
|
||||||
let timeValue = diffInMinutes
|
let timeValue = diffInMinutes
|
||||||
|
@ -64,7 +70,7 @@ const HistoryCard = ({ allHistory, performers, historyEntry, index }) => {
|
||||||
icon = <Timelapse />
|
icon = <Timelapse />
|
||||||
} else {
|
} else {
|
||||||
text = 'No Due Date'
|
text = 'No Due Date'
|
||||||
color = 'info'
|
color = 'neutral'
|
||||||
icon = <CalendarViewDay />
|
icon = <CalendarViewDay />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,7 +83,7 @@ const HistoryCard = ({ allHistory, performers, historyEntry, index }) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ListItem sx={{ gap: 1.5, alignItems: 'flex-start' }}>
|
<ListItem sx={{ gap: 1.5, alignItems: 'flex-start' }} onClick={onClick}>
|
||||||
{' '}
|
{' '}
|
||||||
{/* Adjusted spacing and alignment */}
|
{/* Adjusted spacing and alignment */}
|
||||||
<ListItemDecorator>
|
<ListItemDecorator>
|
||||||
|
@ -98,7 +104,11 @@ const HistoryCard = ({ allHistory, performers, historyEntry, index }) => {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography level='body1' sx={{ fontWeight: 'md' }}>
|
<Typography level='body1' sx={{ fontWeight: 'md' }}>
|
||||||
{moment(historyEntry.completedAt).format('ddd MM/DD/yyyy HH:mm')}
|
{historyEntry.completedAt
|
||||||
|
? moment(historyEntry.completedAt).format(
|
||||||
|
'ddd MM/DD/yyyy HH:mm',
|
||||||
|
)
|
||||||
|
: 'Skipped'}
|
||||||
</Typography>
|
</Typography>
|
||||||
{getCompletedChip(historyEntry)}
|
{getCompletedChip(historyEntry)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
121
src/views/Modals/EditHistoryModal.jsx
Normal file
121
src/views/Modals/EditHistoryModal.jsx
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
FormLabel,
|
||||||
|
Input,
|
||||||
|
Modal,
|
||||||
|
ModalDialog,
|
||||||
|
Typography,
|
||||||
|
} from '@mui/joy'
|
||||||
|
import moment from 'moment'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import ConfirmationModal from './Inputs/ConfirmationModal'
|
||||||
|
|
||||||
|
function EditHistoryModal({ config, historyRecord }) {
|
||||||
|
useEffect(() => {
|
||||||
|
setCompletedDate(
|
||||||
|
moment(historyRecord.completedAt).format('YYYY-MM-DDTHH:mm'),
|
||||||
|
)
|
||||||
|
setDueDate(moment(historyRecord.dueDate).format('YYYY-MM-DDTHH:mm'))
|
||||||
|
setNotes(historyRecord.notes)
|
||||||
|
}, [historyRecord])
|
||||||
|
|
||||||
|
const [completedDate, setCompletedDate] = useState(
|
||||||
|
moment(historyRecord.completedDate).format('YYYY-MM-DDTHH:mm'),
|
||||||
|
)
|
||||||
|
const [dueDate, setDueDate] = useState(
|
||||||
|
moment(historyRecord.dueDate).format('YYYY-MM-DDTHH:mm'),
|
||||||
|
)
|
||||||
|
const [notes, setNotes] = useState(historyRecord.notes)
|
||||||
|
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false)
|
||||||
|
return (
|
||||||
|
<Modal open={config?.isOpen} onClose={config?.onClose}>
|
||||||
|
<ModalDialog>
|
||||||
|
<Typography level='h4' mb={1}>
|
||||||
|
Edit History
|
||||||
|
</Typography>
|
||||||
|
<FormLabel>Due Date</FormLabel>
|
||||||
|
<Input
|
||||||
|
type='datetime-local'
|
||||||
|
value={dueDate}
|
||||||
|
onChange={e => {
|
||||||
|
setDueDate(e.target.value)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<FormLabel>Completed Date</FormLabel>
|
||||||
|
<Input
|
||||||
|
type='datetime-local'
|
||||||
|
value={completedDate}
|
||||||
|
onChange={e => {
|
||||||
|
setCompletedDate(e.target.value)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<FormLabel>Note</FormLabel>
|
||||||
|
<Input
|
||||||
|
fullWidth
|
||||||
|
multiline
|
||||||
|
label='Additional Notes'
|
||||||
|
placeholder='Additional Notes'
|
||||||
|
value={notes}
|
||||||
|
onChange={e => {
|
||||||
|
if (e.target.value.trim() === '') {
|
||||||
|
setNotes(null)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setNotes(e.target.value)
|
||||||
|
}}
|
||||||
|
size='md'
|
||||||
|
sx={{
|
||||||
|
mb: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 3 button save , cancel and delete */}
|
||||||
|
<Box display={'flex'} justifyContent={'space-around'} mt={1}>
|
||||||
|
<Button
|
||||||
|
onClick={() =>
|
||||||
|
config.onSave({
|
||||||
|
id: historyRecord.id,
|
||||||
|
completedAt: moment(completedDate).toISOString(),
|
||||||
|
dueDate: moment(dueDate).toISOString(),
|
||||||
|
notes,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
fullWidth
|
||||||
|
sx={{ mr: 1 }}
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
<Button onClick={config.onClose} variant='outlined'>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setIsDeleteModalOpen(true)
|
||||||
|
}}
|
||||||
|
variant='outlined'
|
||||||
|
color='danger'
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
<ConfirmationModal
|
||||||
|
config={{
|
||||||
|
isOpen: isDeleteModalOpen,
|
||||||
|
onClose: isConfirm => {
|
||||||
|
if (isConfirm) {
|
||||||
|
config.onDelete(historyRecord.id)
|
||||||
|
}
|
||||||
|
setIsDeleteModalOpen(false)
|
||||||
|
},
|
||||||
|
title: 'Delete History',
|
||||||
|
message: 'Are you sure you want to delete this history?',
|
||||||
|
confirmText: 'Delete',
|
||||||
|
cancelText: 'Cancel',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ModalDialog>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default EditHistoryModal
|
|
@ -38,7 +38,7 @@ const APITokenSettings = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='grid gap-4 py-4' id='apitokens'>
|
<div className='grid gap-4 py-4' id='apitokens'>
|
||||||
<Typography level='h3'>Long Live Token</Typography>
|
<Typography level='h3'>Access Token</Typography>
|
||||||
<Divider />
|
<Divider />
|
||||||
<Typography level='body-sm'>
|
<Typography level='body-sm'>
|
||||||
Create token to use with the API to update things that trigger task or
|
Create token to use with the API to update things that trigger task or
|
||||||
|
|
Loading…
Add table
Reference in a new issue