first commit
This commit is contained in:
11
resources/css/app.css
Normal file
11
resources/css/app.css
Normal 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
1
resources/js/app.js
Normal file
@@ -0,0 +1 @@
|
||||
import './bootstrap';
|
||||
4
resources/js/bootstrap.js
vendored
Normal file
4
resources/js/bootstrap.js
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
import axios from 'axios';
|
||||
window.axios = axios;
|
||||
|
||||
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
|
||||
170
resources/views/admin/dashboard.blade.php
Normal file
170
resources/views/admin/dashboard.blade.php
Normal 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
|
||||
41
resources/views/admin/responses/detail.blade.php
Normal file
41
resources/views/admin/responses/detail.blade.php
Normal 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
|
||||
61
resources/views/admin/responses/list.blade.php
Normal file
61
resources/views/admin/responses/list.blade.php
Normal 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
|
||||
66
resources/views/admin/responses/respondents.blade.php
Normal file
66
resources/views/admin/responses/respondents.blade.php
Normal 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
|
||||
342
resources/views/admin/surveys/create.blade.php
Normal file
342
resources/views/admin/surveys/create.blade.php
Normal 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
|
||||
380
resources/views/admin/surveys/edit.blade.php
Normal file
380
resources/views/admin/surveys/edit.blade.php
Normal 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
|
||||
235
resources/views/admin/surveys/index.blade.php
Normal file
235
resources/views/admin/surveys/index.blade.php
Normal 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
|
||||
198
resources/views/admin/surveys/print_statistics.blade.php
Normal file
198
resources/views/admin/surveys/print_statistics.blade.php
Normal 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>
|
||||
189
resources/views/admin/surveys/statistics.blade.php
Normal file
189
resources/views/admin/surveys/statistics.blade.php
Normal 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
|
||||
106
resources/views/admin/surveys/ulasan.blade.php
Normal file
106
resources/views/admin/surveys/ulasan.blade.php
Normal 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
|
||||
182
resources/views/admin/users/index.blade.php
Normal file
182
resources/views/admin/users/index.blade.php
Normal 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
|
||||
62
resources/views/auth/login.blade.php
Normal file
62
resources/views/auth/login.blade.php
Normal 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
|
||||
160
resources/views/layouts/app.blade.php
Normal file
160
resources/views/layouts/app.blade.php
Normal 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>
|
||||
18
resources/views/layouts/page.blade.php
Normal file
18
resources/views/layouts/page.blade.php
Normal 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>
|
||||
22
resources/views/layouts/staff.blade.php
Normal file
22
resources/views/layouts/staff.blade.php
Normal 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>
|
||||
34
resources/views/staff/success.blade.php
Normal file
34
resources/views/staff/success.blade.php
Normal 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>
|
||||
262
resources/views/staff/surveys/soalan.blade.php
Normal file
262
resources/views/staff/surveys/soalan.blade.php
Normal 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
|
||||
Reference in New Issue
Block a user