Skip to content

Commit 3d2a560

Browse files
author
pho
committed
feat: fix EditModal MediaType switching and Extras conversion flow
EditModal MediaType Dropdown Fixes: - Remove selectedMediaType from useEffect dependency - Files now fetched once on modal open, not on MediaType change - Files remain visible when switching between Movies/TV Shows MediaType Switch Confirmation: - Add AlertDialog confirmation before switching MediaType - Clear all metadata (tmdbId, season, episode) on confirmation - Show toast notification after metadata cleared - Warning message explains consequences Extras Conversion via EditModal: - Convert to Movie/TV Show now opens EditModal directly - User can assign TMDb metadata immediately - No more AlertDialog for Movie/TV Show conversions - Canceling EditModal leaves files as Extras Save Logic Updates: - Include mediaType in updateScannedFile API calls - Refresh table data after conversion - Show conversion success toast - Prevent files from getting stuck in Processing status Related to PRD 0002 - Tasks 1.0-4.0
1 parent da134d0 commit 3d2a560

File tree

3 files changed

+174
-22
lines changed

3 files changed

+174
-22
lines changed

frontend/src/components/scanned-files-table/edit-modal.tsx

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,16 @@ import {
77
DialogHeader,
88
DialogTitle,
99
} from "@/components/ui/dialog"
10+
import {
11+
AlertDialog,
12+
AlertDialogAction,
13+
AlertDialogCancel,
14+
AlertDialogContent,
15+
AlertDialogDescription,
16+
AlertDialogFooter,
17+
AlertDialogHeader,
18+
AlertDialogTitle,
19+
} from "@/components/ui/alert-dialog"
1020
import { Button } from "@/components/ui/button"
1121
import {
1222
Select,
@@ -21,6 +31,7 @@ import { MovieEditTable } from "@/components/scanned-files-table/movie-edit-tabl
2131
import { TvShowEditTable } from "@/components/scanned-files-table/tv-show-edit-table"
2232
import { MediaType, ScannedFile, Row } from "@/lib/api/types"
2333
import { mediaApi } from "@/lib/api/endpoints"
34+
import { useToast } from "@/hooks/use-toast"
2435

2536
interface EditModalProps {
2637
readonly isOpen: boolean
@@ -47,6 +58,9 @@ export function EditModal({ isOpen, onClose, selectedRows, onSave, initialMediaT
4758
const [editableRows, setEditableRows] = useState<EditableRow[]>([])
4859
const [loading, setLoading] = useState(false)
4960
const [selectedMediaType, setSelectedMediaType] = useState<MediaType.Movies | MediaType.TvShows>(initialMediaType)
61+
const [showMediaTypeWarning, setShowMediaTypeWarning] = useState(false)
62+
const [pendingMediaType, setPendingMediaType] = useState<MediaType.Movies | MediaType.TvShows | null>(null)
63+
const { toast } = useToast()
5064

5165
useEffect(() => {
5266
setSelectedMediaType(initialMediaType)
@@ -61,7 +75,6 @@ export function EditModal({ isOpen, onClose, selectedRows, onSave, initialMediaT
6175
const selectedIds = selectedRows.map((row) => row.key)
6276
const result = await mediaApi.getScannedFiles({
6377
ids: selectedIds,
64-
mediaType: selectedMediaType,
6578
pageSize: selectedIds.length,
6679
sortBy: "sourceFile",
6780
sortOrder: "asc",
@@ -79,10 +92,38 @@ export function EditModal({ isOpen, onClose, selectedRows, onSave, initialMediaT
7992
}
8093

8194
fetchSelectedFiles()
82-
}, [isOpen, selectedRows, selectedMediaType])
95+
}, [isOpen, selectedRows])
8396

8497
const handleMediaTypeChange = (type: string) => {
85-
setSelectedMediaType(type as MediaType.Movies | MediaType.TvShows)
98+
const newType = type as MediaType.Movies | MediaType.TvShows
99+
if (newType !== selectedMediaType) {
100+
setPendingMediaType(newType)
101+
setShowMediaTypeWarning(true)
102+
}
103+
}
104+
105+
const handleConfirmMediaTypeChange = () => {
106+
if (!pendingMediaType) return
107+
108+
setEditableRows((prev) =>
109+
prev.map((row) => ({
110+
...row,
111+
tmdbId: 0,
112+
seasonNumber: undefined,
113+
episodeNumber: undefined,
114+
episodeNumber2: undefined,
115+
mediaType: pendingMediaType,
116+
}))
117+
)
118+
119+
setSelectedMediaType(pendingMediaType)
120+
setShowMediaTypeWarning(false)
121+
setPendingMediaType(null)
122+
123+
toast({
124+
title: "Media type changed",
125+
description: "All metadata has been cleared.",
126+
})
86127
}
87128

88129
const handleMediaSelect = (tmdbId: number) => {
@@ -177,6 +218,28 @@ export function EditModal({ isOpen, onClose, selectedRows, onSave, initialMediaT
177218
</Button>
178219
</DialogFooter>
179220
</DialogContent>
221+
<AlertDialog open={showMediaTypeWarning} onOpenChange={setShowMediaTypeWarning}>
222+
<AlertDialogContent>
223+
<AlertDialogHeader>
224+
<AlertDialogTitle>Switch Media Type?</AlertDialogTitle>
225+
<AlertDialogDescription>
226+
Switching media type will clear all metadata (TMDb ID, season, episode numbers).
227+
This action cannot be undone. Continue?
228+
</AlertDialogDescription>
229+
</AlertDialogHeader>
230+
<AlertDialogFooter>
231+
<AlertDialogCancel onClick={() => {
232+
setShowMediaTypeWarning(false)
233+
setPendingMediaType(null)
234+
}}>
235+
Cancel
236+
</AlertDialogCancel>
237+
<AlertDialogAction onClick={handleConfirmMediaTypeChange}>
238+
Continue
239+
</AlertDialogAction>
240+
</AlertDialogFooter>
241+
</AlertDialogContent>
242+
</AlertDialog>
180243
</Dialog>
181244
)
182245
}

frontend/src/components/scanned-files-table/index.tsx

Lines changed: 42 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ export function ScannedFilesTable({
7474
const [mediaTypeFilter, setMediaTypeFilter] = useState<MediaType>(initialMediaType)
7575
const [newEntries, setNewEntries] = useState<Set<number>>(new Set())
7676
const [isEditModalOpen, setIsEditModalOpen] = useState(false)
77+
const [conversionTargetType, setConversionTargetType] = useState<MediaType.Movies | MediaType.TvShows | null>(null)
7778
const { visibleColumns, setVisibleColumns} = useColumnVisibility()
7879
const [columnsPopoverOpen, setColumnsPopoverOpen] = useState(false)
7980
const [density, setDensity] = useState<"comfortable" | "compact">("comfortable")
@@ -170,7 +171,9 @@ export function ScannedFilesTable({
170171
}, [])
171172

172173
const handleSaveEdits = useCallback(async (updatedRows: Row[]) => {
174+
const isConversion = conversionTargetType !== null
173175
setIsEditModalOpen(false)
176+
setConversionTargetType(null)
174177

175178
try {
176179
// Update each file
@@ -181,27 +184,55 @@ export function ScannedFilesTable({
181184
seasonNumber: row.seasonNumber,
182185
episodeNumber: row.episodeNumber,
183186
episodeNumber2: row.episodeNumber2,
187+
mediaType: row.mediaType as MediaType,
184188
})
185189
)
186190
)
187191

188192
// Recreate all symlinks
189193
await mediaApi.recreateAllSymlinks()
194+
195+
if (isConversion) {
196+
toast({
197+
title: "Success",
198+
description: `Converted ${updatedRows.length} file(s) to ${conversionTargetType}`,
199+
})
200+
}
201+
202+
const result = await mediaApi.getScannedFiles({
203+
page,
204+
pageSize,
205+
sortBy,
206+
sortOrder,
207+
searchTerm: filterValue,
208+
status: statusFilter,
209+
mediaType: mediaTypeFilter,
210+
})
211+
212+
setData(result)
213+
setSelectedKeys(new Set())
190214
} catch (error) {
191215
console.error("Failed to save changes:", error)
216+
toast({
217+
title: "Error",
218+
description: "Failed to save changes. Please try again.",
219+
variant: "destructive",
220+
})
192221
}
193-
}, [])
222+
}, [conversionTargetType, page, pageSize, sortBy, sortOrder, filterValue, statusFilter, mediaTypeFilter, toast, setSelectedKeys])
194223

195224
const handleConvertToExtras = useCallback(() => {
196225
setConversionDialog({ isOpen: true, targetType: MediaType.Extras })
197226
}, [])
198227

199228
const handleConvertToMovie = useCallback(() => {
200-
setConversionDialog({ isOpen: true, targetType: MediaType.Movies })
229+
setConversionTargetType(MediaType.Movies)
230+
setIsEditModalOpen(true)
201231
}, [])
202232

203233
const handleConvertToTvShow = useCallback(() => {
204-
setConversionDialog({ isOpen: true, targetType: MediaType.TvShows })
234+
setConversionTargetType(MediaType.TvShows)
235+
setIsEditModalOpen(true)
205236
}, [])
206237

207238
const handleConfirmConversion = useCallback(async () => {
@@ -400,29 +431,21 @@ export function ScannedFilesTable({
400431
/>
401432
<EditModal
402433
isOpen={isEditModalOpen}
403-
onClose={() => setIsEditModalOpen(false)}
434+
onClose={() => {
435+
setIsEditModalOpen(false)
436+
setConversionTargetType(null)
437+
}}
404438
selectedRows={selectedRows}
405439
onSave={handleSaveEdits}
406-
initialMediaType={mediaTypeFilter as MediaType.Movies | MediaType.TvShows}
440+
initialMediaType={(conversionTargetType || mediaTypeFilter) as MediaType.Movies | MediaType.TvShows}
407441
/>
408442
<AlertDialog open={conversionDialog.isOpen} onOpenChange={(open) => !open && setConversionDialog({ isOpen: false, targetType: null })}>
409443
<AlertDialogContent>
410444
<AlertDialogHeader>
411-
<AlertDialogTitle>
412-
Convert to {conversionDialog.targetType === MediaType.Extras ? "Extras" : conversionDialog.targetType === MediaType.Movies ? "Movies" : "TV Shows"}?
413-
</AlertDialogTitle>
445+
<AlertDialogTitle>Mark as Extra?</AlertDialogTitle>
414446
<AlertDialogDescription>
415-
{conversionDialog.targetType === MediaType.Extras ? (
416-
<>
417-
This will remove TMDb metadata and delete any existing symlinks for the selected file(s).
418-
The files will be marked as Extras with Success status.
419-
</>
420-
) : (
421-
<>
422-
This will convert the selected Extra file(s) to {conversionDialog.targetType === MediaType.Movies ? "Movies" : "TV Shows"}.
423-
The files will be set to Processing status for re-detection and metadata lookup.
424-
</>
425-
)}
447+
This will remove TMDb metadata and delete any existing symlinks for the selected file(s).
448+
The files will be marked as Extras with Success status.
426449
</AlertDialogDescription>
427450
</AlertDialogHeader>
428451
<AlertDialogFooter>
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# Task List: Fix EditModal MediaType Switching and Extras Conversion Flow
2+
3+
## Relevant Files
4+
5+
### Frontend Files
6+
- `frontend/src/components/scanned-files-table/edit-modal.tsx` - Main EditModal component that needs MediaType switching fixes
7+
- `frontend/src/components/scanned-files-table/index.tsx` - Parent component that manages conversion handlers and EditModal opening
8+
- `frontend/src/hooks/use-toast.ts` - Toast notification hook for user feedback
9+
- `frontend/src/components/ui/alert-dialog.tsx` - AlertDialog component for MediaType switch confirmation
10+
11+
### Notes
12+
- No backend changes required - API already supports mediaType updates
13+
- AlertDialog component already exists from previous implementation
14+
- Toast hook already exists for notifications
15+
- Focus is on fixing EditModal behavior and conversion flow
16+
17+
## Tasks
18+
19+
- [x] 1.0 Fix EditModal MediaType Dropdown to Switch UI Modes Instead of Filtering
20+
- [x] 1.1 Remove `selectedMediaType` from useEffect dependency array (line 82 in edit-modal.tsx)
21+
- [x] 1.2 Verify files are fetched only once on modal open, not when MediaType dropdown changes
22+
- [x] 1.3 Test that files remain visible in EditModal when switching between Movies/TV Shows
23+
24+
- [x] 2.0 Add Confirmation Dialog for MediaType Switch with Metadata Clearing
25+
- [x] 2.1 Add state `showMediaTypeWarning` and `pendingMediaType` to edit-modal.tsx
26+
- [x] 2.2 Import useToast hook and AlertDialog components
27+
- [x] 2.3 Modify `handleMediaTypeChange` to store pending type and show confirmation dialog
28+
- [x] 2.4 Create `handleConfirmMediaTypeChange` function to clear metadata and update type
29+
- [x] 2.5 Clear metadata: set tmdbId=0, seasonNumber/episodeNumber/episodeNumber2=undefined
30+
- [x] 2.6 Show toast notification: "Media type changed. All metadata has been cleared."
31+
- [x] 2.7 Add AlertDialog with title "Switch Media Type?" and warning message
32+
- [x] 2.8 Test that metadata clears correctly and toast appears
33+
34+
- [x] 3.0 Update Extras Conversion Handlers to Open EditModal
35+
- [x] 3.1 Modify `handleConvertToMovie` to set `initialMediaType` to Movies and open EditModal
36+
- [x] 3.2 Modify `handleConvertToTvShow` to set `initialMediaType` to TvShows and open EditModal
37+
- [x] 3.3 Remove AlertDialog logic from Movie/TV Show conversion handlers
38+
- [x] 3.4 Keep `handleConvertToExtras` with existing AlertDialog behavior (no changes)
39+
- [x] 3.5 Update `conversionDialog` state to only apply to Extras conversions
40+
- [x] 3.6 Test that clicking "Convert to Movie/TV Show" opens EditModal directly
41+
42+
- [x] 4.0 Update Save Logic to Include MediaType in API Calls
43+
- [x] 4.1 Modify `handleSaveEdits` to include `mediaType: row.mediaType` in updateScannedFile calls
44+
- [x] 4.2 Ensure EditModal passes correct mediaType from editableRows
45+
- [x] 4.3 Verify symlink recreation is triggered after save
46+
- [x] 4.4 Update success toast for conversions: "Converted {count} file(s) to {MediaType}"
47+
- [x] 4.5 Test that converted Extras progress from Processing to Success status
48+
49+
- [ ] 5.0 Testing and Validation
50+
- [ ] 5.1 Test: Convert single Extra to Movie via EditModal, assign TMDb ID, verify Success status
51+
- [ ] 5.2 Test: Convert multiple Extras to TV Show, assign series and episodes, verify all succeed
52+
- [ ] 5.3 Test: Switch MediaType in EditModal from Movie to TV Show, confirm warning appears
53+
- [ ] 5.4 Test: Verify metadata is cleared after MediaType switch and toast notification shows
54+
- [ ] 5.5 Test: Cancel EditModal during Extras conversion, verify files remain as Extras
55+
- [ ] 5.6 Test: "Mark as Extra" button still shows AlertDialog (unchanged behavior)
56+
- [ ] 5.7 Test: Multi-file editing with MediaType changes works correctly
57+
- [ ] 5.8 Run frontend linting: `bun run lint` from frontend directory
58+
59+
---
60+
61+
## Implementation Progress
62+
63+
**Current Task:** Ready to begin
64+
**Next Step:** Start with Task 1.0 - Fix EditModal MediaType Dropdown
65+
66+
Ready to start implementing? I'll begin with Task 1.1 when you give the go-ahead.

0 commit comments

Comments
 (0)