Support for history Modification and deletion
This commit is contained in:
parent
aed179d6b3
commit
90efb5be00
6 changed files with 207 additions and 9 deletions
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "donetick",
|
||||
"private": true,
|
||||
"version": "0.1.64",
|
||||
"version": "0.1.65",
|
||||
"type": "module",
|
||||
"lint-staged": {
|
||||
"*.{js,jsx,ts,tsx}": [
|
||||
|
|
|
@ -112,6 +112,20 @@ const GetChoreHistory = choreId => {
|
|||
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 = () => {
|
||||
return Fetch(`${API_URL}/circles/members`, {
|
||||
|
@ -264,6 +278,7 @@ export {
|
|||
CreateLongLiveToken,
|
||||
CreateThing,
|
||||
DeleteChore,
|
||||
DeleteChoreHistory,
|
||||
DeleteCircleMember,
|
||||
DeleteLongLiveToken,
|
||||
DeleteThing,
|
||||
|
@ -288,6 +303,7 @@ export {
|
|||
SaveThing,
|
||||
signUp,
|
||||
SkipChore,
|
||||
UpdateChoreHistory,
|
||||
UpdateThingState,
|
||||
UpdateUserDetails,
|
||||
}
|
||||
|
|
|
@ -15,9 +15,14 @@ import moment from 'moment'
|
|||
import React, { useEffect, useState } from 'react'
|
||||
import { Link, useParams } from 'react-router-dom'
|
||||
import { API_URL } from '../../Config'
|
||||
import { GetAllCircleMembers } from '../../utils/Fetcher'
|
||||
import {
|
||||
DeleteChoreHistory,
|
||||
GetAllCircleMembers,
|
||||
UpdateChoreHistory,
|
||||
} from '../../utils/Fetcher'
|
||||
import { Fetch } from '../../utils/TokenManager'
|
||||
import LoadingComponent from '../components/Loading'
|
||||
import EditHistoryModal from '../Modals/EditHistoryModal'
|
||||
import HistoryCard from './HistoryCard'
|
||||
|
||||
const ChoreHistory = () => {
|
||||
|
@ -28,6 +33,8 @@ const ChoreHistory = () => {
|
|||
|
||||
const [isLoading, setIsLoading] = useState(true) // Add loading state
|
||||
const { choreId } = useParams()
|
||||
const [isEditModalOpen, setIsEditModalOpen] = useState(false)
|
||||
const [editHistory, setEditHistory] = useState({})
|
||||
|
||||
useEffect(() => {
|
||||
setIsLoading(true) // Start loading
|
||||
|
@ -63,12 +70,12 @@ const ChoreHistory = () => {
|
|||
|
||||
const averageDelay =
|
||||
histories.reduce((acc, chore) => {
|
||||
if (chore.dueDate) {
|
||||
if (chore.dueDate && chore.completedAt) {
|
||||
// Only consider chores with a due date
|
||||
return acc + moment(chore.completedAt).diff(chore.dueDate, 'hours')
|
||||
}
|
||||
return acc
|
||||
}, 0) / histories.length
|
||||
}, 0) / histories.filter(chore => chore.dueDate).length
|
||||
const averageDelayMoment = moment.duration(averageDelay, 'hours')
|
||||
const maximumDelay = histories.reduce((acc, chore) => {
|
||||
if (chore.dueDate) {
|
||||
|
@ -215,6 +222,10 @@ const ChoreHistory = () => {
|
|||
<List sx={{ p: 0 }}>
|
||||
{choreHistory.map((historyEntry, index) => (
|
||||
<HistoryCard
|
||||
onClick={() => {
|
||||
setIsEditModalOpen(true)
|
||||
setEditHistory(historyEntry)
|
||||
}}
|
||||
historyEntry={historyEntry}
|
||||
performers={performers}
|
||||
allHistory={choreHistory}
|
||||
|
@ -224,6 +235,46 @@ const ChoreHistory = () => {
|
|||
))}
|
||||
</List>
|
||||
</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>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -11,7 +11,13 @@ import {
|
|||
} from '@mui/joy'
|
||||
import moment from 'moment'
|
||||
|
||||
const HistoryCard = ({ allHistory, performers, historyEntry, index }) => {
|
||||
const HistoryCard = ({
|
||||
allHistory,
|
||||
performers,
|
||||
historyEntry,
|
||||
index,
|
||||
onClick,
|
||||
}) => {
|
||||
function formatTimeDifference(startDate, endDate) {
|
||||
const diffInMinutes = moment(startDate).diff(endDate, 'minutes')
|
||||
let timeValue = diffInMinutes
|
||||
|
@ -64,7 +70,7 @@ const HistoryCard = ({ allHistory, performers, historyEntry, index }) => {
|
|||
icon = <Timelapse />
|
||||
} else {
|
||||
text = 'No Due Date'
|
||||
color = 'info'
|
||||
color = 'neutral'
|
||||
icon = <CalendarViewDay />
|
||||
}
|
||||
|
||||
|
@ -77,7 +83,7 @@ const HistoryCard = ({ allHistory, performers, historyEntry, index }) => {
|
|||
|
||||
return (
|
||||
<>
|
||||
<ListItem sx={{ gap: 1.5, alignItems: 'flex-start' }}>
|
||||
<ListItem sx={{ gap: 1.5, alignItems: 'flex-start' }} onClick={onClick}>
|
||||
{' '}
|
||||
{/* Adjusted spacing and alignment */}
|
||||
<ListItemDecorator>
|
||||
|
@ -98,7 +104,11 @@ const HistoryCard = ({ allHistory, performers, historyEntry, index }) => {
|
|||
}}
|
||||
>
|
||||
<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>
|
||||
{getCompletedChip(historyEntry)}
|
||||
</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 (
|
||||
<div className='grid gap-4 py-4' id='apitokens'>
|
||||
<Typography level='h3'>Long Live Token</Typography>
|
||||
<Typography level='h3'>Access Token</Typography>
|
||||
<Divider />
|
||||
<Typography level='body-sm'>
|
||||
Create token to use with the API to update things that trigger task or
|
||||
|
|
Loading…
Add table
Reference in a new issue