fix: prevent queue settings modal from disappearing when tasks change (#1430)
* fix: add missing namespace prefix to queue settings modal translations Fixed translation keys that were missing the 'tasks:' namespace prefix, which caused the queue settings modal to not display correctly. - Fixed DialogTitle translation - Fixed DialogDescription translation - Fixed Label translation - Fixed hint text translation Co-Authored-By: Claude <noreply@anthropic.com> * fix: prevent queue settings modal from disappearing when tasks change Fixed a race condition where the queue settings modal would not open reliably or would disappear when the tasks list changed. Root cause: The modal was conditionally rendered based on `projectId` which was derived from `tasks[0]?.projectId`. When there were no tasks or when tasks changed, `projectId` would become undefined, causing the modal to not render even though `showQueueSettings` was true. Solution: Store the `projectId` in a ref when the modal opens and use that stored value for rendering, ensuring the modal remains visible regardless of task state changes. Changes: - Added `queueSettingsProjectIdRef` to store projectId when modal opens - Update onQueueSettings handler to capture projectId before opening modal - Update modal rendering condition to use stored projectId from ref - Clear stored projectId when modal closes Co-Authored-By: Claude <noreply@anthropic.com> * docs: add JSDoc comments to QueueSettingsModal Added JSDoc docstrings to: - QueueSettingsModalProps interface - QueueSettingsModal component - handleSave function - handleInputChange function This improves code documentation and helps with docstring coverage. Co-Authored-By: Claude <noreply@anthropic.com> * fix: use stored projectId ref in handleSaveQueueSettings Fix a bug where the handleSaveQueueSettings function used the component-scoped projectId variable (derived from tasks[0]?.projectId) instead of the stored ref value. This caused saves to fail silently if tasks changed while the modal was open and projectId became undefined. Co-Authored-By: Claude <noreply@anthropic.com> * fix: prevent queue settings modal from opening when no projectId exists When the queue column is empty (no tasks), projectId is undefined because it's derived from tasks[0]?.projectId. The queue settings button should not open the modal in this case since there's no valid project to configure settings for. This fixes a silent failure where the button was clickable but had no effect when the queue was empty. Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -489,6 +489,8 @@ export function KanbanBoard({ tasks, onTaskClick, onNewTaskClick, onRefresh, isR
|
||||
|
||||
// Queue settings modal state
|
||||
const [showQueueSettings, setShowQueueSettings] = useState(false);
|
||||
// Store projectId when modal opens to prevent modal from disappearing if tasks change
|
||||
const queueSettingsProjectIdRef = useRef<string | null>(null);
|
||||
|
||||
// Queue processing lock to prevent race conditions
|
||||
const isProcessingQueueRef = useRef(false);
|
||||
@@ -794,11 +796,15 @@ export function KanbanBoard({ tasks, onTaskClick, onNewTaskClick, onRefresh, isR
|
||||
|
||||
/**
|
||||
* Save queue settings (maxParallelTasks)
|
||||
*
|
||||
* Uses the stored ref value to ensure the save works even if tasks
|
||||
* change while the modal is open.
|
||||
*/
|
||||
const handleSaveQueueSettings = async (maxParallel: number) => {
|
||||
if (!projectId) return;
|
||||
const savedProjectId = queueSettingsProjectIdRef.current || projectId;
|
||||
if (!savedProjectId) return;
|
||||
|
||||
const success = await updateProjectSettings(projectId, { maxParallelTasks: maxParallel });
|
||||
const success = await updateProjectSettings(savedProjectId, { maxParallelTasks: maxParallel });
|
||||
if (success) {
|
||||
toast({
|
||||
title: t('queue.settings.saved'),
|
||||
@@ -1112,7 +1118,12 @@ export function KanbanBoard({ tasks, onTaskClick, onNewTaskClick, onRefresh, isR
|
||||
isOver={overColumnId === status}
|
||||
onAddClick={status === 'backlog' ? onNewTaskClick : undefined}
|
||||
onQueueAll={status === 'backlog' ? handleQueueAll : undefined}
|
||||
onQueueSettings={status === 'queue' ? () => setShowQueueSettings(true) : undefined}
|
||||
onQueueSettings={status === 'queue' ? () => {
|
||||
// Only open modal if we have a valid projectId
|
||||
if (!projectId) return;
|
||||
queueSettingsProjectIdRef.current = projectId;
|
||||
setShowQueueSettings(true);
|
||||
} : undefined}
|
||||
onArchiveAll={status === 'done' ? handleArchiveAll : undefined}
|
||||
maxParallelTasks={status === 'in_progress' ? maxParallelTasks : undefined}
|
||||
archivedCount={status === 'done' ? archivedCount : undefined}
|
||||
@@ -1181,11 +1192,16 @@ export function KanbanBoard({ tasks, onTaskClick, onNewTaskClick, onRefresh, isR
|
||||
/>
|
||||
|
||||
{/* Queue Settings Modal */}
|
||||
{projectId && (
|
||||
{(queueSettingsProjectIdRef.current || projectId) && (
|
||||
<QueueSettingsModal
|
||||
open={showQueueSettings}
|
||||
onOpenChange={setShowQueueSettings}
|
||||
projectId={projectId}
|
||||
onOpenChange={(open) => {
|
||||
setShowQueueSettings(open);
|
||||
if (!open) {
|
||||
queueSettingsProjectIdRef.current = null;
|
||||
}
|
||||
}}
|
||||
projectId={queueSettingsProjectIdRef.current || projectId || ''}
|
||||
currentMaxParallel={maxParallelTasks}
|
||||
onSave={handleSaveQueueSettings}
|
||||
/>
|
||||
|
||||
@@ -12,14 +12,28 @@ import { Button } from './ui/button';
|
||||
import { Label } from './ui/label';
|
||||
import { Input } from './ui/input';
|
||||
|
||||
/**
|
||||
* Props for QueueSettingsModal component
|
||||
*/
|
||||
interface QueueSettingsModalProps {
|
||||
/** Whether the modal is currently open */
|
||||
open: boolean;
|
||||
/** Callback to control modal open state */
|
||||
onOpenChange: (open: boolean) => void;
|
||||
/** The project ID to update settings for */
|
||||
projectId: string;
|
||||
/** Current maximum parallel tasks setting (default: 3) */
|
||||
currentMaxParallel?: number;
|
||||
/** Callback when user saves the new max parallel value */
|
||||
onSave: (maxParallel: number) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* QueueSettingsModal - Modal for configuring queue parallel task limits
|
||||
*
|
||||
* Allows users to adjust the maximum number of tasks that can run in parallel
|
||||
* for a specific project. Validates input between 1-10 tasks.
|
||||
*/
|
||||
export function QueueSettingsModal({
|
||||
open,
|
||||
onOpenChange,
|
||||
@@ -39,14 +53,20 @@ export function QueueSettingsModal({
|
||||
}
|
||||
}, [open, currentMaxParallel]);
|
||||
|
||||
/**
|
||||
* Validates and saves the max parallel tasks setting
|
||||
*
|
||||
* Validates that the value is between 1-10, sets an error message
|
||||
* if invalid, otherwise calls onSave and closes the modal.
|
||||
*/
|
||||
const handleSave = () => {
|
||||
// Validate the input
|
||||
if (maxParallel < 1) {
|
||||
setError(t('queue.settings.minValueError'));
|
||||
setError(t('tasks:queue.settings.minValueError'));
|
||||
return;
|
||||
}
|
||||
if (maxParallel > 10) {
|
||||
setError(t('queue.settings.maxValueError'));
|
||||
setError(t('tasks:queue.settings.maxValueError'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -54,6 +74,14 @@ export function QueueSettingsModal({
|
||||
onOpenChange(false);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles input field changes for the max parallel tasks value
|
||||
*
|
||||
* Parses the input value, validates it's a number, and updates state.
|
||||
* Allows empty input for editing purposes (will fail validation on save).
|
||||
*
|
||||
* @param e - The input change event from the number input field
|
||||
*/
|
||||
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const inputValue = e.target.value;
|
||||
|
||||
@@ -75,16 +103,16 @@ export function QueueSettingsModal({
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t('queue.settings.title')}</DialogTitle>
|
||||
<DialogTitle>{t('tasks:queue.settings.title')}</DialogTitle>
|
||||
<DialogDescription>
|
||||
{t('queue.settings.description')}
|
||||
{t('tasks:queue.settings.description')}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4 py-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="maxParallel">
|
||||
{t('queue.settings.maxParallelLabel')}
|
||||
{t('tasks:queue.settings.maxParallelLabel')}
|
||||
</Label>
|
||||
<Input
|
||||
id="maxParallel"
|
||||
@@ -99,7 +127,7 @@ export function QueueSettingsModal({
|
||||
<p className="text-sm text-destructive">{error}</p>
|
||||
)}
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t('queue.settings.hint')}
|
||||
{t('tasks:queue.settings.hint')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user