4
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

実務1年目駆け出しエンジニアがLaravel&ReactでWebアプリケーション開発に挑戦してみた!(フロントエンド実装編⑥)~大学詳細・学部一覧画面作成~

4
Posted at

実務1年目駆け出しエンジニアがLaravel&ReactでWebアプリケーション開発に挑戦してみた!(その27)

0. 初めに

あけましておめでとうございます!
2026年もよろしくお願いします!

見習いエンジニアの僕がWebアプリケーションを一から作り上げる様子をお届けしているシリーズです!

2週間も投稿をさぼってしまいました。
食中毒で下痢と発熱に苦しみました。
みなさんも生肉には気を付けましょう...

前回は、検索結果大学一覧ページを作りました。
今日は、学部一覧ページを作成したいと思います!

1. ブランチ運用

いつも通り、developブランチを最新化して、新規ブランチを切りましょう。

ブランチ名は、feature/frontend/faculties-index-pageとかでいききましょうか。

作業が終わったら、コミット・プッシュを忘れずに行いましょう。

2. 画面デザイン

いつも通り、実装に入る前に今日のお手本を確認しておきましょう!

スクリーンショット 2025-12-08 211744.png

3. シーディング修正

現状だと、なぜか教育学部が足りないので、追加して再シーディングしましょう。

\project-root\src\database\seeders\FacultySeeder.php
<?php

namespace Database\Seeders;

use App\Models\Faculty;
use App\Models\User;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;

class FacultySeeder extends Seeder
{
    /**
     * Run the database seeds.
     */
    public function run(): void
    {
        // 作成者を管理者に設定
        $adminId = User::where('email', 'admin@example.com')->value('id')
            ?? User::first()->id;

        // 修正: 学部を適当に5つ作成
        Faculty::create([
            'university_id' => 1,
            'name' => '教養学部',
            'created_by' => $adminId,
        ]);

        Faculty::create([
            'university_id' => 1,
            'name' => '経済学部',
            'created_by' => $adminId,
        ]);

        Faculty::create([
            'university_id' => 1,
            'name' => '教育学部',
            'created_by' => $adminId,
        ]);

        Faculty::create([
            'university_id' => 1,
            'name' => '理学部',
            'created_by' => $adminId,
        ]);

        Faculty::create([
            'university_id' => 1,
            'name' => '工学部',
            'created_by' => $adminId,
        ]);
    }
}

実行コマンド

/var/www
$ php artisan migrate:fresh --seed

これで、検索結果ページの「テストA大学」をクリックしたときに、これらの学部が一覧で表示できる用意が整いました!

4. ルーティング修正

今回、学部一覧に表示される一つ一つの学部カードをクリックするとその学部に紐づいている研究室一覧ページを表示できるように例によって、Inertia.jsのrouterを使用したいと思います。

使用したいのは、web.phpの以下の部分です。

\project-root\src\routes\web.php
Route::get('/faculty/{faculty}/labs', [LabController::class, 'index'])->name('labs.index');

これをそのまま使っても問題ないのですが、少し気に食わぬ部分があったのでこれを機に直したいと思います。

それが、最初のfacultyが単数形であることです。

本シリーズでは、いわゆるREST APIを意識した実装を行っていました。
REST APIでは、最初の部分は複数形で書くのが一般的です。

したがって、以下のように修正します。

\project-root\src\routes\web.php
Route::get('/faculties/{faculty}/labs', [LabController::class, 'index'])->name('labs.index');

...それだけです!(≧◇≦)

5. コントローラー修正

フロントエンド編だよね?

というツッコミはもうよいでしょう!

っていうのは冗談で、FacultyController.phpを修正する必要があるかなと思います。

修正するのは、indexメソッドです。

\project-root\src\app\Http\Controllers\FacultyController.php
    public function index(Request $request, University $university)
    {
        $query = $request->input('query', ''); // 追加
        $faculties = $university->faculties()->orderBy('name')->get();
        return Inertia::render('Faculty/Index', [
            'faculties' => $faculties,
            'university' => $university,
            'query' => $query, // 追加
        ]);
    }

なぜこのような修正が必要なのかは、後で解説しますね。

6. Reactコンポーネント作成

では、いよいよ画面を作っていきましょう!

Faculty/Index.jsxを作成する

バックエンド編で動作確認用に既に作成していましたが、この度フロントエンド編でございますので、潔くすべて消して最初から書いていきましょう。

ただ、雰囲気は、前回作ったUniversity/Index.jsxに似ていると思うので、参考にしていきましょう。

\project-root\src\resources\js\Pages\Faculty\Index.jsx
import { Head } from "@inertiajs/react";
import AppLayout from '@/Layouts/AppLayout';
import FacultyCard from '../../Components/Faculty/FacultyCard';
import BackButton from '../../Components/Common/BackButton';

const Index = ({ faculties, university, query = '' }) => {
  const hasResults = faculties.length > 0;

  return (
    <AppLayout title={`${university.name}の学部一覧`}>
      <Head title={`${university.name}の学部一覧`} />
      {hasResults ? (
        <div className="flex flex-col items-center min-h-full">
          <div className="w-full flex justify-end">
            <p className="text-[#747D8C]">{faculties.length}件の検索結果</p>
          </div>
          <div className="w-full max-w-xl space-y-6 mt-8">
            {faculties.map(faculty => (
              <FacultyCard key={faculty.id} faculty={faculty} />
            ))}
          </div>
          <div className="mt-auto pt-8 pb-12">
            <BackButton routerName="universities.index" params={{ query }} />
          </div>
        </div>
      ) : (
        <div className="flex flex-col items-center min-h-full">
          <div className="flex-1 flex items-center justify-center">
            <p className="text-[#747D8C]">0件の学部</p>
          </div>
          <div className="pt-8 pb-12">
            <BackButton routerName="universities.index" params={{ query }}/>
          </div>
        </div>
      )}
    </AppLayout>
  )	
}

export default Index;

ページネーションがないくらいで、大学検索結果画面のものとほとんど同じです。

鋭い方なら、もしかしたらあることに気が付いたかもしれません。

queryはもう必要ないのでは?

確かに大学検索結果画面と違って学部一覧は検索結果を表示するわけではないので、検索文字列の情報は一見必要ないように思います。

しかしながら、これは必要です。

なぜなら、このコンポーネント内でページ遷移するために必要だからです。
このページで使われているBackButtonに注目してください。

\project-root\src\resources\js\Pages\Faculty\Index.jsx
<BackButton routerName="universities.index" params={{ query }}/>

大学の検索結果は、クエリパラメータとしてURLに埋め込まれます。

image.png

そのため、単にrouterName="univsersites.index"としてもパラメータ不足となり、どの言葉で検索された大学一覧の画面に遷移すればよいかをコンピュータがわからず、エラーになってしまいます。

そのため、BackButtonに渡すために、queryをpropsとして受け取る必要があります。

\project-root\src\resources\js\Pages\Faculty\Index.jsx
const Index = ({ faculties, university, query = '' }) => {

では、このqueryはこのコンポーネントに対してどのようにして渡せばよいのでしょうか?

UniversityCard.jsxを修正する

結論として、学部一覧ページに遷移するためにクリックされる大学カードで渡せばよいです。

\project-root\src\resources\js\Components\University\UniversityCard.jsx
import { router } from "@inertiajs/react";
import TypeBadge from "./TypeBadge";

const UniversityCard = ({ university, query = '' }) => { // 修正: queryをpropsとして受け取る
  return (
    <div
      className="bg-[#EEF7FB] rounded-lg shadow-md px-4 py-3 hover:shadow-lg transition-shadow cursor-pointer flex items-center gap-4"
      onClick={() => router.get(route('faculties.index', { university: university.id, query }))} // 修正: queryをparamsに含める
    >
      <TypeBadge type={university.type} className="flex-shrink-0 text-3xl" />
      <span className="text-2xl font-bold text-[#747D8C]">
        {university.name}
      </span>
    </div>
  );
}

export default UniversityCard;

ん?Faculty/Index.jsxでは、あくまでpropsとしてqueryを受け取るようにしたはず。これだけでは、propsとして渡せていないじゃないか?てか、そもそもこのUniversityCard.jsxが受け取っているpropsはどこから来とるねん...

そのように思うかもしれません。
あともう少しです!

まず、一つ目の疑問ですが、こちらは既に対処済みでした。

解説を後回しにした、FacultyController.phpindexメソッドの修正で対応しています。

まず、UniversityCard.jsxがクリックされると、routerにより、クエリパラメータ込みで学部一覧ページに移動します。

\project-root\src\resources\js\Components\University\UniversityCard.jsx
onClick={() => router.get(route('faculties.index', { university: university.id, query }))}

そして、そのクエリの情報込みのリクエストをFacultyController.phpが受け取って、先ほど修正したFaculty/Index.jsxにpropsとしてqueryという変数名で渡すというわけです!

\project-root\src\app\Http\Controllers\FacultyController.php
    public function index(Request $request, University $university)
    {
        $query = $request->input('query', ''); // 追加
        $faculties = $university->faculties()->orderBy('name')->get();
        return Inertia::render('Faculty/Index', [
            'faculties' => $faculties,
            'university' => $university,
            'query' => $query, // 追加
        ]);
    }

次に、二つ目の疑問ですが、こちらは以下のようにして対処します。

University/Index.jsxを修正する

シンプルにUniversity/Index.jsx内で呼び出されている部分でpropsとして渡せばOKです!

\project-root\src\resources\js\Pages\University\Index.jsx
import { Head } from "@inertiajs/react";
import AppLayout from '@/Layouts/AppLayout';
import UniversityCard from '../../Components/University/UniversityCard';
import BackButton from '../../Components/Common/BackButton';
import Pagination from "../../Components/Common/Pagination";

const Index = ({ universities, query}) => {
  const hasResults = universities.data.length > 0;

  return (
    <AppLayout title={`「${query}」を含む大学一覧`}>
      <Head title={`「${query}」を含む大学一覧`} />

      {hasResults ? (
        // 1件以上の場合:コンテンツが少なければ戻るボタンは画面下部、多ければスクロール後に表示
        <div className="flex flex-col items-center min-h-full">
          <div className="w-full flex justify-end">
            <p className="text-[#747D8C]">{universities.total}件の検索結果</p>
          </div>
          <div className="w-full max-w-xl space-y-6 mt-8">
            {universities.data.map(university => (
              <UniversityCard key={university.id} university={university} query={query} /> // 修正: queryもpropsとして渡すようにする
            ))}
          </div>

          {/* ページネーション */}
          <Pagination paginator={universities} />
          
          <div className="mt-auto pt-8 pb-12">
            <BackButton routerName="home" />
          </div>
        </div>
      ) : (
        // 0件の場合:メッセージを画面中央に、戻るボタンは下部に固定
        <div className="flex flex-col items-center min-h-full">
          <div className="flex-1 flex items-center justify-center">
            <p className="text-[#747D8C]">0件の検索結果</p>
          </div>
          <div className="pt-8 pb-12">
            <BackButton routerName="home" />
          </div>
        </div>
      )}
    </AppLayout>
  )
}

export default Index;

これで、戻るボタンを押して大学一覧に戻ることができるようになりましたね!

説明の順番が前後してしまったところもあったので、混乱している方もいらっしゃるかと思うので、もう一度流れだけを簡単におさらいしておきましょう!

  1. Home.jsxにて、ユーザーが検索ワードを入力して大学を検索する
  2. UniversityController.phpUniversity/Index.jsxを開く
    (この時、この時、URLにクエリが含まれます。例: http://localhost/universities?query=テスト
  3. ユーザーが大学カードをクリックする
  4. UniversityCard.jsxにて、onClickイベントが発火し、クエリ情報をもとに'faculties.index'ルーティングが叩かれる
  5. ルーティングが叩かれたので、FacultyController.phpが受け取ったリクエスト(クエリ付き)をもとにFaculty/Index.jsxをprospとしてqueryを渡して開きます。
    University/Index.jsxでpropsとして UniversityCard.jsxqueryを渡しているので、Faculty/Index.jsxを開いているときのURLにもクエリパラメータが含まれます。例: http://localhost/universities/1/faculties?query=テスト
  6. Faculty/Index.jsxにて、BackButtonに対して、そのpropsで受け取ったqueryをrouterで渡します。
  7. クエリ情報があるので、検索した文字列が含まれるUnmiversity/Index.jsxに戻ってこられます(例: http://localhost/universities?query=テスト

ちょ~っと厄介でしたかねw

ゆっくり焦らず、落ち着いて一つずつ見ていけばきっとわかるはずですよ。(*´ω`)

とりあえず、今のところのが画面はこんな感じです!
image.png

ちょこっと修正する

FacultyController.phpindexメソッドをまたもや修正。

\project-root\src\app\Http\Controllers\FacultyController.php
    public function index(Request $request, University $university)
    {
        $query = $request->input('query', '');
        $faculties = $university->faculties()->get(); // 修正: 名前のソートを削除
        return Inertia::render('Faculty/Index', [
            'faculties' => $faculties,
            'university' => $university,
            'query' => $query,
        ]);
    }

特に名前順である必要はないかなとある日思いました。(笑)

後は、お手本と違って3列表示になっていないので、それを修正します。

import { Head } from "@inertiajs/react";
import AppLayout from '@/Layouts/AppLayout';
import FacultyCard from '../../Components/Faculty/FacultyCard';
import BackButton from '../../Components/Common/BackButton';

const Index = ({ faculties, university, query = '' }) => {
  // テスト用: 極端に長い学部名を追加
  const testFaculties = [
    ...faculties,
    { id: 'test-long-name', name: 'グローバルメディアスタディーズ学部' }
  ];
  const hasResults = testFaculties.length > 0;

  return (
    <AppLayout title={`${university.name}の学部一覧`}>
      <Head title={`${university.name}の学部一覧`} />
      {hasResults ? (
        <div className="flex flex-col items-center min-h-full">
          <div className="w-full flex justify-end">
            <p className="text-[#747D8C]">{testFaculties.length}件の検索結果</p>
          </div>
          <div className="w-full grid grid-cols-3 gap-6 mt-8 justify-items-center"> {/* 修正: 3カラムのグリッドレイアウトに変更 */}
            {testFaculties.map(faculty => (
              <FacultyCard key={faculty.id} faculty={faculty} />
            ))}
          </div>
          <div className="mt-auto pt-8 pb-12">
            <BackButton routerName="universities.index" params={{ query }} />
          </div>
        </div>
      ) : (
        <div className="flex flex-col items-center min-h-full">
          <div className="flex-1 flex items-center justify-center">
            <p className="text-[#747D8C]">0件の学部</p>
          </div>
          <div className="pt-8 pb-12">
            <BackButton routerName="universities.index" params={{ query }}/>
          </div>
        </div>
      )}
    </AppLayout>
  )	
}

export default Index;

グリッドレイアウトを適用することで、3列の表示にすることができます。

また、3列にすると一つずつのカードの横幅がどうしても狭くなるので、長い名前の学部を表示しようとするとレイアウトが崩れてしまうことがありそうです。
そのため、日本一長い学部名とされている、駒沢大学の「グローバルメディアスタディーズ学部」をテスト用にコンポーネント内に直書きしてみました。

いったいどんなことを勉強するところなのか...気になるところではありますが、画面を見てみましょう。

image.png
このように、「グローバルメディアスタディーズ学部」が改行されてしまい、グリッドレイアウトの特性上、隣の「理学部」と「工学部」の幅も引きずられて伸びてしまっています。

そこで、tailwindcssのtruncateを使ってみたいと思います。
これを使うことで、はみ出した分は切り取って、足りない分は省略記号(...)で補ってくれます!

学部カードのコンポーネントに追加しましょう。

\project-root\src\resources\js\Components\Faculty\FacultyCard.jsx
import { router } from "@inertiajs/react";

const FacultyCard = ({ faculty }) => {
  return (
    <div
      className="bg-[#EEF7FB] rounded-lg shadow-md px-4 py-2 hover:shadow-lg transition-shadow cursor-pointer
                 flex items-center justify-center w-full max-w-[250px] min-h-[56px]"
      onClick={() => router.get(`/faculties/${faculty.id}/labs`)}
    >
      <span
        className="text-2xl font-bold text-[#747D8C] text-center leading-tight truncate w-full"
        title={faculty.name}
      >
        {faculty.name}
      </span>
    </div>
  );
};

export default FacultyCard;

良い感じに対応できましたね!
image.png

学部が未作成の時のレイアウトも問題なさそうですね!
image.png

7. (おまけその3)修正: routerの使い方について

前回に引き続き、こちらのおまけコーナーでこれまでの実装で直したかった部分を少しずつ直してより良いものを作っていきたいと思います。

今回直したいものとしては、routerがあります。
これは、今このコーナーを書くまですっかり忘れていました。((´∀`))ケラケラ

例えば、前々回作成したHome.jsxを見てください。

\project-root\src\resources\js\Pages\Home.jsx
import { useState } from "react";
import { router, Head } from "@inertiajs/react";
import AppLayout from "@/Layouts/AppLayout";
import logo from "../Assets/logo/Home.svg";
import SearchBar from "../Components/SearchBar";

const Home = ({ query=''}) => {
  const [search, setSearch] = useState(query || '');

  // 検索フォームを送信する関数
  const handleSubmit = e => {
    e.preventDefault();
    router.get('/universities', { query: search }); // URLが直書きされている
  }

  return (
    <AppLayout mode="home"> {/* ホームモードをpropsで渡す */}
      <Head title="ホーム" />
      <div className="flex flex-col items-center gap-8 pt-24">

        {/* ロゴ */}
        <img src={logo} alt="App Logo" className="h-32 w-auto" />

        {/* 検索フォーム */}
        <SearchBar
          value={search}
          onChange={e => setSearch(e.target.value)}
          onSubmit={handleSubmit}
        />

        {/* 検索メッセージ */}
        <p className="text-[#747D8C]">まずは大学を検索してみましょう。</p>
      </div>
    </AppLayout>
  )
};

export default Home;

検索フォームを送信する関数であるhandleSubmitに着目してください。
routerの中で'/universities'のようにURLが直接書き込まれております。

一方で、BackButton.jsxとかを見てみてください。

\project-root\src\resources\js\Components\Common\BackButton.jsx
import { router } from "@inertiajs/react";

const BackButton = ({ routerName, params = {}}) => {
  return (
    <button
        className="px-16 py-2 bg-[#EEF7FB] text-[#747D8C] shadow-md font-bold rounded-md hover:shadow-lg transition-shadow cursor-pointer"
        onClick={() => router.get(route(routerName, params))}
      >
        戻る
    </button>
  );
}

export default BackButton;

ここでは、onClick時のコールバック関数内でrouterを使っていますが、名前付きルートを使っています。

統一感がないんすよね...
バックエンド編で、せっかく名前付きルートをweb.phpで定義してきたので、BackButton.jsxの方の書き方に統一していきたいと思います!

Ziggyによって、PHP(バックエンド)で書かれた名前付きルートを簡単にJavaScript(フロントエンド)で呼び出せるのでしたね。

さっそく今日作ったFacultyCard.jsxがそれを考慮できていなかったので直しますw

\project-root\src\resources\js\Components\Faculty\FacultyCard.jsx
import { router } from "@inertiajs/react";

const FacultyCard = ({ routerName, faculty }) => {
  return (
    <div
      className="bg-[#EEF7FB] rounded-lg shadow-md px-4 py-2 hover:shadow-lg transition-shadow cursor-pointer
                 flex items-center justify-center w-full max-w-[250px] min-h-[56px]"
      onClick={() => router.get(route(routerName, { faculty: faculty.id }))} // 修正: 名前付きルートを使用
    >
      <span
        className="text-2xl font-bold text-[#747D8C] text-center leading-tight truncate w-full"
        title={faculty.name}
      >
        {faculty.name}
      </span>
    </div>
  );
};

export default FacultyCard;

当然、親コンポーネントであるからpropsとして、routerNameを渡すように修正する必要があります。

\project-root\src\resources\js\Pages\Faculty\Index.jsx
import { Head } from "@inertiajs/react";
import AppLayout from '@/Layouts/AppLayout';
import FacultyCard from '../../Components/Faculty/FacultyCard';
import BackButton from '../../Components/Common/BackButton';

const Index = ({ faculties, university, query = '' }) => {
  const hasResults = faculties.length > 0;

  return (
    <AppLayout title={`${university.name}の学部一覧`}>
      <Head title={`${university.name}の学部一覧`} />
      {hasResults ? (
        <div className="flex flex-col items-center min-h-full">
          <div className="w-full flex justify-end">
            <p className="text-[#747D8C]">{faculties.length}件の検索結果</p>
          </div>
          <div className="w-full grid grid-cols-3 gap-6 mt-8 justify-items-center">
            {faculties.map(faculty => (
              <FacultyCard key={faculty.id} faculty={faculty} routerName="labs.index" />
            ))} {/* 修正: 名前付きルートを使用 */}
          </div>
          <div className="mt-auto pt-8 pb-12">
            <BackButton routerName="universities.index" params={{ query }} />
          </div>
        </div>
      ) : (
        <div className="flex flex-col items-center min-h-full">
          <div className="flex-1 flex items-center justify-center">
            <p className="text-[#747D8C]">0件の学部</p>
          </div>
          <div className="pt-8 pb-12">
            <BackButton routerName="universities.index" params={{ query }}/>
          </div>
        </div>
      )}
    </AppLayout>
  )	
}

export default Index;

Home.jsxを直します。

\project-root\src\resources\js\Pages\Home.jsx
import { useState } from "react";
import { router, Head } from "@inertiajs/react";
import AppLayout from "@/Layouts/AppLayout";
import logo from "../Assets/logo/Home.svg";
import SearchBar from "../Components/SearchBar";

const Home = ({ query=''}) => {
  const [search, setSearch] = useState(query || '');

  // 検索フォームを送信する関数
  const handleSubmit = e => {
    e.preventDefault();
    router.get(route('universities.index', { query: search })); // 修正: 名前付きルートを使用
  }

  return (
    <AppLayout mode="home"> {/* ホームモードをpropsで渡す */}
      <Head title="ホーム" />
      <div className="flex flex-col items-center gap-8 pt-24">

        {/* ロゴ */}
        <img src={logo} alt="App Logo" className="h-32 w-auto" />

        {/* 検索フォーム */}
        <SearchBar
          value={search}
          onChange={e => setSearch(e.target.value)}
          onSubmit={handleSubmit}
        />

        {/* 検索メッセージ */}
        <p className="text-[#747D8C]">まずは大学を検索してみましょう。</p>
      </div>
    </AppLayout>
  )
};

export default Home;

8. (おまけその4)修正: コメントアウトについて

前回、範囲が広くて諦めたものです。
このシリーズでは、コメントアウトをかなりテキトーにやってきました。

見やすいコードを書くためには、コメントアウトを適切に書くことが重要です。
今回は、JSDocという関数の上に書くコメントアウトを書いてみたいと思います。

以下の記事が参考になると思います。
https://qiita.com/yamatai12/items/3482fc31b9c73c6938bd

...やべ、業務でTypeScript書いているけど、引数の型とか書いちゃっているな。大丈夫かな...w

↑TypeScriptは型注釈ができるので、TSDocの方にはいらないようですw

まあ、今回はJavaScriptなので気にせずこれに従いますか。

関数の上に概要引数とその型と説明戻り値とその型とその説明をそれぞれ付けていく感じでいきましょう!

AppLayout.jsxの関数コンポーネントの上あたりで/**と入力すると、VS Codeが候補を出してくれるのでそのまま「Enter」を押します。

すると..
image.png

良い感じでフォーマットを出してくれました。
これに沿って書いていきます。

\project-root\src\resources\js\Layouts\AppLayout.jsx
/**
 * アプリケーションのレイアウトコンポーネント
 * @param {Object} props - コンポーネントのprops
 * @param {React.ReactNode} props.children - レイアウト内に表示するコンテンツ
 * @param {string} props.title - ページタイトル
 * @param {string} [props.mode='default'] - レイアウトモード
 * @returns {JSX.Element} コンポーネントのJSX
 */
 const AppLayout = ({ children, title, mode='default' }) => {

こんな感じでしょうか!
次に進みましょう。
次は、Header.jsxです。

/**
 * ヘッダーコンポーネント
 * @param {Object} props - コンポーネントのprops
 * @param {string} props.title - ページタイトル
 * @param {Function} props.onOpenSidebar - サイドバーを開くためのコールバック関数
 * @returns {JSX.Element} コンポーネントのJSX
 */
 const Header = ({ title, onOpenSidebar }) => {

...面倒くさいって?

そうですね。
では、裏技をお教えしよう...!!

GitHub Copilotを使います!

GitHub Copilotとは、我々開発者を手助けしてくれる優秀なAIアシスタントです!
VS Code上で動かすこともでき、主に画面の右側でチャットする機能があります。

ここで、指示を出すことで、ファイルを編集してくれちゃいます。
こんな感じです。
image.png

僕は、課金をしていますが、無料でもある程度使えます。
無料だと選べるAIモデルや使用料に制限があるかもしれません。
そのため、僕と同じ結果になるとは限らないのですが(そもそも生成AIは、プロンプトが同じでも、毎回同じ結果を返さないけど)、試してみる価値はありますよ!

今の例で言うと、「AppLayout.jsxに倣って、Header関数コンポーネントにも同様のコメントアウトを付与してください」みたいに指示すると勝手にコメントアウトを付けてくれます。

すべての.jsxファイルに対しても行ってください」と頼めば、おっしまい!

僕は、全部を任せるのはやや不安なので、一つずつ行っていく予定です。

どんどん行きましょう~!

\project-root\src\resources\js\Components\Header.jsx
/**
 * ヘッダーコンポーネント
 * @param {Object} props - コンポーネントのprops
 * @param {string} props.title - ページタイトル
 * @param {Function} props.onOpenSidebar - サイドバーを開くためのコールバック関数
 * @returns {JSX.Element} コンポーネントのJSX
 */
const Header = ({ title, onOpenSidebar }) => {
\project-root\src\resources\js\Components\Sidebar.jsx
/**
 * サイドバーコンポーネント
 * @param {Object} props - コンポーネントのprops
 * @param {boolean} props.isOpen - サイドバーの開閉状態
 * @param {Function} props.onClose - サイドバーを閉じるためのコールバック関数
 * @param {boolean} props.isLoggedIn - ユーザーのログイン状態
 * @returns {JSX.Element} コンポーネントのJSX
 */
const Sidebar = ({ isOpen, onClose, isLoggedIn }) => {
\project-root\src\resources\js\Pages\University\Index.jsx
/**
 * 大学一覧ページコンポーネント
 * @param {Object} props - コンポーネントのprops
 * @param {Object} props.universities - ページネーション付き大学データ
 * @param {string} props.query - 検索クエリ文字列
 * @returns {JSX.Element} コンポーネントのJSX
 */
const Index = ({ universities, query}) => {
\project-root\src\resources\js\Components\HamburgerMenu.jsx
/**
 * ハンバーガーメニューボタンコンポーネント
 * @param {Object} props - コンポーネントのprops
 * @param {Function} props.onOpenSidebar - サイドバーを開くためのコールバック関数
 * @returns {JSX.Element} コンポーネントのJSX
 */
const HamburgerMenu = ({ onOpenSidebar }) => {
\project-root\src\resources\js\Components\SearchBar.jsx
/**
 * 検索バーコンポーネント
 * @param {Object} props - コンポーネントのprops
 * @param {string} props.value - 入力値
 * @param {Function} props.onChange - 入力値変更時のコールバック関数
 * @param {Function} props.onSubmit - フォーム送信時のコールバック関数
 * @param {string} [props.placeholder='大学名を入力...'] - プレースホルダーテキスト
 * @returns {JSX.Element} コンポーネントのJSX
 */
const SearchBar = ({

Home.jsxは、関数コンポーネント内にさらに関数があるので、そちらもお忘れなく!

\project-root\src\resources\js\Pages\Home.jsx
import { useState } from "react";
import { router, Head } from "@inertiajs/react";
import AppLayout from "@/Layouts/AppLayout";
import logo from "../Assets/logo/Home.svg";
import SearchBar from "../Components/SearchBar";

/**
 * ホームページコンポーネント
 * @param {Object} props - コンポーネントのprops
 * @param {string} [props.query=''] - 検索クエリの初期値
 * @returns {JSX.Element} コンポーネントのJSX
 */
const Home = ({ query=''}) => {
  const [search, setSearch] = useState(query || '');

  /**
   * 検索フォームを送信する関数
   * @param {Event} e - フォーム送信イベント
   * @returns {void}
   */
  const handleSubmit = e => {
    e.preventDefault();
    router.get(route('universities.index', { query: search }));
  }

  return (
    <AppLayout mode="home"> {/* ホームモードをpropsで渡す */}
      <Head title="ホーム" />
      <div className="flex flex-col items-center gap-8 pt-24">

        {/* ロゴ */}
        <img src={logo} alt="App Logo" className="h-32 w-auto" />

        {/* 検索フォーム */}
        <SearchBar
          value={search}
          onChange={e => setSearch(e.target.value)}
          onSubmit={handleSubmit}
        />

        {/* 検索メッセージ */}
        <p className="text-[#747D8C]">まずは大学を検索してみましょう。</p>
      </div>
    </AppLayout>
  )
};

export default Home;

あと一息です...!!

\project-root\src\resources\js\Components\Common\BackButton.jsx
/**
 * 戻るボタンコンポーネント
 * @param {Object} props - コンポーネントのprops
 * @param {string} props.routerName - 遷移先のルート名
 * @param {Object} [props.params={}] - ルートに渡すパラメータ
 * @returns {JSX.Element} コンポーネントのJSX
 */
const BackButton = ({ routerName, params = {}}) => {
\project-root\src\resources\js\Components\Common\Pagination.jsx
/**
 * ページネーションコンポーネント
 * @param {Object} props - コンポーネントのprops
 * @param {Object} props.paginator - Laravelのページネーションオブジェクト
 * @returns {JSX.Element|null} コンポーネントのJSX(1ページのみの場合はnull)
 */
const Pagination = ({ paginator }) => {
\project-root\src\resources\js\Components\University\TypeBadge.jsx
/**
 * 大学種別バッジコンポーネント
 * @param {Object} props - コンポーネントのprops
 * @param {string} props.type - 大学の種別(national, public, private)
 * @returns {JSX.Element} コンポーネントのJSX
 */
const TypeBadge = ({ type }) => {
\project-root\src\resources\js\Components\University\UniversityCard.jsx
/**
 * 大学カードコンポーネント
 * @param {Object} props - コンポーネントのprops
 * @param {Object} props.university - 大学オブジェクト
 * @param {string} [props.query=''] - 検索クエリ文字列
 * @returns {JSX.Element} コンポーネントのJSX
 */
const UniversityCard = ({ university, query = '' }) => {
\project-root\src\resources\js\Pages\Faculty\Index.jsx
/**
 * 学部一覧ページコンポーネント
 * @param {Object} props - コンポーネントのprops
 * @param {Array} props.faculties - 学部データの配列
 * @param {Object} props.university - 大学オブジェクト
 * @param {string} [props.query=''] - 検索クエリ文字列
 * @returns {JSX.Element} コンポーネントのJSX
 */
const Index = ({ faculties, university, query = '' }) => {

以上です!
これで、かなり現場ライクな仕上がりに近づいたかなと思います。(*´ω`)

9. まとめ・次回予告

お疲れ様でした!

今日は、学部一覧ページを作成しましたね!
戻るボタンをうまく機能させるために検索クエリ文字列をpropsでやり取りする様は、慣れていない人からすると理解するのが結構大変かなと思います。

一つずつ、ゆっくりで全然かまわないと思うので、抹茶でも飲みながらのんびり考えてみてくださいな。(*´ω`)

ホーム => 大学一覧 => 学部一覧 (今日はココ!!)

...来たので、次回は研究室一覧ページを作っていきましょう!

コミット・プッシュを忘れずにして、また次回お会いしましょう~!

これまでの記事一覧

☆要件定義・設計編

☆環境構築編

☆バックエンド実装編

☆フロントエンド実装編

軽く宣伝

YouTubeを始めました(というか始めてました)。
内容としては、Webエンジニアの生活や稼げるようになるまでの成長記録などを発信していく予定です。

現在、まったく再生されておらず、落ち込みそうなので、見てくださる方はぜひ高評価を教えもらえると励みになると思います。"(-""-)"

4
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?