dramaling-vocab-learning/frontend/lib/services/flashcards.ts

177 lines
5.1 KiB
TypeScript

// Flashcards API service
export interface ExampleImage {
id: string;
imageUrl: string;
isPrimary: boolean;
qualityScore?: number;
fileSize?: number;
createdAt: string;
}
export interface Flashcard {
id: string;
word: string;
translation: string;
definition: string;
partOfSpeech: string;
pronunciation: string;
example: string;
exampleTranslation?: string;
masteryLevel: number;
timesReviewed: number;
isFavorite: boolean;
nextReviewDate: string;
difficultyLevel: string;
createdAt: string;
updatedAt?: string;
// 新增圖片相關欄位
exampleImages: ExampleImage[];
hasExampleImage: boolean;
primaryImageUrl?: string;
}
export interface CreateFlashcardRequest {
word: string;
translation: string;
definition: string;
pronunciation: string;
partOfSpeech: string;
example: string;
exampleTranslation?: string;
difficultyLevel?: string; // A1, A2, B1, B2, C1, C2
}
export interface ApiResponse<T> {
success: boolean;
data?: T;
error?: string;
message?: string;
}
class FlashcardsService {
private readonly baseURL = `${process.env.NEXT_PUBLIC_API_URL || 'http://localhost:5008'}/api`;
private async makeRequest<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
const response = await fetch(`${this.baseURL}${endpoint}`, {
headers: {
'Content-Type': 'application/json',
...options.headers,
},
...options,
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({ error: 'Network error' }));
throw new Error(errorData.error || errorData.details || `HTTP ${response.status}`);
}
return response.json();
}
// 詞卡查詢方法 (支援進階篩選、排序和分頁)
async getFlashcards(
search?: string,
favoritesOnly: boolean = false,
cefrLevel?: string,
partOfSpeech?: string,
masteryLevel?: string,
sortBy?: string,
sortOrder?: 'asc' | 'desc',
page?: number,
limit?: number
): Promise<ApiResponse<{ flashcards: Flashcard[], count: number }>> {
try {
const params = new URLSearchParams();
if (search) params.append('search', search);
if (favoritesOnly) params.append('favoritesOnly', 'true');
if (cefrLevel) params.append('cefrLevel', cefrLevel);
if (partOfSpeech) params.append('partOfSpeech', partOfSpeech);
if (masteryLevel) params.append('masteryLevel', masteryLevel);
// 排序和分頁參數
if (sortBy) params.append('sortBy', sortBy);
if (sortOrder) params.append('sortOrder', sortOrder);
if (page) params.append('page', page.toString());
if (limit) params.append('limit', limit.toString());
const queryString = params.toString();
const endpoint = `/flashcards${queryString ? `?${queryString}` : ''}`;
return await this.makeRequest<ApiResponse<{ flashcards: Flashcard[], count: number }>>(endpoint);
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'Failed to fetch flashcards',
};
}
}
async createFlashcard(data: CreateFlashcardRequest): Promise<ApiResponse<Flashcard>> {
try {
return await this.makeRequest<ApiResponse<Flashcard>>('/flashcards', {
method: 'POST',
body: JSON.stringify(data),
});
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'Failed to create flashcard',
};
}
}
async deleteFlashcard(id: string): Promise<ApiResponse<void>> {
try {
return await this.makeRequest<ApiResponse<void>>(`/flashcards/${id}`, {
method: 'DELETE',
});
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'Failed to delete flashcard',
};
}
}
async getFlashcard(id: string): Promise<ApiResponse<Flashcard>> {
try {
return await this.makeRequest<ApiResponse<Flashcard>>(`/flashcards/${id}`);
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'Failed to get flashcard',
};
}
}
async updateFlashcard(id: string, data: CreateFlashcardRequest): Promise<ApiResponse<Flashcard>> {
try {
return await this.makeRequest<ApiResponse<Flashcard>>(`/flashcards/${id}`, {
method: 'PUT',
body: JSON.stringify(data),
});
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'Failed to update flashcard',
};
}
}
async toggleFavorite(id: string): Promise<ApiResponse<void>> {
try {
return await this.makeRequest<ApiResponse<void>>(`/flashcards/${id}/favorite`, {
method: 'POST',
});
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'Failed to toggle favorite',
};
}
}
}
export const flashcardsService = new FlashcardsService();