feat(task-list): refine task list components and improve UI consistency
- Updated SubtaskLoadingSkeleton and TaskRow components for better spacing and visual consistency. - Simplified TaskGroupHeader by removing unnecessary elements and enhancing the display of group names. - Adjusted TaskListV2 to improve column rendering and added state management for field visibility synchronization with the database. - Enhanced AddTaskRow and AddSubtaskRow components for improved user interaction and layout. - Updated placeholder texts in CustomColumnComponents for better clarity.
This commit is contained in:
@@ -26,7 +26,6 @@ const SubtaskLoadingSkeleton: React.FC<SubtaskLoadingSkeletonProps> = ({ visible
|
||||
case 'title':
|
||||
return (
|
||||
<div style={baseStyle} className="flex items-center">
|
||||
{/* Subtask indentation - tighter spacing */}
|
||||
<div className="w-4" />
|
||||
<div className="w-2" />
|
||||
<div className="h-4 w-32 bg-gray-200 dark:bg-gray-700 rounded animate-pulse" />
|
||||
@@ -35,7 +34,7 @@ const SubtaskLoadingSkeleton: React.FC<SubtaskLoadingSkeletonProps> = ({ visible
|
||||
case 'description':
|
||||
return (
|
||||
<div style={baseStyle} className="flex items-center px-2">
|
||||
<div className="h-4 w-40 bg-gray-200 dark:bg-gray-700 rounded animate-pulse" />
|
||||
<div className="h-4 w-24 bg-gray-200 dark:bg-gray-700 rounded animate-pulse" />
|
||||
</div>
|
||||
);
|
||||
case 'status':
|
||||
@@ -66,7 +65,7 @@ const SubtaskLoadingSkeleton: React.FC<SubtaskLoadingSkeletonProps> = ({ visible
|
||||
case 'progress':
|
||||
return (
|
||||
<div style={baseStyle} className="flex items-center">
|
||||
<div className="h-2 w-16 bg-gray-200 dark:bg-gray-700 rounded-full animate-pulse" />
|
||||
<div className="h-4 w-16 bg-gray-200 dark:bg-gray-700 rounded animate-pulse" />
|
||||
</div>
|
||||
);
|
||||
case 'labels':
|
||||
@@ -91,7 +90,7 @@ const SubtaskLoadingSkeleton: React.FC<SubtaskLoadingSkeletonProps> = ({ visible
|
||||
case 'estimation':
|
||||
return (
|
||||
<div style={baseStyle} className="flex items-center">
|
||||
<div className="h-4 w-10 bg-gray-200 dark:bg-gray-700 rounded animate-pulse" />
|
||||
<div className="h-4 w-8 bg-gray-200 dark:bg-gray-700 rounded animate-pulse" />
|
||||
</div>
|
||||
);
|
||||
case 'startDate':
|
||||
@@ -132,7 +131,7 @@ const SubtaskLoadingSkeleton: React.FC<SubtaskLoadingSkeletonProps> = ({ visible
|
||||
return (
|
||||
<div className="bg-gray-50 dark:bg-gray-800/50 border-l-2 border-blue-200 dark:border-blue-700">
|
||||
<div className="flex items-center min-w-max px-4 py-2 border-b border-gray-200 dark:border-gray-700">
|
||||
{visibleColumns.map((column) => (
|
||||
{visibleColumns.map((column, index) => (
|
||||
<div key={column.id}>
|
||||
{renderColumn(column.id, column.width)}
|
||||
</div>
|
||||
|
||||
@@ -166,8 +166,6 @@ const TaskGroupHeader: React.FC<TaskGroupHeaderProps> = ({ group, isCollapsed, o
|
||||
setCategoryModalVisible(true);
|
||||
}, []);
|
||||
|
||||
|
||||
|
||||
// Handle category change
|
||||
const handleCategoryChange = useCallback(async (categoryId: string, e?: React.MouseEvent) => {
|
||||
e?.stopPropagation();
|
||||
@@ -292,108 +290,18 @@ const TaskGroupHeader: React.FC<TaskGroupHeaderProps> = ({ group, isCollapsed, o
|
||||
<div className="flex items-center flex-1 ml-1">
|
||||
{/* Group name and count */}
|
||||
<div className="flex items-center">
|
||||
{isEditingName && isOwnerOrAdmin ? (
|
||||
<Input
|
||||
value={editingName}
|
||||
onChange={(e) => setEditingName(e.target.value)}
|
||||
onKeyDown={handleNameKeyDown}
|
||||
onBlur={handleNameBlur}
|
||||
className="text-sm font-semibold px-2 py-1 rounded-md transition-all duration-200 focus:ring-2 focus:ring-blue-500 focus:ring-opacity-50"
|
||||
style={{
|
||||
color: headerTextColor,
|
||||
fontSize: '14px',
|
||||
fontWeight: 600,
|
||||
width: `${Math.max(editingName.length * 8 + 16, 80)}px`,
|
||||
minWidth: '80px',
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.1)',
|
||||
border: `1px solid ${headerTextColor}40`,
|
||||
backdropFilter: 'blur(4px)'
|
||||
}}
|
||||
styles={{
|
||||
input: {
|
||||
color: headerTextColor,
|
||||
backgroundColor: 'transparent',
|
||||
border: 'none',
|
||||
outline: 'none',
|
||||
boxShadow: 'none',
|
||||
padding: '0'
|
||||
}
|
||||
}}
|
||||
autoFocus
|
||||
disabled={isRenaming}
|
||||
placeholder={t('enterGroupName')}
|
||||
/>
|
||||
) : (
|
||||
<span
|
||||
className={`text-sm font-semibold ${isOwnerOrAdmin ? 'cursor-pointer hover:bg-black hover:bg-opacity-10 dark:hover:bg-white dark:hover:bg-opacity-10 rounded px-2 py-1 transition-all duration-200 hover:shadow-sm' : ''}`}
|
||||
onClick={handleNameClick}
|
||||
className="text-sm font-semibold"
|
||||
style={{ color: headerTextColor }}
|
||||
title={isOwnerOrAdmin ? t('clickToEditGroupName') : ''}
|
||||
>
|
||||
{group.name}
|
||||
</span>
|
||||
)}
|
||||
<span className="text-sm font-semibold ml-1" style={{ color: headerTextColor }}>
|
||||
({group.count})
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Three dots menu */}
|
||||
<div className="flex items-center justify-center ml-2">
|
||||
<Dropdown
|
||||
menu={{ items: menuItems }}
|
||||
trigger={['click']}
|
||||
open={dropdownVisible}
|
||||
onOpenChange={setDropdownVisible}
|
||||
placement="bottomLeft"
|
||||
>
|
||||
<button
|
||||
className="p-1 rounded-sm hover:bg-black hover:bg-opacity-10 transition-all duration-200 ease-out"
|
||||
style={{ color: headerTextColor }}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setDropdownVisible(!dropdownVisible);
|
||||
}}
|
||||
>
|
||||
<EllipsisHorizontalIcon className="h-4 w-4" />
|
||||
</button>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{/* Change Category Modal */}
|
||||
<Modal
|
||||
title="Change Category"
|
||||
open={categoryModalVisible}
|
||||
onCancel={() => setCategoryModalVisible(false)}
|
||||
footer={null}
|
||||
width={400}
|
||||
>
|
||||
<div className="py-4">
|
||||
<div className="space-y-2">
|
||||
{statusCategories?.map((category) => (
|
||||
<div
|
||||
key={category.id}
|
||||
className="flex items-center justify-between p-3 border rounded-lg cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
|
||||
onClick={(e) => category.id && handleCategoryChange(category.id, e)}
|
||||
>
|
||||
<Flex align="center" gap={12}>
|
||||
<Badge color={category.color_code} />
|
||||
<span className="font-medium">{category.name}</span>
|
||||
</Flex>
|
||||
{isChangingCategory && (
|
||||
<div className="text-blue-500">
|
||||
<ArrowPathIcon className="h-4 w-4 animate-spin" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useCallback, useMemo, useEffect } from 'react';
|
||||
import React, { useCallback, useMemo, useEffect, useState } from 'react';
|
||||
import { GroupedVirtuoso } from 'react-virtuoso';
|
||||
import {
|
||||
DndContext,
|
||||
@@ -156,7 +156,7 @@ const TaskListV2: React.FC = () => {
|
||||
const fieldType = column.custom_column_obj?.fieldType;
|
||||
let defaultWidth = 160;
|
||||
if (fieldType === 'selection') {
|
||||
defaultWidth = 180; // Extra width for selection dropdowns
|
||||
defaultWidth = 150; // Reduced width for selection dropdowns
|
||||
} else if (fieldType === 'people') {
|
||||
defaultWidth = 170; // Extra width for people with avatars
|
||||
}
|
||||
@@ -177,36 +177,6 @@ const TaskListV2: React.FC = () => {
|
||||
return [...baseVisibleColumns, ...visibleCustomColumns];
|
||||
}, [fields, columns, customColumns, t]);
|
||||
|
||||
// Sync local field changes with backend column configuration (debounced)
|
||||
useEffect(() => {
|
||||
if (!urlProjectId || columns.length === 0 || fields.length === 0) return;
|
||||
|
||||
const timeoutId = setTimeout(() => {
|
||||
const changedFields = fields.filter(field => {
|
||||
const backendColumn = columns.find(c => c.key === field.key);
|
||||
if (backendColumn) {
|
||||
return (backendColumn.pinned ?? false) !== field.visible;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
changedFields.forEach(field => {
|
||||
const backendColumn = columns.find(c => c.key === field.key);
|
||||
if (backendColumn) {
|
||||
dispatch(updateColumnVisibility({
|
||||
projectId: urlProjectId,
|
||||
item: {
|
||||
...backendColumn,
|
||||
pinned: field.visible
|
||||
}
|
||||
}));
|
||||
}
|
||||
});
|
||||
}, 500);
|
||||
|
||||
return () => clearTimeout(timeoutId);
|
||||
}, [fields, columns, urlProjectId, dispatch]);
|
||||
|
||||
// Effects
|
||||
useEffect(() => {
|
||||
if (urlProjectId) {
|
||||
@@ -215,6 +185,38 @@ const TaskListV2: React.FC = () => {
|
||||
}
|
||||
}, [dispatch, urlProjectId]);
|
||||
|
||||
// Initialize field visibility from database when columns are loaded (only once)
|
||||
const [initializedFromDatabase, setInitializedFromDatabase] = useState(false);
|
||||
useEffect(() => {
|
||||
if (columns.length > 0 && fields.length > 0 && !initializedFromDatabase) {
|
||||
// Update local fields to match database state only on initial load
|
||||
import('@/features/task-management/taskListFields.slice').then(({ setFields }) => {
|
||||
// Create updated fields based on database column state
|
||||
const updatedFields = fields.map(field => {
|
||||
const backendColumn = columns.find(c => c.key === field.key);
|
||||
if (backendColumn) {
|
||||
return {
|
||||
...field,
|
||||
visible: backendColumn.pinned ?? field.visible
|
||||
};
|
||||
}
|
||||
return field;
|
||||
});
|
||||
|
||||
// Only update if there are actual changes
|
||||
const hasChanges = updatedFields.some((field, index) =>
|
||||
field.visible !== fields[index].visible
|
||||
);
|
||||
|
||||
if (hasChanges) {
|
||||
dispatch(setFields(updatedFields));
|
||||
}
|
||||
|
||||
setInitializedFromDatabase(true);
|
||||
});
|
||||
}
|
||||
}, [columns, fields, dispatch, initializedFromDatabase]);
|
||||
|
||||
// Event handlers
|
||||
const handleTaskSelect = useCallback(
|
||||
(taskId: string, event: React.MouseEvent) => {
|
||||
@@ -360,7 +362,7 @@ const TaskListV2: React.FC = () => {
|
||||
{isGroupEmpty && !isGroupCollapsed && (
|
||||
<div className="relative w-full">
|
||||
<div className="flex items-center min-w-max px-1 py-3">
|
||||
{visibleColumns.map((column) => (
|
||||
{visibleColumns.map((column, index) => (
|
||||
<div
|
||||
key={`empty-${column.id}`}
|
||||
style={{ width: column.width, flexShrink: 0 }}
|
||||
@@ -413,8 +415,8 @@ const TaskListV2: React.FC = () => {
|
||||
// Render column headers
|
||||
const renderColumnHeaders = useCallback(() => (
|
||||
<div className="sticky top-0 z-30 bg-gray-50 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700">
|
||||
<div className="flex items-center px-1 py-3 w-full" style={{ minWidth: 'max-content', height: '44px' }}>
|
||||
{visibleColumns.map(column => {
|
||||
<div className="flex items-center px-3 py-3 w-full" style={{ minWidth: 'max-content', height: '44px' }}>
|
||||
{visibleColumns.map((column, index) => {
|
||||
const columnStyle: ColumnStyle = {
|
||||
width: column.width,
|
||||
flexShrink: 0,
|
||||
@@ -470,13 +472,16 @@ const TaskListV2: React.FC = () => {
|
||||
>
|
||||
<div className="flex flex-col bg-white dark:bg-gray-900" style={{ height: '100vh', overflow: 'hidden' }}>
|
||||
{/* Task Filters */}
|
||||
<div className="flex-none px-4 py-3" style={{ height: '66px', flexShrink: 0 }}>
|
||||
<div className="flex-none px-6 py-4" style={{ height: '74px', flexShrink: 0 }}>
|
||||
<ImprovedTaskFilters position="list" />
|
||||
</div>
|
||||
|
||||
{/* Spacing between filters and table */}
|
||||
<div className="flex-none h-4" style={{ flexShrink: 0 }}></div>
|
||||
|
||||
{/* Table Container */}
|
||||
<div
|
||||
className="flex-1 overflow-auto border border-gray-200 dark:border-gray-700"
|
||||
className="flex-1 overflow-auto border border-gray-200 dark:border-gray-700 mx-6 rounded-lg"
|
||||
style={{
|
||||
height: '600px',
|
||||
maxHeight: '600px'
|
||||
|
||||
@@ -261,7 +261,7 @@ const TaskRow: React.FC<TaskRowProps> = memo(({ taskId, projectId, visibleColumn
|
||||
case 'dragHandle':
|
||||
return (
|
||||
<div
|
||||
className={`flex items-center justify-center ${isSubtask ? '' : 'cursor-grab active:cursor-grabbing'}`}
|
||||
className="flex items-center justify-center"
|
||||
style={baseStyle}
|
||||
{...(isSubtask ? {} : { ...attributes, ...listeners })}
|
||||
>
|
||||
|
||||
@@ -109,7 +109,7 @@ const AddSubtaskRow: React.FC<AddSubtaskRowProps> = memo(({
|
||||
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')}
|
||||
{t('addSubtaskText')}
|
||||
</button>
|
||||
) : (
|
||||
<Input
|
||||
@@ -121,7 +121,7 @@ const AddSubtaskRow: React.FC<AddSubtaskRowProps> = memo(({
|
||||
className="w-full h-full border-none shadow-none bg-transparent"
|
||||
style={{
|
||||
height: '100%',
|
||||
minHeight: '42px',
|
||||
minHeight: '32px',
|
||||
padding: '0',
|
||||
fontSize: '14px'
|
||||
}}
|
||||
@@ -137,10 +137,12 @@ const AddSubtaskRow: React.FC<AddSubtaskRowProps> = memo(({
|
||||
}, [isAdding, subtaskName, handleAddSubtask, handleCancel, t]);
|
||||
|
||||
return (
|
||||
<div className="flex items-center min-w-max px-1 py-2 border-b border-gray-200 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-800 min-h-[42px]">
|
||||
{visibleColumns.map((column) =>
|
||||
renderColumn(column.id, column.width)
|
||||
)}
|
||||
<div className="flex items-center min-w-max px-1 py-0.5 hover:bg-gray-50 dark:hover:bg-gray-800 min-h-[36px] border-b border-gray-200 dark:border-gray-700">
|
||||
{visibleColumns.map((column, index) => (
|
||||
<React.Fragment key={column.id}>
|
||||
{renderColumn(column.id, column.width)}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -135,9 +135,11 @@ const AddTaskRow: React.FC<AddTaskRowProps> = memo(({
|
||||
|
||||
return (
|
||||
<div className="flex items-center min-w-max px-1 py-0.5 hover:bg-gray-50 dark:hover:bg-gray-800 min-h-[36px] border-b border-gray-200 dark:border-gray-700">
|
||||
{visibleColumns.map((column) =>
|
||||
renderColumn(column.id, column.width)
|
||||
)}
|
||||
{visibleColumns.map((column, index) => (
|
||||
<React.Fragment key={column.id}>
|
||||
{renderColumn(column.id, column.width)}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -285,7 +285,7 @@ export const DateCustomColumnCell: React.FC<{
|
||||
onOpenChange={setIsOpen}
|
||||
value={dateValue}
|
||||
onChange={handleDateChange}
|
||||
placeholder={dateValue ? "" : "Click to set date"}
|
||||
placeholder={dateValue ? "" : "Set date"}
|
||||
format="MMM DD, YYYY"
|
||||
suffixIcon={null}
|
||||
size="small"
|
||||
@@ -468,7 +468,7 @@ export const SelectionCustomColumnCell: React.FC<{
|
||||
: 'border-gray-200 text-gray-600 bg-gray-50'
|
||||
}
|
||||
`}>
|
||||
Select an option
|
||||
Select option
|
||||
</div>
|
||||
|
||||
{/* Options */}
|
||||
@@ -569,7 +569,7 @@ export const SelectionCustomColumnCell: React.FC<{
|
||||
<>
|
||||
<div className={`w-3 h-3 rounded-full border-2 border-dashed ${isDarkMode ? 'border-gray-600' : 'border-gray-300'}`} />
|
||||
<span className={`text-sm ${isDarkMode ? 'text-gray-500' : 'text-gray-400'}`}>
|
||||
Select option
|
||||
Select
|
||||
</span>
|
||||
<svg className={`w-4 h-4 ml-auto transition-transform duration-200 ${isDropdownOpen ? 'rotate-180' : ''} ${isDarkMode ? 'text-gray-500' : 'text-gray-400'}`} fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
||||
|
||||
@@ -20,7 +20,8 @@ import { useAppSelector } from '@/hooks/useAppSelector';
|
||||
import { useAppDispatch } from '@/hooks/useAppDispatch';
|
||||
import useTabSearchParam from '@/hooks/useTabSearchParam';
|
||||
import { useFilterDataLoader } from '@/hooks/useFilterDataLoader';
|
||||
import { toggleField } from '@/features/task-management/taskListFields.slice';
|
||||
import { toggleField, syncFieldWithDatabase } from '@/features/task-management/taskListFields.slice';
|
||||
import { selectColumns } from '@/features/task-management/task-management.slice';
|
||||
|
||||
// Import Redux actions
|
||||
import {
|
||||
@@ -698,8 +699,10 @@ const FieldsDropdown: React.FC<{ themeClasses: any; isDarkMode: boolean }> = ({
|
||||
isDarkMode,
|
||||
}) => {
|
||||
const { t } = useTranslation('task-list-filters');
|
||||
const dispatch = useDispatch();
|
||||
const dispatch = useAppDispatch();
|
||||
const fieldsRaw = useSelector((state: RootState) => state.taskManagementFields);
|
||||
const columns = useSelector(selectColumns);
|
||||
const projectId = useAppSelector(state => state.projectReducer.projectId);
|
||||
const fields = Array.isArray(fieldsRaw) ? fieldsRaw : [];
|
||||
const sortedFields = useMemo(() => [...fields].sort((a, b) => a.order - b.order), [fields]);
|
||||
|
||||
@@ -792,7 +795,20 @@ const FieldsDropdown: React.FC<{ themeClasses: any; isDarkMode: boolean }> = ({
|
||||
return (
|
||||
<button
|
||||
key={field.key}
|
||||
onClick={() => dispatch(toggleField(field.key))}
|
||||
onClick={() => {
|
||||
// Toggle field locally first
|
||||
dispatch(toggleField(field.key));
|
||||
|
||||
// Sync with database if projectId is available
|
||||
if (projectId) {
|
||||
dispatch(syncFieldWithDatabase({
|
||||
projectId,
|
||||
fieldKey: field.key,
|
||||
visible: !field.visible,
|
||||
columns
|
||||
}));
|
||||
}
|
||||
}}
|
||||
className={`
|
||||
w-full flex items-center gap-2 px-2 py-1.5 text-xs rounded
|
||||
transition-colors duration-150 text-left
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||
import { createSlice, PayloadAction, createAsyncThunk } from '@reduxjs/toolkit';
|
||||
import { updateColumnVisibility } from './task-management.slice';
|
||||
import { ITaskListColumn } from '@/types/tasks/taskList.types';
|
||||
|
||||
export interface TaskListField {
|
||||
key: string;
|
||||
@@ -49,6 +51,71 @@ function saveFields(fields: TaskListField[]) {
|
||||
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(fields));
|
||||
}
|
||||
|
||||
// Async thunk to sync field visibility with database
|
||||
export const syncFieldWithDatabase = createAsyncThunk(
|
||||
'taskManagementFields/syncFieldWithDatabase',
|
||||
async (
|
||||
{ projectId, fieldKey, visible, columns }: {
|
||||
projectId: string;
|
||||
fieldKey: string;
|
||||
visible: boolean;
|
||||
columns: ITaskListColumn[]
|
||||
},
|
||||
{ dispatch }
|
||||
) => {
|
||||
// Find the corresponding backend column
|
||||
const backendColumn = columns.find(c => c.key === fieldKey);
|
||||
if (backendColumn) {
|
||||
// Update the column visibility in the database
|
||||
await dispatch(updateColumnVisibility({
|
||||
projectId,
|
||||
item: {
|
||||
...backendColumn,
|
||||
pinned: visible
|
||||
}
|
||||
}));
|
||||
}
|
||||
return { fieldKey, visible };
|
||||
}
|
||||
);
|
||||
|
||||
// Async thunk to sync all fields with database
|
||||
export const syncAllFieldsWithDatabase = createAsyncThunk(
|
||||
'taskManagementFields/syncAllFieldsWithDatabase',
|
||||
async (
|
||||
{ projectId, fields, columns }: {
|
||||
projectId: string;
|
||||
fields: TaskListField[];
|
||||
columns: ITaskListColumn[]
|
||||
},
|
||||
{ dispatch }
|
||||
) => {
|
||||
// Find fields that need to be synced
|
||||
const fieldsToSync = fields.filter(field => {
|
||||
const backendColumn = columns.find(c => c.key === field.key);
|
||||
return backendColumn && (backendColumn.pinned ?? false) !== field.visible;
|
||||
});
|
||||
|
||||
// Sync each field
|
||||
const syncPromises = fieldsToSync.map(field => {
|
||||
const backendColumn = columns.find(c => c.key === field.key);
|
||||
if (backendColumn) {
|
||||
return dispatch(updateColumnVisibility({
|
||||
projectId,
|
||||
item: {
|
||||
...backendColumn,
|
||||
pinned: field.visible
|
||||
}
|
||||
}));
|
||||
}
|
||||
return Promise.resolve();
|
||||
});
|
||||
|
||||
await Promise.all(syncPromises);
|
||||
return fieldsToSync.map(f => ({ fieldKey: f.key, visible: f.visible }));
|
||||
}
|
||||
);
|
||||
|
||||
const initialState: TaskListField[] = loadFields();
|
||||
|
||||
const taskListFieldsSlice = createSlice({
|
||||
@@ -75,10 +142,42 @@ const taskListFieldsSlice = createSlice({
|
||||
saveFields(defaultFields);
|
||||
return defaultFields;
|
||||
},
|
||||
// New action to update field visibility from database
|
||||
updateFieldVisibilityFromDatabase(state, action: PayloadAction<{ fieldKey: string; visible: boolean }>) {
|
||||
const { fieldKey, visible } = action.payload;
|
||||
const field = state.find(f => f.key === fieldKey);
|
||||
if (field) {
|
||||
field.visible = visible;
|
||||
// Save to localStorage
|
||||
saveFields(state);
|
||||
}
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder
|
||||
.addCase(syncFieldWithDatabase.fulfilled, (state, action) => {
|
||||
// Field visibility has been synced with database
|
||||
const { fieldKey, visible } = action.payload;
|
||||
const field = state.find(f => f.key === fieldKey);
|
||||
if (field) {
|
||||
field.visible = visible;
|
||||
saveFields(state);
|
||||
}
|
||||
})
|
||||
.addCase(syncAllFieldsWithDatabase.fulfilled, (state, action) => {
|
||||
// All fields have been synced with database
|
||||
action.payload.forEach(({ fieldKey, visible }) => {
|
||||
const field = state.find(f => f.key === fieldKey);
|
||||
if (field) {
|
||||
field.visible = visible;
|
||||
}
|
||||
});
|
||||
saveFields(state);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export const { toggleField, setFields, resetFields } = taskListFieldsSlice.actions;
|
||||
export const { toggleField, setFields, resetFields, updateFieldVisibilityFromDatabase } = taskListFieldsSlice.actions;
|
||||
|
||||
// Utility function to force reset fields (can be called from browser console)
|
||||
export const forceResetFields = () => {
|
||||
|
||||
Reference in New Issue
Block a user