halaman-psikolog #1

Merged
Mahayoga merged 52 commits from halaman-psikolog into master 2026-07-06 12:37:30 +07:00
6 changed files with 323 additions and 18 deletions
Showing only changes of commit fdda679141 - Show all commits

View File

@ -0,0 +1,107 @@
<?php
namespace App\Http\Controllers;
use App\Models\DataSoalModel;
use App\Models\TestSessionsModel;
use Illuminate\Http\Request;
class HalamanUjianController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index(Request $request)
{
$id_session = null;
if(session('id_test_sessions') != null) {
$id_session = session('id_test_sessions');
} else if($request->id_test_sessions != null) {
$id_session = $request->id_test_sessions;
} else {
return redirect()->route('cek-status.index')->with('error', 'Data sesi tes tidak ditemukan. Silakan mulai tes terlebih dahulu.');
}
$data_session = TestSessionsModel::where('id_test_sessions', $id_session)->first();
if($data_session != null) {
if($data_session->status_session != '1') {
return redirect()->route('cek-status.index')->with('error', 'Data sesi tes belum dimulai atau telah usai. Silahkan cek status dengan kode untuk informasi lebih lanjut.');
}
} else {
return redirect()->route('cek-status.index')->with('error', 'Data sesi tes tidak ditemukan. Silakan mulai tes terlebih dahulu.');
}
$question_number = null;
$data_question = null;
if(session('question') != null) {
$question_number = session('question');
} else if($request->question != null) {
$question_number = $request->question;
} else {
$question_number = 1;
}
$data_question = DataSoalModel::all();
$total_question = null;
$is_last_question = false;
if($question_number <= 0 || $question_number > count($data_question)) {
$question_number = 1;
}
if(count($data_question) == $question_number) {
$is_last_question = true;
}
$total_question = count($data_question);
$data_question = $data_question[$question_number - 1];
return view("user.pages.ujian.index", compact('data_session', 'data_question', 'total_question', 'is_last_question', 'question_number'));
}
/**
* Show the form for creating a new resource.
*/
public function create()
{
//
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
//
}
/**
* Display the specified resource.
*/
public function show(string $id)
{
//
}
/**
* Show the form for editing the specified resource.
*/
public function edit(string $id)
{
//
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, string $id)
{
//
}
/**
* Remove the specified resource from storage.
*/
public function destroy(string $id)
{
//
}
}

View File

@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('soal', function (Blueprint $table) {
$table->text('pertanyaan')->change();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('soal', function (Blueprint $table) {
$table->string('pertanyaan', 255)->change();
});
}
};

View File

@ -40,11 +40,11 @@
======================================================== --> ======================================================== -->
</head> </head>
<body class="index-page"> <body class="index-page" style="@yield('body_status_overflow')">
{{-- @include('user.layouts.navbar') --}} {{-- @include('user.layouts.navbar') --}}
<main class="main" style="width: 100vw; height: 100vh; display: flex; align-items: center; justify-content: center;"> <main class="main" style="@yield('main_style')">
@yield('content') @yield('content')

View File

@ -1,4 +1,6 @@
@section('title', 'Tutorial Test Kecemasan') @section('title', 'Tutorial Test Kecemasan')
@section('body_status_overflow', 'overflow: hidden;')
@section('main_style', 'width: 100vw; height: 100vh; display: flex; align-items: center; justify-content: center;')
@extends('user.layouts.test_layout.index') @extends('user.layouts.test_layout.index')
@section('content') @section('content')
<div class="container" id="tutorial_panel"> <div class="container" id="tutorial_panel">
@ -73,7 +75,7 @@
<button class="btn btn-primary" id="back_btn" onclick="showTutorial(2, 'tutorial_3')">Back</button> <button class="btn btn-primary" id="back_btn" onclick="showTutorial(2, 'tutorial_3')">Back</button>
<button class="btn btn-secondary" id="fullscreen_btn" onclick="openFullscreen(this)" data-status="fullscreen">Fullscreen</button> <button class="btn btn-secondary" id="fullscreen_btn" onclick="openFullscreen(this)" data-status="fullscreen">Fullscreen</button>
<button class="btn btn-secondary" id="hide_btn" onclick="hidePanelTutorial(this)" data-status="hide">Hide</button> <button class="btn btn-secondary" id="hide_btn" onclick="hidePanelTutorial(this)" data-status="hide">Hide</button>
<a class="btn btn-primary">Mulai Ujian</a> <a class="btn btn-primary" id="start_ujian" href="#">Mulai Ujian</a>
</div> </div>
</div> </div>
</div> </div>
@ -147,7 +149,8 @@ function showTutorial(step, tutorialId) {
$(`#${tutorialId}`).hide(); $(`#${tutorialId}`).hide();
$(`#tutorial_${step}`).show(); $(`#tutorial_${step}`).show();
if(step === 2) { if(step === 2 || step === 3) {
startTobii();
checkTobiiInterval = setInterval(() => { checkTobiiInterval = setInterval(() => {
checkTobiiConnection(); checkTobiiConnection();
}, 2000); }, 2000);
@ -155,26 +158,18 @@ function showTutorial(step, tutorialId) {
if(checkTobiiInterval) { if(checkTobiiInterval) {
clearInterval(checkTobiiInterval); clearInterval(checkTobiiInterval);
} }
stopTobii();
} }
if(step === 3) { if(step === 3) {
$('#calibration_point').show(); $('#calibration_point').show();
let test_url = '{{ route('ujian.index', ['id_test_sessions' => '__ID_TEST_SESSIONS__']) }}';
$('#start_ujian').attr('href', test_url.replace('__ID_TEST_SESSIONS__', SESSION_ID));
} else { } else {
$('#calibration_point').hide(); $('#calibration_point').hide();
} }
} }
async function startPolling() {
while(getDataInterval) {
await fetchDataTobii();
await delay(300);
}
}
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function hidePanelTutorial(btn) { function hidePanelTutorial(btn) {
const status = $(btn).attr('data-status'); const status = $(btn).attr('data-status');
if (status === 'hide') { if (status === 'hide') {
@ -288,8 +283,8 @@ function copyLinkTokenTobii() {
function checkTobiiConnection() { function checkTobiiConnection() {
ws.send(JSON.stringify({ ws.send(JSON.stringify({
type: "check_temp_data", type: "check_tobii_connection",
id_session: SESSION_ID session_id: SESSION_ID,
})); }));
if(tobiiConnectedStatus) { if(tobiiConnectedStatus) {
@ -314,6 +309,22 @@ function checkTobiiConnection() {
} }
} }
function startTobii() {
ws.send(JSON.stringify({
type: "command",
action: "start",
session_id: SESSION_ID
}));
}
function stopTobii() {
ws.send(JSON.stringify({
type: "command",
action: "stop",
session_id: SESSION_ID
}));
}
$(document).ready(function() { $(document).ready(function() {
$('#tutorial_2').hide(); $('#tutorial_2').hide();
$('#tutorial_3').hide(); $('#tutorial_3').hide();
@ -352,13 +363,24 @@ function checkTobiiConnection() {
// } // }
if(data.type === 'tobii_data') { if(data.type === 'tobii_data') {
console.log('Tobii data not active:', data);
try { try {
moveTheEye(data.gaze_x, data.gaze_y); moveTheEye(data.gaze_x, data.gaze_y);
} catch(error) { } catch(error) {
console.log(error); console.log(error);
} }
} }
// if(data.type === 'status_tobii') {
// if(data.status) {
// tobiiConnectedStatus = true;
// } else if(!data.status) {
// tobiiConnectedStatus = false;
// }
// }
if(data.type === 'reset_start_tobii') {
startTobii();
}
}; };
}); });

View File

@ -0,0 +1,146 @@
@section('title', 'Ujian Kecemasan')
{{-- @section('body_status_overflow', 'overflow: hidden;') --}}
@section('main_style', 'min-height: 100vh;')
@extends('user.layouts.test_layout.index')
@section('content')
<div class="container-fluid p-0 d-flex flex-column" style="min-height: 100vh">
<nav class="navbar navbar-expand-lg bg-body-tertiary px-2">
<div class="container-fluid">
<a class="navbar-brand" href="#">Smart Anxiety</a>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0"></ul>
<div class="d-flex fw-bold">Time Left: <span id="time_left"></span></div>
</div>
</div>
</nav>
<div class="d-flex flex-grow-1 container-fluid">
<div class="row flex-grow-1">
<div class="col-md-9 border-end">
<div class="row justify-content-center flex-column p-4">
<div class="col-md-12 border-bottom fs-4 fw-bold p-2">Soal</div>
<div class="col-md-12 p-2" style="text-align: justify">{{ $data_question->pertanyaan }}</div>
<div class="col-md-12">
<div class="fs-4 fw-bold p-2">Jawaban:</div>
<div class="form-check m-2">
<input class="form-check-input" type="radio" name="jawaban_final" id="jawaban_a">
<label class="form-check-label" for="jawaban_a">{{ $data_question->jawaban_a }}</label>
</div>
<div class="form-check m-2">
<input class="form-check-input" type="radio" name="jawaban_final" id="jawaban_b">
<label class="form-check-label" for="jawaban_b">{{ $data_question->jawaban_b }}</label>
</div>
<div class="form-check m-2">
<input class="form-check-input" type="radio" name="jawaban_final" id="jawaban_c">
<label class="form-check-label" for="jawaban_c">{{ $data_question->jawaban_c }}</label>
</div>
<div class="form-check m-2">
<input class="form-check-input" type="radio" name="jawaban_final" id="jawaban_d">
<label class="form-check-label" for="jawaban_d">{{ $data_question->jawaban_d }}</label>
</div>
<div class="form-check m-2">
<input class="form-check-input" type="radio" name="jawaban_final" id="jawaban_e">
<label class="form-check-label" for="jawaban_e">{{ $data_question->jawaban_e }}</label>
</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="row p-3">
<div class="col-md-12 border-bottom fs-4 fw-bold p-2 mb-3">Navigasi Soal</div>
@for ($i = 0; $i < $total_question; $i++)
<div onclick="changeQuestion({{ $i + 1 }})" class="col-md-1 btn d-flex justify-content-center align-items-center bg-light-subtle border rounded p-0 m-1" style="aspect-ratio: 1/1.5;">{{ $i + 1 }}</div>
@endfor
<div class="col-md-12 p-0 mt-3">
@if ($question_number > 1)
<div class="btn btn-secondary" onclick="changeQuestion({{ $question_number - 1 }})">Previous</div>
@endif
@if ($is_last_question)
<div class="btn btn-success" onclick="finishExam()">Finish</div>
@else
<div class="btn btn-primary" onclick="changeQuestion({{ $question_number + 1 }})">Next</div>
@endif
</div>
</div>
</div>
</div>
</div>
</div>
@endsection
@section('script')
<script>
const ws = new WebSocket('{{ env("WEBSOCKET_URL") }}');
let SESSION_ID = '{{ $data_session->id_test_sessions }}';
let TOKEN_SESSION = '{{ $data_session->token_session }}';
function changeQuestion(num) {
let url_temp = window.location.href;
let current_num = new URLSearchParams(window.location.search).get('question');
stopTobii();
if(!url_temp.includes('question')) {
window.location.href = url_temp + '&question=' + num;
} else if(url_temp.includes('question')) {
window.location.href = window.location.href.replace('&question=' + current_num, '&question=' + num);
}
}
function finishExam() {
ws.send(JSON.stringify({
type: "command",
action: "stop",
session_id: SESSION_ID
}));
//send_data_to_db
ws.send(JSON.stringify({
type: "send_data_to_db",
session_id: SESSION_ID
}));
ws.send(JSON.stringify({
type: "update_status",
status_sessions: false,
session_id: SESSION_ID
}));
}
function startTobii() {
ws.send(JSON.stringify({
type: "command",
action: "start",
session_id: SESSION_ID
}));
}
function stopTobii() {
ws.send(JSON.stringify({
type: "command",
action: "stop",
session_id: SESSION_ID
}));
}
$(document).ready(function() {
ws.onopen = () => {
console.log("Connected to WebSocket");
ws.send(JSON.stringify({
type: "register",
session_id: SESSION_ID,
token_session: TOKEN_SESSION,
role: "web"
}));
ws.send(JSON.stringify({
type: "update_status",
status_sessions: true,
session_id: SESSION_ID
}));
startTobii();
};
});
</script>
@endsection

View File

@ -4,6 +4,7 @@
use App\Http\Controllers\CekStatusController; use App\Http\Controllers\CekStatusController;
use App\Http\Controllers\DashboardController; use App\Http\Controllers\DashboardController;
use App\Http\Controllers\DataSoalController; use App\Http\Controllers\DataSoalController;
use App\Http\Controllers\HalamanUjianController;
use App\Http\Controllers\HasilTesController; use App\Http\Controllers\HasilTesController;
use App\Http\Controllers\LandingController; use App\Http\Controllers\LandingController;
use App\Http\Controllers\ProfileController; use App\Http\Controllers\ProfileController;
@ -36,6 +37,7 @@
Route::resource('test-sessions', TestSessionsController::class)->only(['store', 'show']); Route::resource('test-sessions', TestSessionsController::class)->only(['store', 'show']);
Route::resource('tobii-receiver', TobiiReceiverController::class)->only(['store', 'show']); Route::resource('tobii-receiver', TobiiReceiverController::class)->only(['store', 'show']);
Route::get('tobii-receiver/fetch/data/{id_test_sessions}', [TobiiReceiverController::class, 'fetchDataTobii'])->name('tobii-receiver.fetchData'); Route::get('tobii-receiver/fetch/data/{id_test_sessions}', [TobiiReceiverController::class, 'fetchDataTobii'])->name('tobii-receiver.fetchData');
Route::resource('ujian', HalamanUjianController::class);
Route::middleware(['auth', 'role.auth'])->group(function() { Route::middleware(['auth', 'role.auth'])->group(function() {
Route::resource('dashboard', DashboardController::class); Route::resource('dashboard', DashboardController::class);
Route::resource('users', UsersController::class); Route::resource('users', UsersController::class);