๐ฏ ์จ๋ผ์ธ ํด์ฆ ์์ฑ & ๊ณต์ ํ๋ซํผ - ์น๊ตฌ๋ค๊ณผ ํจ๊ปํ๋ ์ง์ ๋ฐฐํ
quiz-maker-sharing-platform
๋ฌธ์ ์ค๋ช
๐ ํ๋ก์ ํธ ๊ฐ์
๋๊ตฌ๋ ์ฝ๊ฒ ํด์ฆ๋ฅผ ๋ง๋ค๊ณ ์น๊ตฌ๋ค๊ณผ ๊ณต์ ํ ์ ์๋ ์น ํ๋ซํผ์ ๋ง๋ญ๋๋ค. ํด์ฆ ์ ์์๋ ์นดํ ๊ณ ๋ฆฌ๋ณ๋ก ๋ฌธ์ ๋ฅผ ๋ฑ๋กํ๊ณ , ์ฐธ๊ฐ์๋ค์ ํด์ฆ๋ฅผ ํ๊ณ ์ ์๋ฅผ ํ์ธํ ์ ์์ต๋๋ค. Laravel + Inertia.js + React๋ก ๋ชจ๋ ธ๋ฆฌ์ค ํ์คํ ๊ตฌ์กฐ๋ฅผ ๊ตฌํํ๋ฉฐ, ์ค์๊ฐ ์ ์ ๊ณ์ฐ๊ณผ ์์ ํ์ ๊ธฐ๋ฅ์ ํฌํจํฉ๋๋ค.
โจ ํต์ฌ ๊ธฐ๋ฅ
ํ์ ๊ธฐ๋ฅ
- ํด์ฆ ์์ฑ: ์ ๋ชฉ, ์ค๋ช , ์นดํ ๊ณ ๋ฆฌ, ๋์ด๋ ์ค์
- ๋ฌธ์ ๋ฑ๋ก: ๊ฐ๊ด์ 4์ง์ ๋ค ๋ฌธ์ ์ถ๊ฐ (์ ๋ต 1๊ฐ ์ ํ)
- ํด์ฆ ๋ชฉ๋ก: ์นดํ ๊ณ ๋ฆฌ/๋์ด๋ ํํฐ๋ง, ๊ฒ์ ๊ธฐ๋ฅ
- ํด์ฆ ํ๊ธฐ: ๋ฌธ์ ๋ฅผ ์์๋๋ก ํ๊ณ ์ ๋ต ์ฒดํฌ
- ๊ฒฐ๊ณผ ํ์ด์ง: ์ด์ , ์ ๋ต๋ฅ , ์ค๋ต ๋ฌธ์ ๋ฆฌ๋ทฐ
- ์์ํ: ํด์ฆ๋ณ ์์ 10๋ช ์ ์ & ์ฐธ๊ฐ์ ์์
- ๋ง์ดํ์ด์ง: ๋ด๊ฐ ๋ง๋ ํด์ฆ, ์ฐธ์ฌํ ํด์ฆ ํ์คํ ๋ฆฌ
๋ณด๋์ค ๊ธฐ๋ฅ
- ํด์ฆ ๊ณต์ ๋งํฌ ์์ฑ (URL ๋ณต์ฌ)
- ๋ฌธ์ ์ด๋ฏธ์ง ์ ๋ก๋ (์ค๋ช ์ด๋ฏธ์ง ์ถ๊ฐ)
- ์๊ฐ ์ ํ ๋ชจ๋ (๋ฌธ์ ๋น ์ ํ ์๊ฐ)
- ํด์ฆ ์ข์์ & ๋ถ๋งํฌ
- ์ค๋ต ๋ ธํธ (ํ๋ฆฐ ๋ฌธ์ ๋ค์ ํ๊ธฐ)
- ์นดํ ๊ณ ๋ฆฌ๋ณ ํต๊ณ (๋ด๊ฐ ๊ฐํ ๋ถ์ผ ๋ถ์)
๐จ UI/UX ํฌ์ธํธ
- ํด์ฆ ํ์ด ์ ์ ํ์ง ํด๋ฆญ ์ ์ฆ๊ฐ์ ์ธ ์ ๋ต/์ค๋ต ํผ๋๋ฐฑ (์์ ๋ณ๊ฒฝ)
- ์งํ๋ฅ ํ๋ก๊ทธ๋ ์ค ๋ฐ (ํ์ฌ ๋ฌธ์ / ์ ์ฒด ๋ฌธ์ )
- ๊ฒฐ๊ณผ ํ์ด์ง์์ ์ ๋๋ฉ์ด์ ํจ๊ณผ๋ก ์ ์ ์นด์ดํธ์
- ์์ํ ์์๊ถ ๋ฐฐ์ง ํ์ (๊ธ/์/๋ ๋ฉ๋ฌ)
- ๋ฐ์ํ ๋์์ธ (๋ชจ๋ฐ์ผ์์๋ ํธํ๊ฒ ํด์ฆ ํ์ด)
๐ ๏ธ ๊ธฐ์ ์คํ
- Laravel 13.x (๋ฐฑ์๋ ๋ก์ง, DB, ์ธ์ฆ)
- Inertia.js (Laravel โ React ์ฐ๊ฒฐ)
- React 19 (ํ๋ก ํธ์๋ UI)
- Tailwind CSS (์คํ์ผ๋ง)
- SQLite (๊ฐ๋ฐ DB)
๐ฆ ๋ฐ์ดํฐ ๋ชจ๋ธ
- Users: ์ฌ์ฉ์ ์ ๋ณด
- Quizzes: ํด์ฆ ๋ฉํ๋ฐ์ดํฐ (์ ๋ชฉ, ์ค๋ช , ์นดํ ๊ณ ๋ฆฌ, ๋์ด๋, ์์ฑ์)
- Questions: ํด์ฆ ๋ฌธ์ (์ง๋ฌธ, ์ ํ์ง, ์ ๋ต, ์ด๋ฏธ์ง)
- Attempts: ํด์ฆ ์ฐธ์ฌ ๊ธฐ๋ก (์ฌ์ฉ์, ํด์ฆ, ์ ์, ์ ์ถ ์๊ฐ)
- Answers: ์ฌ์ฉ์๊ฐ ์ ํํ ๋ต๋ณ ๊ธฐ๋ก
์ ์ฝ ์กฐ๊ฑด
- โข Laravel 13.x + Inertia.js + React 19 ๋ชจ๋ ธ๋ฆฌ์ค ๊ตฌ์กฐ (๋ณ๋ API ๋ผ์ฐํธ ์์)
- โข Laravel Breeze(React + Inertia) starter kit์ผ๋ก ํ๋ก์ ํธ ์ด๊ธฐํ
- โข Inertia::render()๋ก ์ปจํธ๋กค๋ฌ์์ React ์ปดํฌ๋ํธ์ ๋ฐ์ดํฐ ์ ๋ฌ
- โข SQLite ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฌ์ฉ (๋ง์ด๊ทธ๋ ์ด์ ํ์)
- โข Eloquent ORM์ผ๋ก ๋ชจ๋ธ ๊ด๊ณ ์ ์ (hasMany, belongsTo)
- โข Tailwind CSS๋ก ์คํ์ผ๋ง (CDN ๋๋ Vite ๋น๋)
- โข React hooks(useState, useEffect) ํ์ฉ
- โข ํผ ์ ์ถ ์ Inertia form helper ์ฌ์ฉ (router.post, router.put ๋ฑ)
- โข ์ธ์ฆ ๊ธฐ๋ฅ (Laravel Breeze ๊ธฐ๋ณธ ์ ๊ณต)
- โข ์ค์๊ฐ ์ฑํ ๊ธ์ง (HTTP ๊ธฐ๋ฐ ๊ธฐ๋ฅ๋ง ์ฌ์ฉ)
ํ๋กฌํํธ ๋ ์ํผ
์๋ ํ๋กฌํํธ๋ฅผ ์์๋๋ก AI์๊ฒ ๋ณด๋ด๋ฉด ๋ฉ๋๋ค. ๋ณต์ฌ ๋ฒํผ์ ๋๋ฌ ๋ฐ๋ก ์ฌ์ฉํ์ธ์.
mkdir quiz-maker-sharing-platform && cd quiz-maker-sharing-platform ์ด ํด๋์์ ํ๋ก์ ํธ๋ฅผ ์์ํฉ๋๋ค. Laravel 13.x + Inertia.js + React 19 ํ๋ก์ ํธ๋ฅผ ์์ฑํ์ธ์. 1. Laravel Breeze(React + Inertia) starter kit์ผ๋ก ํ๋ก์ ํธ ์ด๊ธฐํ: - `composer create-project laravel/laravel .` - `composer require laravel/breeze --dev` - `php artisan breeze:install react` - `npm install && npm run dev` 2. SQLite ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ค์ : - `.env` ํ์ผ์์ `DB_CONNECTION=sqlite` ์ค์ - `database/database.sqlite` ํ์ผ ์์ฑ 3. ๊ธฐ๋ณธ ๋ง์ด๊ทธ๋ ์ด์ ์คํ: - `php artisan migrate` 4. Tailwind CSS ์ค์ ํ์ธ (Breeze๊ฐ ์๋ ์ค์ ) 5. ๊ฐ๋ฐ ์๋ฒ ์คํ: - `php artisan serve` (๋ฐฑ์๋) - `npm run dev` (ํ๋ก ํธ์๋ Vite) 6. ๋ธ๋ผ์ฐ์ ์์ `http://localhost:8000` ์ ์ํ์ฌ Laravel Breeze ๊ธฐ๋ณธ ํ์ด์ง ํ์ธ ์๋ฃ ํ ํ๋ก์ ํธ ๊ตฌ์กฐ์ Breeze ๊ธฐ๋ณธ ์ธ์ฆ ํ์ด์ง(๋ก๊ทธ์ธ/ํ์๊ฐ์ )๊ฐ ์๋ํ๋์ง ํ์ธํ์ธ์.
๊ธฐ๋ ๊ฒฐ๊ณผ: Laravel 13.x + Breeze(React + Inertia) ํ๋ก์ ํธ ์์ฑ ์๋ฃ. ๊ธฐ๋ณธ ์ธ์ฆ ๊ธฐ๋ฅ(๋ก๊ทธ์ธ/ํ์๊ฐ์ ) ์๋. SQLite DB ์ฐ๊ฒฐ ์๋ฃ. Tailwind CSS ์ ์ฉ๋ ๊ธฐ๋ณธ ๋ ์ด์์ ํ์ธ ๊ฐ๋ฅ.
ํด์ฆ ํ๋ซํผ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ๊ตฌ์กฐ๋ฅผ ์ค๊ณํ๊ณ ๋ง์ด๊ทธ๋ ์ด์ ์ ์์ฑํ์ธ์. 1. Quizzes ํ ์ด๋ธ ๋ง์ด๊ทธ๋ ์ด์ ์์ฑ: - `php artisan make:migration create_quizzes_table` - ์ปฌ๋ผ: id, user_id(์ธ๋ํค), title(string), description(text), category(string), difficulty(enum: easy/medium/hard), created_at, updated_at 2. Questions ํ ์ด๋ธ ๋ง์ด๊ทธ๋ ์ด์ ์์ฑ: - `php artisan make:migration create_questions_table` - ์ปฌ๋ผ: id, quiz_id(์ธ๋ํค), question_text(text), option_a(string), option_b(string), option_c(string), option_d(string), correct_answer(enum: a/b/c/d), image_url(string, nullable), order(integer), created_at, updated_at 3. Attempts ํ ์ด๋ธ ๋ง์ด๊ทธ๋ ์ด์ ์์ฑ: - `php artisan make:migration create_attempts_table` - ์ปฌ๋ผ: id, user_id(์ธ๋ํค), quiz_id(์ธ๋ํค), score(integer), total_questions(integer), completed_at(timestamp), created_at, updated_at 4. Answers ํ ์ด๋ธ ๋ง์ด๊ทธ๋ ์ด์ ์์ฑ: - `php artisan make:migration create_answers_table` - ์ปฌ๋ผ: id, attempt_id(์ธ๋ํค), question_id(์ธ๋ํค), selected_answer(enum: a/b/c/d), is_correct(boolean), created_at, updated_at 5. ๋ง์ด๊ทธ๋ ์ด์ ์คํ: - `php artisan migrate` 6. ๋ชจ๋ธ ์์ฑ: - `php artisan make:model Quiz` - `php artisan make:model Question` - `php artisan make:model Attempt` - `php artisan make:model Answer` 7. ๋ชจ๋ธ์ ๊ด๊ณ ์ ์: - Quiz: hasMany(Questions), hasMany(Attempts), belongsTo(User) - Question: belongsTo(Quiz) - Attempt: belongsTo(User), belongsTo(Quiz), hasMany(Answers) - Answer: belongsTo(Attempt), belongsTo(Question) ์๋ฃ ํ `php artisan tinker`๋ก ๋ชจ๋ธ ๊ด๊ณ๊ฐ ์ ๋๋ก ์๋ํ๋์ง ํ ์คํธํ์ธ์.
๊ธฐ๋ ๊ฒฐ๊ณผ: 4๊ฐ ํ ์ด๋ธ(quizzes, questions, attempts, answers) ๋ง์ด๊ทธ๋ ์ด์ ์๋ฃ. ๋ชจ๋ธ ํ์ผ ์์ฑ ๋ฐ Eloquent ๊ด๊ณ ์ ์ ์๋ฃ. ์ธ๋ํค ์ ์ฝ์กฐ๊ฑด ์ค์ ์๋ฃ.
ํด์ฆ ์์ฑ ๊ธฐ๋ฅ์ ๊ตฌํํ์ธ์.
1. QuizController ์์ฑ:
- `php artisan make:controller QuizController`
2. ๋ผ์ฐํธ ์ ์ (`routes/web.php`):
- `Route::middleware('auth')->group(function() { ... });` ์์ ์ถ๊ฐ
- GET `/quizzes/create` โ QuizController@create (ํด์ฆ ์์ฑ ํผ)
- POST `/quizzes` โ QuizController@store (ํด์ฆ ์ ์ฅ)
- GET `/quizzes/{quiz}/questions/create` โ QuizController@createQuestion (๋ฌธ์ ์ถ๊ฐ ํผ)
- POST `/quizzes/{quiz}/questions` โ QuizController@storeQuestion (๋ฌธ์ ์ ์ฅ)
3. QuizController ๋ฉ์๋ ๊ตฌํ:
- `create()`: `Inertia::render('Quiz/Create')` ๋ฐํ
- `store(Request $request)`: ํผ ๋ฐ์ดํฐ ๊ฒ์ฆ ํ Quiz ๋ชจ๋ธ ์์ฑ, `/quizzes/{quiz}/questions/create`๋ก ๋ฆฌ๋ค์ด๋ ํธ
- `createQuestion(Quiz $quiz)`: ํด์ฆ ์ ๋ณด์ ํจ๊ป `Inertia::render('Quiz/AddQuestion', ['quiz' => $quiz])` ๋ฐํ
- `storeQuestion(Request $request, Quiz $quiz)`: ๋ฌธ์ ๋ฐ์ดํฐ ๊ฒ์ฆ ํ Question ๋ชจ๋ธ ์์ฑ, ๊ฐ์ ํ์ด์ง๋ก ๋ฆฌ๋ค์ด๋ ํธ (๊ณ์ ๋ฌธ์ ์ถ๊ฐ ๊ฐ๋ฅ)
4. React ์ปดํฌ๋ํธ ์์ฑ:
- `resources/js/Pages/Quiz/Create.jsx`: ํด์ฆ ๋ฉํ๋ฐ์ดํฐ ์
๋ ฅ ํผ (์ ๋ชฉ, ์ค๋ช
, ์นดํ
๊ณ ๋ฆฌ ์ ํ, ๋์ด๋ ๋ผ๋์ค ๋ฒํผ)
- `resources/js/Pages/Quiz/AddQuestion.jsx`: ๋ฌธ์ ์
๋ ฅ ํผ (์ง๋ฌธ, 4๊ฐ ์ ํ์ง, ์ ๋ต ๋ผ๋์ค ๋ฒํผ, ์ด๋ฏธ์ง URL ์ต์
)
- Inertia form helper ์ฌ์ฉ: `import { useForm } from '@inertiajs/react';`
- `form.post('/quizzes')` ๋๋ `form.post('/quizzes/{quiz.id}/questions')` ํํ๋ก ์ ์ถ
5. Tailwind CSS๋ก ํผ ์คํ์ผ๋ง:
- ์
๋ ฅ ํ๋, ๋ฒํผ, ๋ผ๋์ค ๋ฒํผ ๋์์ธ
- ์๋ฌ ๋ฉ์์ง ํ์ (Inertia errors prop ํ์ฉ)
์๋ฃ ํ ๋ก๊ทธ์ธํ ์ํ์์ `/quizzes/create`๋ก ์ด๋ํ์ฌ ํด์ฆ๋ฅผ ์์ฑํ๊ณ ๋ฌธ์ ๋ฅผ ์ถ๊ฐํ ์ ์๋์ง ํ
์คํธํ์ธ์.
๊ธฐ๋ ๊ฒฐ๊ณผ: ํด์ฆ ์์ฑ ํผ๊ณผ ๋ฌธ์ ์ถ๊ฐ ํผ์ด ์๋. ํด์ฆ ๋ฉํ๋ฐ์ดํฐ์ ๋ฌธ์ ๊ฐ DB์ ์ ์ฅ๋จ. React ์ปดํฌ๋ํธ์์ Inertia form helper๋ก ๋ฐ์ดํฐ ์ ์ถ ๊ฐ๋ฅ. ํผ ๊ฒ์ฆ ์๋ฌ ๋ฉ์์ง ํ์.
ํด์ฆ ๋ชฉ๋ก ํ์ด์ง์ ์์ธ ํ์ด์ง๋ฅผ ๊ตฌํํ์ธ์.
1. QuizController์ ๋ฉ์๋ ์ถ๊ฐ:
- `index(Request $request)`: ํด์ฆ ๋ชฉ๋ก ์กฐํ (์นดํ
๊ณ ๋ฆฌ/๋์ด๋ ํํฐ๋ง, ๊ฒ์ ๊ธฐ๋ฅ)
- ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ๋ก `category`, `difficulty`, `search` ๋ฐ๊ธฐ
- `Quiz::with('user')->when(...)->paginate(12)` ํํ๋ก ํํฐ๋ง ๊ตฌํ
- `Inertia::render('Quiz/Index', ['quizzes' => $quizzes, 'filters' => $request->only(['category', 'difficulty', 'search'])])` ๋ฐํ
- `show(Quiz $quiz)`: ํด์ฆ ์์ธ ์ ๋ณด (๋ฌธ์ ๊ฐ์, ์์ฑ์, ์ค๋ช
๋ฑ)
- `$quiz->load('questions', 'user');`
- `Inertia::render('Quiz/Show', ['quiz' => $quiz])` ๋ฐํ
2. ๋ผ์ฐํธ ์ถ๊ฐ:
- GET `/quizzes` โ QuizController@index
- GET `/quizzes/{quiz}` โ QuizController@show
3. React ์ปดํฌ๋ํธ ์์ฑ:
- `resources/js/Pages/Quiz/Index.jsx`: ํด์ฆ ์นด๋ ๊ทธ๋ฆฌ๋ ๋ ์ด์์
- ํํฐ๋ง UI (์นดํ
๊ณ ๋ฆฌ ๋๋กญ๋ค์ด, ๋์ด๋ ์ฒดํฌ๋ฐ์ค, ๊ฒ์ ์
๋ ฅ)
- Inertia Link๋ก ํด์ฆ ์์ธ ํ์ด์ง ์ด๋: `<Link href={`/quizzes/${quiz.id}`}>`
- ํ์ด์ง๋ค์ด์
์ปดํฌ๋ํธ (Inertia์์ ์ ๊ณตํ๋ links prop ํ์ฉ)
- `resources/js/Pages/Quiz/Show.jsx`: ํด์ฆ ์์ธ ์ ๋ณด ํ์
- ์ ๋ชฉ, ์ค๋ช
, ์นดํ
๊ณ ๋ฆฌ, ๋์ด๋, ๋ฌธ์ ๊ฐ์, ์์ฑ์ ํ์
- "ํด์ฆ ์์ํ๊ธฐ" ๋ฒํผ โ `/quizzes/{quiz.id}/play`๋ก ์ด๋
4. Tailwind CSS๋ก ์คํ์ผ๋ง:
- ํด์ฆ ์นด๋: ๊ทธ๋ฆผ์, ํธ๋ฒ ํจ๊ณผ, ๋ฐฐ์ง(์นดํ
๊ณ ๋ฆฌ/๋์ด๋)
- ํํฐ๋ง UI: ๋๋กญ๋ค์ด, ์ฒดํฌ๋ฐ์ค ์คํ์ผ
- ์์ธ ํ์ด์ง: ํฐ ์ ๋ชฉ, ์ค๋ช
ํ
์คํธ, CTA ๋ฒํผ
์๋ฃ ํ `/quizzes`์์ ํด์ฆ ๋ชฉ๋ก์ด ํ์๋๊ณ , ํํฐ๋ง/๊ฒ์์ด ์๋ํ๋ฉฐ, ์์ธ ํ์ด์ง๋ก ์ด๋ํ ์ ์๋์ง ํ
์คํธํ์ธ์.
๊ธฐ๋ ๊ฒฐ๊ณผ: ํด์ฆ ๋ชฉ๋ก ํ์ด์ง์์ ์นดํ ๊ณ ๋ฆฌ/๋์ด๋ ํํฐ๋ง๊ณผ ๊ฒ์ ๊ธฐ๋ฅ ์๋. ํด์ฆ ์นด๋ ํด๋ฆญ ์ ์์ธ ํ์ด์ง๋ก ์ด๋. ํ์ด์ง๋ค์ด์ ์๋. ํด์ฆ ์์ธ ์ ๋ณด ํ์.
ํด์ฆ ํ์ด ๊ธฐ๋ฅ์ ๊ตฌํํ์ธ์.
1. QuizController์ ๋ฉ์๋ ์ถ๊ฐ:
- `play(Quiz $quiz)`: ํด์ฆ ํ์ด ์์
- `$quiz->load('questions');`
- ์๋ก์ด Attempt ๋ ์ฝ๋ ์์ฑ (user_id, quiz_id, score=0, total_questions)
- `Inertia::render('Quiz/Play', ['quiz' => $quiz, 'attempt' => $attempt])` ๋ฐํ
- `submitAnswer(Request $request, Quiz $quiz, Attempt $attempt)`: ๋ต๋ณ ์ ์ถ
- ์์ฒญ ๋ฐ์ดํฐ: `question_id`, `selected_answer`
- Answer ๋ ์ฝ๋ ์์ฑ (attempt_id, question_id, selected_answer, is_correct)
- ์ ๋ต ์ฌ๋ถ ํ์ธ ํ Attempt์ score ์
๋ฐ์ดํธ
- JSON ์๋ต: `{ correct: true/false, correct_answer: 'a' }`
- `result(Quiz $quiz, Attempt $attempt)`: ๊ฒฐ๊ณผ ํ์ด์ง
- `$attempt->load('answers.question');`
- Attempt์ completed_at ํ์์คํฌํ ์
๋ฐ์ดํธ
- `Inertia::render('Quiz/Result', ['quiz' => $quiz, 'attempt' => $attempt])` ๋ฐํ
2. ๋ผ์ฐํธ ์ถ๊ฐ:
- GET `/quizzes/{quiz}/play` โ QuizController@play (middleware: auth)
- POST `/quizzes/{quiz}/attempts/{attempt}/answer` โ QuizController@submitAnswer
- GET `/quizzes/{quiz}/attempts/{attempt}/result` โ QuizController@result
3. React ์ปดํฌ๋ํธ ์์ฑ:
- `resources/js/Pages/Quiz/Play.jsx`: ํด์ฆ ํ์ด ์ธํฐํ์ด์ค
- useState๋ก ํ์ฌ ๋ฌธ์ ์ธ๋ฑ์ค ๊ด๋ฆฌ
- ๋ฌธ์ ํ์: ์ง๋ฌธ, 4๊ฐ ์ ํ์ง ๋ฒํผ
- ์ ํ์ง ํด๋ฆญ ์ `/quizzes/{quiz.id}/attempts/{attempt.id}/answer`๋ก POST ์์ฒญ (axios ์ฌ์ฉ)
- ์๋ต ๋ฐ์ผ๋ฉด ์ ๋ต/์ค๋ต ํผ๋๋ฐฑ ํ์ (๋ฒํผ ์์ ๋ณ๊ฒฝ: ์ ๋ต=์ด๋ก, ์ค๋ต=๋นจ๊ฐ)
- 2์ด ํ ๋ค์ ๋ฌธ์ ๋ก ์ด๋ (์ธ๋ฑ์ค ์ฆ๊ฐ)
- ๋ง์ง๋ง ๋ฌธ์ ์๋ฃ ์ `/quizzes/{quiz.id}/attempts/{attempt.id}/result`๋ก ๋ฆฌ๋ค์ด๋ ํธ
- ์งํ๋ฅ ํ๋ก๊ทธ๋ ์ค ๋ฐ: `(currentIndex + 1) / totalQuestions * 100`
- `resources/js/Pages/Quiz/Result.jsx`: ๊ฒฐ๊ณผ ํ์ด์ง
- ์ด์ , ์ ๋ต๋ฅ ํ์
- ์ค๋ต ๋ฌธ์ ๋ฆฌ์คํธ (์ง๋ฌธ, ๋ด๊ฐ ์ ํํ ๋ต, ์ ๋ต)
- "๋ค์ ํ๊ธฐ" ๋ฒํผ, "ํด์ฆ ๋ชฉ๋ก์ผ๋ก" ๋ฒํผ
4. Tailwind CSS๋ก ์คํ์ผ๋ง:
- ์ ํ์ง ๋ฒํผ: ํฐ ํฌ๊ธฐ, ํธ๋ฒ ํจ๊ณผ, ์ ๋ต/์ค๋ต ์์ ์ ๋๋ฉ์ด์
- ํ๋ก๊ทธ๋ ์ค ๋ฐ: ์๋จ ๊ณ ์ , ์์ ๊ทธ๋ผ๋ฐ์ด์
- ๊ฒฐ๊ณผ ํ์ด์ง: ์ ์ ์นด์ดํธ์
์ ๋๋ฉ์ด์
(CSS transition)
์๋ฃ ํ ํด์ฆ๋ฅผ ํ๊ณ ๊ฒฐ๊ณผ ํ์ด์ง์์ ์ ์์ ์ค๋ต ๋ฆฌ๋ทฐ๋ฅผ ํ์ธํ ์ ์๋์ง ํ
์คํธํ์ธ์.
๊ธฐ๋ ๊ฒฐ๊ณผ: ํด์ฆ ํ์ด ํ์ด์ง์์ ๋ฌธ์ ๋ฅผ ์์๋๋ก ํ ์ ์์. ์ ํ์ง ํด๋ฆญ ์ ์ฆ๊ฐ์ ์ธ ์ ๋ต/์ค๋ต ํผ๋๋ฐฑ. ์งํ๋ฅ ํ๋ก๊ทธ๋ ์ค ๋ฐ ์๋. ๊ฒฐ๊ณผ ํ์ด์ง์์ ์ ์์ ์ค๋ต ๋ฆฌ์คํธ ํ์.
์์ํ์ ๋ง์ดํ์ด์ง ๊ธฐ๋ฅ์ ๊ตฌํํ์ธ์.
1. QuizController์ ๋ฉ์๋ ์ถ๊ฐ:
- `leaderboard(Quiz $quiz)`: ํด์ฆ๋ณ ์์ํ
- `Attempt::where('quiz_id', $quiz->id)->with('user')->orderBy('score', 'desc')->orderBy('completed_at', 'asc')->take(10)->get();`
- `Inertia::render('Quiz/Leaderboard', ['quiz' => $quiz, 'topAttempts' => $topAttempts])` ๋ฐํ
2. UserController ์์ฑ ๋ฐ ๋ฉ์๋ ์ถ๊ฐ:
- `php artisan make:controller UserController`
- `myQuizzes()`: ๋ด๊ฐ ๋ง๋ ํด์ฆ ๋ชฉ๋ก
- `Quiz::where('user_id', auth()->id())->with('questions')->get();`
- `Inertia::render('User/MyQuizzes', ['quizzes' => $quizzes])` ๋ฐํ
- `myAttempts()`: ๋ด๊ฐ ์ฐธ์ฌํ ํด์ฆ ํ์คํ ๋ฆฌ
- `Attempt::where('user_id', auth()->id())->with('quiz')->orderBy('completed_at', 'desc')->get();`
- `Inertia::render('User/MyAttempts', ['attempts' => $attempts])` ๋ฐํ
3. ๋ผ์ฐํธ ์ถ๊ฐ:
- GET `/quizzes/{quiz}/leaderboard` โ QuizController@leaderboard
- GET `/my-quizzes` โ UserController@myQuizzes (middleware: auth)
- GET `/my-attempts` โ UserController@myAttempts (middleware: auth)
4. React ์ปดํฌ๋ํธ ์์ฑ:
- `resources/js/Pages/Quiz/Leaderboard.jsx`: ์์ํ ํ
์ด๋ธ
- ์์, ์ฌ์ฉ์ ์ด๋ฆ, ์ ์, ์๋ฃ ์๊ฐ ํ์
- ์์ 3๋ช
์๊ฒ ๋ฉ๋ฌ ์์ด์ฝ ํ์ (๐ฅ๐ฅ๐ฅ)
- ๋ด ์์ ํ์ด๋ผ์ดํธ (๋ฐฐ๊ฒฝ ์์ ๋ณ๊ฒฝ)
- `resources/js/Pages/User/MyQuizzes.jsx`: ๋ด๊ฐ ๋ง๋ ํด์ฆ ์นด๋ ๊ทธ๋ฆฌ๋
- ํด์ฆ ์ ๋ชฉ, ๋ฌธ์ ๊ฐ์, ์์ฑ ๋ ์ง
- "์์ " ๋ฒํผ, "์ญ์ " ๋ฒํผ
- `resources/js/Pages/User/MyAttempts.jsx`: ์ฐธ์ฌ ํ์คํ ๋ฆฌ ๋ฆฌ์คํธ
- ํด์ฆ ์ ๋ชฉ, ์ ์, ์ ๋ต๋ฅ , ์ฐธ์ฌ ๋ ์ง
- "๋ค์ ํ๊ธฐ" ๋ฒํผ, "๊ฒฐ๊ณผ ๋ณด๊ธฐ" ๋ฒํผ
5. ๋ค๋น๊ฒ์ด์
๋ฉ๋ด ์ถ๊ฐ:
- `resources/js/Layouts/AuthenticatedLayout.jsx` ๋๋ ํค๋ ์ปดํฌ๋ํธ์ ๋ฉ๋ด ์ถ๊ฐ
- "ํด์ฆ ๋ง๋ค๊ธฐ", "๋ด ํด์ฆ", "์ฐธ์ฌ ํ์คํ ๋ฆฌ" ๋งํฌ
6. Tailwind CSS๋ก ์คํ์ผ๋ง:
- ์์ํ: ํ
์ด๋ธ ์คํ์ผ, ๋ฉ๋ฌ ์์ด์ฝ, ๋ด ์์ ํ์ด๋ผ์ดํธ
- ๋ง์ดํ์ด์ง: ์นด๋ ๋ ์ด์์, ๋ฒํผ ์คํ์ผ
์๋ฃ ํ ์์ํ์์ ์์ 10๋ช
์ด ํ์๋๊ณ , ๋ง์ดํ์ด์ง์์ ๋ด๊ฐ ๋ง๋ ํด์ฆ์ ์ฐธ์ฌ ํ์คํ ๋ฆฌ๋ฅผ ํ์ธํ ์ ์๋์ง ํ
์คํธํ์ธ์.
๊ธฐ๋ ๊ฒฐ๊ณผ: ํด์ฆ๋ณ ์์ํ์์ ์์ 10๋ช ํ์. ์์ 3๋ช ์๊ฒ ๋ฉ๋ฌ ํ์. ๋ง์ดํ์ด์ง์์ ๋ด๊ฐ ๋ง๋ ํด์ฆ์ ์ฐธ์ฌ ํ์คํ ๋ฆฌ ํ์ธ ๊ฐ๋ฅ. ๋ค๋น๊ฒ์ด์ ๋ฉ๋ด๋ก ์ฝ๊ฒ ์ด๋ ๊ฐ๋ฅ.
์ ์ถ๋ ์ํ (0)
๋ก๊ทธ์ธ ํ ์ ์ถ์์ง ์ ์ถ๋ ์ํ์ด ์์ต๋๋ค. ์ฒซ ๋ฒ์งธ ๋์ ์๊ฐ ๋์ด๋ณด์ธ์!