feat(client): integrate bulk delete in Meetings page
- Add selection state management with Set<string> - Integrate useDeleteMeetings hook with confirmation dialog - Implement select/deselect/selectAll handlers - Render BulkActionToolbar when selections > 0 - Clear selections on filter/pagination changes - Add ConfirmationDialog for bulk delete confirmation - Fix missing index.ts for request types - Fix useToast import path Completes full bulk delete flow from UI to backend. Refs: mass-delete-meetings plan task 8
This commit is contained in:
11
client/src/api/types/requests/index.ts
Normal file
11
client/src/api/types/requests/index.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export * from './ai';
|
||||
export * from './annotations';
|
||||
export * from './assistant';
|
||||
export * from './audio';
|
||||
export * from './integrations';
|
||||
export * from './meetings';
|
||||
export * from './oidc';
|
||||
export * from './preferences';
|
||||
export * from './recording-apps';
|
||||
export * from './templates';
|
||||
export * from './triggers';
|
||||
@@ -4,7 +4,7 @@ import { meetingCache } from '@/lib/cache/meeting-cache';
|
||||
import { getAPI } from '@/api/interface';
|
||||
import type { Meeting } from '@/api/types';
|
||||
import type { CreateMeetingRequest, DeleteMeetingsResult } from '@/api/types/requests/meetings';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import { useToast } from '@/hooks/ui/use-toast';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
|
||||
interface CreateMeetingContext {
|
||||
|
||||
@@ -7,15 +7,17 @@ import { getAPI } from '@/api/interface';
|
||||
import type { Meeting, MeetingState } from '@/api/types';
|
||||
import type { ProjectScope } from '@/api/types/requests';
|
||||
import { EmptyState } from '@/components/common';
|
||||
import { MeetingCard } from '@/components/features/meetings';
|
||||
import { BulkActionToolbar, MeetingCard } from '@/components/features/meetings';
|
||||
import { ProjectScopeFilter } from '@/components/features/projects/ProjectScopeFilter';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { ConfirmationDialog } from '@/components/ui/confirmation-dialog';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { SearchIcon } from '@/components/ui/search-icon';
|
||||
import { SkeletonMeetingCard } from '@/components/ui/skeleton';
|
||||
import { UpcomingMeetings } from '@/components/features/calendar';
|
||||
import { useProjects } from '@/contexts/project-state';
|
||||
import { useGuardedMutation } from '@/hooks';
|
||||
import { useDeleteMeetings } from '@/hooks/meetings/use-meeting-mutations';
|
||||
import { addClientLog } from '@/lib/observability/client';
|
||||
import { preferences } from '@/lib/preferences';
|
||||
import { MEETINGS_PAGE_LIMIT, SKELETON_CARDS_COUNT } from '@/lib/constants/timing';
|
||||
@@ -49,6 +51,11 @@ export default function MeetingsPage() {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [loadingMore, setLoadingMore] = useState(false);
|
||||
|
||||
// Bulk selection state
|
||||
const [selectedMeetingIds, setSelectedMeetingIds] = useState<Set<string>>(new Set());
|
||||
const [showBulkDeleteDialog, setShowBulkDeleteDialog] = useState(false);
|
||||
const { mutate: deleteMeetings, isLoading: isDeleting } = useDeleteMeetings();
|
||||
|
||||
const shouldSkipFetch =
|
||||
(projectScope === 'selected' && selectedProjectIds.length === 0) ||
|
||||
(projectScope === 'active' && !resolvedProjectId && !projectsLoading);
|
||||
@@ -107,11 +114,57 @@ export default function MeetingsPage() {
|
||||
preferences.setMeetingsProjectFilter(projectScope, selectedProjectIds);
|
||||
}, [projectScope, selectedProjectIds]);
|
||||
|
||||
// Clear selections when filters change
|
||||
useEffect(() => {
|
||||
setSelectedMeetingIds(new Set());
|
||||
}, [searchQuery, stateFilter, projectScope, resolvedProjectId]);
|
||||
|
||||
const filteredMeetings = useMemo(
|
||||
() => meetings.filter((m) => m.title.toLowerCase().includes(searchQuery.toLowerCase())),
|
||||
[meetings, searchQuery]
|
||||
);
|
||||
|
||||
const deletableMeetings = useMemo(
|
||||
() => meetings.filter((m) => m.state !== 'recording'),
|
||||
[meetings]
|
||||
);
|
||||
|
||||
const deletableMeetingIds = useMemo(
|
||||
() => new Set(deletableMeetings.map((m) => m.id)),
|
||||
[deletableMeetings]
|
||||
);
|
||||
|
||||
const handleSelect = useCallback((meetingId: string, selected: boolean) => {
|
||||
setSelectedMeetingIds((prev) => {
|
||||
const next = new Set(prev);
|
||||
if (selected) next.add(meetingId);
|
||||
else next.delete(meetingId);
|
||||
return next;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handleSelectAll = useCallback(() => {
|
||||
setSelectedMeetingIds(new Set(deletableMeetingIds));
|
||||
}, [deletableMeetingIds]);
|
||||
|
||||
const handleDeselectAll = useCallback(() => {
|
||||
setSelectedMeetingIds(new Set());
|
||||
}, []);
|
||||
|
||||
const handleBulkDelete = useCallback(() => {
|
||||
setShowBulkDeleteDialog(true);
|
||||
}, []);
|
||||
|
||||
const confirmBulkDelete = useCallback(() => {
|
||||
deleteMeetings(Array.from(selectedMeetingIds), {
|
||||
onSuccess: () => {
|
||||
setSelectedMeetingIds(new Set());
|
||||
setShowBulkDeleteDialog(false);
|
||||
fetchMeetings(0, false);
|
||||
},
|
||||
});
|
||||
}, [deleteMeetings, selectedMeetingIds, fetchMeetings]);
|
||||
|
||||
const hasMore = meetings.length < totalCount;
|
||||
|
||||
const handleLoadMore = () => {
|
||||
@@ -209,6 +262,9 @@ export default function MeetingsPage() {
|
||||
index={i}
|
||||
onDelete={handleDelete}
|
||||
showDeleteButton
|
||||
isSelectable={meeting.state !== 'recording'}
|
||||
isSelected={selectedMeetingIds.has(meeting.id)}
|
||||
onSelect={handleSelect}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
@@ -234,6 +290,26 @@ export default function MeetingsPage() {
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<BulkActionToolbar
|
||||
selectedCount={selectedMeetingIds.size}
|
||||
totalCount={deletableMeetings.length}
|
||||
isDeleting={isDeleting}
|
||||
onSelectAll={handleSelectAll}
|
||||
onDeselectAll={handleDeselectAll}
|
||||
onDelete={handleBulkDelete}
|
||||
/>
|
||||
|
||||
<ConfirmationDialog
|
||||
open={showBulkDeleteDialog}
|
||||
onOpenChange={setShowBulkDeleteDialog}
|
||||
onConfirm={confirmBulkDelete}
|
||||
title="Delete Meetings"
|
||||
description={`Are you sure you want to delete ${selectedMeetingIds.size} meeting(s)? This action cannot be undone.`}
|
||||
confirmText="Delete"
|
||||
variant="destructive"
|
||||
isLoading={isDeleting}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user