feat: 實現自動「未分類」預設卡組功能
- 新增 CardSet.IsDefault 欄位標識預設卡組 - 實現用戶註冊時自動創建「未分類」卡組 - 添加 ensure-default API 端點確保預設卡組存在 - 優化詞卡創建邏輯,支援自動歸類到預設卡組 - 改善前端表單處理,智能選擇預設卡組 - 修復預設卡組顯示對比度問題 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
452cdef641
commit
715b735c4d
|
|
@ -30,6 +30,30 @@ public class CardSetsController : ControllerBase
|
||||||
throw new UnauthorizedAccessException("Invalid user ID");
|
throw new UnauthorizedAccessException("Invalid user ID");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task EnsureDefaultCardSetAsync(Guid userId)
|
||||||
|
{
|
||||||
|
// 檢查用戶是否已有預設卡組
|
||||||
|
var hasDefaultCardSet = await _context.CardSets
|
||||||
|
.AnyAsync(cs => cs.UserId == userId && cs.IsDefault);
|
||||||
|
|
||||||
|
if (!hasDefaultCardSet)
|
||||||
|
{
|
||||||
|
// 創建預設「未分類」卡組
|
||||||
|
var defaultCardSet = new CardSet
|
||||||
|
{
|
||||||
|
Id = Guid.NewGuid(),
|
||||||
|
UserId = userId,
|
||||||
|
Name = "未分類",
|
||||||
|
Description = "系統預設卡組,用於存放尚未分類的詞卡",
|
||||||
|
Color = "bg-slate-700",
|
||||||
|
IsDefault = true
|
||||||
|
};
|
||||||
|
|
||||||
|
_context.CardSets.Add(defaultCardSet);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public async Task<ActionResult> GetCardSets()
|
public async Task<ActionResult> GetCardSets()
|
||||||
{
|
{
|
||||||
|
|
@ -37,9 +61,13 @@ public class CardSetsController : ControllerBase
|
||||||
{
|
{
|
||||||
var userId = GetUserId();
|
var userId = GetUserId();
|
||||||
|
|
||||||
|
// 確保用戶有預設卡組
|
||||||
|
await EnsureDefaultCardSetAsync(userId);
|
||||||
|
|
||||||
var cardSets = await _context.CardSets
|
var cardSets = await _context.CardSets
|
||||||
.Where(cs => cs.UserId == userId)
|
.Where(cs => cs.UserId == userId)
|
||||||
.OrderByDescending(cs => cs.CreatedAt)
|
.OrderBy(cs => cs.IsDefault ? 0 : 1) // 預設卡組排在最前面
|
||||||
|
.ThenByDescending(cs => cs.CreatedAt)
|
||||||
.Select(cs => new
|
.Select(cs => new
|
||||||
{
|
{
|
||||||
cs.Id,
|
cs.Id,
|
||||||
|
|
@ -49,6 +77,7 @@ public class CardSetsController : ControllerBase
|
||||||
cs.CardCount,
|
cs.CardCount,
|
||||||
cs.CreatedAt,
|
cs.CreatedAt,
|
||||||
cs.UpdatedAt,
|
cs.UpdatedAt,
|
||||||
|
cs.IsDefault,
|
||||||
// 計算進度 (簡化版)
|
// 計算進度 (簡化版)
|
||||||
Progress = cs.CardCount > 0 ?
|
Progress = cs.CardCount > 0 ?
|
||||||
_context.Flashcards
|
_context.Flashcards
|
||||||
|
|
@ -186,6 +215,10 @@ public class CardSetsController : ControllerBase
|
||||||
if (cardSet == null)
|
if (cardSet == null)
|
||||||
return NotFound(new { Success = false, Error = "Card set not found" });
|
return NotFound(new { Success = false, Error = "Card set not found" });
|
||||||
|
|
||||||
|
// 防止刪除預設卡組
|
||||||
|
if (cardSet.IsDefault)
|
||||||
|
return BadRequest(new { Success = false, Error = "Cannot delete default card set" });
|
||||||
|
|
||||||
_context.CardSets.Remove(cardSet);
|
_context.CardSets.Remove(cardSet);
|
||||||
await _context.SaveChangesAsync();
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
|
@ -209,6 +242,43 @@ public class CardSetsController : ControllerBase
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpPost("ensure-default")]
|
||||||
|
public async Task<ActionResult> EnsureDefaultCardSet()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var userId = GetUserId();
|
||||||
|
await EnsureDefaultCardSetAsync(userId);
|
||||||
|
|
||||||
|
// 返回預設卡組
|
||||||
|
var defaultCardSet = await _context.CardSets
|
||||||
|
.FirstOrDefaultAsync(cs => cs.UserId == userId && cs.IsDefault);
|
||||||
|
|
||||||
|
if (defaultCardSet == null)
|
||||||
|
return StatusCode(500, new { Success = false, Error = "Failed to create default card set" });
|
||||||
|
|
||||||
|
return Ok(new
|
||||||
|
{
|
||||||
|
Success = true,
|
||||||
|
Data = defaultCardSet,
|
||||||
|
Message = "Default card set ensured"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (UnauthorizedAccessException)
|
||||||
|
{
|
||||||
|
return Unauthorized(new { Success = false, Error = "Unauthorized" });
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return StatusCode(500, new
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
Error = "Failed to ensure default card set",
|
||||||
|
Timestamp = DateTime.UtcNow
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Request DTOs
|
// Request DTOs
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,31 @@ public class FlashcardsController : ControllerBase
|
||||||
throw new UnauthorizedAccessException("Invalid user ID");
|
throw new UnauthorizedAccessException("Invalid user ID");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<Guid> GetOrCreateDefaultCardSetAsync(Guid userId)
|
||||||
|
{
|
||||||
|
// 嘗試找到預設卡組
|
||||||
|
var defaultCardSet = await _context.CardSets
|
||||||
|
.FirstOrDefaultAsync(cs => cs.UserId == userId && cs.IsDefault);
|
||||||
|
|
||||||
|
if (defaultCardSet != null)
|
||||||
|
return defaultCardSet.Id;
|
||||||
|
|
||||||
|
// 如果沒有預設卡組,創建一個
|
||||||
|
var newDefaultCardSet = new CardSet
|
||||||
|
{
|
||||||
|
Id = Guid.NewGuid(),
|
||||||
|
UserId = userId,
|
||||||
|
Name = "未分類",
|
||||||
|
Description = "系統預設卡組,用於存放尚未分類的詞卡",
|
||||||
|
Color = "bg-slate-700",
|
||||||
|
IsDefault = true
|
||||||
|
};
|
||||||
|
|
||||||
|
_context.CardSets.Add(newDefaultCardSet);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
return newDefaultCardSet.Id;
|
||||||
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public async Task<ActionResult> GetFlashcards(
|
public async Task<ActionResult> GetFlashcards(
|
||||||
[FromQuery] Guid? setId,
|
[FromQuery] Guid? setId,
|
||||||
|
|
@ -116,18 +141,30 @@ public class FlashcardsController : ControllerBase
|
||||||
{
|
{
|
||||||
var userId = GetUserId();
|
var userId = GetUserId();
|
||||||
|
|
||||||
// 驗證卡組是否屬於用戶
|
// 確定要使用的卡組ID
|
||||||
var cardSet = await _context.CardSets
|
Guid cardSetId;
|
||||||
.FirstOrDefaultAsync(cs => cs.Id == request.CardSetId && cs.UserId == userId);
|
if (request.CardSetId.HasValue)
|
||||||
|
{
|
||||||
|
// 如果指定了卡組,驗證是否屬於用戶
|
||||||
|
var cardSet = await _context.CardSets
|
||||||
|
.FirstOrDefaultAsync(cs => cs.Id == request.CardSetId.Value && cs.UserId == userId);
|
||||||
|
|
||||||
if (cardSet == null)
|
if (cardSet == null)
|
||||||
return NotFound(new { Success = false, Error = "Card set not found" });
|
return NotFound(new { Success = false, Error = "Card set not found" });
|
||||||
|
|
||||||
|
cardSetId = request.CardSetId.Value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 如果沒有指定卡組,使用或創建預設卡組
|
||||||
|
cardSetId = await GetOrCreateDefaultCardSetAsync(userId);
|
||||||
|
}
|
||||||
|
|
||||||
var flashcard = new Flashcard
|
var flashcard = new Flashcard
|
||||||
{
|
{
|
||||||
Id = Guid.NewGuid(),
|
Id = Guid.NewGuid(),
|
||||||
UserId = userId,
|
UserId = userId,
|
||||||
CardSetId = request.CardSetId,
|
CardSetId = cardSetId,
|
||||||
Word = request.Word.Trim(),
|
Word = request.Word.Trim(),
|
||||||
Translation = request.Translation.Trim(),
|
Translation = request.Translation.Trim(),
|
||||||
Definition = request.Definition.Trim(),
|
Definition = request.Definition.Trim(),
|
||||||
|
|
@ -290,7 +327,7 @@ public class FlashcardsController : ControllerBase
|
||||||
// DTOs
|
// DTOs
|
||||||
public class CreateFlashcardRequest
|
public class CreateFlashcardRequest
|
||||||
{
|
{
|
||||||
public Guid CardSetId { get; set; }
|
public Guid? CardSetId { get; set; }
|
||||||
public string Word { get; set; } = string.Empty;
|
public string Word { get; set; } = string.Empty;
|
||||||
public string Translation { get; set; } = string.Empty;
|
public string Translation { get; set; } = string.Empty;
|
||||||
public string Definition { get; set; } = string.Empty;
|
public string Definition { get; set; } = string.Empty;
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,8 @@ public class CardSet
|
||||||
|
|
||||||
public int CardCount { get; set; } = 0;
|
public int CardCount { get; set; } = 0;
|
||||||
|
|
||||||
|
public bool IsDefault { get; set; } = false;
|
||||||
|
|
||||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||||
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,23 @@ function FlashcardsContent() {
|
||||||
try {
|
try {
|
||||||
const result = await flashcardsService.getCardSets()
|
const result = await flashcardsService.getCardSets()
|
||||||
if (result.success && result.data) {
|
if (result.success && result.data) {
|
||||||
setCardSets(result.data.sets)
|
if (result.data.sets.length === 0) {
|
||||||
|
// 如果沒有卡組,確保創建預設卡組
|
||||||
|
const ensureResult = await flashcardsService.ensureDefaultCardSet()
|
||||||
|
if (ensureResult.success) {
|
||||||
|
// 重新載入卡組
|
||||||
|
const retryResult = await flashcardsService.getCardSets()
|
||||||
|
if (retryResult.success && retryResult.data) {
|
||||||
|
setCardSets(retryResult.data.sets)
|
||||||
|
} else {
|
||||||
|
setError('Failed to load card sets after creating default')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setError(ensureResult.error || 'Failed to create default card set')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setCardSets(result.data.sets)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
setError(result.error || 'Failed to load card sets')
|
setError(result.error || 'Failed to load card sets')
|
||||||
}
|
}
|
||||||
|
|
@ -175,6 +191,22 @@ function FlashcardsContent() {
|
||||||
>
|
>
|
||||||
所有詞卡 ({filteredCards.length})
|
所有詞卡 ({filteredCards.length})
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
const defaultSet = cardSets.find(set => set.isDefault)
|
||||||
|
if (defaultSet) {
|
||||||
|
setSelectedSet(defaultSet.id)
|
||||||
|
setActiveTab('all-cards')
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className={`pb-4 px-1 border-b-2 font-medium text-sm ${
|
||||||
|
selectedSet && cardSets.find(set => set.id === selectedSet)?.isDefault
|
||||||
|
? 'border-primary text-primary'
|
||||||
|
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
📂 未分類詞卡
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{/* Search */}
|
{/* Search */}
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
|
|
@ -216,14 +248,22 @@ function FlashcardsContent() {
|
||||||
{filteredSets.map(set => (
|
{filteredSets.map(set => (
|
||||||
<div
|
<div
|
||||||
key={set.id}
|
key={set.id}
|
||||||
className="border rounded-lg hover:shadow-lg transition-shadow cursor-pointer"
|
className={`border rounded-lg hover:shadow-lg transition-shadow cursor-pointer ${
|
||||||
|
set.isDefault ? 'ring-2 ring-gray-300' : ''
|
||||||
|
}`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSelectedSet(set.id)
|
setSelectedSet(set.id)
|
||||||
setActiveTab('all-cards')
|
setActiveTab('all-cards')
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className={`${set.color} text-white p-4 rounded-t-lg`}>
|
<div className={`${set.isDefault ? 'bg-slate-700' : set.color} text-white p-4 rounded-t-lg`}>
|
||||||
<h4 className="font-semibold text-lg">{set.name}</h4>
|
<div className="flex items-center space-x-2">
|
||||||
|
{set.isDefault && <span>📂</span>}
|
||||||
|
<h4 className="font-semibold text-lg">
|
||||||
|
{set.name}
|
||||||
|
{set.isDefault && <span className="text-xs ml-2 opacity-75">(預設)</span>}
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
<p className="text-sm opacity-90">{set.description}</p>
|
<p className="text-sm opacity-90">{set.description}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-4 bg-white rounded-b-lg">
|
<div className="p-4 bg-white rounded-b-lg">
|
||||||
|
|
@ -254,6 +294,26 @@ function FlashcardsContent() {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* 未分類提醒 */}
|
||||||
|
{selectedSet && cardSets.find(set => set.id === selectedSet)?.isDefault && filteredCards.length > 15 && (
|
||||||
|
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4 mb-4">
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<span className="text-blue-600">💡</span>
|
||||||
|
<div className="flex-1">
|
||||||
|
<p className="text-blue-800 text-sm">
|
||||||
|
您有 {filteredCards.length} 個未分類詞卡,建議整理到不同主題的卡組中,有助於更好地組織學習內容。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={() => setActiveTab('my-cards')}
|
||||||
|
className="text-blue-600 text-sm font-medium hover:text-blue-800"
|
||||||
|
>
|
||||||
|
查看卡組
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{filteredCards.length === 0 ? (
|
{filteredCards.length === 0 ? (
|
||||||
<div className="text-center py-12">
|
<div className="text-center py-12">
|
||||||
<p className="text-gray-500 mb-4">沒有找到詞卡</p>
|
<p className="text-gray-500 mb-4">沒有找到詞卡</p>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { useState, useEffect } from 'react'
|
import React, { useState, useEffect } from 'react'
|
||||||
import { flashcardsService, type CreateFlashcardRequest, type CardSet } from '@/lib/services/flashcards'
|
import { flashcardsService, type CreateFlashcardRequest, type CardSet } from '@/lib/services/flashcards'
|
||||||
|
|
||||||
interface FlashcardFormProps {
|
interface FlashcardFormProps {
|
||||||
|
|
@ -12,8 +12,23 @@ interface FlashcardFormProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function FlashcardForm({ cardSets, initialData, isEdit = false, onSuccess, onCancel }: FlashcardFormProps) {
|
export function FlashcardForm({ cardSets, initialData, isEdit = false, onSuccess, onCancel }: FlashcardFormProps) {
|
||||||
|
// 找到預設卡組或第一個卡組
|
||||||
|
const getDefaultCardSetId = () => {
|
||||||
|
if (initialData?.cardSetId) return initialData.cardSetId
|
||||||
|
|
||||||
|
// 優先選擇預設卡組
|
||||||
|
const defaultCardSet = cardSets.find(set => set.isDefault)
|
||||||
|
if (defaultCardSet) return defaultCardSet.id
|
||||||
|
|
||||||
|
// 如果沒有預設卡組,選擇第一個卡組
|
||||||
|
if (cardSets.length > 0) return cardSets[0].id
|
||||||
|
|
||||||
|
// 如果沒有任何卡組,返回空字串
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
const [formData, setFormData] = useState<CreateFlashcardRequest>({
|
const [formData, setFormData] = useState<CreateFlashcardRequest>({
|
||||||
cardSetId: initialData?.cardSetId || (cardSets[0]?.id || ''),
|
cardSetId: getDefaultCardSetId(),
|
||||||
english: initialData?.english || '',
|
english: initialData?.english || '',
|
||||||
chinese: initialData?.chinese || '',
|
chinese: initialData?.chinese || '',
|
||||||
pronunciation: initialData?.pronunciation || '',
|
pronunciation: initialData?.pronunciation || '',
|
||||||
|
|
@ -21,6 +36,16 @@ export function FlashcardForm({ cardSets, initialData, isEdit = false, onSuccess
|
||||||
example: initialData?.example || '',
|
example: initialData?.example || '',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 當 cardSets 改變時,重新設定 cardSetId(處理初始載入的情況)
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!formData.cardSetId && cardSets.length > 0) {
|
||||||
|
const defaultId = getDefaultCardSetId()
|
||||||
|
if (defaultId) {
|
||||||
|
setFormData(prev => ({ ...prev, cardSetId: defaultId }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [cardSets])
|
||||||
|
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const [error, setError] = useState<string | null>(null)
|
const [error, setError] = useState<string | null>(null)
|
||||||
|
|
||||||
|
|
@ -87,18 +112,41 @@ export function FlashcardForm({ cardSets, initialData, isEdit = false, onSuccess
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||||
詞卡集合 *
|
詞卡集合 *
|
||||||
</label>
|
</label>
|
||||||
<select
|
{cardSets.length === 0 ? (
|
||||||
value={formData.cardSetId}
|
<div className="w-full px-3 py-2 border border-gray-300 rounded-lg bg-gray-100 text-gray-500">
|
||||||
onChange={(e) => handleChange('cardSetId', e.target.value)}
|
載入卡組中...
|
||||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent"
|
</div>
|
||||||
required
|
) : (
|
||||||
>
|
<select
|
||||||
{cardSets.map(set => (
|
value={formData.cardSetId}
|
||||||
<option key={set.id} value={set.id}>
|
onChange={(e) => handleChange('cardSetId', e.target.value)}
|
||||||
{set.name}
|
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent"
|
||||||
</option>
|
required
|
||||||
))}
|
>
|
||||||
</select>
|
{/* 如果沒有選中任何卡組,顯示提示 */}
|
||||||
|
{!formData.cardSetId && (
|
||||||
|
<option value="" disabled>
|
||||||
|
請選擇卡組
|
||||||
|
</option>
|
||||||
|
)}
|
||||||
|
{/* 先顯示預設卡組 */}
|
||||||
|
{cardSets
|
||||||
|
.filter(set => set.isDefault)
|
||||||
|
.map(set => (
|
||||||
|
<option key={set.id} value={set.id}>
|
||||||
|
📂 {set.name} (預設)
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
{/* 再顯示其他卡組 */}
|
||||||
|
{cardSets
|
||||||
|
.filter(set => !set.isDefault)
|
||||||
|
.map(set => (
|
||||||
|
<option key={set.id} value={set.id}>
|
||||||
|
{set.name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 英文單字 */}
|
{/* 英文單字 */}
|
||||||
|
|
@ -190,10 +238,12 @@ export function FlashcardForm({ cardSets, initialData, isEdit = false, onSuccess
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={loading}
|
disabled={loading || cardSets.length === 0 || !formData.cardSetId}
|
||||||
className="px-4 py-2 bg-primary text-white rounded-lg hover:bg-primary/90 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
className="px-4 py-2 bg-primary text-white rounded-lg hover:bg-primary/90 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
>
|
>
|
||||||
{loading ? '處理中...' : (isEdit ? '更新詞卡' : '新增詞卡')}
|
{loading ? '處理中...' :
|
||||||
|
cardSets.length === 0 ? '載入中...' :
|
||||||
|
(isEdit ? '更新詞卡' : '新增詞卡')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ export interface CardSet {
|
||||||
cardCount: number;
|
cardCount: number;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
|
isDefault: boolean;
|
||||||
progress: number;
|
progress: number;
|
||||||
lastStudied: string;
|
lastStudied: string;
|
||||||
tags: string[];
|
tags: string[];
|
||||||
|
|
@ -40,7 +41,7 @@ export interface CreateCardSetRequest {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CreateFlashcardRequest {
|
export interface CreateFlashcardRequest {
|
||||||
cardSetId: string;
|
cardSetId?: string;
|
||||||
english: string;
|
english: string;
|
||||||
chinese: string;
|
chinese: string;
|
||||||
pronunciation: string;
|
pronunciation: string;
|
||||||
|
|
@ -192,6 +193,19 @@ class FlashcardsService {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async ensureDefaultCardSet(): Promise<ApiResponse<CardSet>> {
|
||||||
|
try {
|
||||||
|
return await this.makeRequest<ApiResponse<CardSet>>('/cardsets/ensure-default', {
|
||||||
|
method: 'POST',
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: error instanceof Error ? error.message : 'Failed to ensure default card set',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const flashcardsService = new FlashcardsService();
|
export const flashcardsService = new FlashcardsService();
|
||||||
Loading…
Reference in New Issue