diff --git a/mnemo_cards_admin/src/api/cardsWithVoice.ts b/mnemo_cards_admin/src/api/cardsWithVoice.ts index 328c0dd..b512692 100644 --- a/mnemo_cards_admin/src/api/cardsWithVoice.ts +++ b/mnemo_cards_admin/src/api/cardsWithVoice.ts @@ -11,17 +11,49 @@ export async function upsertCardWithOptionalVoice( card: GameCardDto, voice?: PendingVoiceUpload, ): Promise<{ success: boolean; card: GameCardDto }> { + console.log('🔍 upsertCardWithOptionalVoice: Starting', { + cardId: card.id, + hasVoice: !!voice, + voiceUrl: voice?.voiceUrl, + language: voice?.language, + }) + const response = await cardsApi.upsertCard(card) const responseCardId = response.card?.id != null ? String(response.card.id) : '' const requestCardId = card.id != null ? String(card.id) : '' const cardId = responseCardId || requestCardId + console.log('🔍 upsertCardWithOptionalVoice: After upsertCard', { + responseCardId, + requestCardId, + cardId, + hasVoice: !!voice, + voiceUrl: voice?.voiceUrl, + }) + if (!voice || !voice.voiceUrl || !cardId) { + console.log('⚠️ upsertCardWithOptionalVoice: Skipping voice link', { + hasVoice: !!voice, + voiceUrl: voice?.voiceUrl, + cardId, + }) return response } - await voicesApi.addCardVoice(cardId, voice.voiceUrl, voice.language) + console.log('✅ upsertCardWithOptionalVoice: Calling addCardVoice', { + cardId, + voiceUrl: voice.voiceUrl, + language: voice.language, + }) + + try { + await voicesApi.addCardVoice(cardId, voice.voiceUrl, voice.language) + console.log('✅ upsertCardWithOptionalVoice: Successfully added voice') + } catch (error) { + console.error('❌ upsertCardWithOptionalVoice: Error adding voice', error) + throw error + } return response } diff --git a/mnemo_cards_admin/src/components/CardVoicesManager.tsx b/mnemo_cards_admin/src/components/CardVoicesManager.tsx index 941a59f..4de18a5 100644 --- a/mnemo_cards_admin/src/components/CardVoicesManager.tsx +++ b/mnemo_cards_admin/src/components/CardVoicesManager.tsx @@ -30,9 +30,16 @@ export function CardVoicesManager({ cardId, disabled = false }: CardVoicesManage // Add voice mutation const addVoiceMutation = useMutation({ - mutationFn: ({ voiceUrl, language }: { voiceUrl: string; language: string }) => - voicesApi.addCardVoice(cardId, voiceUrl, language), + mutationFn: ({ voiceUrl, language }: { voiceUrl: string; language: string }) => { + console.log('🔍 CardVoicesManager: addVoiceMutation.mutationFn called', { + cardId, + voiceUrl, + language, + }) + return voicesApi.addCardVoice(cardId, voiceUrl, language) + }, onSuccess: () => { + console.log('✅ CardVoicesManager: addVoiceMutation.onSuccess') queryClient.invalidateQueries({ queryKey: ['cardVoices', cardId] }) toast.success('Voice added successfully') setShowAddForm(false) @@ -40,6 +47,7 @@ export function CardVoicesManager({ cardId, disabled = false }: CardVoicesManage setNewLanguage('en') }, onError: (error: unknown) => { + console.error('❌ CardVoicesManager: addVoiceMutation.onError', error) const axiosError = error as AxiosError<{ message?: string }> toast.error(axiosError.response?.data?.message || 'Failed to add voice') }, @@ -59,11 +67,24 @@ export function CardVoicesManager({ cardId, disabled = false }: CardVoicesManage }) const handleAddVoice = () => { + console.log('🔍 CardVoicesManager: handleAddVoice called', { + newAudio, + newLanguage, + cardId, + }) + if (!newAudio) { + console.warn('⚠️ CardVoicesManager: newAudio is empty') toast.error('Please upload an audio file') return } + console.log('✅ CardVoicesManager: Calling addVoiceMutation.mutate', { + cardId, + voiceUrl: newAudio, + language: newLanguage, + }) + addVoiceMutation.mutate({ voiceUrl: newAudio, language: newLanguage, @@ -103,7 +124,10 @@ export function CardVoicesManager({ cardId, disabled = false }: CardVoicesManage { + console.log('🔍 CardVoicesManager: AudioUpload onChange', { value, cardId }) + setNewAudio(value) + }} language={newLanguage} onLanguageChange={setNewLanguage} disabled={disabled || addVoiceMutation.isPending} diff --git a/mnemo_cards_backend/lib/api/v2/admin_cards_api_v2.dart b/mnemo_cards_backend/lib/api/v2/admin_cards_api_v2.dart index e2c3d93..eddcd39 100644 --- a/mnemo_cards_backend/lib/api/v2/admin_cards_api_v2.dart +++ b/mnemo_cards_backend/lib/api/v2/admin_cards_api_v2.dart @@ -868,6 +868,8 @@ class AdminCardsApiV2 { // Create voice record with object ID print('🔍 addCardVoice: Creating voice record with objectId $objectId for card $cardId'); + print('🔍 addCardVoice: VoiceModelsCompanion.insert params: cardId=$cardId, voiceUrl=$objectId, language=${requestDto.language ?? 'en'}'); + final voiceId = await _db.packDao.createVoice( VoiceModelsCompanion.insert( cardId: cardId, @@ -881,6 +883,10 @@ class AdminCardsApiV2 { print('🔍 addCardVoice: Linking voice $voiceId to card $cardId'); await _db.packDao.addVoiceToCard(cardId, voiceId); print('✅ addCardVoice: Successfully linked voice $voiceId to card $cardId'); + + // Verify the link was created + final verifyVoices = await _db.packDao.getCardVoices(cardId); + print('🔍 addCardVoice: Verification - found ${verifyVoices.length} voices for card $cardId'); final voice = await _db.packDao.getVoiceById(voiceId); diff --git a/mnemo_cards_web_v2/lib/presentation/pages/pack_details/pack_details_page.dart b/mnemo_cards_web_v2/lib/presentation/pages/pack_details/pack_details_page.dart index d492a1b..d5ca802 100644 --- a/mnemo_cards_web_v2/lib/presentation/pages/pack_details/pack_details_page.dart +++ b/mnemo_cards_web_v2/lib/presentation/pages/pack_details/pack_details_page.dart @@ -8,7 +8,6 @@ import 'package:mnemo_cards_common/mnemo_cards_common.dart'; import 'package:yx_scope_flutter/yx_scope_flutter.dart'; import '../../../di/app_scope/app_scope_container.dart'; -import '../../../domain/config/api_config_v2.dart'; import '../../../presentation/theme/app_colors.dart'; import '../../../presentation/widgets/pack_card_item.dart'; import '../../../presentation/widgets/pack_details_header.dart'; @@ -20,6 +19,7 @@ import '../../../presentation/widgets/card_viewer.dart'; import '../../../presentation/widgets/shuffle_animated_switcher.dart'; import '../../../presentation/widgets/shuffle_movement_wrapper.dart'; import '../../../presentation/widgets/utils/card_movement_utils.dart'; +import '../../../utils/card_image_utils.dart'; import '../../../utils/color_extension.dart'; import '../../../utils/responsive.dart'; @@ -823,8 +823,8 @@ class _PackDetailsPageState extends State { /// Изображение карточки (вынесено в отдельный метод для переиспользования) Widget _buildCardImage(GameCardDto card, double width, double height) { - // Use presigned URL from DTO - final imageUrl = card.imageUrl; + // Use helper to get image URL (handles imageUrl, image URL, and filename) + final imageUrl = CardImageUtils.getCardImageUrl(card, widget.packId); if (imageUrl == null || imageUrl.isEmpty) { return Center( diff --git a/mnemo_cards_web_v2/lib/presentation/widgets/card_flipper/card_flipper.dart b/mnemo_cards_web_v2/lib/presentation/widgets/card_flipper/card_flipper.dart index 0caaf9b..04c91d4 100644 --- a/mnemo_cards_web_v2/lib/presentation/widgets/card_flipper/card_flipper.dart +++ b/mnemo_cards_web_v2/lib/presentation/widgets/card_flipper/card_flipper.dart @@ -7,10 +7,10 @@ import 'package:mnemo_cards_web_v2/di/user_scope/user_scope.dart'; import 'package:yx_scope_flutter/yx_scope_flutter.dart'; import 'package:yx_state_flutter/yx_state_flutter.dart'; -import '../../../domain/config/api_config_v2.dart'; import '../../../domain/state/card_flipper_state_manager.dart'; import '../../../presentation/widgets/card_voice_controls.dart'; import '../../../presentation/widgets/mnemo_text.dart'; +import '../../../utils/card_image_utils.dart'; /// Card flipper widget for studying cards /// @@ -720,8 +720,8 @@ class _CardSide extends StatelessWidget { } Widget _buildImage() { - // Use presigned URL from DTO - final imageUrl = card.imageUrl; + // Use helper to get image URL (handles imageUrl, image URL, and filename) + final imageUrl = CardImageUtils.getCardImageUrl(card, packId); if (imageUrl == null || imageUrl.isEmpty) { return Container( diff --git a/mnemo_cards_web_v2/lib/presentation/widgets/card_viewer.dart b/mnemo_cards_web_v2/lib/presentation/widgets/card_viewer.dart index 5e7c1f7..bddd27a 100644 --- a/mnemo_cards_web_v2/lib/presentation/widgets/card_viewer.dart +++ b/mnemo_cards_web_v2/lib/presentation/widgets/card_viewer.dart @@ -4,8 +4,8 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:mnemo_cards_common/mnemo_cards_common.dart'; -import '../../domain/config/api_config_v2.dart'; import '../../presentation/theme/app_colors.dart'; +import '../../utils/card_image_utils.dart'; import 'card_favorite_button.dart'; import 'card_voice_controls.dart'; import 'mnemo_text.dart'; @@ -565,8 +565,8 @@ class _CardSide extends StatelessWidget { } Widget _buildImage() { - // Use presigned URL from DTO - final imageUrl = card.imageUrl; + // Use helper to get image URL (handles imageUrl, image URL, and filename) + final imageUrl = CardImageUtils.getCardImageUrl(card, packId); if (imageUrl == null || imageUrl.isEmpty) { return Container( @@ -600,8 +600,8 @@ class _CardSide extends StatelessWidget { } Widget _buildImageBack() { - // Use presigned URL from DTO - final imageBackUrl = card.imageBackUrl; + // Use helper to get back image URL (handles imageBackUrl, imageBack URL, and filename) + final imageBackUrl = CardImageUtils.getCardImageBackUrl(card, packId); if (imageBackUrl == null || imageBackUrl.isEmpty) { return Container( diff --git a/mnemo_cards_web_v2/lib/presentation/widgets/pack_card_item.dart b/mnemo_cards_web_v2/lib/presentation/widgets/pack_card_item.dart index 3a73024..51a594f 100644 --- a/mnemo_cards_web_v2/lib/presentation/widgets/pack_card_item.dart +++ b/mnemo_cards_web_v2/lib/presentation/widgets/pack_card_item.dart @@ -3,7 +3,7 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:mnemo_cards_common/mnemo_cards_common.dart'; -import '../../domain/config/api_config_v2.dart'; +import '../../utils/card_image_utils.dart'; import 'mnemo_text.dart'; /// Виджет для отображения отдельной карточки в сетке @@ -260,8 +260,8 @@ class PackCardItem extends StatelessWidget { /// Отображает изображение карточки /// Загружает изображение с бэкенда по URL Widget _buildImage(BuildContext context, double cardWidth, double cardHeight) { - // Use presigned URL from DTO - final imageUrl = card.imageUrl; + // Use helper to get image URL (handles imageUrl, image URL, and filename) + final imageUrl = CardImageUtils.getCardImageUrl(card, packId); if (imageUrl == null || imageUrl.isEmpty) { return const SizedBox.shrink();