Skip to content

Commit

Permalink
If you load the calendar, then go to inbox, then set a date to a tas…
Browse files Browse the repository at this point in the history
…k, then switch back to the calendar, then the calendar won't be updated #76

 - Store all the lists including the schedule in the state.tasks.taskLists.
 - Send a request to the server even when you change the order of the tasks inside the same schedule section. This will be needed to be able to store the order of the tasks inside a schedule section.
 - Use tags to fix the original issue.
  • Loading branch information
yaskovdev committed Nov 17, 2024
1 parent 6e16af6 commit 1fff9b6
Show file tree
Hide file tree
Showing 10 changed files with 54 additions and 99 deletions.
4 changes: 2 additions & 2 deletions src/components/Inbox/InboxView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,11 @@ const InboxView: FC = () => {
}, [updateTaskMutation])

const updateTaskPositionIndex: OnDragEndResponder = useCallback((result) => {
const { source, destination } = result
const { draggableId, source, destination } = result
if (userReallyChangedOrder(source, destination)) {
updateTasksOrderAsyncMutation({
sourceListType: source.droppableId,
sourceIndex: source.index,
taskId: parseInt(draggableId),
destinationListType: source.droppableId,
destinationIndex: destination!.index
})
Expand Down
3 changes: 2 additions & 1 deletion src/components/Root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import Spinner from './common/Spinner'
import ProtectedRoute from './ProtectedRoute'
import { TASK_LIST_ID } from '../models/appModel'
import { DEFAULT_LIMIT } from '../config'
import { SCHEDULE_TAG } from '../redux/tags'

const Root: FC = () => {
const dispatch = useDispatch<AppDispatch>()
Expand All @@ -24,7 +25,7 @@ const Root: FC = () => {

const handleSynchronize = useCallback(() => {
dispatch(resetTaskLists())
dispatch(api.util.invalidateTags(['Schedule']));
dispatch(api.util.invalidateTags([SCHEDULE_TAG]));
[TASK_LIST_ID.INBOX, TASK_LIST_ID.CLOSED].forEach(taskListId => fetchTaskLists({ type: taskListId, offset: 0, limit: DEFAULT_LIMIT }))
}, [])

Expand Down
46 changes: 19 additions & 27 deletions src/components/Schedule/ScheduleView.tsx
Original file line number Diff line number Diff line change
@@ -1,47 +1,39 @@
import { DateTime } from 'luxon'
import React, { FC, useCallback } from 'react'
import { DragDropContext, DraggableLocation, DropResult } from '@hello-pangea/dnd'
import { useTranslation } from 'react-i18next'
import { useDispatch, useSelector } from 'react-redux'
import { updateScheduleTaskPositionIndex } from '../../redux/reducers/scheduleSlice'
import { RootState } from '../../redux/store'
import { useCloseTaskMutation, useFetchScheduleQuery, useUpdateTaskMutation } from '../../redux/api'
import { IScheduleTaskPositionIndex, UpdateTaskRequest } from '../../models/appModel'
import { useCloseTaskMutation, useFetchScheduleQuery, useUpdateTaskMutation, useUpdateTasksOrderAsyncMutation } from '../../redux/api'
import { UpdateTaskRequest } from '../../models/appModel'
import SpinnerView from '../common/Spinner'
import { userChangedLists, userReallyChangedOrder } from '../../utils/dragAndDropUtils'
import { userReallyChangedOrder } from '../../utils/dragAndDropUtils'
import DroppableTaskListWithHeader from './DroppableTaskListWithHeader'

const ScheduleView: FC = () => {
const { t } = useTranslation()
const dispatch = useDispatch()

const schedule = useSelector((state: RootState) => state.schedule)
const byId = useSelector((state: RootState) => state.tasks.byId)
const schedule = useSelector((state: RootState) => state.tasks.taskLists)
const { isLoading, isFetching } = useFetchScheduleQuery()
const [updateTaskMutation] = useUpdateTaskMutation()
const [updateTasksOrderAsyncMutation] = useUpdateTasksOrderAsyncMutation()
const [closeTaskMutation] = useCloseTaskMutation()

const updateTaskPositionIndex = useCallback((result: DropResult) => {
const { source, destination } = result
const { draggableId, source, destination } = result
if (userReallyChangedOrder(source, destination as DraggableLocation)) {
const scheduleTaskPositionIndex: IScheduleTaskPositionIndex = {
sourceDroppableId: source.droppableId,
sourceIndex: source.index,
destinationDroppableId: destination!.droppableId,
destinationIndex: destination!.index
}

const newDay = destination!.droppableId
if (userChangedLists(source, destination as DraggableLocation) && newDay !== 'overdue') {
const taskId = parseInt(result.draggableId)
const dueDate = newDay === 'future'
? DateTime.utc().plus({ weeks: 1 }).endOf('day').toUTC()
: DateTime.fromISO(newDay!).toUTC()
updateTaskMutation({ id: taskId, request: { dueDate } })
if (newDay !== 'overdue') {
updateTasksOrderAsyncMutation({
sourceListType: source.droppableId,
taskId: parseInt(draggableId),
destinationListType: destination!.droppableId,
destinationIndex: destination!.index
})
}

dispatch(updateScheduleTaskPositionIndex(scheduleTaskPositionIndex))
}
}, [dispatch, updateTaskMutation])
}, [dispatch, updateTasksOrderAsyncMutation])

const closeTask = useCallback(async (id: number) => {
await closeTaskMutation(id)
Expand All @@ -55,7 +47,7 @@ const ScheduleView: FC = () => {

const weekdays = Object
.keys(schedule)
.filter(day => !['future', 'overdue'].includes(day))
.filter(day => !['INBOX', 'CLOSED', 'future', 'overdue'].includes(day))

return (
<DragDropContext onDragEnd={updateTaskPositionIndex}>
Expand All @@ -66,7 +58,7 @@ const ScheduleView: FC = () => {
droppableId={date}
isDraggable
header={t('dueDate', { date })}
tasks={schedule[date]}
tasks={schedule[date].allIds.map(id => byId[id])}
onTaskClose={closeTask}
onSaveTask={updateTask}
/>)
Expand All @@ -75,7 +67,7 @@ const ScheduleView: FC = () => {
droppableId="future"
isDraggable
header={t('futureTasks')}
tasks={schedule.future}
tasks={schedule['future'].allIds.map(id => byId[id])}
onTaskClose={closeTask}
onSaveTask={updateTask}
/>
Expand All @@ -84,7 +76,7 @@ const ScheduleView: FC = () => {
isDraggable
isDropDisabled
header={t('overdueTasks')}
tasks={schedule.overdue}
tasks={schedule['overdue'].allIds.map(id => byId[id])}
onTaskClose={closeTask}
onSaveTask={updateTask}
/>
Expand Down
9 changes: 1 addition & 8 deletions src/models/appModel.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
import { DateTime } from 'luxon'

export type IScheduleTaskPositionIndex = {
sourceDroppableId: string
destinationDroppableId: string
sourceIndex: number
destinationIndex: number
}

export type IPage = {
size: number
number: number
Expand Down Expand Up @@ -53,8 +46,8 @@ export type DueDateExtractionResult = {

export type ITaskPositionIndex = {
sourceListType: string
taskId: number
destinationListType: string
sourceIndex: number
destinationIndex: number
}

Expand Down
17 changes: 10 additions & 7 deletions src/redux/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@ import {
} from '../models/appModel'
import { API_URL } from '../config'
import { DateTime } from 'luxon'
import { SCHEDULE_TAG } from './tags'

export const api = createApi({
reducerPath: 'api',
baseQuery: fetchBaseQuery({ baseUrl: API_URL, credentials: 'include' }),
tagTypes: ['Schedule'],
tagTypes: [SCHEDULE_TAG],
endpoints: (builder) => ({
rephraseTask: builder.mutation<IRephrasedTask, string>({
query: (originalTask) => ({
Expand All @@ -35,9 +36,9 @@ export const api = createApi({
future: response.future,
overdue: response.overdue
}),
providesTags: ['Schedule']
providesTags: [SCHEDULE_TAG]
}),
updateTasksOrderAsync: builder.mutation<void, ITaskPositionIndex>({
updateTasksOrderAsync: builder.mutation<ITask, ITaskPositionIndex>({
query: (taskPositionIndex) => ({
url: '/orders',
method: 'POST',
Expand All @@ -50,27 +51,29 @@ export const api = createApi({
method: 'POST',
body: task
}),
invalidatesTags: (result) => result?.dueDate ? ['Schedule'] : []
invalidatesTags: (result) => result?.dueDate ? [SCHEDULE_TAG] : []
}),
closeTask: builder.mutation<ITask, number>({
query: (id) => ({
url: `/tasks/${id}/closing`,
method: 'PUT'
})
}),
invalidatesTags: () => [SCHEDULE_TAG]
}),
reopenTask: builder.mutation<ITask, number>({
query: (id) => ({
url: `/tasks/${id}/reopen`,
method: 'PUT'
}),
invalidatesTags: (result) => result?.dueDate ? ['Schedule'] : []
invalidatesTags: (result) => result?.dueDate ? [SCHEDULE_TAG] : []
}),
updateTask: builder.mutation<ITask, { id: number, request: UpdateTaskRequest }>({
query: ({ id, request }) => ({
url: `/tasks/${id}`,
method: 'PUT',
body: request
})
}),
invalidatesTags: () => [SCHEDULE_TAG]
})
})
})
Expand Down
46 changes: 0 additions & 46 deletions src/redux/reducers/scheduleSlice.ts

This file was deleted.

22 changes: 19 additions & 3 deletions src/redux/reducers/tasksSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,19 @@ const tasksSlice = createSlice({
const taskListId = meta.arg.type
state.taskLists[taskListId].status = 'FAILED'
})
.addMatcher(
api.endpoints.fetchSchedule.matchFulfilled,
(state, { payload }) => {
Object.entries(payload).forEach(([key, value]) => {
state.taskLists[key] = {
status: 'SUCCEEDED',
totalElements: value.length,
allIds: value.map(it => it.id)
}
value.forEach(it => state.byId[it.id] = it)
})
}
)
.addMatcher(api.endpoints.createTask.matchFulfilled, (state, { payload }) => {
const taskList = state.taskLists[TASK_LIST_ID.INBOX]
taskList.totalElements += 1
Expand All @@ -87,9 +100,12 @@ const tasksSlice = createSlice({
state.byId[payload.id] = payload
})
.addMatcher(api.endpoints.updateTasksOrderAsync.matchPending, (state, { meta }) => {
const { sourceListType, sourceIndex, destinationListType, destinationIndex } = meta.arg.originalArgs
const id = state.taskLists[sourceListType].allIds.splice(sourceIndex, 1)[0]
state.taskLists[destinationListType].allIds.splice(destinationIndex, 0, id)
const { sourceListType, taskId, destinationListType, destinationIndex } = meta.arg.originalArgs
state.taskLists[sourceListType].allIds = state.taskLists[sourceListType].allIds.filter(it => it != taskId)
state.taskLists[destinationListType].allIds.splice(destinationIndex, 0, taskId)
})
.addMatcher(api.endpoints.updateTasksOrderAsync.matchFulfilled, (state, { payload }) => {
state.byId[payload.id] = payload
})
}
})
Expand Down
2 changes: 0 additions & 2 deletions src/redux/store.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import { configureStore } from '@reduxjs/toolkit'
import { api } from './api'
import scheduleReducer from './reducers/scheduleSlice'
import tasksReducer from './reducers/tasksSlice'
import { REDUX_DEV_TOOLS_ENABLED } from '../config'

export const store = configureStore({
reducer: {
tasks: tasksReducer,
schedule: scheduleReducer,
[api.reducerPath]: api.reducer
},
middleware: getDefaultMiddleware => getDefaultMiddleware().concat(api.middleware),
Expand Down
1 change: 1 addition & 0 deletions src/redux/tags.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const SCHEDULE_TAG = 'Schedule' as const
3 changes: 0 additions & 3 deletions src/utils/dragAndDropUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,3 @@ import { DraggableLocation } from '@hello-pangea/dnd'

export const userReallyChangedOrder = (source: DraggableLocation, destination: DraggableLocation | null): boolean =>
!!destination && (source.droppableId !== destination.droppableId || source.index !== destination.index)

export const userChangedLists = (source: DraggableLocation, destination: DraggableLocation): boolean =>
destination && source.droppableId !== destination.droppableId

0 comments on commit 1fff9b6

Please sign in to comment.