Skip to content

Commit 70f046b

Browse files
committed
Extract session deletion dialogs into separate components
Move DeleteSessionDialog and DeleteOrphanedSpeakersDialog into their own files to keep EventSession.tsx focused on the page layout. https://claude.ai/code/session_01SdkRECfqLSaJCouvYNPuKB
1 parent b27ef92 commit 70f046b

File tree

3 files changed

+126
-76
lines changed

3 files changed

+126
-76
lines changed
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { useState } from 'react'
2+
import { Checkbox, DialogContentText, FormControlLabel, List, ListItem } from '@mui/material'
3+
import { ConfirmDialog } from '../../../components/ConfirmDialog'
4+
import { Speaker } from '../../../types'
5+
6+
export type DeleteOrphanedSpeakersDialogProps = {
7+
open: boolean
8+
loading: boolean
9+
speakers: Speaker[]
10+
onClose: () => void
11+
onAccept: (speakerIds: string[]) => void
12+
}
13+
14+
export const DeleteOrphanedSpeakersDialog = ({
15+
open,
16+
loading,
17+
speakers,
18+
onClose,
19+
onAccept,
20+
}: DeleteOrphanedSpeakersDialogProps) => {
21+
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set(speakers.map((s) => s.id)))
22+
23+
// Keep selectedIds in sync when speakers list changes
24+
if (speakers.length > 0 && selectedIds.size === 0) {
25+
setSelectedIds(new Set(speakers.map((s) => s.id)))
26+
}
27+
28+
return (
29+
<ConfirmDialog
30+
open={open}
31+
title="Delete orphaned speaker(s)?"
32+
acceptButton="Delete selected speaker(s)"
33+
disabled={loading || selectedIds.size === 0}
34+
loading={loading}
35+
cancelButton="Keep all"
36+
handleClose={onClose}
37+
handleAccept={() => onAccept(Array.from(selectedIds))}>
38+
<DialogContentText>
39+
The following speaker(s) are not linked to any other session. Do you want to delete them?
40+
</DialogContentText>
41+
<List dense>
42+
{speakers.map((speaker) => (
43+
<ListItem key={speaker.id} disablePadding>
44+
<FormControlLabel
45+
control={
46+
<Checkbox
47+
checked={selectedIds.has(speaker.id)}
48+
onChange={(e) => {
49+
setSelectedIds((prev) => {
50+
const next = new Set(prev)
51+
if (e.target.checked) {
52+
next.add(speaker.id)
53+
} else {
54+
next.delete(speaker.id)
55+
}
56+
return next
57+
})
58+
}}
59+
/>
60+
}
61+
label={speaker.name}
62+
/>
63+
</ListItem>
64+
))}
65+
</List>
66+
</ConfirmDialog>
67+
)
68+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { DialogContentText } from '@mui/material'
2+
import { ConfirmDialog } from '../../../components/ConfirmDialog'
3+
import { Session } from '../../../types'
4+
5+
export type DeleteSessionDialogProps = {
6+
open: boolean
7+
loading: boolean
8+
session: Session
9+
onClose: () => void
10+
onAccept: () => void
11+
}
12+
13+
export const DeleteSessionDialog = ({ open, loading, session, onClose, onAccept }: DeleteSessionDialogProps) => {
14+
return (
15+
<ConfirmDialog
16+
open={open}
17+
title="Delete this session?"
18+
acceptButton="Delete session"
19+
disabled={loading}
20+
loading={loading}
21+
cancelButton="cancel"
22+
handleClose={onClose}
23+
handleAccept={onAccept}>
24+
<DialogContentText id="alert-dialog-description">
25+
{' '}
26+
Delete the session {session.title} from this event (not the session's speaker(s))
27+
</DialogContentText>
28+
</ConfirmDialog>
29+
)
30+
}
Lines changed: 28 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,20 @@
11
import { useCallback, useState } from 'react'
22
import { Event, Speaker } from '../../../types'
3-
import {
4-
Box,
5-
Button,
6-
Card,
7-
Checkbox,
8-
Container,
9-
DialogContentText,
10-
FormControlLabel,
11-
List,
12-
ListItem,
13-
Typography,
14-
} from '@mui/material'
3+
import { Box, Button, Card, Container, Typography } from '@mui/material'
154
import { useSession } from '../../../services/hooks/useSession'
165
import { useLocation, useRoute } from 'wouter'
176
import { FirestoreQueryLoaderAndErrorDisplay } from '../../../components/FirestoreQueryLoaderAndErrorDisplay'
187
import { ArrowBack } from '@mui/icons-material'
19-
import { getQueryParams } from '../../../utils/getQuerySearchParameters'
208
import { EventSessionForm } from './EventSessionForm'
219
import { deleteDoc, doc, getDocs, query, where } from 'firebase/firestore'
2210
import { collections } from '../../../services/firebase'
23-
import { ConfirmDialog } from '../../../components/ConfirmDialog'
2411
import {
2512
useFirestoreDocumentDeletion,
2613
useFirestoreDocumentMutation,
2714
} from '../../../services/hooks/firestoreMutationHooks'
2815
import { useSpeakersMap } from '../../../services/hooks/useSpeakersMap'
16+
import { DeleteSessionDialog } from './DeleteSessionDialog'
17+
import { DeleteOrphanedSpeakersDialog } from './DeleteOrphanedSpeakersDialog'
2918

3019
export type EventSessionProps = {
3120
event: Event
@@ -41,7 +30,6 @@ export const EventSession = ({ event }: EventSessionProps) => {
4130
const [orphanedSpeakers, setOrphanedSpeakers] = useState<Speaker[]>([])
4231
const [orphanDeleteOpen, setOrphanDeleteOpen] = useState(false)
4332
const [orphanDeleting, setOrphanDeleting] = useState(false)
44-
const [selectedOrphanIds, setSelectedOrphanIds] = useState<Set<string>>(new Set())
4533
const documentDeletion = useFirestoreDocumentDeletion(doc(collections.sessions(event.id), sessionId))
4634
const mutation = useFirestoreDocumentMutation(doc(collections.sessions(event.id), sessionId))
4735

@@ -66,18 +54,21 @@ export const EventSession = ({ event }: EventSessionProps) => {
6654
[event.id, speakersMap.data]
6755
)
6856

69-
const deleteSelectedOrphans = useCallback(async () => {
70-
setOrphanDeleting(true)
71-
try {
72-
for (const speakerId of selectedOrphanIds) {
73-
await deleteDoc(doc(collections.speakers(event.id), speakerId))
57+
const deleteOrphans = useCallback(
58+
async (speakerIds: string[]) => {
59+
setOrphanDeleting(true)
60+
try {
61+
for (const speakerId of speakerIds) {
62+
await deleteDoc(doc(collections.speakers(event.id), speakerId))
63+
}
64+
} finally {
65+
setOrphanDeleting(false)
66+
setOrphanDeleteOpen(false)
67+
setLocation('/sessions')
7468
}
75-
} finally {
76-
setOrphanDeleting(false)
77-
setOrphanDeleteOpen(false)
78-
setLocation('/sessions')
79-
}
80-
}, [event.id, selectedOrphanIds])
69+
},
70+
[event.id]
71+
)
8172

8273
if (sessionResult.isLoading || !sessionResult.data) {
8374
return <FirestoreQueryLoaderAndErrorDisplay hookResult={sessionResult} />
@@ -115,15 +106,12 @@ export const EventSession = ({ event }: EventSessionProps) => {
115106
)}
116107
</Box>
117108

118-
<ConfirmDialog
109+
<DeleteSessionDialog
119110
open={deleteOpen}
120-
title="Delete this session?"
121-
acceptButton="Delete session"
122-
disabled={documentDeletion.isLoading}
123111
loading={documentDeletion.isLoading}
124-
cancelButton="cancel"
125-
handleClose={() => setDeleteOpen(false)}
126-
handleAccept={async () => {
112+
session={session}
113+
onClose={() => setDeleteOpen(false)}
114+
onAccept={async () => {
127115
const speakerIds = session.speakers || []
128116
await documentDeletion.mutate()
129117
setDeleteOpen(false)
@@ -132,60 +120,24 @@ export const EventSession = ({ event }: EventSessionProps) => {
132120
const orphaned = await findOrphanedSpeakers(speakerIds)
133121
if (orphaned.length > 0) {
134122
setOrphanedSpeakers(orphaned)
135-
setSelectedOrphanIds(new Set(orphaned.map((s) => s.id)))
136123
setOrphanDeleteOpen(true)
137124
return
138125
}
139126
}
140127
setLocation('/sessions')
141-
}}>
142-
<DialogContentText id="alert-dialog-description">
143-
{' '}
144-
Delete the session {session.title} from this event (not the session's speaker(s))
145-
</DialogContentText>
146-
</ConfirmDialog>
128+
}}
129+
/>
147130

148-
<ConfirmDialog
131+
<DeleteOrphanedSpeakersDialog
149132
open={orphanDeleteOpen}
150-
title="Delete orphaned speaker(s)?"
151-
acceptButton="Delete selected speaker(s)"
152-
disabled={orphanDeleting || selectedOrphanIds.size === 0}
153133
loading={orphanDeleting}
154-
cancelButton="Keep all"
155-
handleClose={() => {
134+
speakers={orphanedSpeakers}
135+
onClose={() => {
156136
setOrphanDeleteOpen(false)
157137
setLocation('/sessions')
158138
}}
159-
handleAccept={deleteSelectedOrphans}>
160-
<DialogContentText>
161-
The following speaker(s) are not linked to any other session. Do you want to delete them?
162-
</DialogContentText>
163-
<List dense>
164-
{orphanedSpeakers.map((speaker) => (
165-
<ListItem key={speaker.id} disablePadding>
166-
<FormControlLabel
167-
control={
168-
<Checkbox
169-
checked={selectedOrphanIds.has(speaker.id)}
170-
onChange={(e) => {
171-
setSelectedOrphanIds((prev) => {
172-
const next = new Set(prev)
173-
if (e.target.checked) {
174-
next.add(speaker.id)
175-
} else {
176-
next.delete(speaker.id)
177-
}
178-
return next
179-
})
180-
}}
181-
/>
182-
}
183-
label={speaker.name}
184-
/>
185-
</ListItem>
186-
))}
187-
</List>
188-
</ConfirmDialog>
139+
onAccept={deleteOrphans}
140+
/>
189141
</Container>
190142
)
191143
}

0 commit comments

Comments
 (0)