admin
Some checks are pending
Deploy Admin Panel / Deploy Admin Panel (push) Waiting to run
Deploy Admin Panel / Admin Panel Verification (push) Blocked by required conditions
Deploy Mnemo Cards / Deploy Backend (push) Waiting to run
Deploy Mnemo Cards / Deploy Web App (push) Blocked by required conditions
Deploy Mnemo Cards / Final Verification (push) Blocked by required conditions

This commit is contained in:
Dmitry 2025-12-14 22:49:40 +03:00
parent b86e840cc6
commit 0cfd5f4524
3 changed files with 98 additions and 55 deletions

View file

@ -52,8 +52,20 @@ export function BulkCardEditor({ images, onComplete, onCancel }: BulkCardEditorP
packsApi packsApi
.getPacks({ page: 1, limit: 100, search: '' }) .getPacks({ page: 1, limit: 100, search: '' })
.then((response) => { .then((response) => {
if (response && response.items) { try {
setPacksData(response.items.map((pack) => ({ id: pack.id, title: pack.title }))) if (response && response.items && Array.isArray(response.items)) {
const packs = response.items.map((pack: any) => ({
id: String(pack.id || ''),
title: String(pack.title || pack.id || '')
}))
setPacksData(packs)
} else {
console.warn('Unexpected packs response format:', response)
setPacksData([])
}
} catch (error) {
console.error('Error processing packs response:', error)
setPacksData([])
} }
}) })
.catch((error) => { .catch((error) => {
@ -66,44 +78,48 @@ export function BulkCardEditor({ images, onComplete, onCancel }: BulkCardEditorP
// Initialize cards data // Initialize cards data
useEffect(() => { useEffect(() => {
const initialData = new Map<string, CardData>() if (images.length > 0) {
images.forEach((image) => { const initialData = new Map<string, CardData>()
initialData.set(image.id, { images.forEach((image) => {
imageId: image.id, initialData.set(image.id, {
packId: '', imageId: image.id,
original: '', packId: '',
translation: '', original: '',
mnemo: '', translation: '',
transcription: '', mnemo: '',
transcriptionMnemo: '', transcription: '',
back: '', transcriptionMnemo: '',
imageBack: undefined, back: '',
isSaved: false, imageBack: undefined,
isSaved: false,
})
}) })
}) setCardsData(initialData)
setCardsData(initialData) }
}, [images]) }, [images])
const currentImage = images[currentIndex] const currentImage = images[currentIndex]
const currentCard = cardsData.get(currentImage?.id || '') const currentCard = currentImage ? cardsData.get(currentImage.id) : undefined
const updateMutation = useMutation({ const updateMutation = useMutation({
mutationFn: (card: GameCardDto) => cardsApi.upsertCard(card), mutationFn: (card: GameCardDto) => cardsApi.upsertCard(card),
onSuccess: (response) => { onSuccess: (response) => {
queryClient.invalidateQueries({ queryKey: ['cards'] }) queryClient.invalidateQueries({ queryKey: ['cards'] })
const imageId = currentImage.id if (currentImage) {
setCardsData((prev) => { const imageId = currentImage.id
const newData = new Map(prev) setCardsData((prev) => {
const cardData = newData.get(imageId) const newData = new Map(prev)
if (cardData && response.card) { const cardData = newData.get(imageId)
newData.set(imageId, { if (cardData && response.card) {
...cardData, newData.set(imageId, {
isSaved: true, ...cardData,
cardId: response.card.id, isSaved: true,
}) cardId: response.card.id,
} })
return newData }
}) return newData
})
}
toast.success('Card saved successfully') toast.success('Card saved successfully')
}, },
onError: (error: unknown) => { onError: (error: unknown) => {
@ -113,7 +129,7 @@ export function BulkCardEditor({ images, onComplete, onCancel }: BulkCardEditorP
}) })
const handleFieldChange = (field: keyof CardData, value: string) => { const handleFieldChange = (field: keyof CardData, value: string) => {
if (!currentCard) return if (!currentCard || !currentImage) return
setCardsData((prev) => { setCardsData((prev) => {
const newData = new Map(prev) const newData = new Map(prev)
@ -170,8 +186,21 @@ export function BulkCardEditor({ images, onComplete, onCancel }: BulkCardEditorP
const savedCount = Array.from(cardsData.values()).filter((card) => card.isSaved).length const savedCount = Array.from(cardsData.values()).filter((card) => card.isSaved).length
const totalCount = images.length const totalCount = images.length
// Wait for images and cards data to be initialized
if (images.length === 0) {
return (
<div className="p-4">
<p className="text-muted-foreground">No images uploaded</p>
</div>
)
}
if (!currentImage || !currentCard) { if (!currentImage || !currentCard) {
return <div>Loading...</div> return (
<div className="p-4">
<p className="text-muted-foreground">Initializing card data...</p>
</div>
)
} }
return ( return (
@ -199,15 +228,25 @@ export function BulkCardEditor({ images, onComplete, onCancel }: BulkCardEditorP
<Card> <Card>
<CardHeader> <CardHeader>
<CardTitle>Image Preview</CardTitle> <CardTitle>Image Preview</CardTitle>
<CardDescription>{currentImage.file.name}</CardDescription> <CardDescription>{currentImage.file?.name || 'Unknown'}</CardDescription>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<div className="relative aspect-square border rounded-lg overflow-hidden bg-muted"> <div className="relative aspect-square border rounded-lg overflow-hidden bg-muted">
<img {currentImage.preview ? (
src={currentImage.preview} <img
alt={currentImage.file.name} src={currentImage.preview}
className="w-full h-full object-contain" alt={currentImage.file?.name || 'Card image'}
/> className="w-full h-full object-contain"
onError={(e) => {
console.error('Failed to load image:', currentImage.preview)
e.currentTarget.style.display = 'none'
}}
/>
) : (
<div className="w-full h-full flex items-center justify-center text-muted-foreground">
Image preview not available
</div>
)}
</div> </div>
</CardContent> </CardContent>
</Card> </Card>
@ -222,19 +261,21 @@ export function BulkCardEditor({ images, onComplete, onCancel }: BulkCardEditorP
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="packId">Pack ID (optional)</Label> <Label htmlFor="packId">Pack ID (optional)</Label>
<Select <Select
value={currentCard.packId} value={currentCard.packId || undefined}
onValueChange={(value: string) => handleFieldChange('packId', value)} onValueChange={(value: string) => handleFieldChange('packId', value || '')}
> >
<SelectTrigger> <SelectTrigger>
<SelectValue placeholder="Select a pack (optional)" /> <SelectValue placeholder="Select a pack (optional)" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectItem value="">None</SelectItem> <SelectItem value="">None</SelectItem>
{packsData.map((pack) => ( {packsData && packsData.length > 0 ? (
<SelectItem key={pack.id} value={pack.id}> packsData.map((pack) => (
{pack.title} <SelectItem key={pack.id} value={pack.id}>
</SelectItem> {pack.title || pack.id}
))} </SelectItem>
))
) : null}
</SelectContent> </SelectContent>
</Select> </Select>
</div> </div>

View file

@ -366,19 +366,21 @@ export default function CardsPage() {
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="packId">Pack ID (optional)</Label> <Label htmlFor="packId">Pack ID (optional)</Label>
<Select <Select
value={formData.packId} value={formData.packId || undefined}
onValueChange={(value: string) => setFormData(prev => ({ ...prev, packId: value }))} onValueChange={(value: string) => setFormData(prev => ({ ...prev, packId: value || '' }))}
> >
<SelectTrigger> <SelectTrigger>
<SelectValue placeholder="Select a pack (optional)" /> <SelectValue placeholder="Select a pack (optional)" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectItem value="">None</SelectItem> <SelectItem value="">None</SelectItem>
{packsData?.items.map((pack) => ( {packsData?.items && packsData.items.length > 0 ? (
<SelectItem key={pack.id} value={pack.id}> packsData.items.map((pack) => (
{pack.title} <SelectItem key={pack.id} value={pack.id}>
</SelectItem> {pack.title || pack.id}
))} </SelectItem>
))
) : null}
</SelectContent> </SelectContent>
</Select> </Select>
</div> </div>

View file

@ -387,10 +387,10 @@ export default function UsersPage() {
<div className="space-y-2"> <div className="space-y-2">
<Label>Packs Owned</Label> <Label>Packs Owned</Label>
<div className="border rounded-lg max-h-[200px] overflow-y-auto p-2"> <div className="border rounded-lg max-h-[200px] overflow-y-auto p-2">
{packsData?.items.length === 0 ? ( {!packsData?.items || packsData.items.length === 0 ? (
<div className="text-sm text-muted-foreground">No packs available</div> <div className="text-sm text-muted-foreground">No packs available</div>
) : ( ) : (
packsData?.items.map((pack) => ( packsData.items.map((pack) => (
<div key={pack.id} className="flex items-center space-x-2 py-1"> <div key={pack.id} className="flex items-center space-x-2 py-1">
<Checkbox <Checkbox
id={`pack-${pack.id}`} id={`pack-${pack.id}`}