381 lines
16 KiB
PHP
381 lines
16 KiB
PHP
@extends('layouts.app')
|
|
|
|
@push('styles')
|
|
<link rel="stylesheet" href="{{ asset('css/adminHeader.css') }}">
|
|
@endpush
|
|
|
|
@section('content')
|
|
<div class="container">
|
|
<div class="admin-header-box mb-4">
|
|
<div>
|
|
<h4>Edit Soal Selidik</h4>
|
|
<p>Kemaskini maklumat borang di bawah.</p>
|
|
</div>
|
|
</div>
|
|
|
|
@if ($errors->any())
|
|
<div class="alert alert-danger">
|
|
<strong>Ralat! Sila semak input anda:</strong>
|
|
<ul>
|
|
@foreach ($errors->all() as $error)
|
|
<li>{{ $error }}</li>
|
|
@endforeach
|
|
</ul>
|
|
</div>
|
|
@endif
|
|
|
|
@if (session('error'))
|
|
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
|
<strong>Ralat Sistem:</strong> {{ session('error') }}
|
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
|
</div>
|
|
@endif
|
|
|
|
<form method="POST" action="{{ route('admin.surveys.update', $survey->id) }}" id="survey-builder-form">
|
|
@csrf
|
|
@method('PUT')
|
|
|
|
<div class="card shadow-sm mb-4">
|
|
<div class="card-body p-4">
|
|
<div class="row">
|
|
<div class="col-md-8 mb-3">
|
|
<label for="title" class="form-label fw-bold">Tajuk Soal Selidik</label>
|
|
<input type="text" name="title" id="title" class="form-control" placeholder="eg: Sambutan Maulidur Rasul" value="{{ old('title', $survey->title) }}" required>
|
|
</div>
|
|
<div class="col-md-4 mb-3">
|
|
<label for="date" class="form-label fw-bold">Tarikh</label>
|
|
<input type="date" name="date" id="date" class="form-control" value="{{ old('date', $survey->date) }}" required>
|
|
</div>
|
|
<div class="col-md-12 mb-3">
|
|
<label for="perincian" class="form-label fw-bold">Perincian</label>
|
|
<textarea name="perincian" id="perincian" class="form-control" rows="3" placeholder="Terangkan tujuan borang ini (Pilihan)">{{ old('perincian', $survey->perincian) }}</textarea>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="sections-container"></div>
|
|
|
|
<button type="button" class="btn btn-outline-primary" id="add-section-btn">+ Tambah Seksyen</button>
|
|
|
|
<div class="text-end mt-4">
|
|
<a href="{{ route('admin.surveys.index') }}" class="btn btn-secondary">Batal</a>
|
|
<button type="submit" class="btn btn-success">Simpan Perubahan</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
{{-- TEMPLATES --}}
|
|
<template id="section-template">
|
|
<div class="card shadow-sm mb-4 section-block">
|
|
<div class="card-header bg-light d-flex justify-content-between align-items-center">
|
|
<h5 class="mb-0 section-title">Bahagian</h5>
|
|
<button type="button" class="btn btn-sm btn-outline-danger remove-section-btn">Buang Seksyen</button>
|
|
</div>
|
|
<div class="card-body p-4">
|
|
<div class="row">
|
|
<div class="col-md-6 mb-3">
|
|
<input type="text" name="sections[0][title]" class="form-control section-title-input" placeholder="Tajuk Bahagian (cth: Maklumat Peribadi)" required>
|
|
</div>
|
|
<div class="col-md-6 mb-3">
|
|
<input type="text" name="sections[0][description]" class="form-control" placeholder="Optional description">
|
|
</div>
|
|
</div>
|
|
|
|
<div class="questions-container"></div>
|
|
|
|
<button type="button" class="btn btn-sm btn-info add-question-btn">+ Tambah soalan</button>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<template id="question-template">
|
|
<div class="border rounded p-3 mb-3 question-block bg-white">
|
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
|
<label class="form-label fw-bold question-title">Soalan 1</label>
|
|
<div>
|
|
<div class="btn-group btn-group-sm" role="group">
|
|
<button type="button" class="btn btn-outline-secondary type-selector" data-type="radio">Radio</button>
|
|
<button type="button" class="btn btn-outline-secondary type-selector" data-type="text">Text</button>
|
|
<button type="button" class="btn btn-outline-secondary type-selector" data-type="checkbox">Checkbox</button>
|
|
</div>
|
|
<input type="hidden" name="sections[0][questions][0][type]" class="question-type-input" value="radio">
|
|
<button type="button" class="btn btn-sm btn-outline-danger ms-2 remove-question-btn">Buang</button>
|
|
</div>
|
|
</div>
|
|
|
|
<input type="text" name="sections[0][questions][0][text]" class="form-control mb-3 question-text-input" placeholder="Isi soalan di sini" required>
|
|
|
|
<div class="form-check allow-other-container mb-3">
|
|
<input class="form-check-input allow-other-option-checkbox" type="checkbox" value="1" name="sections[0][questions][0][allow_other_option]">
|
|
<label class="form-check-label">
|
|
Lain-lain
|
|
</label>
|
|
</div>
|
|
|
|
<div class="options-container">
|
|
<label class="form-label">Jawapan Pilihan</label>
|
|
<button type="button" class="btn btn-sm btn-link add-option-btn p-0">+ Tambah pilihan</button>
|
|
</div>
|
|
|
|
<div class="text-placeholder-container" style="display:none;">
|
|
<input type="text" class="form-control bg-light" placeholder="Ruang responden menjawab" disabled>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<template id="option-template">
|
|
<div class="input-group mb-2 option-group">
|
|
<input type="text" name="sections[0][questions][0][options][]" class="form-control option-input" placeholder="Pilihan jawapan" required>
|
|
<button type="button" class="btn btn-outline-danger remove-option-btn">Buang</button>
|
|
</div>
|
|
</template>
|
|
|
|
@endsection
|
|
|
|
@push('scripts')
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
const sectionsContainer = document.getElementById('sections-container');
|
|
const addSectionBtn = document.getElementById('add-section-btn');
|
|
|
|
const sectionTemplate = document.getElementById('section-template');
|
|
const questionTemplate = document.getElementById('question-template');
|
|
const optionTemplate = document.getElementById('option-template');
|
|
|
|
let sectionCounter = 0;
|
|
|
|
function addSection(data = null) {
|
|
const sectionIndex = sectionCounter++;
|
|
const newSection = sectionTemplate.content.cloneNode(true).firstElementChild;
|
|
newSection.dataset.sectionIndex = sectionIndex;
|
|
|
|
const titleInput = newSection.querySelector('.section-title-input');
|
|
const descInput = newSection.querySelector('input[name$="[description]"]');
|
|
titleInput.value = data?.title ?? '';
|
|
descInput.value = data?.description ?? '';
|
|
newSection.querySelector('.section-title').textContent = data?.title ?? 'Bahagian';
|
|
|
|
// Adjust names
|
|
newSection.querySelectorAll('[name]').forEach(input => {
|
|
input.name = input.name.replace('sections[0]', `sections[${sectionIndex}]`);
|
|
});
|
|
|
|
const questionsContainer = newSection.querySelector('.questions-container');
|
|
|
|
if (data?.questions && data.questions.length) {
|
|
data.questions.forEach(q => addQuestion(questionsContainer, sectionIndex, q));
|
|
} else {
|
|
addQuestion(questionsContainer, sectionIndex);
|
|
}
|
|
|
|
sectionsContainer.appendChild(newSection);
|
|
reindexQuestions();
|
|
}
|
|
|
|
function addQuestion(questionsContainer, sectionIndex, qdata = null) {
|
|
const questionIndex = questionsContainer.querySelectorAll('.question-block').length;
|
|
const newQuestion = questionTemplate.content.cloneNode(true).firstElementChild;
|
|
newQuestion.dataset.questionIndex = questionIndex;
|
|
|
|
newQuestion.querySelector('.question-title').textContent = `Soalan ${questionIndex + 1}`;
|
|
const qtext = newQuestion.querySelector('.question-text-input');
|
|
const qtype = newQuestion.querySelector('.question-type-input');
|
|
const otherCheckbox = newQuestion.querySelector('.allow-other-option-checkbox');
|
|
|
|
qtext.value = qdata?.text ?? '';
|
|
qtype.value = qdata?.type ?? 'radio';
|
|
|
|
// BARU: Set status checkbox Lain-lain based on DB data
|
|
// Check if qdata.allow_other_option is true or '1'
|
|
if (qdata && (qdata.allow_other_option == 1 || qdata.allow_other_option === true)) {
|
|
otherCheckbox.checked = true;
|
|
} else {
|
|
otherCheckbox.checked = false;
|
|
}
|
|
|
|
newQuestion.querySelectorAll('.type-selector').forEach(btn => {
|
|
btn.classList.remove('active','btn-primary');
|
|
if (btn.dataset.type === qtype.value) btn.classList.add('active','btn-primary');
|
|
});
|
|
|
|
newQuestion.querySelectorAll('[name]').forEach(input => {
|
|
input.name = input.name
|
|
.replace('sections[0]', `sections[${sectionIndex}]`)
|
|
.replace('[questions][0]', `[questions][${questionIndex}]`);
|
|
});
|
|
|
|
const optionsContainer = newQuestion.querySelector('.options-container');
|
|
|
|
if (qdata?.options && qdata.options.length && qtype.value !== 'text') {
|
|
qdata.options.forEach(opt => addOption(optionsContainer, sectionIndex, questionIndex, opt.text));
|
|
} else if (qtype.value !== 'text') {
|
|
addOption(optionsContainer, sectionIndex, questionIndex);
|
|
addOption(optionsContainer, sectionIndex, questionIndex);
|
|
}
|
|
|
|
// Initialize display state (options vs text vs other checkbox)
|
|
toggleQuestionType(newQuestion, qtype.value);
|
|
|
|
questionsContainer.appendChild(newQuestion);
|
|
reindexQuestions();
|
|
}
|
|
|
|
function addOption(optionsContainer, sectionIndex, questionIndex, value = '') {
|
|
const newOption = optionTemplate.content.cloneNode(true).firstElementChild;
|
|
const optInput = newOption.querySelector('.option-input');
|
|
optInput.name = `sections[${sectionIndex}][questions][${questionIndex}][options][]`;
|
|
optInput.value = value ?? '';
|
|
const addBtn = optionsContainer.querySelector('.add-option-btn');
|
|
optionsContainer.insertBefore(newOption, addBtn);
|
|
}
|
|
|
|
function reindexQuestions() {
|
|
sectionsContainer.querySelectorAll('.section-block').forEach((section, sIdx) => {
|
|
section.dataset.sectionIndex = sIdx;
|
|
section.querySelectorAll('.section-title-input').forEach(input => {
|
|
input.name = input.name.replace(/sections\[\d+\]/, `sections[${sIdx}]`);
|
|
});
|
|
|
|
section.querySelectorAll('.question-block').forEach((q, qIdx) => {
|
|
q.dataset.questionIndex = qIdx;
|
|
q.querySelector('.question-title').textContent = `Soalan ${qIdx + 1}`;
|
|
|
|
// Reindex all inputs including the new checkbox
|
|
q.querySelectorAll('[name]').forEach(input => {
|
|
input.name = input.name
|
|
.replace(/sections\[\d+\]/, `sections[${sIdx}]`)
|
|
.replace(/questions\[\d+\]/, `questions[${qIdx}]`);
|
|
});
|
|
|
|
// Reindex dynamic options
|
|
q.querySelectorAll('.option-input').forEach((opt, optIdx) => {
|
|
opt.name = `sections[${sIdx}][questions][${qIdx}][options][${optIdx}]`;
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
function toggleQuestionType(questionBlock, type) {
|
|
const optionsContainer = questionBlock.querySelector('.options-container');
|
|
const textPlaceholder = questionBlock.querySelector('.text-placeholder-container');
|
|
const otherContainer = questionBlock.querySelector('.allow-other-container');
|
|
const otherCheckbox = questionBlock.querySelector('.allow-other-option-checkbox');
|
|
|
|
if (type === 'text') {
|
|
// Text Mode
|
|
optionsContainer.style.display = 'none';
|
|
textPlaceholder.style.display = 'block';
|
|
|
|
// Hide 'Other' checkbox and uncheck it
|
|
otherContainer.style.display = 'none';
|
|
otherCheckbox.checked = false;
|
|
|
|
// Remove options inputs
|
|
optionsContainer.querySelectorAll('.option-input').forEach(i=>i.remove());
|
|
} else {
|
|
// Radio/Checkbox Mode
|
|
optionsContainer.style.display = 'block';
|
|
textPlaceholder.style.display = 'none';
|
|
|
|
// Show 'Other' checkbox
|
|
otherContainer.style.display = 'block';
|
|
}
|
|
}
|
|
|
|
addSectionBtn.addEventListener('click', () => addSection());
|
|
|
|
sectionsContainer.addEventListener('click', function (e) {
|
|
if (e.target.classList.contains('add-question-btn')) {
|
|
const questionsContainer = e.target.closest('.card-body').querySelector('.questions-container');
|
|
const sIdx = parseInt(e.target.closest('.section-block').dataset.sectionIndex);
|
|
addQuestion(questionsContainer, sIdx);
|
|
}
|
|
|
|
if (e.target.classList.contains('add-option-btn')) {
|
|
const optionsContainer = e.target.closest('.options-container');
|
|
const qBlock = e.target.closest('.question-block');
|
|
const sIdx = parseInt(e.target.closest('.section-block').dataset.sectionIndex);
|
|
const qIdx = parseInt(qBlock.dataset.questionIndex);
|
|
addOption(optionsContainer, sIdx, qIdx);
|
|
}
|
|
|
|
if (e.target.classList.contains('type-selector')) {
|
|
const type = e.target.dataset.type;
|
|
const qBlock = e.target.closest('.question-block');
|
|
|
|
qBlock.querySelectorAll('.type-selector').forEach(btn => btn.classList.remove('active','btn-primary'));
|
|
e.target.classList.add('active','btn-primary');
|
|
|
|
qBlock.querySelector('.question-type-input').value = type;
|
|
|
|
// Check if we need to add default options when switching from Text to Radio/Checkbox
|
|
const optionsContainer = qBlock.querySelector('.options-container');
|
|
if (type !== 'text' && optionsContainer.querySelectorAll('.option-group').length === 0) {
|
|
const sIdx = parseInt(qBlock.closest('.section-block').dataset.sectionIndex);
|
|
const qIdx = parseInt(qBlock.dataset.questionIndex);
|
|
addOption(optionsContainer, sIdx, qIdx);
|
|
addOption(optionsContainer, sIdx, qIdx);
|
|
}
|
|
|
|
toggleQuestionType(qBlock, type);
|
|
reindexQuestions();
|
|
}
|
|
|
|
if (e.target.closest('.remove-section-btn')) {
|
|
e.target.closest('.section-block').remove();
|
|
reindexQuestions();
|
|
}
|
|
if (e.target.closest('.remove-question-btn')) {
|
|
e.target.closest('.question-block').remove();
|
|
reindexQuestions();
|
|
}
|
|
if (e.target.closest('.remove-option-btn')) {
|
|
e.target.closest('.option-group').remove();
|
|
reindexQuestions();
|
|
}
|
|
});
|
|
|
|
// Form Submission Logic
|
|
document.getElementById('survey-builder-form').addEventListener('submit', function () {
|
|
sectionsContainer.querySelectorAll('.question-block').forEach(q=>{
|
|
const type = q.querySelector('.question-type-input').value;
|
|
|
|
// Handle Options Clean up
|
|
if (type === 'text') {
|
|
q.querySelectorAll('.option-input').forEach(i=>i.remove());
|
|
} else {
|
|
q.querySelectorAll('.option-input').forEach(i => {
|
|
if (typeof i.value === 'undefined') i.value = '';
|
|
});
|
|
}
|
|
|
|
// Handle Other Option Checkbox
|
|
const otherCheckbox = q.querySelector('.allow-other-option-checkbox');
|
|
if (otherCheckbox) {
|
|
if (otherCheckbox.checked) {
|
|
otherCheckbox.value = '1';
|
|
} else {
|
|
// Remove name attribute so it doesn't send "on" or any value if unchecked
|
|
// This relies on the backend treating missing boolean field as false/0
|
|
otherCheckbox.removeAttribute('name');
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
// Initial Data Loading
|
|
const oldSections = @json(old('sections') ?? null);
|
|
const serverSections = @json($sections_with_questions ?? []);
|
|
|
|
const initialSections = oldSections ?? serverSections;
|
|
|
|
if (Array.isArray(initialSections) && initialSections.length) {
|
|
initialSections.forEach(sec => addSection(sec));
|
|
} else {
|
|
addSection();
|
|
}
|
|
});
|
|
</script>
|
|
@endpush
|