feat(store): integrate task management reducers into the store

- Added taskManagementReducer, groupingReducer, and selectionReducer to the Redux store.
- Organized imports and store configuration for better clarity and maintainability.
This commit is contained in:
chamikaJ
2025-06-18 17:02:23 +05:30
parent 20039a07ff
commit c1a303e78c
15 changed files with 2791 additions and 0 deletions

View File

@@ -0,0 +1,189 @@
import { createSlice, PayloadAction, createSelector } from '@reduxjs/toolkit';
import { GroupingState, TaskGroup } from '@/types/task-management.types';
import { RootState } from '@/app/store';
import { taskManagementSelectors } from './task-management.slice';
const initialState: GroupingState = {
currentGrouping: 'status',
customPhases: ['Planning', 'Development', 'Testing', 'Deployment'],
groupOrder: {
status: ['todo', 'doing', 'done'],
priority: ['critical', 'high', 'medium', 'low'],
phase: ['Planning', 'Development', 'Testing', 'Deployment'],
},
groupStates: {},
};
const groupingSlice = createSlice({
name: 'grouping',
initialState,
reducers: {
setCurrentGrouping: (state, action: PayloadAction<'status' | 'priority' | 'phase'>) => {
state.currentGrouping = action.payload;
},
addCustomPhase: (state, action: PayloadAction<string>) => {
const phase = action.payload.trim();
if (phase && !state.customPhases.includes(phase)) {
state.customPhases.push(phase);
state.groupOrder.phase.push(phase);
}
},
removeCustomPhase: (state, action: PayloadAction<string>) => {
const phase = action.payload;
state.customPhases = state.customPhases.filter(p => p !== phase);
state.groupOrder.phase = state.groupOrder.phase.filter(p => p !== phase);
},
updateCustomPhases: (state, action: PayloadAction<string[]>) => {
state.customPhases = action.payload;
state.groupOrder.phase = action.payload;
},
updateGroupOrder: (state, action: PayloadAction<{ groupType: string; order: string[] }>) => {
const { groupType, order } = action.payload;
state.groupOrder[groupType] = order;
},
toggleGroupCollapsed: (state, action: PayloadAction<string>) => {
const groupId = action.payload;
if (!state.groupStates[groupId]) {
state.groupStates[groupId] = { collapsed: false };
}
state.groupStates[groupId].collapsed = !state.groupStates[groupId].collapsed;
},
setGroupCollapsed: (state, action: PayloadAction<{ groupId: string; collapsed: boolean }>) => {
const { groupId, collapsed } = action.payload;
if (!state.groupStates[groupId]) {
state.groupStates[groupId] = { collapsed: false };
}
state.groupStates[groupId].collapsed = collapsed;
},
collapseAllGroups: (state) => {
Object.keys(state.groupStates).forEach(groupId => {
state.groupStates[groupId].collapsed = true;
});
},
expandAllGroups: (state) => {
Object.keys(state.groupStates).forEach(groupId => {
state.groupStates[groupId].collapsed = false;
});
},
},
});
export const {
setCurrentGrouping,
addCustomPhase,
removeCustomPhase,
updateCustomPhases,
updateGroupOrder,
toggleGroupCollapsed,
setGroupCollapsed,
collapseAllGroups,
expandAllGroups,
} = groupingSlice.actions;
// Selectors
export const selectCurrentGrouping = (state: RootState) => state.grouping.currentGrouping;
export const selectCustomPhases = (state: RootState) => state.grouping.customPhases;
export const selectGroupOrder = (state: RootState) => state.grouping.groupOrder;
export const selectGroupStates = (state: RootState) => state.grouping.groupStates;
// Complex selectors using createSelector for memoization
export const selectCurrentGroupOrder = createSelector(
[selectCurrentGrouping, selectGroupOrder],
(currentGrouping, groupOrder) => groupOrder[currentGrouping] || []
);
export const selectTaskGroups = createSelector(
[taskManagementSelectors.selectAll, selectCurrentGrouping, selectCurrentGroupOrder, selectGroupStates],
(tasks, currentGrouping, groupOrder, groupStates) => {
const groups: TaskGroup[] = [];
// Get unique values for the current grouping
const groupValues = groupOrder.length > 0 ? groupOrder :
[...new Set(tasks.map(task => {
if (currentGrouping === 'status') return task.status;
if (currentGrouping === 'priority') return task.priority;
return task.phase;
}))];
groupValues.forEach(value => {
const tasksInGroup = tasks.filter(task => {
if (currentGrouping === 'status') return task.status === value;
if (currentGrouping === 'priority') return task.priority === value;
return task.phase === value;
}).sort((a, b) => a.order - b.order);
const groupId = `${currentGrouping}-${value}`;
groups.push({
id: groupId,
title: value.charAt(0).toUpperCase() + value.slice(1),
groupType: currentGrouping,
groupValue: value,
collapsed: groupStates[groupId]?.collapsed || false,
taskIds: tasksInGroup.map(task => task.id),
color: getGroupColor(currentGrouping, value),
});
});
return groups;
}
);
export const selectTasksByCurrentGrouping = createSelector(
[taskManagementSelectors.selectAll, selectCurrentGrouping],
(tasks, currentGrouping) => {
const grouped: Record<string, typeof tasks> = {};
tasks.forEach(task => {
let key: string;
if (currentGrouping === 'status') key = task.status;
else if (currentGrouping === 'priority') key = task.priority;
else key = task.phase;
if (!grouped[key]) grouped[key] = [];
grouped[key].push(task);
});
// Sort tasks within each group by order
Object.keys(grouped).forEach(key => {
grouped[key].sort((a, b) => a.order - b.order);
});
return grouped;
}
);
// Helper function to get group colors
const getGroupColor = (groupType: string, value: string): string => {
const colorMaps = {
status: {
todo: '#f0f0f0',
doing: '#1890ff',
done: '#52c41a',
},
priority: {
critical: '#ff4d4f',
high: '#ff7a45',
medium: '#faad14',
low: '#52c41a',
},
phase: {
Planning: '#722ed1',
Development: '#1890ff',
Testing: '#faad14',
Deployment: '#52c41a',
},
};
return colorMaps[groupType as keyof typeof colorMaps]?.[value as keyof any] || '#d9d9d9';
};
export default groupingSlice.reducer;

View File

@@ -0,0 +1,110 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { SelectionState } from '@/types/task-management.types';
import { RootState } from '@/app/store';
const initialState: SelectionState = {
selectedTaskIds: [],
lastSelectedId: null,
};
const selectionSlice = createSlice({
name: 'selection',
initialState,
reducers: {
toggleTaskSelection: (state, action: PayloadAction<string>) => {
const taskId = action.payload;
const index = state.selectedTaskIds.indexOf(taskId);
if (index === -1) {
state.selectedTaskIds.push(taskId);
} else {
state.selectedTaskIds.splice(index, 1);
}
state.lastSelectedId = taskId;
},
selectTask: (state, action: PayloadAction<string>) => {
const taskId = action.payload;
if (!state.selectedTaskIds.includes(taskId)) {
state.selectedTaskIds.push(taskId);
}
state.lastSelectedId = taskId;
},
deselectTask: (state, action: PayloadAction<string>) => {
const taskId = action.payload;
state.selectedTaskIds = state.selectedTaskIds.filter(id => id !== taskId);
if (state.lastSelectedId === taskId) {
state.lastSelectedId = state.selectedTaskIds[state.selectedTaskIds.length - 1] || null;
}
},
selectMultipleTasks: (state, action: PayloadAction<string[]>) => {
const taskIds = action.payload;
// Add new task IDs that aren't already selected
taskIds.forEach(id => {
if (!state.selectedTaskIds.includes(id)) {
state.selectedTaskIds.push(id);
}
});
state.lastSelectedId = taskIds[taskIds.length - 1] || state.lastSelectedId;
},
selectRangeTasks: (state, action: PayloadAction<{ startId: string; endId: string; allTaskIds: string[] }>) => {
const { startId, endId, allTaskIds } = action.payload;
const startIndex = allTaskIds.indexOf(startId);
const endIndex = allTaskIds.indexOf(endId);
if (startIndex !== -1 && endIndex !== -1) {
const [start, end] = startIndex <= endIndex ? [startIndex, endIndex] : [endIndex, startIndex];
const rangeIds = allTaskIds.slice(start, end + 1);
// Add range IDs that aren't already selected
rangeIds.forEach(id => {
if (!state.selectedTaskIds.includes(id)) {
state.selectedTaskIds.push(id);
}
});
state.lastSelectedId = endId;
}
},
selectAllTasks: (state, action: PayloadAction<string[]>) => {
state.selectedTaskIds = action.payload;
state.lastSelectedId = action.payload[action.payload.length - 1] || null;
},
clearSelection: (state) => {
state.selectedTaskIds = [];
state.lastSelectedId = null;
},
setSelection: (state, action: PayloadAction<string[]>) => {
state.selectedTaskIds = action.payload;
state.lastSelectedId = action.payload[action.payload.length - 1] || null;
},
},
});
export const {
toggleTaskSelection,
selectTask,
deselectTask,
selectMultipleTasks,
selectRangeTasks,
selectAllTasks,
clearSelection,
setSelection,
} = selectionSlice.actions;
// Selectors
export const selectSelectedTaskIds = (state: RootState) => state.taskManagementSelection.selectedTaskIds;
export const selectLastSelectedId = (state: RootState) => state.taskManagementSelection.lastSelectedId;
export const selectHasSelection = (state: RootState) => state.taskManagementSelection.selectedTaskIds.length > 0;
export const selectSelectionCount = (state: RootState) => state.taskManagementSelection.selectedTaskIds.length;
export const selectIsTaskSelected = (taskId: string) => (state: RootState) =>
state.taskManagementSelection.selectedTaskIds.includes(taskId);
export default selectionSlice.reducer;

View File

@@ -0,0 +1,135 @@
import { createSlice, createEntityAdapter, PayloadAction } from '@reduxjs/toolkit';
import { Task, TaskManagementState } from '@/types/task-management.types';
import { RootState } from '@/app/store';
// Entity adapter for normalized state
const tasksAdapter = createEntityAdapter<Task>({
selectId: (task) => task.id,
sortComparer: (a, b) => a.order - b.order,
});
const initialState: TaskManagementState = {
entities: {},
ids: [],
loading: false,
error: null,
};
const taskManagementSlice = createSlice({
name: 'taskManagement',
initialState: tasksAdapter.getInitialState(initialState),
reducers: {
// Basic CRUD operations
setTasks: (state, action: PayloadAction<Task[]>) => {
tasksAdapter.setAll(state, action.payload);
state.loading = false;
state.error = null;
},
addTask: (state, action: PayloadAction<Task>) => {
tasksAdapter.addOne(state, action.payload);
},
updateTask: (state, action: PayloadAction<{ id: string; changes: Partial<Task> }>) => {
tasksAdapter.updateOne(state, {
id: action.payload.id,
changes: {
...action.payload.changes,
updatedAt: new Date().toISOString(),
},
});
},
deleteTask: (state, action: PayloadAction<string>) => {
tasksAdapter.removeOne(state, action.payload);
},
// Bulk operations
bulkUpdateTasks: (state, action: PayloadAction<{ ids: string[]; changes: Partial<Task> }>) => {
const { ids, changes } = action.payload;
const updates = ids.map(id => ({
id,
changes: {
...changes,
updatedAt: new Date().toISOString(),
},
}));
tasksAdapter.updateMany(state, updates);
},
bulkDeleteTasks: (state, action: PayloadAction<string[]>) => {
tasksAdapter.removeMany(state, action.payload);
},
// Drag and drop operations
reorderTasks: (state, action: PayloadAction<{ taskIds: string[]; newOrder: number[] }>) => {
const { taskIds, newOrder } = action.payload;
const updates = taskIds.map((id, index) => ({
id,
changes: { order: newOrder[index] },
}));
tasksAdapter.updateMany(state, updates);
},
moveTaskToGroup: (state, action: PayloadAction<{ taskId: string; groupType: 'status' | 'priority' | 'phase'; groupValue: string }>) => {
const { taskId, groupType, groupValue } = action.payload;
const changes: Partial<Task> = {
updatedAt: new Date().toISOString(),
};
// Update the appropriate field based on group type
if (groupType === 'status') {
changes.status = groupValue as Task['status'];
} else if (groupType === 'priority') {
changes.priority = groupValue as Task['priority'];
} else if (groupType === 'phase') {
changes.phase = groupValue;
}
tasksAdapter.updateOne(state, { id: taskId, changes });
},
// Loading states
setLoading: (state, action: PayloadAction<boolean>) => {
state.loading = action.payload;
},
setError: (state, action: PayloadAction<string | null>) => {
state.error = action.payload;
state.loading = false;
},
},
});
export const {
setTasks,
addTask,
updateTask,
deleteTask,
bulkUpdateTasks,
bulkDeleteTasks,
reorderTasks,
moveTaskToGroup,
setLoading,
setError,
} = taskManagementSlice.actions;
// Selectors
export const taskManagementSelectors = tasksAdapter.getSelectors<RootState>(
(state) => state.taskManagement
);
// Additional selectors
export const selectTasksByStatus = (state: RootState, status: string) =>
taskManagementSelectors.selectAll(state).filter(task => task.status === status);
export const selectTasksByPriority = (state: RootState, priority: string) =>
taskManagementSelectors.selectAll(state).filter(task => task.priority === priority);
export const selectTasksByPhase = (state: RootState, phase: string) =>
taskManagementSelectors.selectAll(state).filter(task => task.phase === phase);
export const selectTasksLoading = (state: RootState) => state.taskManagement.loading;
export const selectTasksError = (state: RootState) => state.taskManagement.error;
export default taskManagementSlice.reducer;