import { useState, useEffect } from 'react' import { useMutation, useQueryClient } from '@tanstack/react-query' import { toast } from 'sonner' import { cardsApi } from '@/api/cards' import type { GameCardDto } from '@/types/models' import type { AxiosError } from 'axios' import { Button } from './ui/button' import { Input } from './ui/input' import { Label } from './ui/label' import { Textarea } from './ui/textarea' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from './ui/card' import { Badge } from './ui/badge' import { packsApi } from '@/api/packs' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './ui/select' import { ChevronLeft, ChevronRight, Save, X } from 'lucide-react' interface UploadedImage { id: string file: File preview: string base64: string } interface CardData { imageId: string packId: string original: string translation: string mnemo: string transcription: string transcriptionMnemo: string back: string imageBack?: string isSaved: boolean cardId?: number } interface BulkCardEditorProps { images: UploadedImage[] onComplete: () => void onCancel: () => void } export function BulkCardEditor({ images, onComplete, onCancel }: BulkCardEditorProps) { const queryClient = useQueryClient() const [currentIndex, setCurrentIndex] = useState(0) const [cardsData, setCardsData] = useState>(new Map()) const [packsData, setPacksData] = useState<{ id: string; title: string }[]>([]) // Load packs useEffect(() => { packsApi .getPacks({ page: 1, limit: 1000, search: '' }) .then((response) => { setPacksData(response.items.map((pack) => ({ id: pack.id, title: pack.title }))) }) .catch(() => { toast.error('Failed to load packs') }) }, []) // Initialize cards data useEffect(() => { const initialData = new Map() images.forEach((image) => { initialData.set(image.id, { imageId: image.id, packId: '', original: '', translation: '', mnemo: '', transcription: '', transcriptionMnemo: '', back: '', imageBack: undefined, isSaved: false, }) }) setCardsData(initialData) }, [images]) const currentImage = images[currentIndex] const currentCard = cardsData.get(currentImage?.id || '') const updateMutation = useMutation({ mutationFn: (card: GameCardDto) => cardsApi.upsertCard(card), onSuccess: (response) => { queryClient.invalidateQueries({ queryKey: ['cards'] }) const imageId = currentImage.id setCardsData((prev) => { const newData = new Map(prev) const cardData = newData.get(imageId) if (cardData && response.card) { newData.set(imageId, { ...cardData, isSaved: true, cardId: response.card.id, }) } return newData }) toast.success('Card saved successfully') }, onError: (error: unknown) => { const axiosError = error as AxiosError<{ message?: string }> toast.error(axiosError.response?.data?.message || 'Failed to save card') }, }) const handleFieldChange = (field: keyof CardData, value: string) => { if (!currentCard) return setCardsData((prev) => { const newData = new Map(prev) const cardData = newData.get(currentImage.id) if (cardData) { newData.set(currentImage.id, { ...cardData, [field]: value, }) } return newData }) } const handleSave = () => { if (!currentCard) return if (!currentCard.original.trim() || !currentCard.translation.trim() || !currentCard.mnemo.trim()) { toast.error('Original, translation and mnemo are required') return } const image = images.find((img) => img.id === currentCard.imageId) if (!image) return const cardData: GameCardDto = { id: currentCard.cardId || -1, packId: currentCard.packId || undefined, original: currentCard.original.trim(), translation: currentCard.translation.trim(), mnemo: currentCard.mnemo.trim(), transcription: currentCard.transcription.trim() || undefined, transcriptionMnemo: currentCard.transcriptionMnemo.trim() || undefined, back: currentCard.back.trim() || undefined, image: image.base64, imageBack: currentCard.imageBack || undefined, } updateMutation.mutate(cardData) } const handleNext = () => { if (currentIndex < images.length - 1) { setCurrentIndex(currentIndex + 1) } } const handlePrevious = () => { if (currentIndex > 0) { setCurrentIndex(currentIndex - 1) } } const savedCount = Array.from(cardsData.values()).filter((card) => card.isSaved).length const totalCount = images.length if (!currentImage || !currentCard) { return
Loading...
} return (

Fill Card Details

Card {currentIndex + 1} of {totalCount} • {savedCount} saved

{currentCard.isSaved ? 'Saved' : 'Not Saved'}
{/* Image Preview */} Image Preview {currentImage.file.name}
{currentImage.file.name}
{/* Form */} Card Information Fill in the details for this card
handleFieldChange('original', e.target.value)} placeholder="e.g. cerdo" />
handleFieldChange('translation', e.target.value)} placeholder="e.g. pig" />
handleFieldChange('mnemo', e.target.value)} placeholder="e.g. [pig] with heart" />
handleFieldChange('transcription', e.target.value)} placeholder="e.g. sɛrdo" />
handleFieldChange('transcriptionMnemo', e.target.value)} placeholder="e.g. pig with heart" />