first commit
This commit is contained in:
53
app/Http/Controllers/Admin/DashboardController.php
Normal file
53
app/Http/Controllers/Admin/DashboardController.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Survey;
|
||||
use App\Models\Response;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class DashboardController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
// count total borang
|
||||
$totalSurveys = Survey::count();
|
||||
|
||||
// Count total respons
|
||||
$totalResponses = Response::count();
|
||||
|
||||
// Count total users
|
||||
$totalUsers = User::count();
|
||||
|
||||
// 5 recent borang
|
||||
$recentSurveys = Survey::with(['user', 'responses'])
|
||||
->latest()
|
||||
->take(5)
|
||||
->get();
|
||||
|
||||
// Recent Reviews (Ulasan) - "sikit je" (e.g., 5)
|
||||
$recentReviews = Survey::whereNotNull('ulasan')
|
||||
->where('ulasan', '!=', '')
|
||||
->with('user') // Assuming we might want to know who created the survey
|
||||
->latest('updated_at')
|
||||
->take(5)
|
||||
->get();
|
||||
|
||||
// Recent Users - "sikit je" (e.g., 5)
|
||||
$recentUsers = User::latest()
|
||||
->take(5)
|
||||
->get();
|
||||
|
||||
return view('admin.dashboard', compact(
|
||||
'totalSurveys',
|
||||
'totalResponses',
|
||||
'totalUsers',
|
||||
'recentSurveys',
|
||||
'recentReviews',
|
||||
'recentUsers'
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
49
app/Http/Controllers/Admin/ResponseController.php
Normal file
49
app/Http/Controllers/Admin/ResponseController.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Models\Survey;
|
||||
use App\Models\Response;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Controllers\Controller;
|
||||
|
||||
class ResponseController extends Controller
|
||||
{
|
||||
|
||||
public function list(Request $request)
|
||||
{
|
||||
$query = Survey::withCount('responses');
|
||||
|
||||
if ($request->filled('search')) {
|
||||
$search = $request->search;
|
||||
$query->where('title', 'like', "%{$search}%");
|
||||
}
|
||||
|
||||
$surveys = $query->latest()->get();
|
||||
|
||||
return view('admin.responses.list', compact('surveys'));
|
||||
}
|
||||
|
||||
public function showRespondents(Survey $survey)
|
||||
{
|
||||
$responses = $survey->responses()
|
||||
->with('user')
|
||||
->orderByDesc('created_at')
|
||||
->get();
|
||||
|
||||
return view('admin.responses.respondents', compact('survey', 'responses'));
|
||||
}
|
||||
|
||||
|
||||
public function detail(Response $response)
|
||||
{
|
||||
$response->load([
|
||||
'survey.sections.questions',
|
||||
'answers.question'
|
||||
]);
|
||||
|
||||
$answersByQuestionId = $response->answers->keyBy('question_id');
|
||||
|
||||
return view('admin.responses.detail', compact('response', 'answersByQuestionId'));
|
||||
}
|
||||
}
|
||||
173
app/Http/Controllers/Admin/StatsController.php
Normal file
173
app/Http/Controllers/Admin/StatsController.php
Normal file
@@ -0,0 +1,173 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Models\Survey;
|
||||
use App\Http\Controllers\Controller;
|
||||
|
||||
use App\Models\Question;
|
||||
use App\Models\Response;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class StatsController extends Controller
|
||||
{
|
||||
public function showStats($id)
|
||||
{
|
||||
$survey = Survey::with(['sections.questions.options', 'sections.questions.answers'])->findOrFail($id);
|
||||
|
||||
$totalSurveyRespondents = \App\Models\Response::where('survey_id', $id)->count();
|
||||
$totalSurveyQuestions = \App\Models\Question::whereHas('section', function ($q) use ($id) {
|
||||
$q->where('survey_id', $id);
|
||||
})->count();
|
||||
|
||||
$results = [];
|
||||
|
||||
foreach ($survey->sections as $section) {
|
||||
foreach ($section->questions as $question) {
|
||||
|
||||
$totalRespondents = $question->answers->count();
|
||||
$stats = [];
|
||||
$allAnswers = $question->answers->pluck('answer_text')->map(fn($item) => trim($item))->toArray();
|
||||
|
||||
// HANDLE TEXT QUESTIONS
|
||||
if ($question->type === 'text') {
|
||||
$results[$question->id] = [
|
||||
'question' => $question->question_text,
|
||||
'type' => 'text',
|
||||
'total' => $totalRespondents,
|
||||
'data' => array_filter($allAnswers) // Filter empty answers if any
|
||||
];
|
||||
continue; // Skip the rest of the loop (chart logic)
|
||||
}
|
||||
|
||||
// HANDLE RADIO/CHECKBOX (EXISTING LOGIC)
|
||||
foreach ($question->options as $option) {
|
||||
$optionLabel = trim($option->option_text);
|
||||
$count = 0;
|
||||
foreach ($allAnswers as $ans) {
|
||||
if ($ans === $optionLabel) {
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
|
||||
$stats[] = [
|
||||
'label' => $optionLabel,
|
||||
'count' => $count,
|
||||
'percentage' => $totalRespondents > 0 ? round(($count / $totalRespondents) * 100, 1) : 0
|
||||
];
|
||||
}
|
||||
|
||||
$optionTexts = $question->options->pluck('option_text')->map(fn($item) => trim($item))->toArray();
|
||||
$othersCount = 0;
|
||||
$otherAnswersList = [];
|
||||
|
||||
foreach ($allAnswers as $ans) {
|
||||
if (!in_array($ans, $optionTexts)) {
|
||||
$othersCount++;
|
||||
// Clean up "Lain-lain: " prefix if exists (based on public view logic)
|
||||
$cleanAns = str_replace("Lain-lain: ", "", $ans);
|
||||
$otherAnswersList[] = $cleanAns;
|
||||
}
|
||||
}
|
||||
|
||||
if ($question->allow_other_option && $othersCount > 0) {
|
||||
$stats[] = [
|
||||
'label' => 'Lain-lain',
|
||||
'count' => $othersCount,
|
||||
'percentage' => $totalRespondents > 0 ? round(($othersCount / $totalRespondents) * 100, 1) : 0
|
||||
];
|
||||
}
|
||||
|
||||
$results[$question->id] = [
|
||||
'question' => $question->question_text,
|
||||
'type' => 'chart', // Mark as chart
|
||||
'total' => $totalRespondents,
|
||||
'data' => $stats,
|
||||
'other_answers' => $otherAnswersList
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return view('admin.surveys.statistics', compact('survey', 'results', 'totalSurveyRespondents', 'totalSurveyQuestions'));
|
||||
}
|
||||
|
||||
public function printStats($id)
|
||||
{
|
||||
$survey = Survey::with(['sections.questions.options', 'sections.questions.answers'])->findOrFail($id);
|
||||
|
||||
$totalSurveyRespondents = \App\Models\Response::where('survey_id', $id)->count();
|
||||
$totalSurveyQuestions = \App\Models\Question::whereHas('section', function ($q) use ($id) {
|
||||
$q->where('survey_id', $id);
|
||||
})->count();
|
||||
|
||||
$results = [];
|
||||
|
||||
foreach ($survey->sections as $section) {
|
||||
foreach ($section->questions as $question) {
|
||||
|
||||
$totalRespondents = $question->answers->count();
|
||||
$stats = [];
|
||||
$allAnswers = $question->answers->pluck('answer_text')->map(fn($item) => trim($item))->toArray();
|
||||
|
||||
// HANDLE TEXT QUESTIONS
|
||||
if ($question->type === 'text') {
|
||||
$results[$question->id] = [
|
||||
'question' => $question->question_text,
|
||||
'type' => 'text',
|
||||
'total' => $totalRespondents,
|
||||
'data' => array_filter($allAnswers) // Filter empty answers if any
|
||||
];
|
||||
continue;
|
||||
}
|
||||
|
||||
// HANDLE RADIO/CHECKBOX (EXISTING LOGIC)
|
||||
foreach ($question->options as $option) {
|
||||
$optionLabel = trim($option->option_text);
|
||||
$count = 0;
|
||||
foreach ($allAnswers as $ans) {
|
||||
if ($ans === $optionLabel) {
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
|
||||
$stats[] = [
|
||||
'label' => $optionLabel,
|
||||
'count' => $count,
|
||||
'percentage' => $totalRespondents > 0 ? round(($count / $totalRespondents) * 100, 1) : 0
|
||||
];
|
||||
}
|
||||
|
||||
$optionTexts = $question->options->pluck('option_text')->map(fn($item) => trim($item))->toArray();
|
||||
$othersCount = 0;
|
||||
$otherAnswersList = [];
|
||||
|
||||
foreach ($allAnswers as $ans) {
|
||||
if (!in_array($ans, $optionTexts)) {
|
||||
$othersCount++;
|
||||
// Clean up "Lain-lain: " prefix if exists
|
||||
$cleanAns = str_replace("Lain-lain: ", "", $ans);
|
||||
$otherAnswersList[] = $cleanAns;
|
||||
}
|
||||
}
|
||||
|
||||
if ($question->allow_other_option && $othersCount > 0) {
|
||||
$stats[] = [
|
||||
'label' => 'Lain-lain',
|
||||
'count' => $othersCount,
|
||||
'percentage' => $totalRespondents > 0 ? round(($othersCount / $totalRespondents) * 100, 1) : 0
|
||||
];
|
||||
}
|
||||
|
||||
$results[$question->id] = [
|
||||
'question' => $question->question_text,
|
||||
'type' => 'chart',
|
||||
'total' => $totalRespondents,
|
||||
'data' => $stats,
|
||||
'other_answers' => $otherAnswersList
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return view('admin.surveys.print_statistics', compact('survey', 'results', 'totalSurveyRespondents', 'totalSurveyQuestions'));
|
||||
}
|
||||
}
|
||||
209
app/Http/Controllers/Admin/SurveyController.php
Normal file
209
app/Http/Controllers/Admin/SurveyController.php
Normal file
@@ -0,0 +1,209 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Models\Survey;
|
||||
use App\Models\Section;
|
||||
use App\Models\Question;
|
||||
use App\Models\Option;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use App\Http\Controllers\Controller;
|
||||
|
||||
class SurveyController extends Controller
|
||||
{
|
||||
|
||||
public function index(Request $request)
|
||||
{
|
||||
$query = Survey::with('user', 'questions', 'responses');
|
||||
|
||||
if ($request->filled('search')) {
|
||||
$s = $request->search;
|
||||
$query->where('title', 'like', "%$s%");
|
||||
}
|
||||
|
||||
$surveys = $query->latest()->paginate(20);
|
||||
|
||||
return view('admin.surveys.index', compact('surveys'));
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
return view('admin.surveys.create');
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'title' => 'required|string|max:255',
|
||||
'perincian' => 'nullable|string',
|
||||
'date' => 'required|date',
|
||||
'sections' => 'required|array|min:1',
|
||||
'sections.*.title' => 'required|string',
|
||||
'sections.*.questions' => 'required|array|min:1',
|
||||
'sections.*.questions.*.text' => 'required|string',
|
||||
'sections.*.questions.*.type' => 'required|in:radio,text,checkbox',
|
||||
'sections.*.questions.*.allow_other_option' => 'nullable|boolean',
|
||||
'sections.*.questions.*.options' => 'required_if:sections.*.questions.*.type,radio|required_if:sections.*.questions.*.type,checkbox|array',
|
||||
'sections.*.questions.*.options.*' => 'required|string',
|
||||
]);
|
||||
|
||||
try {
|
||||
DB::beginTransaction();
|
||||
|
||||
$survey = Survey::create([
|
||||
'title' => $request->title,
|
||||
'perincian' => $request->perincian,
|
||||
'date' => $request->date,
|
||||
'user_id' => Auth::id(),
|
||||
]);
|
||||
|
||||
|
||||
foreach ($request->sections as $sectionIndex => $sectionData) {
|
||||
$section = $survey->sections()->create([
|
||||
'title' => $sectionData['title'],
|
||||
'description' => $sectionData['description'] ?? null,
|
||||
'order' => $sectionIndex,
|
||||
]);
|
||||
|
||||
|
||||
foreach ($sectionData['questions'] as $questionIndex => $questionData) {
|
||||
$question = $section->questions()->create([
|
||||
'question_text' => $questionData['text'],
|
||||
'type' => $questionData['type'],
|
||||
'allow_other_option' => $questionData['allow_other_option'] ?? false,
|
||||
'order' => $questionIndex,
|
||||
]);
|
||||
|
||||
|
||||
if (isset($questionData['options'])) {
|
||||
foreach ($questionData['options'] as $optionText) {
|
||||
$question->options()->create([
|
||||
'option_text' => $optionText,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DB::commit();
|
||||
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
\Log::error($e->getMessage());
|
||||
return redirect()->back()->with('error', 'Gagal menyimpan borang. Sila cuba lagi.')->withInput();
|
||||
}
|
||||
|
||||
return redirect()->route('admin.surveys.index')->with('success', 'Borang berjaya dicipta.');
|
||||
}
|
||||
|
||||
public function edit(Survey $survey)
|
||||
{
|
||||
$survey->load('sections.questions.options');
|
||||
|
||||
$sections_with_questions = $survey->sections->map(function ($section) {
|
||||
return [
|
||||
'title' => $section->title,
|
||||
'description' => $section->description,
|
||||
'questions' => $section->questions->map(function ($q) {
|
||||
return [
|
||||
'text' => $q->question_text,
|
||||
'type' => $q->type,
|
||||
'allow_other_option' => (bool) $q->allow_other_option,
|
||||
'options' => $q->options->map(function ($opt) {
|
||||
return ['text' => $opt->option_text];
|
||||
})->toArray(),
|
||||
];
|
||||
})->toArray(),
|
||||
];
|
||||
})->toArray();
|
||||
|
||||
return view('admin.surveys.edit', compact('survey', 'sections_with_questions'));
|
||||
}
|
||||
|
||||
public function update(Request $request, Survey $survey)
|
||||
{
|
||||
$request->validate([
|
||||
'title' => 'required|string|max:255',
|
||||
'perincian' => 'nullable|string',
|
||||
'date' => 'required|date',
|
||||
'sections' => 'required|array|min:1',
|
||||
'sections.*.title' => 'required|string',
|
||||
'sections.*.questions' => 'required|array|min:1',
|
||||
'sections.*.questions.*.text' => 'required|string',
|
||||
'sections.*.questions.*.type' => 'required|in:radio,text,checkbox',
|
||||
'sections.*.questions.*.allow_other_option' => 'nullable|boolean',
|
||||
'sections.*.questions.*.options' => 'required_if:sections.*.questions.*.type,radio|required_if:sections.*.questions.*.type,checkbox|array',
|
||||
'sections.*.questions.*.options.*' => 'required|string',
|
||||
]);
|
||||
|
||||
try {
|
||||
DB::beginTransaction();
|
||||
|
||||
$survey->update([
|
||||
'title' => $request->title,
|
||||
'perincian' => $request->perincian,
|
||||
'date' => $request->date,
|
||||
|
||||
]);
|
||||
|
||||
foreach ($survey->sections as $oldSection) {
|
||||
foreach ($oldSection->questions as $oldQuestion) {
|
||||
|
||||
$oldQuestion->options()->delete();
|
||||
}
|
||||
|
||||
$oldSection->questions()->delete();
|
||||
}
|
||||
|
||||
$survey->sections()->delete();
|
||||
|
||||
foreach ($request->sections as $sectionIndex => $sectionData) {
|
||||
$section = $survey->sections()->create([
|
||||
'title' => $sectionData['title'],
|
||||
'description' => $sectionData['description'] ?? null,
|
||||
'order' => $sectionIndex,
|
||||
]);
|
||||
|
||||
foreach ($sectionData['questions'] as $questionIndex => $questionData) {
|
||||
$question = $section->questions()->create([
|
||||
'question_text' => $questionData['text'],
|
||||
'type' => $questionData['type'],
|
||||
'allow_other_option' => $questionData['allow_other_option'] ?? false,
|
||||
'order' => $questionIndex,
|
||||
]);
|
||||
|
||||
if (isset($questionData['options']) && is_array($questionData['options'])) {
|
||||
foreach ($questionData['options'] as $optionText) {
|
||||
if (is_array($optionText)) {
|
||||
$optionText = $optionText['text'] ?? '';
|
||||
}
|
||||
$question->options()->create([
|
||||
'option_text' => $optionText,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DB::commit();
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
\Log::error('Survey update failed: ' . $e->getMessage());
|
||||
return redirect()->back()->with('error', 'Gagal kemaskini borang. Sila cuba lagi.')->withInput();
|
||||
}
|
||||
|
||||
return redirect()->route('admin.surveys.index')->with('success', 'Borang berjaya dikemaskini.');
|
||||
}
|
||||
|
||||
public function destroy(Survey $survey)
|
||||
{
|
||||
$survey->delete();
|
||||
return redirect()->route('admin.surveys.index')->with('success', 'Borang berjaya dihapuskan.');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
70
app/Http/Controllers/Admin/UlasanController.php
Normal file
70
app/Http/Controllers/Admin/UlasanController.php
Normal file
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Controllers\Controller;
|
||||
|
||||
|
||||
class UlasanController extends Controller
|
||||
{
|
||||
public function ulasanPage(Request $request)
|
||||
{
|
||||
$query = \App\Models\Survey::whereNotNull('ulasan')
|
||||
->where('ulasan', '!=', '');
|
||||
|
||||
if ($request->filled('search')) {
|
||||
$search = $request->search;
|
||||
$query->where('title', 'like', "%{$search}%");
|
||||
}
|
||||
|
||||
$surveys = $query->orderBy('updated_at', 'desc')->get();
|
||||
|
||||
return view('admin.surveys.ulasan', compact('surveys'));
|
||||
}
|
||||
|
||||
public function updateUlasan(Request $request, $id)
|
||||
{
|
||||
$survey = \App\Models\Survey::findOrFail($id);
|
||||
|
||||
$survey->ulasan = $request->ulasan;
|
||||
$survey->save();
|
||||
|
||||
return back()->with('success', 'Ulasan berjaya dikemaskini!');
|
||||
}
|
||||
|
||||
public function downloadCSV()
|
||||
{
|
||||
$surveys = \App\Models\Survey::whereNotNull('ulasan')
|
||||
->where('ulasan', '!=', '')
|
||||
->orderBy('updated_at', 'desc')
|
||||
->get();
|
||||
|
||||
$filename = "keputusan_postmortem" . date('Ymd_His') . ".csv";
|
||||
$handle = fopen('php://output', 'w');
|
||||
|
||||
// Add UTF-8 BOM for Excel compatibility
|
||||
fprintf($handle, chr(0xEF) . chr(0xBB) . chr(0xBF));
|
||||
|
||||
// Header
|
||||
fputcsv($handle, ['ID', 'Tarikh Kemaskini', 'Tajuk Borang', 'Ulasan']);
|
||||
|
||||
// Data
|
||||
foreach ($surveys as $survey) {
|
||||
fputcsv($handle, [
|
||||
$survey->id,
|
||||
$survey->updated_at->format('d/m/Y'),
|
||||
$survey->title,
|
||||
$survey->ulasan
|
||||
]);
|
||||
}
|
||||
|
||||
fclose($handle);
|
||||
|
||||
return response()->streamDownload(function () use ($handle) {
|
||||
// Already handled by fputcsv to php://output
|
||||
}, $filename, [
|
||||
'Content-Type' => 'text/csv',
|
||||
]);
|
||||
}
|
||||
}
|
||||
89
app/Http/Controllers/AdminController.php
Normal file
89
app/Http/Controllers/AdminController.php
Normal file
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
|
||||
class AdminController extends Controller
|
||||
{
|
||||
public function dashboard()
|
||||
{
|
||||
return view('admin.dashboard');
|
||||
}
|
||||
|
||||
public function users(Request $request)
|
||||
{
|
||||
$query = User::query();
|
||||
|
||||
if ($request->filled('search')) {
|
||||
$s = $request->search;
|
||||
$query->where(function ($q) use ($s) {
|
||||
$q->where('name', 'like', "%$s%")
|
||||
->orWhere('no_pekerja', 'like', "%$s%")
|
||||
->orWhere('jabatan', 'like', "%$s%");
|
||||
});
|
||||
}
|
||||
|
||||
$users = $query->get();
|
||||
return view('admin.users.index', compact('users'));
|
||||
}
|
||||
|
||||
public function storeUser(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'name'=>'required|string|max:255',
|
||||
'password'=>'required|string|min:6',
|
||||
'role'=>'required|in:admin,staff',
|
||||
'no_pekerja'=>'required|string|unique:users,no_pekerja',
|
||||
'jabatan'=>'required|string|max:255',
|
||||
]);
|
||||
|
||||
User::create([
|
||||
'name'=>$request->name,
|
||||
'password'=>Hash::make($request->password),
|
||||
'role'=>$request->role,
|
||||
'no_pekerja'=>$request->no_pekerja,
|
||||
'jabatan' => $request->jabatan,
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success','Pengguna telah ditambah');
|
||||
}
|
||||
|
||||
public function updateUser(Request $request, $id)
|
||||
{
|
||||
$user = User::findOrFail($id);
|
||||
|
||||
$request->validate([
|
||||
'name'=>'required|string|max:255',
|
||||
'role'=>'required|in:admin,staff',
|
||||
'no_pekerja'=>'required|unique:users,no_pekerja,'.$user->id,
|
||||
'jabatan'=>'required|string|max:255',
|
||||
]);
|
||||
|
||||
$user->update([
|
||||
'name'=>$request->name,
|
||||
'role'=>$request->role,
|
||||
'no_pekerja'=>$request->no_pekerja,
|
||||
'jabatan' => $request->jabatan,
|
||||
]);
|
||||
|
||||
if($request->password){
|
||||
$request->validate(['password' => 'string|min:3']);
|
||||
$user->update(['password'=>Hash::make($request->password)]);
|
||||
}
|
||||
|
||||
return redirect()->back()->with('success','Pengguna telah dikemaskini');
|
||||
}
|
||||
|
||||
public function deleteUser($id)
|
||||
{
|
||||
$user = User::findOrFail($id);
|
||||
$user->delete();
|
||||
|
||||
return redirect()->back()->with('success','Pengguna telah dihapuskan');
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
47
app/Http/Controllers/AuthController.php
Normal file
47
app/Http/Controllers/AuthController.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
|
||||
class AuthController extends Controller
|
||||
{
|
||||
public function showLogin()
|
||||
{
|
||||
return view('auth.login');
|
||||
}
|
||||
|
||||
public function doLogin(Request $request)
|
||||
{
|
||||
$credentials = $request->validate([
|
||||
'no_pekerja' => ['required',],
|
||||
'password' => ['required']
|
||||
]);
|
||||
|
||||
if (Auth::attempt($credentials)) {
|
||||
$request->session()->regenerate();
|
||||
|
||||
$role = auth()->user()->role;
|
||||
if ($role == 'admin') {
|
||||
return redirect()->route('admin.dashboard');
|
||||
} else {
|
||||
Auth::logout();
|
||||
return redirect()->route('login')->withErrors(['no_pekerja' => 'Akses dihalang. Admin sahaja.']);
|
||||
}
|
||||
}
|
||||
|
||||
return back()->withErrors([
|
||||
'no_pekerja' => 'Kata laluan atau nombor pekerja tidak betul.'
|
||||
]);
|
||||
}
|
||||
|
||||
public function logout(Request $request)
|
||||
{
|
||||
Auth::logout();
|
||||
$request->session()->invalidate();
|
||||
$request->session()->regenerateToken();
|
||||
return redirect()->route('login');
|
||||
}
|
||||
}
|
||||
8
app/Http/Controllers/Controller.php
Normal file
8
app/Http/Controllers/Controller.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
abstract class Controller
|
||||
{
|
||||
//
|
||||
}
|
||||
71
app/Http/Controllers/StaffSurveyController.php
Normal file
71
app/Http/Controllers/StaffSurveyController.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Survey;
|
||||
use App\Models\Response;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class StaffSurveyController extends Controller
|
||||
{
|
||||
public function show(Survey $survey)
|
||||
{
|
||||
$survey->load('sections.questions.options');
|
||||
return view('staff.surveys.soalan', compact('survey'));
|
||||
}
|
||||
|
||||
public function store(Request $request, Survey $survey)
|
||||
{
|
||||
$request->validate([
|
||||
'respondent_name' => 'required|string|max:255',
|
||||
'respondent_no_pekerja' => 'required|string|max:255',
|
||||
'respondent_jabatan' => 'required|string|max:255',
|
||||
'answers' => 'required|array',
|
||||
'answers.*' => 'required',
|
||||
], [
|
||||
'answers.*.required' => 'Sila jawab semua soalan yang bertanda *.'
|
||||
]);
|
||||
|
||||
try {
|
||||
DB::beginTransaction();
|
||||
|
||||
// 1. Cipta rekod Response utama
|
||||
$response = $survey->responses()->create([
|
||||
'user_id' => null, // Always null for public
|
||||
'respondent_name' => $request->respondent_name,
|
||||
'respondent_no_pekerja' => $request->respondent_no_pekerja,
|
||||
'respondent_jabatan' => $request->respondent_jabatan,
|
||||
]);
|
||||
|
||||
// 2. Simpan setiap jawapan soalan
|
||||
foreach ($request->answers as $question_id => $answer_data) {
|
||||
if (is_array($answer_data)) {
|
||||
// Untuk soalan jenis Checkbox atau Multiple Select
|
||||
foreach ($answer_data as $single_answer) {
|
||||
$response->answers()->create([
|
||||
'question_id' => $question_id,
|
||||
'answer_text' => $single_answer,
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
// Untuk soalan jenis Radio, Text, atau Textarea
|
||||
$response->answers()->create([
|
||||
'question_id' => $question_id,
|
||||
'answer_text' => $answer_data,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
DB::commit();
|
||||
|
||||
return redirect()->route('surveys.success');
|
||||
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
// Log ralat jika perlu: \Log::error($e->getMessage());
|
||||
return redirect()->back()->with('error', 'Gagal menghantar jawapan. Sila cuba lagi.');
|
||||
}
|
||||
}
|
||||
}
|
||||
33
app/Http/Middleware/RoleMiddleware.php
Normal file
33
app/Http/Middleware/RoleMiddleware.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Auth;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class RoleMiddleware
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
|
||||
*/
|
||||
public function handle(Request $request, Closure $next, $role): Response
|
||||
{
|
||||
if ($request->routeIs('staff.surveys.soalan')) {
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
if (!auth::check()) {
|
||||
return redirect()->route('login');
|
||||
}
|
||||
|
||||
if (Auth::user()->role != $role) {
|
||||
abort(403, 'Tak boleh la begitu ji.');
|
||||
|
||||
}
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
26
app/Models/Answer.php
Normal file
26
app/Models/Answer.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Answer extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'response_id',
|
||||
'question_id',
|
||||
'option_id',
|
||||
'answer_text',
|
||||
];
|
||||
|
||||
public function response()
|
||||
{
|
||||
return $this->belongsTo(Response::class);
|
||||
}
|
||||
|
||||
public function question()
|
||||
{
|
||||
return $this->belongsTo(Question::class);
|
||||
}
|
||||
|
||||
}
|
||||
14
app/Models/Option.php
Normal file
14
app/Models/Option.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Option extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'question_id',
|
||||
'option_text',
|
||||
];
|
||||
|
||||
}
|
||||
33
app/Models/Question.php
Normal file
33
app/Models/Question.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Question extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'section_id',
|
||||
'question_text',
|
||||
'type',
|
||||
'allow_other_option',
|
||||
'order',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'lain_lain' => 'boolean',
|
||||
];
|
||||
|
||||
public function section()
|
||||
{
|
||||
return $this->belongsTo(Section::class);
|
||||
}
|
||||
public function options()
|
||||
{
|
||||
return $this->hasMany(Option::class);
|
||||
}
|
||||
public function answers()
|
||||
{
|
||||
return $this->hasMany(Answer::class);
|
||||
}
|
||||
}
|
||||
30
app/Models/Response.php
Normal file
30
app/Models/Response.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Response extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'survey_id',
|
||||
'user_id',
|
||||
'respondent_name',
|
||||
'respondent_no_pekerja',
|
||||
'respondent_jabatan',
|
||||
];
|
||||
|
||||
|
||||
public function survey()
|
||||
{
|
||||
return $this->belongsTo(Survey::class);
|
||||
}
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
} // Responden
|
||||
public function answers()
|
||||
{
|
||||
return $this->hasMany(Answer::class);
|
||||
}
|
||||
}
|
||||
18
app/Models/Section.php
Normal file
18
app/Models/Section.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Section extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'survey_id',
|
||||
'title',
|
||||
'description',
|
||||
'order',
|
||||
];
|
||||
|
||||
public function survey() { return $this->belongsTo(Survey::class); }
|
||||
public function questions() { return $this->hasMany(Question::class); }
|
||||
}
|
||||
32
app/Models/Survey.php
Normal file
32
app/Models/Survey.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Survey extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'title',
|
||||
'date',
|
||||
'user_id',
|
||||
'uuid',
|
||||
'perincian',
|
||||
];
|
||||
|
||||
protected static function boot()
|
||||
{
|
||||
parent::boot();
|
||||
|
||||
static::creating(function ($model) {
|
||||
if (empty($model->uuid)) {
|
||||
$model->uuid = (string) \Illuminate\Support\Str::uuid();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function user() { return $this->belongsTo(User::class); }
|
||||
public function sections() { return $this->hasMany(Section::class); }
|
||||
public function questions() { return $this->hasManyThrough(Question::class, Section::class); }
|
||||
public function responses() { return $this->hasMany(Response::class); }
|
||||
}
|
||||
71
app/Models/User.php
Normal file
71
app/Models/User.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
|
||||
class User extends Authenticatable
|
||||
{
|
||||
/** @use HasFactory<\Database\Factories\UserFactory> */
|
||||
use HasFactory, Notifiable;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'password',
|
||||
'role',
|
||||
'no_pekerja',
|
||||
'jabatan',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be hidden for serialization.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
protected $hidden = [
|
||||
'password',
|
||||
'remember_token',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the attributes that should be cast.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'email_verified_at' => 'datetime',
|
||||
'password' => 'hashed',
|
||||
];
|
||||
}
|
||||
|
||||
// Helper: isAdmin
|
||||
public function isAdmin(): bool {
|
||||
return $this->role === 'admin';
|
||||
}
|
||||
|
||||
// Helper: isStaff
|
||||
public function isStaff(): bool {
|
||||
return $this->role === 'staff';
|
||||
}
|
||||
|
||||
public function scopeSearch($query, $keyword)
|
||||
{
|
||||
return $query->where('name', 'like', "%$keyword%")
|
||||
->orWhere('no_pekerja', 'like', "%$keyword%")
|
||||
->orWhere('jabatan', 'like', "%$keyword%");
|
||||
}
|
||||
|
||||
public function surveys() { return $this->hasMany(Survey::class); }
|
||||
public function responses() { return $this->hasMany(Response::class); }
|
||||
|
||||
}
|
||||
24
app/Providers/AppServiceProvider.php
Normal file
24
app/Providers/AppServiceProvider.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register any application services.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap any application services.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
\Illuminate\Pagination\Paginator::useBootstrapFive();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user