Compare commits

...

2 Commits

Author SHA1 Message Date
01a8d02d60 fix(client): initialize project loading state to true to prevent race condition
Some checks failed
CI / test-python (push) Successful in 4m53s
CI / test-typescript (push) Failing after 1m8s
CI / test-rust (push) Successful in 1m56s
The Meetings page skips fetching when projectsLoading is false and
activeProject is null. Since isLoading started as false, the initial
render would skip the fetch before projects had a chance to load.

By initializing isLoading to true, the Meetings page waits for the
project context to finish loading before deciding whether to fetch.
2026-01-26 13:50:28 +00:00
f9db9cd8ca fix(client): close delete dialog on successful bulk deletion
Added onSuccess callback to useDeleteMeetings hook and use it in
Meetings.tsx to close dialog, clear selection, and exit selection mode.

Removed flaky useEffect that tried to detect deletion success by
checking if meetings still existed in the list.
2026-01-26 13:40:10 +00:00
3 changed files with 18 additions and 18 deletions

View File

@@ -56,7 +56,7 @@ export function ProjectProvider({ children }: { children: React.ReactNode }) {
const { currentWorkspace } = useWorkspace();
const [projects, setProjects] = useState<Project[]>([]);
const [activeProjectId, setActiveProjectId] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
// Use ref to avoid recreating loadProjects when currentWorkspace changes

View File

@@ -97,7 +97,11 @@ interface DeleteMeetingsContext {
removedMeetings: Map<string, Meeting>;
}
export function useDeleteMeetings() {
interface UseDeleteMeetingsOptions {
onSuccess?: (result: DeleteMeetingsResult) => void;
}
export function useDeleteMeetings(options?: UseDeleteMeetingsOptions) {
const { toast } = useToast();
const queryClient = useQueryClient();
@@ -147,6 +151,8 @@ export function useDeleteMeetings() {
description: `Skipped ${result.skippedIds.length} active meeting(s)`,
});
}
options?.onSuccess?.(result);
},
onError: (_error, _meetingIds, context) => {
// Restore removed meetings to cache on error

View File

@@ -55,7 +55,16 @@ export default function MeetingsPage() {
const [isSelectionMode, setIsSelectionMode] = useState(false);
const [selectedMeetingIds, setSelectedMeetingIds] = useState<Set<string>>(new Set());
const [showBulkDeleteDialog, setShowBulkDeleteDialog] = useState(false);
const { mutate: deleteMeetings, isLoading: isDeleting } = useDeleteMeetings();
const handleDeleteSuccess = useCallback(() => {
setShowBulkDeleteDialog(false);
setSelectedMeetingIds(new Set());
setIsSelectionMode(false);
}, []);
const { mutate: deleteMeetings, isLoading: isDeleting } = useDeleteMeetings({
onSuccess: handleDeleteSuccess,
});
const shouldSkipFetch =
(projectScope === 'selected' && selectedProjectIds.length === 0) ||
@@ -169,21 +178,6 @@ export default function MeetingsPage() {
deleteMeetings(Array.from(selectedMeetingIds));
}, [deleteMeetings, selectedMeetingIds]);
// Handle successful deletion
useEffect(() => {
if (!isDeleting && selectedMeetingIds.size > 0) {
// Check if deletion was successful by verifying meetings were removed
const deletedIds = Array.from(selectedMeetingIds);
const stillExists = meetings.some((m) => deletedIds.includes(m.id));
if (!stillExists) {
setSelectedMeetingIds(new Set());
setShowBulkDeleteDialog(false);
setIsSelectionMode(false);
fetchMeetings(0, false);
}
}
}, [isDeleting, selectedMeetingIds, meetings, fetchMeetings]);
const hasMore = meetings.length < totalCount;
const handleLoadMore = () => {