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:
parent
b2e48c6966
commit
ae2f7d9570
3 changed files with 27 additions and 4 deletions
|
|
@ -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}
|
||||||
|
|
|
||||||
|
|
@ -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()}>
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue