diff --git a/app/Http/Controllers/FormController.php b/app/Http/Controllers/FormController.php index 5ead730..13743e3 100644 --- a/app/Http/Controllers/FormController.php +++ b/app/Http/Controllers/FormController.php @@ -25,6 +25,15 @@ class FormController extends Controller return view('forms.create'); } + public function togglePublish(Form $form) + { + $form->is_published = !$form->is_published; + $form->save(); + + return redirect()->route('forms.show', $form->id)->with('success', 'Form publish status updated.'); + } + + public function edit(Form $form) { // Questions are already fetched with their options cast to array due to the casts property @@ -40,20 +49,22 @@ class FormController extends Controller public function store(Request $request) - { +{ + try { $validatedData = $request->validate([ 'title' => 'required|string|max:255', 'description' => 'nullable|string', 'questions' => 'required|array', - 'questions.*.type' => 'required|string|in:multiple_choice,checkbox,dropdown,short_answer,long_answer', - 'questions.*.text' => 'required|string', // This should match the key used in the JavaScript + 'questions.*.type' => 'required|string|in:multiple_choice,checkbox,dropdown,text', + 'questions.*.text' => 'required|string', 'questions.*.options' => 'nullable|array', + 'questions.*.required' => 'nullable|boolean', ]); $form = new Form(); $form->title = $validatedData['title']; $form->description = $validatedData['description']; - $form->is_published = $request->input('is_published', false); // Default to false if not provided + $form->is_published = $request->input('is_published', false); $form->user_id = Auth::id(); $form->save(); @@ -61,15 +72,18 @@ class FormController extends Controller $question = new Question(); $question->form_id = $form->id; $question->type = $questionData['type']; - $question->question_text = $questionData['text']; // Ensure this matches the key in the validated data + $question->question_text = $questionData['text']; $question->options = isset($questionData['options']) ? json_encode($questionData['options']) : null; - + $question->required = isset($questionData['required']) ? $questionData['required'] : false; $question->save(); } - Session::flash('success', 'Form created successfully!'); - return response()->json(['success' => true, 'form_id' => $form->id]); - } + return response()->json(['success' => true, 'form_id' => $form->id]); + } catch (\Exception $e) { + Log::error('Error saving form: ' . $e->getMessage(), ['exception' => $e]); + return response()->json(['success' => false, 'message' => 'Error saving form'], 500); + } +} public function show(Form $form) { @@ -99,7 +113,7 @@ class FormController extends Controller 'description' => 'nullable|string|max:255', 'questions' => 'required|array', 'questions.*.id' => 'nullable|exists:questions,id', - 'questions.*.type' => 'required|string|in:multiple_choice,checkbox,dropdown,short_answer,long_answer', + 'questions.*.type' => 'required|string|in:multiple_choice,checkbox,dropdown,text', 'questions.*.text' => 'required|string|max:255', 'questions.*.options' => 'nullable|array', 'questions.*.options.*' => 'nullable|string|max:255', @@ -149,6 +163,8 @@ class FormController extends Controller + + public function destroy(Form $form) { // This will also delete all related questions and responses due to foreign key constraints diff --git a/app/Http/Controllers/ResponseController.php b/app/Http/Controllers/ResponseController.php index ab2fe34..c70c197 100644 --- a/app/Http/Controllers/ResponseController.php +++ b/app/Http/Controllers/ResponseController.php @@ -103,12 +103,27 @@ public function viewResponses(Form $form) { Log::info($request->all()); // Log the entire request data for debugging + // Fetch all questions for the form + $questions = $form->questions; + + // Extract IDs of required questions + $requiredQuestionIds = $questions->where('required', true)->pluck('id')->toArray(); + // Validate and process form submission $validatedData = $request->validate([ 'answers' => 'required|array', - 'answers.*' => 'required', + 'answers.*' => 'required', // Ensure all answers are provided ]); + // Ensure all required questions are answered + foreach ($requiredQuestionIds as $requiredQuestionId) { + if (!array_key_exists($requiredQuestionId, $validatedData['answers'])) { + return redirect()->back() + ->withErrors(['errors' => 'Please answer all required questions.']) + ->withInput(); + } + } + Log::info($validatedData); // Log the validated data for debugging // Generate a UUID for response_id @@ -129,4 +144,9 @@ public function viewResponses(Form $form) return redirect()->route('responses.showForm', $form) ->with('success', 'Response submitted successfully.'); } + + + + + } diff --git a/database/migrations/2024_07_10_073443_create_questions_table.php b/database/migrations/2024_07_10_073443_create_questions_table.php index af395ae..16ea7b4 100644 --- a/database/migrations/2024_07_10_073443_create_questions_table.php +++ b/database/migrations/2024_07_10_073443_create_questions_table.php @@ -14,8 +14,9 @@ return new class extends Migration Schema::create('questions', function (Blueprint $table) { $table->id(); $table->foreignId('form_id')->constrained()->onDelete('cascade'); - $table->enum('type', ['multiple_choice', 'checkbox', 'dropdown', 'short_answer', 'long_answer']); + $table->enum('type', ['multiple_choice', 'checkbox', 'dropdown', 'text']); $table->text('question_text'); + $table->boolean('required')->default(false); $table->json('options')->nullable(); // Store options as JSON if applicable $table->timestamps(); }); diff --git a/public/js/script.js b/public/js/script.js index 9fea1f5..4c6026a 100644 --- a/public/js/script.js +++ b/public/js/script.js @@ -1,216 +1,3 @@ -// document.addEventListener("DOMContentLoaded", function () { -// const questionsSection = document.getElementById("questions_section"); - -// function addOption(button) { -// const optionContainer = button.previousElementSibling; -// const optionDiv = document.createElement("div"); -// optionDiv.className = "option"; -// optionDiv.innerHTML = ` -// -// -// `; -// optionContainer.appendChild(optionDiv); -// updateAddButtonPosition(); -// } - -// function deleteOption(span) { -// const optionDiv = span.parentElement; -// optionDiv.remove(); -// updateAddButtonPosition(); -// } - -// function changeQuestionType(select) { -// const questionInput = select.nextElementSibling; -// questionInput.style.display = "block"; -// const optionsContainer = select.nextElementSibling.nextElementSibling; -// optionsContainer.innerHTML = -// ''; -// } - -// let questionCount = document.querySelectorAll(".question").length; - -// function addNewQuestion() { -// const newQuestionDiv = document.createElement("div"); -// newQuestionDiv.className = "question"; -// newQuestionDiv.innerHTML = ` -// -// -//
-//
-// -// -//
-//
-// -// -// `; -// questionsSection.appendChild(newQuestionDiv); -// questionCount++; -// updateAddButtonPosition(); -// } - - // function saveForm() { - // const formTitle = document.getElementById("form-title").value; - // const formDescription = - // document.getElementById("form-description").value; - // const questions = document.querySelectorAll(".question"); - // let formData = []; - - // questions.forEach((question, index) => { - // const questionType = question.querySelector("select").value; - // const questionText = - // question.querySelector(".question-input").value; - // const options = Array.from( - // question.querySelectorAll(".option-input") - // ).map((input) => input.value); - // formData.push({ - // type: questionType, - // text: questionText, // Ensure this matches the key in the PHP validation - // options: options, - // }); - // }); - - // // Get CSRF token - // const csrfTokenMeta = document.querySelector('meta[name="csrf-token"]'); - // let csrfToken = ""; - // if (csrfTokenMeta) { - // csrfToken = csrfTokenMeta.getAttribute("content"); - // } else { - // console.error("CSRF token meta tag not found."); - // // Handle the error condition gracefully or abort further execution - // return; - // } - - // const data = { - // title: formTitle, - // description: formDescription, - // questions: formData, - // }; - - // console.log("Form Data:", data); // Log form data - // console.log("CSRF Token:", csrfToken); // Log CSRF token - - // // Send AJAX request to save the form data - // fetch("/forms", { - // method: "POST", - // headers: { - // "Content-Type": "application/json", - // "X-CSRF-TOKEN": csrfToken, - // }, - // body: JSON.stringify(data), - // }) - // .then((response) => { - // if (!response.ok) { - // throw new Error("Network response was not ok"); - // } - // return response.json(); - // }) - // .then((result) => { - // console.log("Server Response:", result); // Log server response - // if (result.success) { - // alert("Form saved successfully!"); - // window.location.href = "/forms"; // Redirect to forms index page - // } else { - // alert("Failed to save form."); - // } - // }) - // .catch((error) => { - // console.error("Error saving form:", error); - // alert("An error occurred while saving the form."); - // }); - // } - -// window.addNewQuestion = addNewQuestion; -// window.deleteQuestion = deleteQuestion; -// window.addOption = addOption; -// window.changeQuestionType = changeQuestionType; -// window.saveForm = saveForm; - -// window.previewForm = function (formId) { -// const formTitle = document.getElementById("form-title").value; -// const formDescription = -// document.getElementById("form-description").value; -// const questions = document.querySelectorAll(".question"); -// let formData = []; - -// questions.forEach((question, index) => { -// const questionType = question.querySelector("select").value; -// const questionText = -// question.querySelector(".question-input").value; -// const options = Array.from( -// question.querySelectorAll(".option-input") -// ).map((input) => input.value); -// formData.push({ -// type: questionType, -// text: questionText, -// options: options, -// }); -// }); - -// const formParams = new URLSearchParams({ -// title: formTitle, -// description: formDescription, -// data: JSON.stringify(formData), -// }); - -// window.location.href = '/forms/' + formId + '/preview'; -// }; - -// window.addNewQuestion = addNewQuestion; -// window.deleteQuestion = deleteQuestion; -// window.addOption = addOption; -// window.changeQuestionType = changeQuestionType; -// }); - -// function deleteQuestion(element) { -// let questionContainer = element.closest(".question"); -// if (questionContainer) { -// questionContainer.remove(); -// updateAddButtonPosition(); -// } -// } - -// function deleteOption(span) { -// const optionDiv = span.parentElement; -// optionDiv.remove(); -// updateAddButtonPosition(); -// } - -// function updateAddButtonPosition() { -// const questionsSection = document.getElementById("questions_section"); -// const lastQuestion = questionsSection.lastElementChild; - -// // Check if lastQuestion exists before accessing its properties -// if (lastQuestion) { -// const selectQuestionType = lastQuestion.querySelector(".question_type"); - -// // Ensure selectQuestionType is not null before accessing offsetTop -// if (selectQuestionType) { -// const sidebar = document.getElementById("moveableDiv"); -// const offset = selectQuestionType.offsetTop - sidebar.offsetHeight; -// sidebar.style.transform = `translateY(${offset}px)`; -// console.log(`Moving sidebar to: ${offset}px`); -// } else { -// console.warn("No .question_type found in last question."); -// } -// } else { -// const sidebar = document.getElementById("moveableDiv"); -// sidebar.style.transform = `translateY(0px)`; -// console.log(`Moving sidebar to: 0px`); -// } -// } - - - - - document.addEventListener("DOMContentLoaded", function () { const questionsSection = document.getElementById("questions_section"); @@ -233,12 +20,27 @@ document.addEventListener("DOMContentLoaded", function () { updateAddButtonPosition(); } - function changeQuestionType(select) { - const questionInput = select.nextElementSibling; - questionInput.style.display = "block"; - const optionsContainer = select.nextElementSibling.nextElementSibling; - optionsContainer.innerHTML = - ''; + function changeQuestionType(selectElement) { + const questionContainer = selectElement.closest('.question'); + const optionsContainer = questionContainer.querySelector('.options-container'); + const addOptionButton = questionContainer.querySelector('.btn-secondary'); + const questionType = selectElement.value; + + // Clear the options container + optionsContainer.innerHTML = ''; + + if (questionType === 'multiple_choice' || questionType === 'checkbox' || questionType === 'dropdown') { + const optionDiv = document.createElement('div'); + optionDiv.className = 'option d-flex align-items-center mb-2'; + optionDiv.innerHTML = ` + + + `; + optionsContainer.appendChild(optionDiv); + addOptionButton.style.display = 'inline-block'; // Show the "Add Option" button + } else if (questionType === 'text') { + addOptionButton.style.display = 'none'; // Hide the "Add Option" button + } } let questionCount = document.querySelectorAll(".question").length; @@ -247,26 +49,27 @@ document.addEventListener("DOMContentLoaded", function () { const newQuestionDiv = document.createElement("div"); newQuestionDiv.className = "question"; newQuestionDiv.innerHTML = ` -
+
-
- - -
+
- +
`; questionsSection.appendChild(newQuestionDiv); @@ -316,14 +119,21 @@ document.addEventListener("DOMContentLoaded", function () { const questions = document.querySelectorAll(".question"); let formData = []; - questions.forEach((question, index) => { + questions.forEach((question) => { const questionType = question.querySelector("select").value; const questionText = question.querySelector(".question-input").value; - const options = Array.from(question.querySelectorAll(".option-input")).map((input) => input.value); + const isRequired = question.querySelector(".required-checkbox").checked; + let options = []; + + if (questionType === 'multiple_choice' || questionType === 'checkbox' || questionType === 'dropdown') { + options = Array.from(question.querySelectorAll(".option-input")).map((input) => input.value); + } + formData.push({ type: questionType, text: questionText, options: options, + required: isRequired }); }); @@ -339,41 +149,52 @@ document.addEventListener("DOMContentLoaded", function () { const data = { title: formTitle, description: formDescription, - questions: formData, + questions: formData }; - console.log("Form Data:", data); - console.log("CSRF Token:", csrfToken); fetch("/forms", { method: "POST", headers: { "Content-Type": "application/json", - "X-CSRF-TOKEN": csrfToken, + "X-CSRF-TOKEN": csrfToken }, body: JSON.stringify(data), }) - .then((response) => { - if (!response.ok) { - throw new Error("Network response was not ok"); - } - return response.json(); - }) - .then((result) => { - console.log("Server Response:", result); - if (result.success) { - alert("Form saved successfully!"); - window.location.href = "/forms"; - } else { - alert("Failed to save form."); - } - }) - .catch((error) => { - console.error("Error saving form:", error); - alert("An error occurred while saving the form."); - }); + .then((response) => response.json()) + .then((result) => { + if (result.success) { + Swal.fire({ + title: 'Success!', + text: 'Form saved successfully.', + icon: 'success', + confirmButtonText: 'OK' + }).then((result) => { + if (result.isConfirmed) { + window.location.href = "/forms"; + } + }); + } else { + Swal.fire({ + title: 'Error!', + text: 'Failed to save form.', + icon: 'error', + confirmButtonText: 'OK' + }); + } + }) + .catch((error) => { + console.error("Error saving form:", error); + alert("An error occurred while saving the form."); + }); } + + + + + + window.addNewQuestion = addNewQuestion; window.deleteQuestion = deleteQuestion; window.addOption = addOption; diff --git a/resources/views/forms/create.blade.php b/resources/views/forms/create.blade.php index 24f4f45..3afcc33 100644 --- a/resources/views/forms/create.blade.php +++ b/resources/views/forms/create.blade.php @@ -1,4 +1,4 @@ - +{{-- @@ -98,4 +98,121 @@ + --}} + + + + + + + + + + + Google-Form-Clone + + + + + + + + + + + + +

+
+
+
+
+ + +
+
+
+
+
+ + +
+ +
+ + + +
+ +
+
+ +
+ + + +   +   +   + + + +   +   +   +   +   +   +
+ + + + + + diff --git a/resources/views/forms/edit.blade.php b/resources/views/forms/edit.blade.php index fb81c0b..28a6f6b 100644 --- a/resources/views/forms/edit.blade.php +++ b/resources/views/forms/edit.blade.php @@ -60,15 +60,11 @@ @foreach ($questions as $index => $question)
- + + + +
@@ -76,24 +72,25 @@ name="questions[{{ $index }}][text]" class="form-control question-input" value="{{ $question->question_text }}" required>
-
+
+ required ? 'checked' : '' }}> + +
+
@if (is_array($question->options)) @foreach ($question->options as $optionIndex => $option)
- - + +
@endforeach @endif - +
@@ -106,7 +103,6 @@
- @@ -142,22 +138,29 @@ const questionHtml = `
- +
+
+ required ? 'checked' : '' }}> + +
+
-
`; @@ -167,9 +170,35 @@ function deleteQuestion(button) { $(button).closest('.question').remove(); + updateQuestionIndices(); updateAddButtonPosition(); } + function updateQuestionIndices() { + $('#questions-section .question').each((index, element) => { + $(element).attr('data-index', index); + $(element).find('.question-type').attr('name', `questions[${index}][type]`); + $(element).find('.question-input').attr('name', `questions[${index}][text]`); + $(element).find('.question-input').attr('id', `question-text-${index}`); + $(element).find('.form-check-input').attr('name', `questions[${index}][required]`); + $(element).find('.form-check-input').attr('id', `question-required-${index}`); + $(element).find('.options-container').find('.option-input').each((optionIndex, optionElement) => { + $(optionElement).attr('name', `questions[${index}][options][${optionIndex}]`); + }); + }); + } + + function handleQuestionTypeChange(selectElement) { + const selectedType = $(selectElement).val(); + const optionsContainer = $(selectElement).closest('.question').find('.options-container'); + + if (selectedType === 'text') { + optionsContainer.hide(); + } else { + optionsContainer.show(); + } + } + function updateAddButtonPosition() { const questions = document.querySelectorAll("#questions-section .question"); const sidebar = document.getElementById("moveableDiv"); @@ -194,21 +223,32 @@ } } - $(document).ready(function() { - $('#profileMenuButton').on('click', function() { + $(document).ready(function () { + $('#profileMenuButton').click(function () { $('#profileMenu').toggleClass('hidden'); }); - $(document).on('click', function(e) { - if (!$(e.target).closest('#profileMenuButton').length && !$(e.target).closest( - '#profileMenu').length) { + $(document).click(function (event) { + if (!$(event.target).closest('#profileMenuButton, #profileMenu').length) { $('#profileMenu').addClass('hidden'); } }); + $('.question-type').each((index, element) => { + handleQuestionTypeChange(element); + }); + updateAddButtonPosition(); }); + + + + + + + + diff --git a/resources/views/forms/index.blade.php b/resources/views/forms/index.blade.php index a906443..eb618bf 100644 --- a/resources/views/forms/index.blade.php +++ b/resources/views/forms/index.blade.php @@ -74,9 +74,11 @@ Responses - - Actions + + Status + + + @@ -93,11 +95,20 @@ View Responses + + @if ($form->is_published) + Published + @else + Unpublished + @endif + @if (!$form->is_published) Edit @endif + +
@csrf diff --git a/resources/views/forms/show.blade.php b/resources/views/forms/show.blade.php index c88aa1c..d07d85e 100644 --- a/resources/views/forms/show.blade.php +++ b/resources/views/forms/show.blade.php @@ -1,134 +1,4 @@ -{{-- - - - - - - - Show Form - {{ $form->title }} - - - - - - - - -
-
- - - - -
-
- pallette - eye - - - - menu - -
-
-
-
- - - - -
    -
  • - -
  • -
  • - -
  • -
  • - -
  • -
-
-
- - @csrf - @method('PUT') -
-
-
-
-
- - -
-
-
-
- @foreach ($form->questions as $index => $question) -
- - - @if ($question->options) -
- @foreach (json_decode($question->options) as $optionIndex => $option) -
- -
- @endforeach -
- @endif -
- @endforeach -
-
-
- - Edit - -   -   -   - - - -   -   -   - - Return - -
-
- - - - - - - - - - - - - --}} - - - + @@ -194,6 +64,7 @@ + @if ($question->options) @@ -210,9 +81,16 @@
- - Return to Forms - +
+ @csrf + @method('PATCH') + +
+   +   + Return to Forms
diff --git a/resources/views/responses/showForm.blade.php b/resources/views/responses/showForm.blade.php index ad8c260..a520a98 100644 --- a/resources/views/responses/showForm.blade.php +++ b/resources/views/responses/showForm.blade.php @@ -1,54 +1,7 @@ {{-- @extends('layouts.app') - + + -@section('content') -
-

{{ $form->title }}

-

{{ $form->description }}

- -
- @csrf - @foreach ($questions as $question) -
- - @if ($question->type == 'multiple_choice') - @foreach (json_decode($question->options) as $option) - - @endforeach - @elseif($question->type == 'checkbox') - @foreach (json_decode($question->options) as $option) - - @endforeach - @elseif($question->type == 'dropdown') - - @elseif($question->type == 'short_answer') - - @elseif($question->type == 'long_answer') - - @endif -
- @endforeach - - -
-
-@endsection - --}} - - - @extends('layouts.app') @section('content')
@@ -80,9 +33,7 @@ @endforeach - @elseif($question->type == 'short_answer') - - @elseif($question->type == 'long_answer') + @elseif($question->type == 'text') @endif
@@ -94,4 +45,154 @@
+ +@endsection --}} + + + + + + + +@extends('layouts.app') + + + +@section('content') +
+
+

{{ $form->title }}

+

{{ $form->description }}

+ +
+ @csrf + @foreach ($questions as $question) +
+ + @if ($question->type == 'multiple_choice') + @foreach (json_decode($question->options) as $option) + + @endforeach + @elseif($question->type == 'checkbox') + @foreach (json_decode($question->options) as $option) + + @endforeach + @elseif($question->type == 'dropdown') + + @elseif($question->type == 'text') + + @endif +
+ @endforeach + + +
+
+
+ + @endsection diff --git a/routes/web.php b/routes/web.php index 2b4cb3e..2aedfd6 100644 --- a/routes/web.php +++ b/routes/web.php @@ -22,7 +22,7 @@ Route::middleware(['auth'])->group(function () { Route::put('/forms/{form}', [FormController::class, 'update'])->name('forms.update'); Route::delete('/forms/{form}', [FormController::class, 'destroy'])->name('forms.destroy'); Route::get('/forms/{form}/preview', [FormController::class, 'preview'])->name('forms.preview'); - + Route::patch('/forms/{form}/publish', [FormController::class, 'togglePublish'])->name('forms.publish'); }); // Response routes