refactor(task-management): update search handling and improve task filtering

- Modified search handling to utilize the taskManagement slice for consistent state management across components.
- Enhanced placeholder text in search filters for better user guidance.
- Updated task fetching logic to ensure accurate search value retrieval from the correct state slice.
This commit is contained in:
chamikaJ
2025-07-09 17:11:15 +05:30
parent db9b481e8d
commit 8f5de8f1a1
4 changed files with 73 additions and 49 deletions

View File

@@ -109,7 +109,7 @@ export default class TasksControllerV2 extends TasksControllerBase {
} }
private static getQuery(userId: string, options: ParsedQs) { private static getQuery(userId: string, options: ParsedQs) {
const searchField = options.search ? "t.name" : "sort_order"; const searchField = options.search ? ["t.name", "CONCAT((SELECT key FROM projects WHERE id = t.project_id), '-', task_no)"] : "sort_order";
const { searchQuery, sortField } = TasksControllerV2.toPaginationOptions(options, searchField); const { searchQuery, sortField } = TasksControllerV2.toPaginationOptions(options, searchField);
const isSubTasks = !!options.parent_task; const isSubTasks = !!options.parent_task;

View File

@@ -35,6 +35,8 @@ interface AddSubtaskRowProps {
onSubtaskAdded: (rowId: string) => void; onSubtaskAdded: (rowId: string) => void;
rowId: string; // Unique identifier for this add subtask row rowId: string; // Unique identifier for this add subtask row
autoFocus?: boolean; // Whether this row should auto-focus on mount autoFocus?: boolean; // Whether this row should auto-focus on mount
isActive?: boolean; // Whether this row should show the input/button
onActivate?: (rowId: string) => void; // Callback when row becomes active
} }
const AddSubtaskRow: React.FC<AddSubtaskRowProps> = memo(({ const AddSubtaskRow: React.FC<AddSubtaskRowProps> = memo(({
@@ -43,7 +45,9 @@ const AddSubtaskRow: React.FC<AddSubtaskRowProps> = memo(({
visibleColumns, visibleColumns,
onSubtaskAdded, onSubtaskAdded,
rowId, rowId,
autoFocus = false autoFocus = false,
isActive = true,
onActivate
}) => { }) => {
const [isAdding, setIsAdding] = useState(autoFocus); const [isAdding, setIsAdding] = useState(autoFocus);
const [subtaskName, setSubtaskName] = useState(''); const [subtaskName, setSubtaskName] = useState('');
@@ -127,32 +131,40 @@ const AddSubtaskRow: React.FC<AddSubtaskRowProps> = memo(({
<div className="w-4" /> <div className="w-4" />
<div className="w-2" /> <div className="w-2" />
{!isAdding ? ( {isActive ? (
<button !isAdding ? (
onClick={() => setIsAdding(true)} <button
className="flex items-center gap-2 text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 transition-colors h-full" onClick={() => {
> onActivate?.(rowId);
<PlusOutlined className="text-xs" /> setIsAdding(true);
{t('addSubTaskText')} }}
</button> className="flex items-center gap-2 text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 transition-colors h-full"
>
<PlusOutlined className="text-xs" />
{t('addSubTaskText')}
</button>
) : (
<Input
ref={inputRef}
value={subtaskName}
onChange={(e) => setSubtaskName(e.target.value)}
onPressEnter={handleAddSubtask}
onBlur={handleCancel}
onKeyDown={handleKeyDown}
placeholder="Type subtask name and press Enter to save"
className="w-full h-full border-none shadow-none bg-transparent"
style={{
height: '100%',
minHeight: '32px',
padding: '0',
fontSize: '14px'
}}
autoFocus
/>
)
) : ( ) : (
<Input // Empty space when not active
ref={inputRef} <div className="h-full" />
value={subtaskName}
onChange={(e) => setSubtaskName(e.target.value)}
onPressEnter={handleAddSubtask}
onBlur={handleCancel}
onKeyDown={handleKeyDown}
placeholder="Type subtask name and press Enter to save"
className="w-full h-full border-none shadow-none bg-transparent"
style={{
height: '100%',
minHeight: '32px',
padding: '0',
fontSize: '14px'
}}
autoFocus
/>
)} )}
</div> </div>
</div> </div>
@@ -208,6 +220,10 @@ const TaskRowWithSubtasks: React.FC<TaskRowWithSubtasksProps> = memo(({
}); });
}, [taskId]); }, [taskId]);
const handleRowActivate = useCallback((rowId: string) => {
setActiveRowId(rowId);
}, []);
if (!task) { if (!task) {
return null; return null;
} }
@@ -250,18 +266,25 @@ const TaskRowWithSubtasks: React.FC<TaskRowWithSubtasksProps> = memo(({
{!isLoadingSubtasks && ( {!isLoadingSubtasks && (
<> <>
{/* Render all add subtask rows */} {/* Render all add subtask rows */}
{addSubtaskRows.map((rowId, index) => ( {addSubtaskRows.map((rowId, index) => {
<div key={rowId} className="bg-gray-50 dark:bg-gray-800/50 border-l-2 border-blue-200 dark:border-blue-700"> const isLastRow = index === addSubtaskRows.length - 1;
<AddSubtaskRow const isRowActive = activeRowId === null ? isLastRow : activeRowId === rowId;
parentTaskId={taskId}
projectId={projectId} return (
visibleColumns={visibleColumns} <div key={rowId} className="bg-gray-50 dark:bg-gray-800/50 border-l-2 border-blue-200 dark:border-blue-700">
onSubtaskAdded={handleSubtaskAdded} <AddSubtaskRow
rowId={rowId} parentTaskId={taskId}
autoFocus={index === addSubtaskRows.length - 1} // Auto-focus the latest row projectId={projectId}
/> visibleColumns={visibleColumns}
</div> onSubtaskAdded={handleSubtaskAdded}
))} rowId={rowId}
autoFocus={isLastRow && activeRowId === rowId} // Auto-focus the latest row when it becomes active
isActive={isRowActive}
onActivate={handleRowActivate}
/>
</div>
);
})}
</> </>
)} )}
</> </>

View File

@@ -660,7 +660,7 @@ const SearchFilter: React.FC<{
type="text" type="text"
value={localValue} value={localValue}
onChange={e => setLocalValue(e.target.value)} onChange={e => setLocalValue(e.target.value)}
placeholder={placeholder || t('searchTasks')} placeholder={placeholder || t('searchTasks') || 'Search tasks by name or key...'}
className={`w-full pr-4 pl-8 py-1 rounded border focus:outline-none focus:ring-2 focus:ring-gray-500 transition-colors duration-150 ${ className={`w-full pr-4 pl-8 py-1 rounded border focus:outline-none focus:ring-2 focus:ring-gray-500 transition-colors duration-150 ${
isDarkMode isDarkMode
? 'bg-gray-700 text-gray-100 placeholder-gray-400 border-gray-600' ? 'bg-gray-700 text-gray-100 placeholder-gray-400 border-gray-600'
@@ -919,10 +919,10 @@ const ImprovedTaskFilters: React.FC<ImprovedTaskFiltersProps> = ({ position, cla
useFilterDataLoader(); useFilterDataLoader();
// Get search value from Redux based on position // Get search value from Redux based on position
const taskReducerSearch = useAppSelector(state => state.taskReducer?.search || ''); const taskManagementSearch = useAppSelector(state => state.taskManagement?.search || '');
const kanbanSearch = useAppSelector(state => state.enhancedKanbanReducer?.search || ''); const kanbanSearch = useAppSelector(state => state.enhancedKanbanReducer?.search || '');
const searchValue = position === 'board' ? kanbanSearch : taskReducerSearch; const searchValue = position === 'board' ? kanbanSearch : taskManagementSearch;
// Local state for filter sections // Local state for filter sections
const [filterSections, setFilterSections] = useState<FilterSection[]>([]); const [filterSections, setFilterSections] = useState<FilterSection[]>([]);
@@ -1001,8 +1001,8 @@ const ImprovedTaskFilters: React.FC<ImprovedTaskFiltersProps> = ({ position, cla
// Debounced search change function // Debounced search change function
debouncedSearchChangeRef.current = createDebouncedFunction( debouncedSearchChangeRef.current = createDebouncedFunction(
(projectId: string, value: string) => { (projectId: string, value: string) => {
// Always use taskReducer search for list view since that's what we read from // Use taskManagement search for list view
dispatch(setSearch(value)); dispatch(setTaskManagementSearch(value));
// Trigger task refetch with new search value // Trigger task refetch with new search value
dispatch(fetchTasksV3(projectId)); dispatch(fetchTasksV3(projectId));
@@ -1142,6 +1142,7 @@ const ImprovedTaskFilters: React.FC<ImprovedTaskFiltersProps> = ({ position, cla
} }
} else { } else {
// Use debounced search for list view // Use debounced search for list view
dispatch(setTaskManagementSearch(value));
if (projectId) { if (projectId) {
debouncedSearchChangeRef.current?.(projectId, value); debouncedSearchChangeRef.current?.(projectId, value);
} }
@@ -1177,8 +1178,8 @@ const ImprovedTaskFilters: React.FC<ImprovedTaskFiltersProps> = ({ position, cla
// Prepare all Redux actions to be dispatched together // Prepare all Redux actions to be dispatched together
const reduxUpdates = () => { const reduxUpdates = () => {
// Clear search - always use taskReducer for list view // Clear search - use taskManagementSearch for list view
dispatch(setSearch('')); dispatch(setTaskManagementSearch(''));
// Clear label filters // Clear label filters
const clearedLabels = currentTaskLabels.map(label => ({ const clearedLabels = currentTaskLabels.map(label => ({
@@ -1249,7 +1250,7 @@ const ImprovedTaskFilters: React.FC<ImprovedTaskFiltersProps> = ({ position, cla
<SearchFilter <SearchFilter
value={searchValue} value={searchValue}
onChange={handleSearchChange} onChange={handleSearchChange}
placeholder="Search tasks..." placeholder="Search tasks by name or key..."
themeClasses={themeClasses} themeClasses={themeClasses}
/> />

View File

@@ -227,8 +227,8 @@ export const fetchTasksV3 = createAsyncThunk(
// Get selected priorities from taskReducer // Get selected priorities from taskReducer
const selectedPriorities = state.taskReducer.priorities.join(' '); const selectedPriorities = state.taskReducer.priorities.join(' ');
// Get search value from taskReducer // Get search value from taskManagement slice
const searchValue = state.taskReducer.search || ''; const searchValue = state.taskManagement.search || '';
// Get archived state from task management slice // Get archived state from task management slice
const archivedState = state.taskManagement.archived; const archivedState = state.taskManagement.archived;