🐙

【開発ログ】学習イベントの記録と発音ボタンによる学習完了の実装

に公開

学習イベントを登録する際、当初は「学習完了」のボタンを作るか迷いました。しかし、このアプリは子どもが操作することを想定しているため、発音を聞くと自動で学習完了として記録する方針に変更しました。これにより、子どもが操作を迷わず、自然に学習イベントが記録されるようになっています。

前回

  • 学習関連API
    • 今日の単語を10件, 学習済み単語からランダムに10件抽出(テスト用)
    • 取得データをダッシュボードに反映

今回

  • 学習イベントの保存
     - action, lang, wordId を記録
     - 例:{ action: "learn", lang: "ja", wordId: "xxxx" }

① 学習イベントAPI

単語カードで発音ボタンを押したときに、学習イベントを登録するAPIを作成しました。

api/study-event/route.ts
import { getDecodedSessionOrRedirect } from "@/lib/authServer";
import { prisma } from "@/lib/prisma";
import { NextResponse } from "next/server";

export async function POST(request: Request) {
  // ユーザ情報取得
  const user = "ユーザ情報GET";

  if (!user) {
    return NextResponse.json({ error: "User not found" }, { status: 404 });
  }

  // body から情報を取得
  const { wordId, action, lang } = await request.json();

  if (!wordId || !action) {
    return NextResponse.json({ error: "Missing wordId or action" }, { status: 400 });
  }

  // 有効な action チェック
  if (!["learn"].includes(action)) {
    return NextResponse.json({ error: "Invalid action type" }, { status: 400 });
  }

  try {
    // 学習イベント記録
    const studyEvent = await prisma.studyEvent.create({
      data: {
        userId: user.id,
        wordId,
        action,
        lang,
      },
    });

    return NextResponse.json(studyEvent, { status: 201 });
  } catch (error) {
    console.error("Error recording study event:", error);
    return NextResponse.json({ error: "Server error" }, { status: 500 });
  }
}


② コンポーネント修正

学習カードの発音ボタンを押すと、学習イベントを登録するようにしました。

components/learning/LearningCard.tsx
export default function LearningCard() {
  /** 一部省略 */

  // 学習イベント情報
  const [learnedWords, setLearnedWords] = useState<LearnedWord[]>([]);

  // 発音ボタン(学習イベント完了)
  const handleLearned = (wordId: string) => {
    if (learnedWords.some(w => w.id === wordId)) return; // 重複チェック

    const newLearnedWord: LearnedWord = {
      id: wordId,
      action: "learn",
      lang: "ja",
    };
    setLearnedWords(prev => [...prev, newLearnedWord]);

    // サーバー記録
    fetch('/api/study-event', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ wordId, action: "learn", lang: "ja" }),
    })
    .then(res => {
      if (!res.ok) throw new Error('学習イベントの記録に失敗しました');
      return res.json();
    })
    .then(data => {
      console.log('学習イベントが記録されました:', data);
    })
    .catch(err => {
      console.error(err);
    });
  };

  return (
    <>
      {/* 一部省略 */}
      {/** 発音ボタン */}
      <div className="mt-4 flex gap-2">
        <button
          type="button"
          onClick={() => handleLearned(card.id)} // WordID
          className="w-full rounded bg-blue-500 px-4 py-2 text-white"
        >
          はつおん
        </button>
      </div>
    </>
  );
}

悩んだこと / 学んだこと

  • 発音ボタンと学習完了の関係をどうするか考えました。
  • 重複登録を防ぐために、状態管理とサーバー側のチェックを組み合わせる必要があります。

次回やること

  • Firebaseセッションの安定化
     - Cookieの有効期限切れに備え、認証ロジックを再確認・修正

  • UI/UX改善
     - ダッシュボードとエディタのデザイン統一
     - スマホ表示での余白や配置調整

  • TTS機能

    • 韓国語・日本語の発音再生機能

Discussion