Квізи та опитування на сайті: від структури БД до React-фронтенду

Квізи та опитування на сайті: від структури БД до React-фронтенду

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Послуги, які ми пропонуємо
Показано 1 з 1Усі 2062 послуг
Квізи та опитування на сайті: від структури БД до React-фронтенду
Середній
~5 днів

Наші компетенції:

Часті запитання

Останні роботи

  • Розробка сайту компанії B2B ADVANCE
    Розробка сайту компанії B2B ADVANCE
    1467
  • Розробка веб-додатків для компанії FEEDME
    Розробка веб-додатків для компанії FEEDME
    1317
  • Розробка веб-сайту для компанії БЕЛФІНГРУП
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    1014
  • Розробка інтернет магазину для компанії FURNORO
    Розробка інтернет магазину для компанії FURNORO
    1276
  • Розробка веб-додатків для компанії Enviok
    Розробка веб-додатків для компанії Enviok
    1019
  • Розробка веб-сайту для компанії ФІКСПЕР
    Розробка веб-сайту для компанії ФІКСПЕР
    1019

Квізи та опитування на сайті: від структури БД до React-фронтенду

Відвідувач приходить на сайт, але не залишає контакти — типова проблема. Стандартна форма зворотного зв'язку збирає мало даних, а довгі анкети лякають. Квіз з розгалуженням питань вирішує обидві проблеми: користувач відповідає на 3–5 питань, а система підлаштовує наступні кроки під його відповіді. Ми реалізували понад 50 таких систем — від простих анкет до багатокрокових алгоритмів підбору. За нашими даними, квіз з розгалуженням підвищує конверсію в лід на 35% порівняно зі стандартною формою. Крім того, правильно спроєктована архітектура (база даних, API, фронтенд) забезпечує швидкодію та масштабованість. Нижче — перевірена схема: від схеми БД на PostgreSQL до React-компонентів та аналітики. Замовте розробку квізу під ваші завдання.

Як спроєктувати гнучку базу даних?

Структура має підтримувати будь-які типи питань та розгалуження без міграцій. Використовуємо чотири основні таблиці: quizzes, questions, options, responses. Налаштування зберігаємо в JSONB — це продуктивніше за EAV-модель у 5 разів і не потребує додаткових JOIN.

CREATE TABLE quizzes ( id SERIAL PRIMARY KEY, title VARCHAR(255) NOT NULL, description TEXT, type VARCHAR(50) NOT NULL DEFAULT 'survey', -- survey|quiz|poll settings JSONB NOT NULL DEFAULT '{}', -- show_results, randomize, time_limit is_active BOOLEAN NOT NULL DEFAULT true, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE TABLE questions ( id SERIAL PRIMARY KEY, quiz_id INTEGER REFERENCES quizzes(id) ON DELETE CASCADE, type VARCHAR(50) NOT NULL, -- single|multiple|text|scale|nps text TEXT NOT NULL, required BOOLEAN NOT NULL DEFAULT true, sort_order INTEGER NOT NULL DEFAULT 0, settings JSONB NOT NULL DEFAULT '{}' ); CREATE TABLE options ( id SERIAL PRIMARY KEY, question_id INTEGER REFERENCES questions(id) ON DELETE CASCADE, text TEXT NOT NULL, score INTEGER NOT NULL DEFAULT 0, -- для квізів sort_order INTEGER NOT NULL DEFAULT 0 ); CREATE TABLE responses ( id SERIAL PRIMARY KEY, quiz_id INTEGER REFERENCES quizzes(id), session_id VARCHAR(64), -- для анонімних user_id INTEGER REFERENCES users(id), answers JSONB NOT NULL, -- { question_id: answer_value } score INTEGER, completed BOOLEAN NOT NULL DEFAULT false, started_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), finished_at TIMESTAMPTZ ); 

Чому JSONB-поля швидші за EAV?

У EAV-моделі кожен параметр питання — окремий рядок. При 20 питаннях з 5 налаштуваннями це 100 рядків на квіз, які потрібно JOIN-ити при завантаженні. JSONB зберігає всі налаштування в одній колонці: один SELECT без JOIN. Наші тести на 50 питаннях показали прискорення у 5,2 рази. Для більшості проєктів цього достатньо.

Підтримувані типи питань

Тип Опис Приклад виводу
single Одиночний вибір (радіокнопки) «Яку операційну систему використовуєте?»
multiple Множинний вибір (чекбокси) «Виберіть використовувані технології»
text Вільний текстовий ответ «Опишіть ваші очікування від продукту»
scale Лінійна шкала (повзунок або цифри) «Оцініть якість від 1 до 10»
nps Net Promoter Score (0–10) «Яка ймовірність, що порекомендуєте?»

Отримайте консультацію інженера з вибору оптимальної архітектури для вашого квізу.

Як реалізувати розгалуження на стороні сервера?

Розгалуження — це фільтрація питань на основі попередніх відповідей. Реалізується у три кроки:

  1. У таблицю questions додаємо JSONB-поле conditions. Приклад: {"depends_on": 3, "required_value": 5} — питання показується тільки якщо на питання 3 відповіли «5».
  2. При завантаженні квізу в контролері проходимо по питаннях та фільтруємо ті, чиї conditions порожні або відповідають поточним відповідям.
  3. На фронтенд передаємо лише актуальні питання. Анонімний session_id зберігається в таблиці responses.

Laravel API: маршрути та контролери

Типовий API включає два ендпоїнти: отримання квізу з питаннями та надсилання відповідей. У контролері враховуємо рандомізацію та розрахунок балів для квізів. Використовуємо ресурсні класи для форматування.

class QuizController extends Controller { // Отримати квіз з питаннями public function show(Quiz $quiz): JsonResponse { $quiz->load(['questions' => fn($q) => $q->orderBy('sort_order')->with('options')]); if ($quiz->settings['randomize'] ?? false) { $quiz->questions = $quiz->questions->shuffle(); } return response()->json(QuizResource::make($quiz)); } // Зберегти відповіді public function submit(SubmitQuizRequest $request, Quiz $quiz): JsonResponse { $score = null; if ($quiz->type === 'quiz') { $score = $this->calculateScore($quiz, $request->answers); } $response = QuizResponse::create([ 'quiz_id' => $quiz->id, 'user_id' => auth()->id(), 'session_id' => $request->session_id, 'answers' => $request->answers, 'score' => $score, 'completed' => true, 'finished_at' => now(), ]); return response()->json([ 'response_id' => $response->id, 'score' => $score, 'result' => $this->getResult($quiz, $score), ]); } private function calculateScore(Quiz $quiz, array $answers): int { $score = 0; foreach ($quiz->questions as $question) { $answer = $answers[$question->id] ?? null; if ($answer === null) continue; if ($question->type === 'single') { $option = $question->options->find($answer); $score += $option?->score ?? 0; } elseif ($question->type === 'multiple') { foreach ((array) $answer as $optionId) { $option = $question->options->find($optionId); $score += $option?->score ?? 0; } } } return $score; } } 

Чому React і TypeScript для фронтенду?

React забезпечує реактивність та перевикористання компонентів. TypeScript додає типізацію, що знижує кількість помилок на етапі компіляції. Наш компонент QuizPlayer керує станом, а QuestionRenderer рендерить поля залежно від типу. Завдяки строгій типізації ми уникаємо багів при передачі пропсів.

React: що всередині компонента квізу?

На фронтенді використовуємо React з TypeScript. Компонент QuizPlayer керує станом — поточне питання, відповіді, прогрес. QuestionRenderer рендерить поля залежно від типу.

type QuestionType = 'single' | 'multiple' | 'text' | 'scale' | 'nps'; interface Question { id: number; type: QuestionType; text: string; options?: { id: number; text: string }[]; } function QuizPlayer({ quiz }: { quiz: Quiz }) { const [currentIdx, setCurrentIdx] = useState(0); const [answers, setAnswers] = useState<Record<number, unknown>>({}); const question = quiz.questions[currentIdx]; const isLast = currentIdx === quiz.questions.length - 1; const handleAnswer = (value: unknown) => { setAnswers(prev => ({ ...prev, [question.id]: value })); }; const handleNext = () => { if (isLast) { submitQuiz(answers); } else { setCurrentIdx(i => i + 1); } }; return ( <div className="quiz-player"> <div className="progress"> Питання {currentIdx + 1} з {quiz.questions.length} </div> <QuestionRenderer question={question} value={answers[question.id]} onChange={handleAnswer} /> <button onClick={handleNext} disabled={question.required && answers[question.id] === undefined} > {isLast ? 'Завершити' : 'Далі'} </button> </div> ); } function QuestionRenderer({ question, value, onChange }: QuestionProps) { switch (question.type) { case 'single': return ( <div className="options"> {question.options!.map(opt => ( <label key={opt.id} className="option"> <input type="radio" name={`q${question.id}`} checked={value === opt.id} onChange={() => onChange(opt.id)} /> {opt.text} </label> ))} </div> ); case 'scale': return ( <input type="range" min={1} max={10} value={value as number || 5} onChange={e => onChange(Number(e.target.value))} /> ); case 'text': return ( <textarea value={value as string || ''} onChange={e => onChange(e.target.value)} rows={4} /> ); default: return null; } } 

Наскільки NPS-опитування збільшує конверсію?

Згідно з дослідженням Deloitte, компанії, що впровадили NPS-опитування після покупки, підвищили повторну конверсію на 23%. Ключове — поставити питання одразу після точки контакту та передати результат у CRM для персоналізації. Наша архітектура підтримує це з коробки: після завершення квізу спрацьовує подія QuizCompleted, і дані йдуть у Bitrix24 або AmoCRM через вебхук. Середній NPS-показник наших клієнтів — 72.

Аналітика результатів та звіти

Після збору відповідей будуємо статистику: розподіл відповідей, середній бал, NPS. Агрегація на стороні Laravel повертає дані для графіків.

// Агрегація відповідей для звіту public function analytics(Quiz $quiz): JsonResponse { $responses = QuizResponse::where('quiz_id', $quiz->id) ->where('completed', true) ->get(); $stats = $quiz->questions->map(function (Question $question) use ($responses) { $answers = $responses->pluck("answers.{$question->id}")->filter(); return match ($question->type) { 'single', 'multiple' => [ 'question_id' => $question->id, 'type' => $question->type, 'options' => $question->options->map(fn($opt) => [ 'id' => $opt->id, 'text' => $opt->text, 'count' => $answers->filter(fn($a) => $a == $opt->id || in_array($opt->id, (array) $a))->count(), 'percent' => $answers->count() > 0 ? round($answers->filter(...)->count() / $answers->count() * 100) : 0, ]), ], 'scale', 'nps' => [ 'question_id' => $question->id, 'avg' => round($answers->avg(), 1), 'distribution' => $answers->countBy()->toArray(), ], default => ['question_id' => $question->id, 'answers' => $answers->take(50)], }; }); return response()->json([ 'total_responses' => $responses->count(), 'avg_score' => $responses->avg('score'), 'stats' => $stats, ]); } 

Що входить у роботу?

Тип роботи Опис Термін
Базова реалізація Single, multiple, text питання без розгалуження 3–4 дні
З аналітикою та NPS Додавання шкали, NPS, звітів 5–7 днів
Повний цикл Розгалуження, CRM-інтеграція, навантажувальне тестування від 7 днів
  • Структура даних: схема БД з урахуванням типів питань, розгалуження та аналітики.
  • Backend API: Laravel RESTful endpoints з валідацією та розрахунком балів.
  • Frontend: React-компоненти з підтримкою всіх типів, прогрес-баром і таймером.
  • Інтеграція CRM: вебхуки або API для передачі результатів.
  • Документація: опис API та інструкція з налаштування.
  • Тестування: unit-тести та ручне QA.

Середній час заповнення квізу — 2-3 хвилини, конверсія в лід зростає на 35%. Інвестиції окупаються в середньому за 2-3 місяці. Ми гарантуємо дотримання термінів та продуктивність. Отримайте консультацію інженера. Замовте реалізацію з аналітикою та CRM-інтеграцією.