first commit

This commit is contained in:
2026-05-22 20:46:29 +08:00
commit b04f87f2b0
121 changed files with 14851 additions and 0 deletions

11
resources/css/app.css Normal file
View File

@@ -0,0 +1,11 @@
@import 'tailwindcss';
@source '../../vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php';
@source '../../storage/framework/views/*.php';
@source '../**/*.blade.php';
@source '../**/*.js';
@theme {
--font-sans: 'Instrument Sans', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
'Segoe UI Symbol', 'Noto Color Emoji';
}

1
resources/js/app.js Normal file
View File

@@ -0,0 +1 @@
import './bootstrap';

4
resources/js/bootstrap.js vendored Normal file
View File

@@ -0,0 +1,4 @@
import axios from 'axios';
window.axios = axios;
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';

View File

@@ -0,0 +1,170 @@
@extends('layouts.app')
@push('styles')
<link rel="stylesheet" href="{{ asset('css/adminHeader.css') }}">
<link rel="stylesheet" href="{{ asset('css/dashboardAdmin.css') }}">
@endpush
@section('content')
<div class="container-fluid">
<div class="admin-header-box mb-4">
<div>
<h4>Dashboard</h4>
<p>Selamat Datang, <strong>{{ auth()->user()->name }}</strong></p>
</div>
</div>
<div class="row g-4 mb-5">
<div class="col-md-4">
<div class="card dashboard-card bg-gradient-primary h-100">
<div class="card-body text-white p-4">
<h6 class="text-white-50 mb-3 fw-normal">Jumlah borang</h6>
<h1 class="display-stat mb-0">{{ $totalSurveys }}</h1>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card dashboard-card bg-gradient-success h-100">
<div class="card-body text-white p-4">
<h6 class="text-white-50 mb-3 fw-normal">Jumlah respons</h6>
<h1 class="display-stat mb-0">{{ $totalResponses }}</h1>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card dashboard-card bg-gradient-danger h-100">
<div class="card-body text-white p-4">
<h6 class="text-white-50 mb-3 fw-normal">Jumlah pengguna</h6>
<h1 class="display-stat mb-0">{{ $totalUsers }}</h1>
</div>
</div>
</div>
</div>
{{-- recent survey table --}}
<div class="card shadow-sm border-0">
<div class="card-header bg-white border-bottom py-3">
<div class="d-flex justify-content-between align-items-center">
<h5 class="mb-0 fw-bold">Borang Terkini</h5>
<a href="{{ route('admin.surveys.index') }}" class="btn btn-sm btn-outline-primary">
Lihat Semua <i class="bi bi-arrow-right ms-1"></i>
</a>
</div>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead class="table-light">
<tr>
<th class="ps-4">Tajuk</th>
<th>Dicipta oleh</th>
<th>Respons</th>
<th>Tarikh</th>
<th class="pe-4">Tindakan</th>
</tr>
</thead>
<tbody>
@forelse($recentSurveys as $survey)
<tr>
<td class="ps-4">
<span class="fw-semibold text-primary" title="{{ $survey->title }}">
{{ Str::limit($survey->title, 50) }}
</span>
</td>
<td>{{ $survey->user->name }}</td>
<td>
<a href="{{ route('admin.responses.respondents', $survey) }}" class="text-decoration-none">
<span class="badge bg-success">{{ $survey->responses->count() }} respons</span>
</a>
</td>
<td class="text-muted">{{ $survey->created_at->format('d/m/Y') }}</td>
<td class="pe-4">
<a href="{{ route('admin.surveys.edit', $survey->id) }}" class="btn btn-sm btn-outline-secondary" title="Edit">
<i class="bi bi-pencil"></i>
</a>
<form action="{{ route('admin.surveys.destroy', $survey->id) }}" method="POST" class="d-inline" onsubmit="return confirm('Adakah anda pasti mahu memadam borang ini?');">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-sm btn-outline-danger" title="Padam">
<i class="bi bi-trash"></i>
</button>
</form>
</td>
</tr>
@empty
<tr>
<td colspan="5" class="text-center py-4 text-muted">
<i class="bi bi-inbox display-4 d-block mb-2"></i>
Tiada borang lagi
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
</div>
<div class="row g-4 mt-4">
{{-- Ulasan Terkini --}}
<div class="col-md-6">
<div class="card shadow-sm border-0 h-100">
<div class="card-header bg-white border-bottom py-3">
<h5 class="mb-0 fw-bold">Ulasan Terkini</h5>
</div>
<div class="card-body p-0">
<ul class="list-group list-group-flush">
@forelse($recentReviews as $review)
<li class="list-group-item py-3">
<div class="d-flex justify-content-between align-items-center mb-1">
<span class="fw-semibold text-truncate" style="max-width: 70%;">{{ $review->title }}</span>
<small class="text-muted">{{ $review->updated_at->format('d/m/Y') }}</small>
</div>
<p class="mb-0 text-muted small text-truncate">{{ $review->ulasan }}</p>
</li>
@empty
<li class="list-group-item text-center py-4 text-muted">Tiada ulasan lagi</li>
@endforelse
</ul>
</div>
</div>
</div>
{{-- User Terkini --}}
<div class="col-md-6">
<div class="card shadow-sm border-0 h-100">
<div class="card-header bg-white border-bottom py-3">
<h5 class="mb-0 fw-bold">Pengguna Terkini</h5>
</div>
<div class="card-body p-0">
<ul class="list-group list-group-flush">
@forelse($recentUsers as $user)
<li class="list-group-item py-3">
<div class="d-flex align-items-center">
<div class="bg-light rounded-circle p-2 me-3 text-primary">
<i class="bi bi-person"></i>
</div>
<div>
<h6 class="mb-0 fw-semibold">{{ $user->name }}</h6>
<div class="small text-muted">
<span>{{ $user->jabatan ?? '-' }}</span> |
<span>No Pekerja: {{ $user->no_pekerja ?? '-' }}</span>
</div>
</div>
<div class="ms-auto">
<span class="badge bg-{{ $user->role == 'admin' ? 'info' : 'secondary' }} rounded-pill">{{ ucfirst($user->role) }}</span>
</div>
</div>
</li>
@empty
<li class="list-group-item text-center py-4 text-muted">Tiada pengguna lagi</li>
@endforelse
</ul>
</div>
</div>
</div>
</div>
</div>
@endsection

View File

@@ -0,0 +1,41 @@
@extends('layouts.app')
@section('content')
<div class="container">
<div class="card p-2 shadow-sm border-0 mb-3" style="border-radius: 15px;">
<h5 class="mb-3 text-success">Jawapan Responden</h5>
<div class="row">
<div class="col-md-4">
<small class="text-muted d-block">Nama</small>
<strong>{{ $response->respondent_name ?? '-' }}</strong>
</div>
<div class="col-md-4 border-start border-end">
<small class="text-muted d-block">No. Pekerja</small>
<strong>{{ $response->respondent_no_pekerja ?? '-' }}</strong>
</div>
<div class="col-md-4">
<small class="text-muted d-block">Jabatan</small>
<strong>{{ $response->respondent_jabatan ?? '-' }}</strong>
</div>
</div>
</div>
<div class="mt-2 px-2">
<p class="mb-0">
<span class="fw-bold text-primary">{{ $response->survey->title }}</span>
</p>
</div>
<hr>
@foreach($response->answers as $answer)
<div class="mb-3">
<strong>{{ $answer->question->question_text }}</strong>
<p>{{ $answer->answer_text }}</p>
</div>
@endforeach
</div>
@endsection

View File

@@ -0,0 +1,61 @@
@extends('layouts.app')
@push('styles')
<link rel="stylesheet" href="{{ asset('css/adminHeader.css') }}">
<link rel="stylesheet" href="{{ asset('css/responsesAdmin.css') }}">
@endpush
@section('content')
<div class="container">
<div class="admin-header-box">
<div>
<h4>Senarai Borang & Responden</h4>
<p>Klik pada butang tersebut untuk melihat senarai dan jawapan responden.</p>
</div>
<form method="GET" action="{{ route('admin.responses.list') }}" style="max-width: 300px; width: 100%;">
<div class="input-group input-group-sm shadow-sm">
<input type="text" name="search" value="{{ request('search') }}" class="form-control border-0 ps-3" placeholder="Cari tajuk..." style="border-radius: 20px 0 0 20px;">
<button class="btn btn-light border-0 px-3" type="submit" style="color: #4e73df;">
<i class="bi bi-search"></i>
</button>
@if(request('search'))
<a href="{{ route('admin.responses.list') }}" class="btn btn-light border-0 border-start px-2" style="border-radius: 0 20px 20px 0;">
<i class="bi bi-arrow-counterclockwise"></i>
</a>
@else
<div class="bg-light px-2" style="border-radius: 0 20px 20px 0;"></div>
@endif
</div>
</form>
</div>
<div class="row g-3">
@forelse($surveys as $survey)
<div class="col-md-6 col-lg-4 d-flex">
<div class="response-survey-item d-flex align-items-center justify-content-between p-3 shadow-sm w-100">
<div class="d-flex align-items-center flex-grow-1 me-3">
<div class="icon-box-stat me-3">
<span class="fw-bold text-secondary">#{{ $loop->iteration }}</span>
</div>
<div class="flex-grow-1">
<h6 class="mb-1 text-dark text-uppercase">{{ $survey->title }}</h6>
<small class="text-muted">{{ $survey->responses_count }} respons</small>
</div>
</div>
<a href="{{ route('admin.responses.respondents', $survey->id) }}" class="btn btn-primary btn-sm rounded-2 px-3">
<i class="bi bi-box-arrow-up-right"></i>
</a>
</div>
</div>
@empty
<div class="col-12 text-center py-5">
<div class="text-muted">
<i class="bi bi-inbox fs-1 d-block mb-2 opacity-25"></i>
<p class="small">Tiada borang ditemui.</p>
</div>
</div>
@endforelse
</div>
</div>
@endsection

View File

@@ -0,0 +1,66 @@
@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>Responden: {{ $survey->title }}</h4>
<p>Senarai pengguna yang telah menjawab borang ini.</p>
</div>
<a href="{{ route('admin.responses.list') }}" class="btn btn-sm btn-light px-3 fw-bold text-primary">
<i class="bi bi-arrow-left"></i> Kembali
</a>
</div>
<div class="card shadow-sm">
<div class="card-header bg-primary text-white">
<h5 class="mb-0">Jumlah Responden: {{ $responses->count() }}</h5>
</div>
<div class="card-body p-0">
@if($responses->isEmpty())
<div class="alert alert-info mb-0 p-4">
Belum ada responden yang menjawab borang ini.
</div>
@else
<div class="table-responsive">
<table class="table table-striped table-hover mb-0">
<thead>
<tr>
<th>#</th>
<th>Responden</th>
<th>Nombor Pekerja</th>
<th>Tarikh Jawab</th>
<th>Tindakan</th>
</tr>
</thead>
<tbody>
@foreach($responses as $index => $response)
<tr>
<td>{{ $index + 1 }}</td>
<td>
{{-- Klik nama/butang untuk ke halaman detail.blade.php --}}
<a href="{{ route('admin.responses.detail', $response->id) }}" class="text-primary fw-bold">
{{ $response->respondent_name ?? '-' }}
</a>
</td>
<td>{{ $response->respondent_no_pekerja ?? '-' }}</td>
<td>{{ $response->created_at->format('d M Y') }}</td>
<td>
<a href="{{ route('admin.responses.detail', $response->id) }}" class="btn btn-sm btn-info text-white">
Lihat Jawapan
</a>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
@endif
</div>
</div>
</div>
@endsection

View File

@@ -0,0 +1,342 @@
@extends('layouts.app')
@push('styles')
<link rel="stylesheet" href="{{ asset('css/adminHeader.css') }}">
@endpush
@section('content')
@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
<div class="container">
<div class="admin-header-box mb-4">
<div>
<h4>Daftar Soal Selidik</h4>
<p>Sila isi maklumat borang di bawah.</p>
</div>
</div>
@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.store') }}" id="survey-builder-form">
@csrf
<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') }}" 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') }}" 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') }}</textarea>
</div>
</div>
</div>
</div>
<div id="sections-container"></div>
<button type="button" class="btn btn-outline-primary" id="add-section-btn">+ Tambah Bahagian</button>
<div class="text-end mt-4">
<a href="{{ route('admin.surveys.index') }}" class="btn btn-secondary btn-lg">Batal</a>
<button type="submit" class="btn btn-success btn-lg">Daftar</button>
</div>
</form>
</div>
<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">Hapus Bahagian</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="Keterangan (Pilihan)">
</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">Hapus</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" data-allowed-types="radio,checkbox">
<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" for="flexCheckDefault">
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">Hapus</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() {
const sectionIndex = sectionCounter++;
const newSection = sectionTemplate.content.cloneNode(true).firstElementChild;
newSection.dataset.sectionIndex = sectionIndex;
newSection.querySelector('.section-title').textContent = `Bahagian`;
newSection.querySelectorAll('[name]').forEach(input => {
input.name = input.name.replace('sections[0]', `sections[${sectionIndex}]`);
});
addQuestion(newSection.querySelector('.questions-container'), sectionIndex);
sectionsContainer.appendChild(newSection);
reindexQuestions();
}
function addQuestion(questionsContainer, sectionIndex) {
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}`;
newQuestion.querySelectorAll('[name]').forEach(input => {
input.name = input.name
.replace('sections[0]', `sections[${sectionIndex}]`)
.replace('[questions][0]', `[questions][${questionIndex}]`);
});
newQuestion.querySelector('.type-selector[data-type="radio"]').classList.add('active', 'btn-primary');
newQuestion.querySelector('.question-type-input').value = 'radio';
const otherCheckbox = newQuestion.querySelector('.allow-other-option-checkbox');
otherCheckbox.name = `sections[${sectionIndex}][questions][${questionIndex}][allow_other_option]`;
otherCheckbox.checked = false; // Pastikan bermula sebagai tidak dicentang
otherCheckbox.removeAttribute('checked')
const optionsContainer = newQuestion.querySelector('.options-container');
addOption(optionsContainer, sectionIndex, questionIndex);
addOption(optionsContainer, sectionIndex, questionIndex);
questionsContainer.appendChild(newQuestion);
reindexQuestions();
}
function addOption(optionsContainer, sectionIndex, questionIndex) {
const newOption = optionTemplate.content.cloneNode(true).firstElementChild;
newOption.querySelector('.option-input').name = `sections[${sectionIndex}][questions][${questionIndex}][options][]`;
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}`;
q.querySelectorAll('[name]').forEach(input => {
input.name = input.name
.replace(/sections\[\d+\]/, `sections[${sIdx}]`)
.replace(/questions\[\d+\]/, `questions[${qIdx}]`);
});
const type = q.querySelector('.question-type-input').value;
const optionsContainer = q.querySelector('.options-container');
const textPlaceholder = q.querySelector('.text-placeholder-container');
const otherContainer = q.querySelector('.allow-other-container');
if (type === 'text') {
optionsContainer.style.display = 'none';
textPlaceholder.style.display = 'block';
otherContainer.style.display = 'none'; // Sembunyikan untuk jenis 'text'
q.querySelector('.allow-other-option-checkbox').checked = false;
q.querySelector('.allow-other-option-checkbox').removeAttribute('checked');
// Buang input options supaya tidak dihantar jika jenis 'text'
// Ini penting, jika tidak, validation akan gagal
q.querySelectorAll('.option-group').forEach(group => group.remove());
} else {
optionsContainer.style.display = 'block';
textPlaceholder.style.display = 'none';
otherContainer.style.display = 'block'; // Tunjukkan untuk jenis 'radio'/'checkbox'
}
});
});
}
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 sectionIndex = parseInt(e.target.closest('.section-block').dataset.sectionIndex);
addQuestion(questionsContainer, sectionIndex);
}
if (e.target.classList.contains('add-option-btn')) {
const optionsContainer = e.target.closest('.options-container');
const questionBlock = e.target.closest('.question-block');
const sectionIndex = parseInt(e.target.closest('.section-block').dataset.sectionIndex);
const questionIndex = parseInt(questionBlock.dataset.questionIndex);
addOption(optionsContainer, sectionIndex, questionIndex);
}
if (e.target.classList.contains('type-selector')) {
const type = e.target.dataset.type;
const questionBlock = e.target.closest('.question-block');
questionBlock.querySelectorAll('.type-selector').forEach(btn => btn.classList.remove('active', 'btn-primary'));
e.target.classList.add('active', 'btn-primary');
questionBlock.querySelector('.question-type-input').value = type;
const optionsContainer = questionBlock.querySelector('.options-container');
const textPlaceholder = questionBlock.querySelector('.text-placeholder-container');
// BARU: Dapatkan container untuk checkbox 'Lain-lain'
const allowOtherContainer = questionBlock.querySelector('.allow-other-container');
if (type === 'text') {
                optionsContainer.style.display = 'none';
                textPlaceholder.style.display = 'block';
// BARU: Sembunyikan checkbox 'Lain-lain' untuk jenis 'text'
allowOtherContainer.style.display = 'none';
questionBlock.querySelector('.allow-other-option-checkbox').checked = false;
                // Remove options
                optionsContainer.querySelectorAll('.option-group').forEach(group => group.remove());
            } else {
                optionsContainer.style.display = 'block';
                textPlaceholder.style.display = 'none';
// BARU: Tunjukkan checkbox 'Lain-lain' untuk jenis 'radio'/'checkbox'
allowOtherContainer.style.display = 'block';
// Tambah 2 pilihan jika tiada pilihan (bila tukar dari 'text')
if (optionsContainer.querySelectorAll('.option-group').length === 0) {
const sectionIndex = parseInt(e.target.closest('.section-block').dataset.sectionIndex);
const questionIndex = parseInt(questionBlock.dataset.questionIndex);
addOption(optionsContainer, sectionIndex, questionIndex);
addOption(optionsContainer, sectionIndex, questionIndex);
}
            }
            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();
}
});
document.getElementById('survey-builder-form').addEventListener('submit', function (e) {
sectionsContainer.querySelectorAll('.question-block').forEach(q => {
const isText = q.querySelector('.question-type-input').value === 'text';
// 1. Logik untuk soalan 'text' (buang options)
if (isText) {
q.querySelectorAll('.option-group').forEach(group => group.remove());
}
// 2. Logik untuk checkbox 'allow_other_option'
const otherCheckbox = q.querySelector('.allow-other-option-checkbox');
if (otherCheckbox) {
if (otherCheckbox.checked) {
otherCheckbox.value = '1';
otherCheckbox.setAttribute('checked', 'checked'); // Pastikan dihantar
} else {
otherCheckbox.removeAttribute('name');
otherCheckbox.removeAttribute('checked');
}
}
});
});
addSection();
});
</script>
@endpush

View File

@@ -0,0 +1,380 @@
@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

View File

@@ -0,0 +1,235 @@
@extends('layouts.app')
@push('styles')
<link rel="stylesheet" href="{{ asset('css/adminHeader.css') }}">
<link rel="stylesheet" href="{{ asset('css/surveysAdmin.css') }}">
@endpush
@section('content')
<div class="container">
{{-- Header --}}
<div class="admin-header-box">
<div>
<h4>Senarai Borang soal selidik</h4>
<p>Beserta statistik dan analisis Post-Mortem.</p>
</div>
<a href="{{ route('admin.surveys.create') }}" class="btn btn-sm btn-light px-3 fw-bold text-primary">
<i class="bi bi-plus-lg"></i> Daftar soal selidik
</a>
</div>
@if(session('success'))
<div class="alert alert-success alert-dismissible fade show" role="alert">
{{ session('success') }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
@endif
{{-- Carian borang --}}
<form method="GET" action="{{ route('admin.surveys.index') }}" class="mb-4">
<div class="input-group shadow-sm">
<input type="text" name="search" value="{{ request('search') }}" class="form-control border-end-0" placeholder="Cari tajuk atau kata kunci...">
<button class="btn btn-primary" type="submit"><i class="bi bi-search"></i> </button>
<a href="{{ route('admin.surveys.index') }}" class="btn btn-outline-secondary"><i class="bi bi-arrow-counterclockwise"></i></a>
</div>
</form>
<div class="card survey-table-card shadow-sm border-0">
<div class="card-header bg-secondary text-white py-3">
<h5 class="mb-0 text-center fw-bold">Senarai Soal Selidik</h5>
</div>
<div class="card-body p-0">
<div class="table-responsive-lg">
<table class="table table-hover table-striped table-bordered align-middle mb-0">
<thead class="table-light">
<tr>
<th class="text-center">#</th>
<th style="width: 40%;">Tajuk</th>
<th style="width: 20%;">Dibuat oleh</th>
<th class="text-center">Respons</th>
<th>Tarikh</th>
<th class="text-center">Stats & Analisis</th>
<th class="text-center">Tindakan</th>
</tr>
</thead>
<tbody>
@forelse($surveys as $index => $survey)
<tr>
<td class="text-center">{{ $index + 1 }}</td>
<td>
<div class="fw-semibold text-uppercase text-primary position-relative survey-title-container">
<a href="{{ route('surveys.show', $survey) }}"
target="_blank"
class="text-decoration-none fw-bold text-primary">
{{ $survey->title }}
</a>
<button class="btn btn-link btn-sm p-0 ms-2 text-secondary"
onclick="copySurveyLink('{{ $survey->uuid }}')"
title="Copy Link">
<i class="bi bi-copy"></i>
</button>
</div>
</td>
<td>{{ $survey->user->name }}</td>
<td class="text-center">
<span class="badge rounded-pill bg-info text-white px-3">
{{ $survey->responses->count() }}
</span>
</td>
<td>{{ $survey->date }}</td>
<td class="text-center">
<a href="{{ route('admin.surveys.statistics', $survey->id) }}" class="btn btn-sm btn-info text-white fw-semibold">
<i class="bi bi-bar-chart"></i> Graf
</a>
<button type="button" class="btn btn-sm btn-warning fw-semibold" data-bs-toggle="modal" data-bs-target="#modalUlasan{{ $survey->id }}">
<i class="bi bi-clipboard-check"></i> Ulasan
</button>
</td>
<td class="text-center">
<div class="dropdown">
<button class="btn btn-secondary btn-sm dropdown-toggle" type="button" data-bs-toggle="dropdown">
<i class="bi bi-three-dots-vertical"></i>
</button>
<ul class="dropdown-menu shadow border-0">
<li>
<a class="dropdown-item" href="{{ route('admin.surveys.edit', $survey->id) }}">
<i class="bi bi-pencil me-2 text-primary"></i> Edit Borang
</a>
</li>
<li><hr class="dropdown-divider"></li>
<li>
<form action="{{ route('admin.surveys.destroy', $survey->id) }}" method="POST" onsubmit="return confirm('Padam borang ini?')">
@csrf
@method('DELETE')
<button type="submit" class="dropdown-item text-danger">
<i class="bi bi-trash me-2"></i> Hapus
</button>
</form>
</li>
</ul>
</div>
</td>
</tr>
<div class="modal fade" id="modalUlasan{{ $survey->id }}" tabindex="-1" aria-labelledby="modalLabel{{ $survey->id }}" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content border-0 shadow-lg">
<div class="modal-header bg-warning">
<h5 class="modal-title fw-bold" id="modalLabel{{ $survey->id }}">
<i class="bi bi-pencil-square me-2"></i>Ulasan Post-Mortem
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form action="{{ route('admin.surveys.update_ulasan', $survey->id) }}" method="POST">
@csrf
<div class="modal-body py-4">
<div class="mb-3">
<label class="form-label fw-bold">Tajuk Borang:</label>
<p class="text-muted border-bottom pb-2">{{ $survey->title }}</p>
</div>
<div class="mb-3">
<label for="ulasan" class="form-label fw-bold text-dark">Nota Penambahbaikan:</label>
<textarea name="ulasan" class="form-control border-warning shadow-sm" rows="6" placeholder="Tulis ulasan penambahbaikan di sini">{{ $survey->ulasan }}</textarea>
<div class="form-text mt-2">
<i class="bi bi-info-circle me-1"></i> Perkara yang perlu ditambah baik pada masa akan datang.
</div>
</div>
</div>
<div class="modal-footer bg-light text-end">
<button type="button" class="btn btn-secondary px-4" data-bs-dismiss="modal">Batal</button>
<button type="submit" class="btn btn-primary px-4 shadow-sm">
<i class="bi bi-save me-1"></i> Simpan Ulasan
</button>
</div>
</form>
</div>
</div>
</div>
@empty
<tr>
<td colspan="7" class="text-center p-5 text-muted">
<i class="bi bi-clipboard-x display-4 d-block mb-3 opacity-25"></i>
Tiada borang soal selidik ditemui.
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
</div>
{{-- Pagination --}}
<div class="d-flex justify-content-center mt-4">
{{ $surveys->withQueryString()->links() }}
</div>
<!-- Toast Notification for Copy Success -->
<div class="position-fixed bottom-0 end-0 p-3" style="z-index: 11">
<div id="copyToast" class="toast align-items-center text-white bg-success border-0" role="alert" aria-live="assertive" aria-atomic="true">
<div class="d-flex">
<div class="toast-body">
<i class="bi bi-check-circle-fill me-2"></i>Link berjaya disalin!
</div>
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast"></button>
</div>
</div>
</div>
</div>
@push('scripts')
<script>
function copySurveyLink(surveyUuid) {
const baseUrl = "{{ url('/') }}";
const surveyUrl = `${baseUrl}/survey/${surveyUuid}`;
if (navigator.clipboard && window.isSecureContext) {
navigator.clipboard.writeText(surveyUrl).then(function() {
showCopyToast();
}).catch(function(err) {
console.error('Clipboard API failed: ', err);
fallbackCopy(surveyUrl);
});
} else {
fallbackCopy(surveyUrl);
}
}
function fallbackCopy(text) {
const textarea = document.createElement('textarea');
textarea.value = text;
textarea.style.position = 'fixed';
textarea.style.opacity = '0';
document.body.appendChild(textarea);
textarea.focus();
textarea.select();
try {
const successful = document.execCommand('copy');
if (successful) {
showCopyToast();
} else {
alert('Gagal menyalin link. Sila cuba lagi.');
}
} catch (err) {
console.error('Fallback copy failed: ', err);
alert('Gagal menyalin link. Sila cuba lagi.');
}
document.body.removeChild(textarea);
}
// Show toast notification
function showCopyToast() {
const toastEl = document.getElementById('copyToast');
const toast = new bootstrap.Toast(toastEl);
toast.show();
}
</script>
@endpush
@endsection

View File

@@ -0,0 +1,198 @@
<!DOCTYPE html>
<html lang="ms">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Statistik - {{ $survey->title }}</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Inter', sans-serif;
background-color: white !important;
padding: 10px;
font-size: 13px;
}
.break-inside-avoid {
break-inside: avoid;
margin-bottom: 15px !important;
}
.chart-container {
height: 220px;
margin-bottom: 10px;
}
.card-body {
padding: 1rem !important;
}
.card-header {
padding: 0.5rem 1rem !important;
}
h1.h3 { font-size: 1.5rem !important; }
h5 { font-size: 1rem !important; }
.table-sm { font-size: 0.8rem !important; }
@media print {
.no-print { display: none !important; }
.card { border: 1px solid #ddd !important; box-shadow: none !important; }
.badge { border: 1px solid #ccc !important; color: black !important; background: white !important; }
body { padding: 0; }
.container-fluid { padding: 0; }
}
</style>
</head>
<body>
<div class="container-fluid">
<div class="text-center mb-4 border-bottom pb-2">
<h1 class="fw-bold h3 mb-1">{{ $survey->title }}</h1>
<p class="text-muted mb-0 small">Laporan Statistik - {{ date('d/m/Y H:i') }}</p>
</div>
<div class="row g-2 mb-3">
<div class="col-6">
<div class="card text-center py-1 bg-light border-0">
<div class="card-body py-2">
<h4 class="fw-bold mb-0">{{ $totalSurveyRespondents }}</h4>
<small class="text-muted">Responden</small>
</div>
</div>
</div>
<div class="col-6">
<div class="card text-center py-1 bg-light border-0">
<div class="card-body py-2">
<h4 class="fw-bold mb-0">{{ $totalSurveyQuestions }}</h4>
<small class="text-muted">Soalan</small>
</div>
</div>
</div>
</div>
@foreach($results as $questionId => $result)
<div class="card mb-3 break-inside-avoid border shadow-none">
<div class="card-header bg-light border-bottom p-2">
<h6 class="fw-bold mb-0">Soalan {{ $loop->iteration }}: {{ $result['question'] }}</h6>
</div>
<div class="card-body p-2">
@if(isset($result['type']) && $result['type'] == 'text')
<div class="table-responsive">
<table class="table table-bordered table-striped table-sm mb-0">
<thead class="table-light">
<tr>
<th style="width: 40px;">#</th>
<th>Jawapan</th>
</tr>
</thead>
<tbody>
@forelse($result['data'] as $answer)
<tr>
<td>{{ $loop->iteration }}</td>
<td>{{ $answer }}</td>
</tr>
@empty
<tr>
<td colspan="2" class="text-center text-muted">Tiada jawapan.</td>
</tr>
@endforelse
</tbody>
</table>
</div>
@else
<div class="row g-2 align-items-center">
<div class="col-7">
<div class="chart-container">
<canvas id="chart-{{ $questionId }}"></canvas>
</div>
</div>
<div class="col-5">
<table class="table table-bordered table-sm mb-0" style="font-size: 0.75rem;">
<thead class="table-light">
<tr>
<th>Pilihan Jawapan</th>
<th class="text-center">Kekerapan</th>
<th class="text-center">Peratus(%)</th>
</tr>
</thead>
<tbody>
@foreach($result['data'] as $stat)
<tr>
<td>{{ $stat['label'] }}</td>
<td class="text-center">{{ $stat['count'] }}</td>
<td class="text-center fw-bold">{{ $stat['percentage'] }}%</td>
</tr>
@endforeach
<tr class="table-secondary fw-bold">
<td>JUMLAH</td>
<td class="text-center">{{ $result['total'] }}</td>
<td class="text-center">100%</td>
</tr>
</tbody>
</table>
</div>
</div>
@if(!empty($result['other_answers']))
<div class="mt-2 border-top pt-1">
<small class="fw-bold text-secondary" style="font-size: 0.7rem;">Lain-lain:</small>
<div class="d-flex flex-wrap gap-1 mt-1">
@foreach($result['other_answers'] as $otherAns)
<span class="badge bg-white text-dark border p-1 fw-normal" style="font-size: 0.7rem;">{{ $otherAns }}</span>
@endforeach
</div>
</div>
@endif
@endif
</div>
</div>
@endforeach
<div class="text-center mt-3 no-print">
<button onclick="window.print()" class="btn btn-primary px-4 py-1 fw-bold">KLIK UNTUK CETAK</button>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
const results = @json($results);
Object.keys(results).forEach(id => {
const data = results[id];
if(data.type === 'text') return;
const ctx = document.getElementById(`chart-${id}`).getContext('2d');
const labels = data.data.map(item => item.label);
const counts = data.data.map(item => item.count);
new Chart(ctx, {
type: 'bar',
data: {
labels: labels,
datasets: [{
label: 'Bilangan',
data: counts,
backgroundColor: 'rgba(54, 162, 235, 0.5)',
borderColor: 'rgb(54, 162, 235)',
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
animation: { duration: 0 },
scales: {
y: { beginAtZero: true, ticks: { precision: 0, font: { size: 10 } } },
x: { ticks: { font: { size: 10 } } }
},
plugins: {
legend: { display: false }
}
}
});
});
setTimeout(() => {
window.print();
}, 800);
});
</script>
</body>
</html>

View File

@@ -0,0 +1,189 @@
@extends('layouts.app')
@push('styles')
<link rel="stylesheet" href="{{ asset('css/adminHeader.css') }}">
<link rel="stylesheet" href="{{ asset('css/statisticsAdmin.css') }}">
@endpush
@section('content')
<div class="container">
<div class="admin-header-box">
<div>
<h4>Statistik: {{ $survey->title }}</h4>
<p>Analisis data respons bagi borang ini.</p>
</div>
<div class="d-flex gap-2">
<a href="{{ route('admin.surveys.statistics_print', $survey->id) }}" target="_blank" class="btn btn-sm btn-light px-3 fw-bold text-primary">
<i class="bi bi-printer me-1"></i> Cetak
</a>
<a href="{{ route('admin.surveys.index') }}" class="btn btn-sm btn-light px-3 fw-bold text-primary">
<i class="bi bi-arrow-left me-1"></i> Kembali
</a>
</div>
</div>
<div class="row g-2 mb-3">
<div class="col-md-6">
<div class="card shadow-sm border text-center py-2">
<div class="card-body p-2">
<h3 class="stat-card-number">{{ $totalSurveyRespondents }}</h3>
<small class="text-muted">Jumlah Responden</small>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card shadow-sm border text-center py-2">
<div class="card-body p-2">
<h3 class="stat-card-number">{{ $totalSurveyQuestions }}</h3>
<small class="text-muted">Jumlah Soalan</small>
</div>
</div>
</div>
</div>
@foreach($results as $questionId => $result)
<div class="card shadow-sm mb-5 break-inside-avoid">
<div class="card-header bg-white">
<h5 class="fw-bold mb-0">Soalan: {{ $result['question'] }}</h5>
</div>
<div class="card-body">
@if(isset($result['type']) && $result['type'] == 'text')
{{-- DISPLAY TEXT ANSWERS --}}
<div class="table-responsive">
<table class="table table-bordered table-striped">
<thead class="table-light">
<tr>
<th style="width: 50px;">#</th>
<th>Jawapan Responden</th>
</tr>
</thead>
<tbody>
@forelse($result['data'] as $index => $answer)
<tr>
<td>{{ $loop->iteration }}</td>
<td>{{ $answer }}</td>
</tr>
@empty
<tr>
<td colspan="2" class="text-center text-muted">Tiada jawapan.</td>
</tr>
@endforelse
</tbody>
</table>
</div>
@else
{{-- DISPLAY CHART FOR RADIO/CHECKBOX --}}
<div class="row">
<div class="col-md-7 mb-4 mb-md-0">
<div class="chart-container-box">
<canvas id="chart-{{ $questionId }}"></canvas>
</div>
</div>
<div class="col-md-5">
<table class="table table-bordered table-striped table-hover">
<thead class="table-dark">
<tr>
<th class="text-center">Pilihan Jawapan</th>
<th class="text-center">Kekerapan</th>
<th class="text-center">Peratus (%)</th>
</tr>
</thead>
<tbody>
@foreach($result['data'] as $stat)
<tr>
<td>{{ $stat['label'] }}</td>
<td class="text-center">{{ $stat['count'] }}</td>
<td class="text-center fw-bold">{{ $stat['percentage'] }}%</td>
</tr>
@endforeach
<tr class="table-secondary fw-bold">
<td>JUMLAH</td>
<td class="text-center">{{ $result['total'] }}</td>
<td class="text-center">100%</td>
</tr>
</tbody>
</table>
</div>
</div>
@if(!empty($result['other_answers']))
<div class="mt-4">
<h6 class="fw-bold text-secondary">Jawapan Lain-lain:</h6>
<ul class="list-group list-group-flush border rounded">
@foreach($result['other_answers'] as $otherAns)
<li class="list-group-item other-answer-item">{{ $otherAns }}</li>
@endforeach
</ul>
</div>
@endif
@endif
</div>
</div>
@endforeach
</div>
{{-- Load Chart.js CDN (Kalau belum ada dalam layout) --}}
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
const results = @json($results);
Object.keys(results).forEach(id => {
const data = results[id];
if(data.type === 'text') return;
const ctx = document.getElementById(`chart-${id}`).getContext('2d');
// Extract labels dan values untuk chart
const labels = data.data.map(item => item.label);
const counts = data.data.map(item => item.count);
new Chart(ctx, {
type: 'bar',
data: {
labels: labels,
datasets: [{
label: 'Bilangan Responden',
data: counts,
backgroundColor: [
'rgba(255, 99, 132, 0.2)',
'rgba(255, 159, 64, 0.2)',
'rgba(255, 205, 86, 0.2)',
'rgba(75, 192, 192, 0.2)',
'rgba(54, 162, 235, 0.2)',
'rgba(153, 102, 255, 0.2)',
'rgba(201, 203, 207, 0.2)'],
borderColor: [
'rgb(255, 99, 132)',
'rgb(255, 159, 64)',
'rgb(255, 205, 86)',
'rgb(75, 192, 192)',
'rgb(54, 162, 235)',
'rgb(153, 102, 255)',
'rgb(201, 203, 207)'],
borderWidth: 1
}]
},
options: {
// indexAxis: 'y',
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true
}
},
plugins: {
legend: {
display: false
}
}
}
});
});
});
</script>
@endsection

View File

@@ -0,0 +1,106 @@
@extends('layouts.app')
@push('styles')
<link rel="stylesheet" href="{{ asset('css/adminHeader.css') }}">
<link rel="stylesheet" href="{{ asset('css/ulasanAdmin.css') }}">
@endpush
@section('content')
<div class="container">
{{-- Header --}}
<div class="admin-header-box">
<div>
<h4>Nota Post-Mortem</h4>
<p>Klik kad untuk baca ulasan penuh.</p>
</div>
<div class="d-flex gap-2 align-items-center">
<a href="{{ route('admin.surveys.ulasan.export') }}" class="btn btn-sm btn-success rounded-pill px-3 fw-bold me-2">
<i class="bi bi-file-earmark-excel me-1"></i> Export
</a>
{{-- Search Form --}}
<form method="GET" action="{{ route('admin.surveys.ulasan') }}" class="d-flex gap-2 mb-0">
<div class="input-group input-group-sm">
<input type="text" name="search" value="{{ request('search') }}" class="form-control rounded-pill px-3" placeholder="Cari tajuk..." style="width: 200px;">
<button class="btn btn-light rounded-circle ms-1 p-1 d-flex align-items-center justify-content-center" type="submit" style="width: 30px; height: 30px;">
<i class="bi bi-search text-primary"></i>
</button>
@if(request('search'))
<a href="{{ route('admin.surveys.ulasan') }}" class="btn btn-light rounded-circle ms-1 p-1 d-flex align-items-center justify-content-center" style="width: 30px; height: 30px;">
<i class="bi bi-x-lg text-danger"></i>
</a>
@endif
</div>
</form>
<a href="{{ route('admin.surveys.index') }}" class="btn btn-sm btn-light rounded-pill px-3 fw-bold text-primary">
<i class="bi bi-arrow-left me-1"></i> Kembali
</a>
</div>
</div>
<div class="row g-3">
@forelse($surveys as $survey)
<div class="col-6 col-md-4 col-lg-3 mb-2">
<div class="card h-100 border-0 shadow-sm rounded-4 position-relative overflow-hidden cursor-pointer ulasan-hover-effect"
data-bs-toggle="modal"
data-bs-target="#ulasanModal{{ $survey->id }}"
style="cursor: pointer; background: #ffffff;">
<div class="position-absolute top-0 start-0 h-100 bg-primary-subtle" style="width: 3px;"></div>
<div class="card-body p-3">
<div class="d-flex justify-content-between mb-2">
<small class="text-primary fw-bold" style="font-size: 0.65rem;">#{{ $survey->id }}</small>
<small class="text-muted" style="font-size: 0.65rem;">{{ $survey->updated_at->format('d/m/y') }}</small>
</div>
<h6 class="fw-bold text-dark mb-2 text-truncate" style="text-transform: uppercase; font-size: 0.9rem;">
{{ $survey->title }}
</h6>
<div class="p-2 rounded-3" style="background-color: #f8f9fa;">
<p class="text-secondary mb-0" style="font-size: 0.75rem; line-height: 1.4;">
{{ Str::limit($survey->ulasan, 60) }}
@if(strlen($survey->ulasan) > 60)
<span class="text-primary fw-bold" style="font-size: 0.7rem;">...more</span>
@endif
</p>
</div>
</div>
</div>
</div>
{{-- MODAL UNTUK AYAT PENUH --}}
<div class="modal fade" id="ulasanModal{{ $survey->id }}" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content border-0 rounded-4 shadow">
<div class="modal-header border-0 pb-0">
<h6 class="modal-title fw-bold text-primary">Ulasan Penuh</h6>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body pt-2">
<p class="text-muted small mb-1">Tajuk Borang:</p>
<h6 class="fw-bold mb-3">{{ $survey->title }}</h6>
<hr class="opacity-10">
<p class="text-dark" style="white-space: pre-line; line-height: 1.6; font-size: 0.9rem;">
{{ $survey->ulasan }}
</p>
</div>
<div class="modal-footer border-0 pt-0">
<small class="text-muted me-auto" style="font-size: 0.7rem;">Oleh: {{ $survey->user->name ?? 'Admin' }}</small>
<button type="button" class="btn btn-sm btn-secondary rounded-pill px-3" data-bs-dismiss="modal">Tutup</button>
</div>
</div>
</div>
</div>
@empty
<div class="col-12 text-center py-5">
<p class="text-muted small">Tiada ulasan ditemui.</p>
</div>
@endforelse
</div>
</div>
@endsection

View File

@@ -0,0 +1,182 @@
@extends('layouts.app')
@push('styles')
<link rel="stylesheet" href="{{ asset('css/adminHeader.css') }}">
<link rel="stylesheet" href="{{ asset('css/usersAdmin.css') }}">
@endpush
@section('content')
<div class="container">
{{-- Header --}}
<div class="admin-header-box">
<div>
<h4>Pengurusan Pengguna</h4>
<p>Senarai pengguna dalam sistem berserta nombor pekerja, jabatan, dan role.</p>
</div>
<button type="button" class="btn btn-sm btn-light px-3 fw-bold text-primary" data-bs-toggle="modal" data-bs-target="#addUserModal">
<i class="bi bi-plus-lg"></i> Tambah Pengguna
</button>
</div>
@if(session('success'))
<div class="alert alert-success alert-dismissible fade show" role="alert">
<i class="bi bi-check-circle me-2"></i>{{ session('success') }}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
@endif
{{-- Search Form --}}
<form method="GET" action="{{ route('admin.users.index') }}" class="mb-4">
<div class="input-group shadow-sm">
<input type="text" name="search" value="{{ request('search') }}" class="form-control border-end-0" placeholder="Cari nama, nombor pekerja atau jabatan...">
<button class="btn btn-primary" type="submit"><i class="bi bi-search"></i></button>
<a href="{{ route('admin.users.index') }}" class="btn btn-outline-secondary"><i class="bi bi-arrow-counterclockwise"></i></a>
</div>
</form>
<div class="card user-table-card">
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead class="table-light">
<tr>
<th class="px-4">ID</th>
<th>Nama</th>
<th>Nombor Pekerja</th>
<th>Jabatan</th>
<th>Role</th>
<th class="text-center">Tindakan</th>
</tr>
</thead>
<tbody>
@forelse($users as $user)
<tr>
<td class="px-4">{{ $user->id }}</td>
<td>{{ $user->name }}</td>
<td>{{ $user->no_pekerja }}</td>
<td>{{ $user->jabatan ?? 'N/A' }}</td>
<td>
<span class="badge {{ $user->role == 'admin' ? 'badge-admin' : 'badge-staff' }}">
{{ ucfirst($user->role) }}
</span>
</td>
<td class="text-center">
<button class="btn btn-sm btn-warning me-1" data-bs-toggle="modal" data-bs-target="#editUser{{ $user->id }}">
<i class="bi bi-pencil"></i>
</button>
<form action="{{ route('admin.users.delete', $user->id) }}" method="POST" class="d-inline">
@csrf
@method('DELETE')
<button class="btn btn-sm btn-danger" onclick="return confirm('Hapus pengguna ini?')">
<i class="bi bi-trash"></i>
</button>
</form>
</td>
</tr>
{{-- Edit Modal --}}
<div class="modal fade" id="editUser{{ $user->id }}" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<form method="POST" action="{{ route('admin.users.update', $user->id) }}">
@csrf
@method('PUT')
<div class="modal-header">
<h5 class="modal-title">Edit Pengguna</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label class="form-label">Nama</label>
<input type="text" name="name" class="form-control" value="{{ $user->name }}" required>
</div>
<div class="mb-3">
<label class="form-label">Nombor Pekerja</label>
<input type="text" name="no_pekerja" class="form-control" value="{{ $user->no_pekerja }}" required>
</div>
<div class="mb-3">
<label class="form-label">Jabatan</label>
<input type="text" name="jabatan" class="form-control" value="{{ $user->jabatan }}" placeholder="Contoh: IT, HR, Kewangan" required>
</div>
<div class="mb-3">
<label class="form-label">Password</label>
<input type="password" name="password" class="form-control" placeholder="Kosongkan jika tidak mahu tukar">
<small class="text-muted">Biarkan kosong untuk kekalkan password semasa</small>
</div>
<div class="mb-3">
<label class="form-label">Role</label>
<select name="role" class="form-select" required>
<option value="admin" {{ $user->role == 'admin' ? 'selected' : '' }}>Admin</option>
<option value="staff" {{ $user->role == 'staff' ? 'selected' : '' }}>Staff</option>
</select>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Tutup</button>
<button type="submit" class="btn btn-primary">Simpan Perubahan</button>
</div>
</form>
</div>
</div>
</div>
@empty
<tr>
<td colspan="6" class="text-center py-5">
<i class="bi bi-people text-muted" style="font-size: 3rem;"></i>
<p class="text-muted mt-3 mb-0">Tiada pengguna dijumpai</p>
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
</div>
</div>
{{-- Add User Modal --}}
<div class="modal fade" id="addUserModal" tabindex="-1" aria-labelledby="addUserModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<form method="POST" action="{{ route('admin.users.store') }}">
@csrf
<div class="modal-header">
<h5 class="modal-title" id="addUserModalLabel">Tambah Pengguna Baharu</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label for="name" class="form-label">Nama</label>
<input type="text" id="name" name="name" class="form-control" placeholder="Nama penuh" required>
</div>
<div class="mb-3">
<label for="no_pekerja" class="form-label">Nombor Pekerja</label>
<input type="text" id="no_pekerja" name="no_pekerja" class="form-control" placeholder="Nombor pekerja" required>
</div>
<div class="mb-3">
<label for="jabatan" class="form-label">Jabatan</label>
<input type="text" id="jabatan" name="jabatan" class="form-control" placeholder="Contoh: IT, HR, Kewangan" required>
</div>
<div class="mb-3">
<label for="password" class="form-label">Password</label>
<input type="password" id="password" name="password" class="form-control" placeholder="Minimum 6 aksara" required>
<small class="text-muted">Minimum 6 aksara</small>
</div>
<div class="mb-3">
<label for="role" class="form-label">Role</label>
<select id="role" name="role" class="form-select" required>
<option value="" disabled selected>Pilih role</option>
<option value="admin">Admin</option>
<option value="staff">Staff</option>
</select>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Tutup</button>
<button type="submit" class="btn btn-success">Simpan</button>
</div>
</form>
</div>
</div>
</div>
@endsection

View File

@@ -0,0 +1,62 @@
@extends('layouts.page')
@section('content')
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
<link rel="stylesheet" href="{{ asset('css/login.css') }}">
<div class="container">
<div class="login-box">
<div class="form-section">
<img src="{{ asset('images/mbip.png') }}" alt="MBIP Logo" class="logo-mbip">
<h2>Selamat Datang</h2>
<p>Sila masukkan maklumat berikut :</p>
@if(session('success'))
<div class="alert alert-success">
<i class="bi bi-check-circle-fill"></i>{{ session('success') }}
</div>
@endif
@if($errors->any())
<div class="alert alert-danger">
<i class="bi bi-exclamation-circle-fill"></i>{{ $errors->first() }}
</div>
@endif
<form method="POST" action="{{ route('login.post') }}">
@csrf
<div class="input-group">
<label>Nombor Pekerja</label>
<input type="text" name="no_pekerja" placeholder="••••••••"
value="{{ old('no_pekerja') }}" required autofocus>
</div>
<div class="input-group">
<label>Kata Laluan</label>
<input type="password" name="password" placeholder="••••••••" required>
</div>
<button type="submit" class="btn-signin">Log Masuk</button>
</form>
</div>
<div class="content-section">
<h1 class="content-title">myEventPostMortem</h1>
<div class="p-4 text-white border-bottom border-secondary">
<h6 class="mb-0 fw-bold" style="font-size:1.25rem;">Untuk Tujuan Ujicuba. Data akan direset sesuka hati.</h6>
</div>
<p class="content-desc">Sistem Rujukan Majlis-majlis di bawah MBIP.</p>
<div class="image-placeholder">
<img src="{{ asset('images/mbippp.jpg') }}" alt="Gambar 1" class="img-fluid custom-img">
</div>
</div>
</div>
</div>
@endsection

View File

@@ -0,0 +1,160 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>myEventPostmortem</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons/font/bootstrap-icons.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css">
<link rel="stylesheet" href="{{ asset('css/app.css') }}">
@stack('styles')
</head>
<body>
<div class="container-fluid h-100 p-0">
<div class="row g-0 h-100 flex-nowrap">
<!-- Desktop Sidebar -->
<div class="col-auto col-md-3 col-lg-2 bg-dark d-none d-md-flex flex-column h-100 sidebar">
<div class="p-4 text-white border-bottom border-secondary">
<h4 class="mb-0 fw-bold" style="font-size:1.25rem;">myEventPostmortem</h4>
</div>
<div class="p-4 text-white border-bottom border-secondary">
<h6 class="mb-0 fw-bold" style="font-size:1.25rem;">Untuk Tujuan Ujicuba. Data akan direset sesuka hati.</h6>
</div>
@auth
<ul class="nav nav-pills flex-column mb-auto px-2">
{{-- ADMIN Links --}}
@if(auth()->user()->role == 'admin')
<li class="nav-item">
<a href="{{ route('admin.dashboard') }}" class="nav-link {{ request()->routeIs('admin.dashboard') ? 'active' : '' }}">
<i class="bi bi-speedometer2 me-2"></i> Dashboard
</a>
</li>
<li class="nav-item">
<a href="{{ route('admin.surveys.index') }}" class="nav-link {{ request()->routeIs('admin.surveys.index') ? 'active' : '' }}">
<i class="bi bi-clipboard-data me-2"></i> Borang soal selidik
</a>
</li>
<li class="nav-item">
<a href="{{ route('admin.responses.list') }}" class="nav-link {{ request()->routeIs('admin.responses.list') ? 'active' : '' }}">
<i class="bi bi-clipboard-check me-2"></i> Respons
</a>
</li>
<li class="nav-item">
<a href="{{ route('admin.surveys.ulasan') }}" class="nav-link {{ request()->routeIs('admin.surveys.ulasan') ? 'active' : '' }}">
<i class="bi bi-pencil-square me-2"></i> Ulasan / Keputusan
</a>
</li>
<li class="nav-item">
<a href="{{ route('admin.users.index') }}" class="nav-link {{ request()->routeIs('admin.users.index') ? 'active' : '' }}">
<i class="bi bi-people me-2"></i> Senarai pengguna
</a>
</li>
@endif
</ul>
<div class="mt-auto p-3">
<form method="POST" action="{{ route('logout') }}">
@csrf
<button class="btn btn-logout" type="submit">
<i class="bi bi-box-arrow-right me-4"></i> Log keluar
</button>
</form>
</div>
@endauth
</div>
<!-- Offcanvas Sidebar for Mobile -->
<div class="offcanvas offcanvas-start bg-dark text-white" tabindex="-1" id="mobileSidebar">
<div class="offcanvas-header border-bottom border-secondary">
<h5 class="mb-0 fw-bold">myEventPostmortem</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="offcanvas"></button>
</div>
<div class="offcanvas-body d-flex flex-column">
<ul class="nav nav-pills flex-column mb-auto px-2">
{{-- ADMIN Links --}}
@auth
@if(auth()->user()->role == 'admin')
<li class="nav-item">
<a href="{{ route('admin.dashboard') }}" class="nav-link {{ request()->routeIs('admin.dashboard') ? 'active' : '' }}">
<i class="bi bi-speedometer2 me-2"></i> Dashboard
</a>
</li>
<li class="nav-item">
<a href="{{ route('admin.surveys.index') }}" class="nav-link {{ request()->routeIs('admin.surveys.index') ? 'active' : '' }}">
<i class="bi bi-clipboard-data me-2"></i> Borang soal selidik
</a>
</li>
<li class="nav-item">
<a href="{{ route('admin.responses.list') }}" class="nav-link {{ request()->routeIs('admin.responses.list') ? 'active' : '' }}">
<i class="bi bi-clipboard-check me-2"></i> Respons
</a>
</li>
<li class="nav-item">
<a href="{{ route('admin.surveys.ulasan') }}" class="nav-link {{ request()->routeIs('admin.surveys.ulasan') ? 'active' : '' }}">
<i class="bi bi-pencil-square me-2"></i> Ulasan / Keputusan
</a>
</li>
<li class="nav-item">
<a href="{{ route('admin.users.index') }}" class="nav-link {{ request()->routeIs('admin.users.index') ? 'active' : '' }}">
<i class="bi bi-people me-2"></i> Senarai pengguna
</a>
</li>
@endif
@endauth
</ul>
<div class="mt-auto p-3">
@auth
<form method="POST" action="{{ route('logout') }}">
@csrf
<button class="btn btn-logout w-100" type="submit">
<i class="bi bi-box-arrow-right me-2"></i> Log keluar
</button>
</form>
@endauth
</div>
</div>
</div>
<!-- Main Content -->
<div class="col overflow-auto">
<header class="d-flex justify-content-between align-items-center py-3 px-4 border-bottom bg-white sticky-top shadow-sm">
<!-- Hamburger button visible only on small screens -->
<button class="btn btn-outline-secondary d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#mobileSidebar">
<i class="bi bi-list"></i>
</button>
@auth
<h5 class="mb-0 fw-semibold text-dark">
{{ ucfirst(auth()->user()->role) }}
</h5>
<span class="text-secondary fw-semibold">
Hai, {{ auth()->user()->name }}
</span>
@endauth
</header>
<main class="p-3">
@yield('content')
</main>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
@stack('scripts')
</body>
</html>

View File

@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>@yield('title', 'myEventPostmortem')</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons/font/bootstrap-icons.css" rel="stylesheet">
</head>
<body class="bg-light d-flex flex-column min-vh-100">
<div class="container-fluid flex-grow-1 d-flex justify-content-center align-items-center">
@yield('content')
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

View File

@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>@yield('title', $survey->title)</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons/font/bootstrap-icons.css" rel="stylesheet">
</head>
<body class="bg-light d-flex flex-column min-vh-100">
<div class="container-fluid flex-grow-1 d-flex justify-content-center align-items-center">
<div class="p-4 text-white border-bottom border-secondary">
<h6 class="mb-0 fw-bold" style="font-size:1.25rem;">Untuk Tujuan Ujicuba. Data akan direset sesuka hati.</h6>
</div>
@yield('content')
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
@stack('scripts')
</body>
</html>

View File

@@ -0,0 +1,34 @@
<!DOCTYPE html>
<html lang="ms">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Berjaya</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css">
</head>
<body class="bg-light">
<div class="container py-5">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card shadow-lg text-center">
<div class="card-body p-5">
<div class="mb-4">
<i class="bi bi-check-circle-fill text-success" style="font-size: 5rem;"></i>
</div>
<h2 class="text-success mb-3">Terima Kasih!</h2>
<p class="lead mb-4">{{ session('success', 'Jawapan anda telah direkodkan.') }}</p>
<p class="text-muted">Maklum balas anda sangat berharga untuk penambahbaikan program pada masa hadapan.</p>
</div>
</div>
<div class="text-center mt-4 text-muted">
<small>© {{ date('Y') }} myEventPostMortem</small>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

View File

@@ -0,0 +1,262 @@
@extends('layouts.staff')
@section('content')
<div class="container">
<div class="card shadow-sm">
<div class="card-header bg-dark text-white text-center">
<h4 class="mb-1">{{ $survey->title }}</h4>
<small class="text-white-50">Tarikh: {{ \Carbon\Carbon::parse($survey->date)->format('d M Y') }}</small>
</div>
<div class="card-body p-4">
@if($survey->perincian)
<div class="mb-4 text-dark">
{!! nl2br(e($survey->perincian)) !!}
</div>
@endif
@if ($errors->any())
<div class="alert alert-danger">
<strong>Ralat!</strong> {{ $errors->first() }}
</div>
@endif
<form action="{{ route('surveys.store', $survey->uuid) }}" method="POST" id="survey-form">
@csrf
<div class="card shadow-sm mb-4 border-primary">
<div class="card-header bg-primary text-white small fw-bold">MAKLUMAT RESPONDEN</div>
<div class="card-body">
<div class="mb-3">
<label class="form-label small fw-bold">Nama Penuh</label>
<input type="text" name="respondent_name" class="form-control" placeholder="Nama Penuh Anda" required>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label small fw-bold">No. Pekerja</label>
<input type="text" name="respondent_no_pekerja" class="form-control" required>
</div>
<div class="col-md-6 mb-3">
<label class="form-label small fw-bold">Jabatan</label>
<select name="respondent_jabatan" class="form-select" required>
<option value="">Pilih Jabatan</option>
<option value="DatukBandar">Datuk Bandar</option>
<option value="SU">Setiausaha</option>
<option value="TSU">Timbalan Setiausaha</option>
<option value="JKP">Khidmat Pengurusan</option>
<option value="Kewangan">Kewangan</option>
<option value="JPPH">Penilaian dan Pengurusan Harta</option>
<option value="JPP">Perancangan Pembangunan</option>
<option value="Kejuruteraan">Kejuruteraan</option>
<option value="Bangunan">Bangunan</option>
<option value="JKA">Kesihatan Awam</option>
<option value="Pelesenan">Pelesenan</option>
<option value="Penguatkuasaan">Penguatkuasaan</option>
<option value="JPM">Pembangunan Masyarakat</option>
<option value="Lanskap">Lanskap</option>
<option value="Undang-undang">Undang-undang</option>
<option value="Korporat">Korporat dan Perhubungan Awam</option>
<option value="JTM">Teknologi Maklumat</option>
<option value="Audit">Unit Audi Dalam</option>
<option value="PengurusanStrategik">Unit Pengurusan Strategik</option>
<option value="Integriti">Unit Integriti</option>
<option value="OSC">Unit Pusat Sehenti (OSC)</option>
<option value="COB">Unit Pesuruhjaya Bangunan (COB)</option>
<option value="UBP">Unit Bandar Pintar (Smart City)</option>
</select>
</div>
</div>
</div>
</div>
{{-- LOOP BAHAGIAN --}}
@foreach($survey->sections as $section)
<fieldset class="mb-5">
<legend class="border-bottom pb-2 mb-3 fw-bold text-primary">{{ $section->title }}</legend>
@if($section->description)
<p class="text-muted mb-4">{{ $section->description }}</p>
@endif
{{-- LOOP SOALAN --}}
@foreach($section->questions as $question)
<div class="card mb-4 border-0 shadow-sm bg-light">
<div class="card-body">
<label class="form-label fw-bold mb-3">
{{ $loop->iteration }}. {{ $question->question_text }}
@if($question->type != 'checkbox' && $question->type != 'text')
<span class="text-danger">*</span>
@endif
</label>
{{-- TYPE: RADIO --}}
@if($question->type == 'radio')
<div class="radio-group" data-id="{{ $question->id }}">
@foreach($question->options as $option)
<div class="form-check mb-2">
<input class="form-check-input normal-option" type="radio"
name="answers[{{ $question->id }}]"
id="q{{ $question->id }}_opt{{ $option->id }}"
value="{{ $option->option_text }}" required>
<label class="form-check-label" for="q{{ $question->id }}_opt{{ $option->id }}">
{{ $option->option_text }}
</label>
</div>
@endforeach
{{-- OTHERS OPTION FOR RADIO --}}
@if($question->allow_other_option)
<div class="form-check mt-2">
<input class="form-check-input other-radio" type="radio"
name="answers[{{ $question->id }}]"
id="q{{ $question->id }}_other"
value="Other">
<label class="form-check-label fw-bold" for="q{{ $question->id }}_other">
Lain-lain (Sila nyatakan):
</label>
<input type="text" class="form-control mt-1 other-text-input"
style="display:none;"
placeholder="Sila tulis jawapan anda..." disabled>
</div>
@endif
</div>
{{-- TYPE: CHECKBOX --}}
@elseif($question->type == 'checkbox')
<div class="checkbox-group" data-id="{{ $question->id }}">
@foreach($question->options as $option)
<div class="form-check mb-2">
<input class="form-check-input normal-option" type="checkbox"
name="answers[{{ $question->id }}][]"
id="q{{ $question->id }}_opt{{ $option->id }}"
value="{{ $option->option_text }}">
<label class="form-check-label" for="q{{ $question->id }}_opt{{ $option->id }}">
{{ $option->option_text }}
</label>
</div>
@endforeach
{{-- OTHERS OPTION FOR CHECKBOX --}}
@if($question->allow_other_option)
<div class="form-check mt-2">
<input class="form-check-input other-checkbox" type="checkbox"
name="answers[{{ $question->id }}][]"
id="q{{ $question->id }}_other_check"
value="Other">
<label class="form-check-label fw-bold" for="q{{ $question->id }}_other_check">
Lain-lain (Sila nyatakan):
</label>
<input type="text" class="form-control mt-1 other-text-input"
style="display:none;"
placeholder="Sila tulis jawapan anda..." disabled>
</div>
@endif
</div>
{{-- TYPE: TEXT --}}
@elseif($question->type == 'text')
<textarea name="answers[{{ $question->id }}]"
class="form-control"
rows="3"
placeholder="Tulis jawapan anda di sini..." required></textarea>
@endif
</div>
</div>
@endforeach
</fieldset>
@endforeach
<div class="d-grid gap-2 d-md-flex justify-content-md-end mt-4 mb-5">
<button type="submit" class="btn btn-primary btn-lg">Hantar Jawapan</button>
</div>
</form>
</div>
</div>
</div>
@endsection
@push('scripts')
<script>
document.addEventListener('DOMContentLoaded', function() {
// 1. Handle RADIO logic
const radioGroups = document.querySelectorAll('.radio-group');
radioGroups.forEach(group => {
const otherRadio = group.querySelector('.other-radio');
const otherInput = group.querySelector('.other-text-input');
const normalRadios = group.querySelectorAll('.normal-option');
if(otherRadio && otherInput) {
otherRadio.addEventListener('change', function() {
if(this.checked) {
otherInput.style.display = 'block';
otherInput.disabled = false;
otherInput.focus();
otherInput.required = true; // Paksa isi
}
});
normalRadios.forEach(radio => {
radio.addEventListener('change', function() {
if(this.checked) {
otherInput.style.display = 'none';
otherInput.disabled = true;
otherInput.value = '';
otherInput.required = false;
}
});
});
}
});
// 2. Handle CHECKBOX logic
const checkboxGroups = document.querySelectorAll('.checkbox-group');
checkboxGroups.forEach(group => {
const otherCheck = group.querySelector('.other-checkbox');
const otherInput = group.querySelector('.other-text-input');
if(otherCheck && otherInput) {
otherCheck.addEventListener('change', function() {
if(this.checked) {
otherInput.style.display = 'block';
otherInput.disabled = false;
otherInput.focus();
otherInput.required = true;
} else {
otherInput.style.display = 'none';
otherInput.disabled = true;
otherInput.value = '';
otherInput.required = false;
}
});
}
});
// 3. FORM SUBMISSION INTERCEPTOR
// Ini trick paling penting. Sebelum hantar ke server, kita tukar value radio/checkbox
// "Other" tu menjadi text yang user tulis.
const form = document.getElementById('survey-form');
form.addEventListener('submit', function(e) {
// Cari semua input text "Lain-lain" yang aktif (tidak disabled)
const activeOtherInputs = document.querySelectorAll('.other-text-input:not(:disabled)');
activeOtherInputs.forEach(input => {
// Cari radio/checkbox kawannya (previous element sibling dalam DOM structure wrapper)
// Dalam struktur HTML di atas, input text adalah adik beradik dengan label, dan label dengan input radio.
// Kita cari wrapper parent div
const wrapper = input.closest('.form-check');
const relatedOption = wrapper.querySelector('input[type="radio"], input[type="checkbox"]');
if(relatedOption && relatedOption.checked) {
// TUKAR VALUE radio/checkbox menjadi apa yang user tulis
// Contoh: dari value="Other" jadi value="Saya suka makan nasi ayam"
relatedOption.value = "Lain-lain: " + input.value;
}
});
// Benarkan form submit seperti biasa
});
});
</script>
@endpush