見出し画像

inertia.js(react) + survey.js - 7: ユーザーの受験


一般ユーザーの作成

まず、今一般ユーザーが存在していないので、作成する

<?php

namespace Database\Seeders;

// use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use Spatie\Permission\Models\Role;
use Spatie\Permission\Models\Permission;
use App\Models\User;

class DatabaseSeeder extends Seeder
{
    /**
     * Seed the application's database.
     */
    public function run(): void
    {
        // パーミッションの作成
        $editArticlesPermission = Permission::create(['name' => 'edit articles']);

        // ロールの作成
        $adminRole = Role::create(['name' => 'admin']);

        // ロールにパーミッションを付与
        $adminRole->givePermissionTo($editArticlesPermission);

        // admin1 ユーザーの作成
        $admin1 = User::factory()->create([
            'name' => 'Admin 1',
            'email' => 'admin1@example.com',
            'password' => bcrypt('password'),
        ]);

        // admin2 ユーザーの作成
        $admin2 = User::factory()->create([
            'name' => 'Admin 2',
            'email' => 'admin2@example.com',
            'password' => bcrypt('password'),
        ]);

        // ユーザーにadminロールを割り当て
        $admin1->assignRole('admin');
        $admin2->assignRole('admin');

        // 一般ユーザー
        $user1 = User::factory()->create([
            'name' => 'User 1',
            'email' => 'user1@example.com',
            'password' => bcrypt('password'),
        ]);

        // User::factory(50)->create();
        $this->call([
            SurveyQuestionSeeder::class,
        ]);
    }
}

これでfreshすれば、user1@example.com / passwordにてログイン可能になるだろう。

User 1でログインした状態。当然だが何もない。

ダッシュボードをControllerに移動

今dashboardはこれで出している
routes/web.php

Route::get('/dashboard', function () {
    return Inertia::render('Dashboard');
})->middleware(['auth', 'verified'])->name('dashboard');

これはControllerにした方がいいだろう

php artisan make:controller DashboardController

これでroutes/web.php

use App\Http\Controllers\DashboardController;

Route::get('/dashboard', [DashboardController::class, 'index'])
    ->middleware(['auth', 'verified'])
    ->name('dashboard');

app/Http/Controllers/DashboardController.php

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Inertia\Inertia;
use Inertia\Response;

class DashboardController extends Controller
{

    public function index(): Response
    {
        return Inertia::render('Dashboard', [ ]);
    }
}

これでControllerに移動できた

利用可能なsurveyの一覧を取得

今回は特にsurvey単位でのアクセス制限を儲けていないので、単純にsurveyがあればそれを取ればよい。とはいえdbをリフレッシュするとsurveyもクリアされちゃうので、なんか1つくらいないと開発が厳しいと思われる。

開発用ダミーsurvey

今、実は database/seeders/SurveySeeder.php が用意されているので、ここに作成していけばよい。の前に呼出を増やしておこう。

database/seeders/DatabaseSeeder.php

        $this->call([
            SurveyQuestionSeeder::class,
            SurveySeeder::class,
        ]);
<?php

namespace Database\Seeders;

use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use App\Models\Survey;

class SurveySeeder extends Seeder
{
    /**
     * Run the database seeds.
     */
    public function run(): void
    {
        Survey::factory()->create();
    }
}

こんな感じで作るんだけど、factoryが未設定なのでエラーとなる。ここも整理しておこう


英語の謎文章

実はあんま効果ないんだけどfakerを日本語にしたいなら

config/app.php

    'faker_locale' => 'ja_JP',

としておく

日本語だとカオス

まあ結局wordは日本語にならんし、realTextはこんなのなら無い方がいいんじゃないかという気もするけど…

このままだと質問が定義されてないのでpreviewを押してもこんな感じになる。

表示サレヌ

ってわけなので質問セットもseedしよう

storeのリファクタリング

app/Http/Controllers/SurveyController.php

    public function store(Request $request): RedirectResponse
    {
        $data = $request->all();
        $data['settings'] = [];
        // TODO: 本来はvalidationをかける
        $surveyQuestionSetId = $request->survey_question_set_id;
        $surveyStructure = SurveyQuestion::find($surveyQuestionSetId)->question_data;

        DB::beginTransaction();
        $survey = Survey::create($data);
        foreach ($surveyStructure['pages'] as $page) {
            $data = [
                'name'      => $page['name'],
                'survey_id' => $survey['id'],
            ];
            $surveyPage = SurveyPage::create($data);

            foreach ($page['elements'] as $element) {
                $data                 = [
                    'survey_page_id' => $surveyPage->id,
                    'type'           => $element['type'],
                    'title'          => $element['title'],
                    'is_required'    => $element['isRequired'] ?? false,
                ];
                $surveyElement = SurveyElement::create($data);

                if ($choices = $element['choices'] ?? null) {
                    foreach ($choices as $choice) {
                        $data = [
                            'survey_element_id' => $surveyElement->id,
                            'choice'            => $choice,
                        ];
                        SurveyElementChoice::create($data);
                    }
                }
            }
        }
        DB::commit();
        return redirect(route('surveys.index'))
            ->with(['success' => __('New Survey Created')])
        ;
    }

コントローラー長いのでforeachをサービスに移してしまいまっしょう。

app/Services/SurveyService.php

    public function createSurveyPage(Survey $survey, array $surveyStructure): void
    {
        foreach ($surveyStructure['pages'] as $page) {
            $data = [
                'name'      => $page['name'],
                'survey_id' => $survey->id,
            ];
            $surveyPage = SurveyPage::create($data);

            foreach ($page['elements'] as $element) {
                $data                 = [
                    'survey_page_id' => $surveyPage->id,
                    'type'           => $element['type'],
                    'title'          => $element['title'],
                    'is_required'    => $element['isRequired'] ?? false,
                ];
                $surveyElement = SurveyElement::create($data);

                if ($choices = $element['choices'] ?? null) {
                    foreach ($choices as $choice) {
                        $data = [
                            'survey_element_id' => $surveyElement->id,
                            'choice'            => $choice,
                        ];
                        SurveyElementChoice::create($data);
                    }
                }
            }
        }
    }

app/Http/Controllers/SurveyController.php

    public function store(Request $request, SurveyService $surveyService): RedirectResponse
    {
        $data = $request->all();
        $data['settings'] = [];
        // TODO: 本来はvalidationをかける
        $surveyQuestionSetId = $request->survey_question_set_id;
        $surveyStructure = SurveyQuestion::find($surveyQuestionSetId)->question_data;

        DB::beginTransaction();
        $survey = Survey::create($data);
        $surveyService->createSurveyPage($survey, $surveyStructure);
        DB::commit();
        return redirect(route('surveys.index'))
            ->with(['success' => __('New Survey Created')])
        ;
    }

再度seed

<?php

namespace Database\Seeders;

use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use App\Models\Survey;
use App\Models\SurveyQuestion;
use App\Services\SurveyService;

class SurveySeeder extends Seeder
{

    /**
     * Run the database seeds.
     */
    public function run(SurveyService $surveyService): void
    {
        $survey = Survey::factory()->create();
        $surveyStructure = SurveyQuestion::first()->question_data;
        $surveyService->createSurveyPage($survey, $surveyStructure);
    }
}

こうしておけば非常にシンプルに書けるよねっていう。

あらためてsurveyを取る

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Inertia\Inertia;
use Inertia\Response;
use App\Models\Survey;

class DashboardController extends Controller
{
    public function index(): Response
    {
        $surveys = Survey::latest()->get();
        return Inertia::render('Dashboard', [
            'surveys' => $surveys
        ]);
    }
}
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';
import { Head } from '@inertiajs/react';
import { useLaravelReactI18n } from 'laravel-react-i18n';
import PrimaryButton from '@/Components/PrimaryButton';
import SecondaryButton from '@/Components/SecondaryButton';

import {
  VscChecklist,
} from 'react-icons/vsc';

export default function Dashboard({ auth, surveys }) {
  const { t } = useLaravelReactI18n();
  return (
    <AuthenticatedLayout
      user={auth.user}
      header={<h2 className="font-semibold text-xl text-gray-800 leading-tight">{t('Dashboard')}</h2>}
    >
      <Head title={t('Dashboard')} />

      <div className="py-12">
        <div className="max-w-7xl mx-auto sm:px-6 lg:px-8">
          <div className="bg-white p-6 rounded shadow-md max-w-7xl mx-auto">
            <div className="flex justify-between items-center mb-4">
              <h3 className="text-2xl font-semibold">
                {t('Available Surveys')}
              </h3>
            </div>

            <div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
              {surveys.map((survey, index) => (
                <div key={index} className="bg-white rounded-lg shadow-lg p-4 border border-gray-300">
                  <div className="flex justify-between items-center mb-2">
                    <h4 className="text-lg font-semibold">
                      {survey.title}
                    </h4>
                  </div>

                  <p className="text-sm text-gray-700 mb-3">
                    {survey.description}
                  </p>

                  <PrimaryButton href={route('surveys.show', survey.id)}>
                    <VscChecklist className="mr-2" /> {t('Take')}
                  </PrimaryButton>
                </div>
              ))}
            </div>
          </div>
        </div>
      </div>
    </AuthenticatedLayout>
  );
}

こんな感じだが、surveys.showは今previewで使っている。ここはsurveyのtake用のコントローラーを新設してみよう。

artisan make:controller SurveyTakingController

routes/web.php

use App\Http\Controllers\SurveyTakingController;
Route::get('/dashboard', [DashboardController::class, 'index'])
    ->middleware(['auth', 'verified'])
    ->name('dashboard');
Route::get('surveys/{survey}/take', [SurveyTakingController::class, 'show'])
    ->middleware(['auth', 'verified'])
    ->name('surveys.take');

グループにしてもいいかもね

コントローラー

class SurveyTakingController extends Controller
{
    public function show(Survey $survey): Response
    {
        dd($survey);
        return Inertia::render('Surveys/Take', [
            'survey' => $survey
        ]);
    }
}

SurveyTakingController

ここで質問をちゃんと表示するようにする、っつってもほぼコピペ対応である

class SurveyTakingController extends Controller
{
    public function show(Survey $survey, SurveyService $surveyService): Response
    {
        $pagesData = $surveyService->getSurveyData($survey);
        $settings = $survey->settings;
        $surveyData = [
            'pages' => $pagesData,
        ];
        if ($settings) {
            $surveyData = array_merge($surveyData, $settings);
        }
        $surveyData = json_encode($surveyData, JSON_UNESCAPED_UNICODE);

        $responseCount = 0; // TODO


        return Inertia::render('Surveys/Take', [
            'surveyModel'   => $survey,
            'surveyData'    => $surveyData,
            'responseData'  => null, // TODO
            'readOnly'      => $responseCount ? true : false,
        ]);
    }
}

resources/js/Pages/Surveys/Take.jsx

import React, { useCallback } from 'react';
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';
import 'survey-core/defaultV2.min.css';
import { Model } from 'survey-core';
import { Survey } from 'survey-react-ui';
import { Head, router } from '@inertiajs/react';

import { VscTrash } from 'react-icons/vsc';
import { useLaravelReactI18n } from 'laravel-react-i18n';

export default function SurveyTake({
  auth, surveyModel, surveyData, responseData, readOnly,
}) {

  const survey = new Model(surveyData);
  if (readOnly) {
    survey.mode = 'display';
  }
  const surveyComplete = useCallback((sender) => {
    // TODO
    router.post(route('surveys.previewStore', surveyModel.id), sender.data);
  }, []);
  survey.onComplete.add(surveyComplete);
  survey.data = responseData;

  return (
    <AuthenticatedLayout
      user={auth.user}
      header={<h2 className="font-semibold text-xl text-gray-800 leading-tight">{surveyModel.title}</h2>}
    >
      <Head title={surveyModel.title} />

      <div className="py-12">
        <div className="max-w-7xl mx-auto sm:px-6 lg:px-8">
          <Survey model={survey} />
        </div>
      </div>
    </AuthenticatedLayout>
  );
}

これでまあいいんだけどPreviewStoreになってるのが問題なので保存用のコントローラーも作るし、保存用のモデル、tableも作る必要があるわね。

長くなったからここまでにしとこか?



いいなと思ったら応援しよう!