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にてログイン可能になるだろう。
ダッシュボードを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も作る必要があるわね。
長くなったからここまでにしとこか?