feat: enhance GlobalTaskDetailDialog and TaskDetailDialog with loading state management

- Added a loading state to the TaskDetailDialog to display a loading indicator while fetching team data.
- Updated GlobalTaskDetailDialog to pass the loading state to TaskDetailDialog.
- Modified the selectTeam function in teamSlice to accept options for skipping project auto-selection, improving team data handling.
This commit is contained in:
iliya 2026-02-28 21:47:44 +02:00
parent b2e48c6966
commit ae2f7d9570
3 changed files with 27 additions and 4 deletions

View file

@ -69,7 +69,8 @@ export const GlobalTaskDetailDialog = (): React.JSX.Element | null => {
return ( return (
<TaskDetailDialog <TaskDetailDialog
open open
task={selectedTeamLoading ? null : task} loading={selectedTeamLoading}
task={task}
teamName={teamName} teamName={teamName}
kanbanTaskState={kanbanTaskState} kanbanTaskState={kanbanTaskState}
taskMap={taskMap} taskMap={taskMap}

View file

@ -61,6 +61,7 @@ import type { KanbanTaskState, ResolvedTeamMember, TeamTaskWithKanban } from '@s
interface TaskDetailDialogProps { interface TaskDetailDialogProps {
open: boolean; open: boolean;
loading?: boolean;
task: TeamTaskWithKanban | null; task: TeamTaskWithKanban | null;
teamName: string; teamName: string;
kanbanTaskState?: KanbanTaskState; kanbanTaskState?: KanbanTaskState;
@ -77,6 +78,7 @@ interface TaskDetailDialogProps {
export const TaskDetailDialog = ({ export const TaskDetailDialog = ({
open, open,
loading = false,
task, task,
teamName, teamName,
kanbanTaskState, kanbanTaskState,
@ -225,6 +227,22 @@ export const TaskDetailDialog = ({
onScrollToTask?.(taskId); onScrollToTask?.(taskId);
}; };
if (loading) {
return (
<Dialog open={open} onOpenChange={(v) => !v && onClose()}>
<DialogContent className="sm:max-w-4xl">
<DialogHeader>
<DialogTitle>Loading task</DialogTitle>
</DialogHeader>
<div className="flex items-center gap-2 text-sm text-[var(--color-text-muted)]">
<Loader2 className="size-4 animate-spin" />
<span>Fetching team data</span>
</div>
</DialogContent>
</Dialog>
);
}
if (!currentTask) { if (!currentTask) {
return ( return (
<Dialog open={open} onOpenChange={(v) => !v && handleClose()}> <Dialog open={open} onOpenChange={(v) => !v && handleClose()}>

View file

@ -126,7 +126,7 @@ export interface TeamSlice {
openTeamsTab: () => void; openTeamsTab: () => void;
openTeamTab: (teamName: string, projectPath?: string, taskId?: string) => void; openTeamTab: (teamName: string, projectPath?: string, taskId?: string) => void;
clearKanbanFilter: () => void; clearKanbanFilter: () => void;
selectTeam: (teamName: string) => Promise<void>; selectTeam: (teamName: string, opts?: { skipProjectAutoSelect?: boolean }) => Promise<void>;
refreshTeamData: (teamName: string) => Promise<void>; refreshTeamData: (teamName: string) => Promise<void>;
sendTeamMessage: (teamName: string, request: SendMessageRequest) => Promise<void>; sendTeamMessage: (teamName: string, request: SendMessageRequest) => Promise<void>;
requestReview: (teamName: string, taskId: string) => Promise<void>; requestReview: (teamName: string, taskId: string) => Promise<void>;
@ -207,7 +207,7 @@ export const createTeamSlice: StateCreator<AppState, [], [], TeamSlice> = (set,
// Ensure team data is loaded for the dialog // Ensure team data is loaded for the dialog
const state = get(); const state = get();
if (state.selectedTeamName !== teamName || !state.selectedTeamData) { if (state.selectedTeamName !== teamName || !state.selectedTeamData) {
void state.selectTeam(teamName); void state.selectTeam(teamName, { skipProjectAutoSelect: true });
} }
}, },
closeGlobalTaskDetail: () => set({ globalTaskDetail: null }), closeGlobalTaskDetail: () => set({ globalTaskDetail: null }),
@ -358,7 +358,7 @@ export const createTeamSlice: StateCreator<AppState, [], [], TeamSlice> = (set,
set({ kanbanFilterQuery: null }); set({ kanbanFilterQuery: null });
}, },
selectTeam: async (teamName: string) => { selectTeam: async (teamName: string, opts) => {
// Clear stale data immediately to prevent flash of previous team's content // Clear stale data immediately to prevent flash of previous team's content
const prev = get().selectedTeamName; const prev = get().selectedTeamName;
set({ set({
@ -407,6 +407,10 @@ export const createTeamSlice: StateCreator<AppState, [], [], TeamSlice> = (set,
get().updateTabLabel(teamTab.id, displayName); get().updateTabLabel(teamTab.id, displayName);
} }
if (opts?.skipProjectAutoSelect) {
return;
}
// Auto-select the project associated with this team's cwd/projectPath. // Auto-select the project associated with this team's cwd/projectPath.
// Must search both flat projects and grouped repositoryGroups/worktrees // Must search both flat projects and grouped repositoryGroups/worktrees
// because the default viewMode is 'grouped' and flat projects may be empty. // because the default viewMode is 'grouped' and flat projects may be empty.