a lot things added and fixed

This commit is contained in:
RetasyaSalsabila 2026-04-19 22:42:42 +07:00
parent 67dacd6671
commit 5785ad88fc
30 changed files with 1939 additions and 487 deletions

View File

@ -38,8 +38,8 @@ public function store(Request $request)
'judul_challenge' => 'required|string|max:200',
'deskripsi' => 'nullable|string',
'exp' => 'required|integer|min:0',
'id_badge' => 'nullable|exists:badges,id_badge',
'tenggat_waktu' => 'required|date|after:now',
'durasi_pengerjaan' => 'required|integer|min:1|max:360',
'id_kelas' => 'required|array|min:1',
'id_kelas.*' => 'exists:kelas,id_kelas',
// Soal
@ -67,11 +67,14 @@ public function store(Request $request)
'exp' => $request->exp,
'id_badge' => $request->id_badge,
'tenggat_waktu' => $request->tenggat_waktu,
'durasi_pengerjaan' => $request->durasi_pengerjaan,
]);
// Attach ke kelas
$challenge->kelas()->sync($request->id_kelas);
$challenge->soal()->delete();
// Simpan soal
foreach ($request->pertanyaan as $i => $pertanyaan) {
SoalChallenge::create([
@ -113,8 +116,8 @@ public function update(Request $request, $id)
'judul_challenge' => 'required|string|max:200',
'deskripsi' => 'nullable|string',
'exp' => 'required|integer|min:0',
'id_badge' => 'nullable|exists:badges,id_badge',
'tenggat_waktu' => 'required|date',
'durasi_pengerjaan' => 'required|integer|min:1|max:360',
'id_kelas' => 'required|array|min:1',
'id_kelas.*' => 'exists:kelas,id_kelas',
'pertanyaan' => 'required|array|min:1',
@ -134,6 +137,7 @@ public function update(Request $request, $id)
'exp' => $request->exp,
'id_badge' => $request->id_badge,
'tenggat_waktu' => $request->tenggat_waktu,
'durasi_pengerjaan' => $request->durasi_pengerjaan,
]);
$challenge->kelas()->sync($request->id_kelas);
@ -178,8 +182,8 @@ public function editData($id)
'judul_challenge' => $challenge->judul_challenge,
'deskripsi' => $challenge->deskripsi,
'exp' => $challenge->exp,
'id_badge' => $challenge->id_badge,
'tenggat_waktu' => $challenge->tenggat_waktu,
'durasi_pengerjaan' => $challenge->durasi_pengerjaan,
'kelas' => $challenge->kelas->pluck('id_kelas'),
'soal' => $challenge->soal,
]);

View File

@ -9,6 +9,7 @@
use App\Models\Mengajar;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Barryvdh\DomPDF\Facade\Pdf;
class GuruController extends Controller
{
@ -148,6 +149,58 @@ public function destroy(string $id)
->with('success', 'Data guru berhasil dihapus.');
}
public function downloadPdf(Request $request)
{
$query = Guru::with('mengajars.mapel', 'mengajars.kelas');
if ($request->filled('search')) {
$search = $request->search;
$query->where(function($q) use ($search) {
$q->where('nama', 'like', "%$search%")
->orWhere('nip', 'like', "%$search%");
});
}
$gurus = $query->get();
$pdf = Pdf::loadView('admin.guru.pdf', compact('gurus'))
->setPaper('a4', 'landscape');
return $pdf->download('daftar-guru-' . date('Ymd') . '.pdf');
}
public function downloadExcel(Request $request)
{
$query = Guru::with('mengajars.mapel', 'mengajars.kelas');
if ($request->filled('search')) {
$search = $request->search;
$query->where(function($q) use ($search) {
$q->where('nama', 'like', "%$search%")
->orWhere('nip', 'like', "%$search%");
});
}
$gurus = $query->get();
$filename = 'daftar-guru-' . date('Ymd') . '.csv';
$headers = [
'Content-Type' => 'text/csv',
'Content-Disposition' => "attachment; filename=\"$filename\"",
];
$callback = function () use ($gurus) {
$file = fopen('php://output', 'w');
// BOM agar Excel bisa baca UTF-8
fprintf($file, chr(0xEF).chr(0xBB).chr(0xBF));
fputcsv($file, ['No', 'Nama', 'NIP', 'Mata Pelajaran', 'Kelas']);
foreach ($gurus as $i => $guru) {
$mapels = $guru->mengajars->map(fn($m) => optional($m->mapel)->nama_mapel ?? '-')->join(', ');
$kelas = $guru->mengajars->map(fn($m) => (optional($m->kelas)->tingkat . ' ' . optional($m->kelas)->nama_kelas))->join(', ');
fputcsv($file, [$i + 1, $guru->nama, $guru->nip, $mapels, $kelas]);
}
fclose($file);
};
return response()->stream($callback, 200, $headers);
}
/**
* API: Ambil kelas yang memiliki mapel tertentu (lewat tabel mengajars)
* Dipanggil via AJAX saat admin pilih mapel di modal

View File

@ -6,6 +6,7 @@
use App\Models\Kelas;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
use Barryvdh\DomPDF\Facade\Pdf;
class KelasController extends Controller
@ -88,4 +89,49 @@ public function destroy($id_kelas)
return redirect()->route('admin.kelas.index')
->with('success', 'Data kelas berhasil dihapus!');
}
public function downloadPdf(Request $request)
{
$query = Kelas::query();
if ($request->filled('search')) {
$search = $request->search;
$query->where('nama_kelas', 'like', "%$search%")
->orWhere('id_kelas', 'like', "%$search%");
}
$kelass = $query->get();
$pdf = Pdf::loadView('admin.kelas.pdf', compact('kelass'))
->setPaper('a4', 'portrait');
return $pdf->download('daftar-kelas-' . date('Ymd') . '.pdf');
}
public function downloadExcel(Request $request)
{
$query = Kelas::query();
if ($request->filled('search')) {
$search = $request->search;
$query->where('nama_kelas', 'like', "%$search%")
->orWhere('id_kelas', 'like', "%$search%");
}
$kelass = $query->get();
$filename = 'daftar-kelas-' . date('Ymd') . '.csv';
$headers = [
'Content-Type' => 'text/csv',
'Content-Disposition' => "attachment; filename=\"$filename\"",
];
$callback = function () use ($kelass) {
$file = fopen('php://output', 'w');
fprintf($file, chr(0xEF).chr(0xBB).chr(0xBF));
fputcsv($file, ['No', 'ID Kelas', 'Nama Kelas', 'Tingkat']);
foreach ($kelass as $i => $kelas) {
fputcsv($file, [$i + 1, $kelas->id_kelas, $kelas->nama_kelas, $kelas->tingkat]);
}
fclose($file);
};
return response()->stream($callback, 200, $headers);
}
}

View File

@ -7,6 +7,7 @@
use App\Models\Kelas;
use App\Models\Mengajar;
use Illuminate\Http\Request;
use Barryvdh\DomPDF\Facade\Pdf;
class MapelController extends Controller
{
@ -116,8 +117,6 @@ public function update(Request $request, $id)
->with('success', 'Data mata pelajaran berhasil diupdate!');
}
public function destroy($id)
{
@ -130,4 +129,62 @@ public function destroy($id)
return redirect()->route('admin.mapel.index')
->with('success', 'Data mata pelajaran berhasil dihapus!');
}
public function downloadPdf(Request $request)
{
$query = Mapel::with('kelas');
if ($request->filled('search')) {
$query->where(function($q) use ($request) {
$q->where('nama_mapel', 'like', "%$request->search%")
->orWhere('id_mapel', 'like', "%$request->search%");
});
}
if ($request->filled('filter_kelas')) {
$query->whereHas('kelas', function($q) use ($request) {
$q->where('kelas.id_kelas', $request->filter_kelas);
});
}
$mapels = $query->get();
$pdf = Pdf::loadView('admin.mapel.pdf', compact('mapels'))
->setPaper('a4', 'portrait');
return $pdf->download('daftar-mapel-' . date('Ymd') . '.pdf');
}
public function downloadExcel(Request $request)
{
$query = Mapel::with('kelas');
if ($request->filled('search')) {
$query->where(function($q) use ($request) {
$q->where('nama_mapel', 'like', "%$request->search%")
->orWhere('id_mapel', 'like', "%$request->search%");
});
}
if ($request->filled('filter_kelas')) {
$query->whereHas('kelas', function($q) use ($request) {
$q->where('kelas.id_kelas', $request->filter_kelas);
});
}
$mapels = $query->get();
$filename = 'daftar-mapel-' . date('Ymd') . '.csv';
$headers = [
'Content-Type' => 'text/csv',
'Content-Disposition' => "attachment; filename=\"$filename\"",
];
$callback = function () use ($mapels) {
$file = fopen('php://output', 'w');
fprintf($file, chr(0xEF).chr(0xBB).chr(0xBF));
fputcsv($file, ['No', 'ID Mapel', 'Nama Mata Pelajaran', 'Kelas']);
foreach ($mapels as $i => $mapel) {
$kelas = $mapel->kelas->map(fn($k) => $k->tingkat . ' ' . $k->nama_kelas)->join(', ');
fputcsv($file, [$i + 1, $mapel->id_mapel, $mapel->nama_mapel, $kelas]);
}
fclose($file);
};
return response()->stream($callback, 200, $headers);
}
}

View File

@ -7,6 +7,7 @@
use App\Models\Kelas;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Barryvdh\DomPDF\Facade\Pdf;
class SiswaController extends Controller
{
@ -113,4 +114,62 @@ public function destroy($id)
return redirect()->route('admin.siswa.index')
->with('success', 'Data siswa berhasil dihapus!');
}
public function downloadPdf(Request $request)
{
$query = Siswa::with('kelas');
if ($request->filled('search')) {
$search = $request->search;
$query->where('nama', 'like', "%$search%")
->orWhere('nisn', 'like', "%$search%");
}
if ($request->filled('filter_kelas')) {
$query->where('id_kelas', $request->filter_kelas);
}
$siswas = $query->get();
$pdf = Pdf::loadView('admin.siswa.pdf', compact('siswas'))
->setPaper('a4', 'landscape');
return $pdf->download('daftar-siswa-' . date('Ymd') . '.pdf');
}
public function downloadExcel(Request $request)
{
$query = Siswa::with('kelas');
if ($request->filled('search')) {
$search = $request->search;
$query->where('nama', 'like', "%$search%")
->orWhere('nisn', 'like', "%$search%");
}
if ($request->filled('filter_kelas')) {
$query->where('id_kelas', $request->filter_kelas);
}
$siswas = $query->get();
$filename = 'daftar-siswa-' . date('Ymd') . '.csv';
$headers = [
'Content-Type' => 'text/csv',
'Content-Disposition' => "attachment; filename=\"$filename\"",
];
$callback = function () use ($siswas) {
$file = fopen('php://output', 'w');
fprintf($file, chr(0xEF).chr(0xBB).chr(0xBF));
fputcsv($file, ['No', 'NISN', 'Nama', 'Tempat Lahir', 'Tanggal Lahir', 'Kelas']);
foreach ($siswas as $i => $siswa) {
fputcsv($file, [
$i + 1,
$siswa->nisn,
$siswa->nama,
$siswa->tempat_lahir,
\Carbon\Carbon::parse($siswa->tanggal_lahir)->format('d M Y'),
$siswa->kelas->tingkat . ' - ' . $siswa->kelas->nama_kelas,
]);
}
fclose($file);
};
return response()->stream($callback, 200, $headers);
}
}

View File

@ -19,8 +19,12 @@ class Challenge extends Model
'deskripsi',
'exp',
'id_badge',
'durasi_pengerjaan',
'tenggat_waktu',
];
protected $casts = [
'durasi_pengerjaan' => 'integer',
];
public function kelas()
{

View File

@ -3,22 +3,17 @@
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Pagination\Paginator;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
//
}
/**
* Bootstrap any application services.
*/
public function boot(): void
{
//
Paginator::defaultView('vendor.pagination.bootstrap-5');
}
}

View File

@ -7,6 +7,7 @@
"license": "MIT",
"require": {
"php": "^8.2",
"barryvdh/laravel-dompdf": "^3.1",
"doctrine/dbal": "^4.4",
"laravel/framework": "^12.0",
"laravel/tinker": "^2.10.1"

524
composer.lock generated
View File

@ -4,8 +4,85 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "b431c9cde1a46957fa68826ee65622af",
"content-hash": "ff0b1f9e9305b6ad7e2e60e462da8ee3",
"packages": [
{
"name": "barryvdh/laravel-dompdf",
"version": "v3.1.2",
"source": {
"type": "git",
"url": "https://github.com/barryvdh/laravel-dompdf.git",
"reference": "ee3b72b19ccdf57d0243116ecb2b90261344dedc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/barryvdh/laravel-dompdf/zipball/ee3b72b19ccdf57d0243116ecb2b90261344dedc",
"reference": "ee3b72b19ccdf57d0243116ecb2b90261344dedc",
"shasum": ""
},
"require": {
"dompdf/dompdf": "^3.0",
"illuminate/support": "^9|^10|^11|^12|^13.0",
"php": "^8.1"
},
"require-dev": {
"larastan/larastan": "^2.7|^3.0",
"orchestra/testbench": "^7|^8|^9.16|^10|^11.0",
"phpro/grumphp": "^2.5",
"squizlabs/php_codesniffer": "^3.5"
},
"type": "library",
"extra": {
"laravel": {
"aliases": {
"PDF": "Barryvdh\\DomPDF\\Facade\\Pdf",
"Pdf": "Barryvdh\\DomPDF\\Facade\\Pdf"
},
"providers": [
"Barryvdh\\DomPDF\\ServiceProvider"
]
},
"branch-alias": {
"dev-master": "3.0-dev"
}
},
"autoload": {
"psr-4": {
"Barryvdh\\DomPDF\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Barry vd. Heuvel",
"email": "barryvdh@gmail.com"
}
],
"description": "A DOMPDF Wrapper for Laravel",
"keywords": [
"dompdf",
"laravel",
"pdf"
],
"support": {
"issues": "https://github.com/barryvdh/laravel-dompdf/issues",
"source": "https://github.com/barryvdh/laravel-dompdf/tree/v3.1.2"
},
"funding": [
{
"url": "https://fruitcake.nl",
"type": "custom"
},
{
"url": "https://github.com/barryvdh",
"type": "github"
}
],
"time": "2026-02-21T08:51:10+00:00"
},
{
"name": "brick/math",
"version": "0.14.0",
@ -531,6 +608,161 @@
],
"time": "2024-02-05T11:56:58+00:00"
},
{
"name": "dompdf/dompdf",
"version": "v3.1.5",
"source": {
"type": "git",
"url": "https://github.com/dompdf/dompdf.git",
"reference": "f11ead23a8a76d0ff9bbc6c7c8fd7e05ca328496"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/dompdf/dompdf/zipball/f11ead23a8a76d0ff9bbc6c7c8fd7e05ca328496",
"reference": "f11ead23a8a76d0ff9bbc6c7c8fd7e05ca328496",
"shasum": ""
},
"require": {
"dompdf/php-font-lib": "^1.0.0",
"dompdf/php-svg-lib": "^1.0.0",
"ext-dom": "*",
"ext-mbstring": "*",
"masterminds/html5": "^2.0",
"php": "^7.1 || ^8.0"
},
"require-dev": {
"ext-gd": "*",
"ext-json": "*",
"ext-zip": "*",
"mockery/mockery": "^1.3",
"phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10 || ^11",
"squizlabs/php_codesniffer": "^3.5",
"symfony/process": "^4.4 || ^5.4 || ^6.2 || ^7.0"
},
"suggest": {
"ext-gd": "Needed to process images",
"ext-gmagick": "Improves image processing performance",
"ext-imagick": "Improves image processing performance",
"ext-zlib": "Needed for pdf stream compression"
},
"type": "library",
"autoload": {
"psr-4": {
"Dompdf\\": "src/"
},
"classmap": [
"lib/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-2.1"
],
"authors": [
{
"name": "The Dompdf Community",
"homepage": "https://github.com/dompdf/dompdf/blob/master/AUTHORS.md"
}
],
"description": "DOMPDF is a CSS 2.1 compliant HTML to PDF converter",
"homepage": "https://github.com/dompdf/dompdf",
"support": {
"issues": "https://github.com/dompdf/dompdf/issues",
"source": "https://github.com/dompdf/dompdf/tree/v3.1.5"
},
"time": "2026-03-03T13:54:37+00:00"
},
{
"name": "dompdf/php-font-lib",
"version": "1.0.2",
"source": {
"type": "git",
"url": "https://github.com/dompdf/php-font-lib.git",
"reference": "a6e9a688a2a80016ac080b97be73d3e10c444c9a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/dompdf/php-font-lib/zipball/a6e9a688a2a80016ac080b97be73d3e10c444c9a",
"reference": "a6e9a688a2a80016ac080b97be73d3e10c444c9a",
"shasum": ""
},
"require": {
"ext-mbstring": "*",
"php": "^7.1 || ^8.0"
},
"require-dev": {
"phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10 || ^11 || ^12"
},
"type": "library",
"autoload": {
"psr-4": {
"FontLib\\": "src/FontLib"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-2.1-or-later"
],
"authors": [
{
"name": "The FontLib Community",
"homepage": "https://github.com/dompdf/php-font-lib/blob/master/AUTHORS.md"
}
],
"description": "A library to read, parse, export and make subsets of different types of font files.",
"homepage": "https://github.com/dompdf/php-font-lib",
"support": {
"issues": "https://github.com/dompdf/php-font-lib/issues",
"source": "https://github.com/dompdf/php-font-lib/tree/1.0.2"
},
"time": "2026-01-20T14:10:26+00:00"
},
{
"name": "dompdf/php-svg-lib",
"version": "1.0.2",
"source": {
"type": "git",
"url": "https://github.com/dompdf/php-svg-lib.git",
"reference": "8259ffb930817e72b1ff1caef5d226501f3dfeb1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/dompdf/php-svg-lib/zipball/8259ffb930817e72b1ff1caef5d226501f3dfeb1",
"reference": "8259ffb930817e72b1ff1caef5d226501f3dfeb1",
"shasum": ""
},
"require": {
"ext-mbstring": "*",
"php": "^7.1 || ^8.0",
"sabberworm/php-css-parser": "^8.4 || ^9.0"
},
"require-dev": {
"phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10 || ^11"
},
"type": "library",
"autoload": {
"psr-4": {
"Svg\\": "src/Svg"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-3.0-or-later"
],
"authors": [
{
"name": "The SvgLib Community",
"homepage": "https://github.com/dompdf/php-svg-lib/blob/master/AUTHORS.md"
}
],
"description": "A library to read, parse and export to PDF SVG files.",
"homepage": "https://github.com/dompdf/php-svg-lib",
"support": {
"issues": "https://github.com/dompdf/php-svg-lib/issues",
"source": "https://github.com/dompdf/php-svg-lib/tree/1.0.2"
},
"time": "2026-01-02T16:01:13+00:00"
},
{
"name": "dragonmantank/cron-expression",
"version": "v3.4.0",
@ -2163,6 +2395,73 @@
],
"time": "2024-12-08T08:18:47+00:00"
},
{
"name": "masterminds/html5",
"version": "2.10.0",
"source": {
"type": "git",
"url": "https://github.com/Masterminds/html5-php.git",
"reference": "fcf91eb64359852f00d921887b219479b4f21251"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Masterminds/html5-php/zipball/fcf91eb64359852f00d921887b219479b4f21251",
"reference": "fcf91eb64359852f00d921887b219479b4f21251",
"shasum": ""
},
"require": {
"ext-dom": "*",
"php": ">=5.3.0"
},
"require-dev": {
"phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8 || ^9"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.7-dev"
}
},
"autoload": {
"psr-4": {
"Masterminds\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Matt Butcher",
"email": "technosophos@gmail.com"
},
{
"name": "Matt Farina",
"email": "matt@mattfarina.com"
},
{
"name": "Asmir Mustafic",
"email": "goetas@gmail.com"
}
],
"description": "An HTML5 parser and serializer.",
"homepage": "http://masterminds.github.io/html5-php",
"keywords": [
"HTML5",
"dom",
"html",
"parser",
"querypath",
"serializer",
"xml"
],
"support": {
"issues": "https://github.com/Masterminds/html5-php/issues",
"source": "https://github.com/Masterminds/html5-php/tree/2.10.0"
},
"time": "2025-07-25T09:04:22+00:00"
},
{
"name": "monolog/monolog",
"version": "3.9.0",
@ -3479,6 +3778,86 @@
},
"time": "2025-09-04T20:59:21+00:00"
},
{
"name": "sabberworm/php-css-parser",
"version": "v9.3.0",
"source": {
"type": "git",
"url": "https://github.com/MyIntervals/PHP-CSS-Parser.git",
"reference": "88dbd0f7f91abbfe4402d0a3071e9ff4d81ed949"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/88dbd0f7f91abbfe4402d0a3071e9ff4d81ed949",
"reference": "88dbd0f7f91abbfe4402d0a3071e9ff4d81ed949",
"shasum": ""
},
"require": {
"ext-iconv": "*",
"php": "^7.2.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0",
"thecodingmachine/safe": "^1.3 || ^2.5 || ^3.4"
},
"require-dev": {
"php-parallel-lint/php-parallel-lint": "1.4.0",
"phpstan/extension-installer": "1.4.3",
"phpstan/phpstan": "1.12.32 || 2.1.32",
"phpstan/phpstan-phpunit": "1.4.2 || 2.0.8",
"phpstan/phpstan-strict-rules": "1.6.2 || 2.0.7",
"phpunit/phpunit": "8.5.52",
"rawr/phpunit-data-provider": "3.3.1",
"rector/rector": "1.2.10 || 2.2.8",
"rector/type-perfect": "1.0.0 || 2.1.0",
"squizlabs/php_codesniffer": "4.0.1",
"thecodingmachine/phpstan-safe-rule": "1.2.0 || 1.4.1"
},
"suggest": {
"ext-mbstring": "for parsing UTF-8 CSS"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "9.4.x-dev"
}
},
"autoload": {
"files": [
"src/Rule/Rule.php",
"src/RuleSet/RuleContainer.php"
],
"psr-4": {
"Sabberworm\\CSS\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Raphael Schweikert"
},
{
"name": "Oliver Klee",
"email": "github@oliverklee.de"
},
{
"name": "Jake Hotson",
"email": "jake.github@qzdesign.co.uk"
}
],
"description": "Parser for CSS Files written in PHP",
"homepage": "https://www.sabberworm.com/blog/2010/6/10/php-css-parser",
"keywords": [
"css",
"parser",
"stylesheet"
],
"support": {
"issues": "https://github.com/MyIntervals/PHP-CSS-Parser/issues",
"source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v9.3.0"
},
"time": "2026-03-03T17:31:43+00:00"
},
{
"name": "symfony/clock",
"version": "v7.3.0",
@ -5956,6 +6335,149 @@
],
"time": "2025-09-11T10:12:26+00:00"
},
{
"name": "thecodingmachine/safe",
"version": "v3.4.0",
"source": {
"type": "git",
"url": "https://github.com/thecodingmachine/safe.git",
"reference": "705683a25bacf0d4860c7dea4d7947bfd09eea19"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thecodingmachine/safe/zipball/705683a25bacf0d4860c7dea4d7947bfd09eea19",
"reference": "705683a25bacf0d4860c7dea4d7947bfd09eea19",
"shasum": ""
},
"require": {
"php": "^8.1"
},
"require-dev": {
"php-parallel-lint/php-parallel-lint": "^1.4",
"phpstan/phpstan": "^2",
"phpunit/phpunit": "^10",
"squizlabs/php_codesniffer": "^3.2"
},
"type": "library",
"autoload": {
"files": [
"lib/special_cases.php",
"generated/apache.php",
"generated/apcu.php",
"generated/array.php",
"generated/bzip2.php",
"generated/calendar.php",
"generated/classobj.php",
"generated/com.php",
"generated/cubrid.php",
"generated/curl.php",
"generated/datetime.php",
"generated/dir.php",
"generated/eio.php",
"generated/errorfunc.php",
"generated/exec.php",
"generated/fileinfo.php",
"generated/filesystem.php",
"generated/filter.php",
"generated/fpm.php",
"generated/ftp.php",
"generated/funchand.php",
"generated/gettext.php",
"generated/gmp.php",
"generated/gnupg.php",
"generated/hash.php",
"generated/ibase.php",
"generated/ibmDb2.php",
"generated/iconv.php",
"generated/image.php",
"generated/imap.php",
"generated/info.php",
"generated/inotify.php",
"generated/json.php",
"generated/ldap.php",
"generated/libxml.php",
"generated/lzf.php",
"generated/mailparse.php",
"generated/mbstring.php",
"generated/misc.php",
"generated/mysql.php",
"generated/mysqli.php",
"generated/network.php",
"generated/oci8.php",
"generated/opcache.php",
"generated/openssl.php",
"generated/outcontrol.php",
"generated/pcntl.php",
"generated/pcre.php",
"generated/pgsql.php",
"generated/posix.php",
"generated/ps.php",
"generated/pspell.php",
"generated/readline.php",
"generated/rnp.php",
"generated/rpminfo.php",
"generated/rrd.php",
"generated/sem.php",
"generated/session.php",
"generated/shmop.php",
"generated/sockets.php",
"generated/sodium.php",
"generated/solr.php",
"generated/spl.php",
"generated/sqlsrv.php",
"generated/ssdeep.php",
"generated/ssh2.php",
"generated/stream.php",
"generated/strings.php",
"generated/swoole.php",
"generated/uodbc.php",
"generated/uopz.php",
"generated/url.php",
"generated/var.php",
"generated/xdiff.php",
"generated/xml.php",
"generated/xmlrpc.php",
"generated/yaml.php",
"generated/yaz.php",
"generated/zip.php",
"generated/zlib.php"
],
"classmap": [
"lib/DateTime.php",
"lib/DateTimeImmutable.php",
"lib/Exceptions/",
"generated/Exceptions/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "PHP core functions that throw exceptions instead of returning FALSE on error",
"support": {
"issues": "https://github.com/thecodingmachine/safe/issues",
"source": "https://github.com/thecodingmachine/safe/tree/v3.4.0"
},
"funding": [
{
"url": "https://github.com/OskarStark",
"type": "github"
},
{
"url": "https://github.com/shish",
"type": "github"
},
{
"url": "https://github.com/silasjoisten",
"type": "github"
},
{
"url": "https://github.com/staabm",
"type": "github"
}
],
"time": "2026-02-04T18:08:13+00:00"
},
{
"name": "tijsverkoyen/css-to-inline-styles",
"version": "v2.3.0",

View File

@ -0,0 +1,24 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('challenges', function (Blueprint $table) {
// Simpan dalam menit, nullable agar data lama tidak error
$table->unsignedSmallInteger('durasi_pengerjaan')->nullable()->after('tenggat_waktu')
->comment('Durasi pengerjaan dalam menit. NULL = tidak ada batas durasi.');
});
}
public function down(): void
{
Schema::table('challenges', function (Blueprint $table) {
$table->dropColumn('durasi_pengerjaan');
});
}
};

File diff suppressed because it is too large Load Diff

View File

@ -170,14 +170,12 @@
<img src="{{ asset('images/icon/main/add.png') }}" width="18" height="18" alt="Tambah">
Tambah Data
</button>
<button class="btn-primary-custom">
<img src="{{ asset('images/icon/main/download.png') }}" width="18" height="18" alt="Download">
Download PDF
</button>
<button class="btn-primary-custom">
<img src="{{ asset('images/icon/main/download.png') }}" width="18" height="18" alt="Download">
Download Excel
</button>
<a href="{{ route('admin.guru.downloadPdf', request()->query()) }}" target="_blank" class="btn-primary-custom">
<img src="{{ asset('images/icon/main/download.png') }}" width="18" height="18" alt="Download"> Download PDF
</a>
<a href="{{ route('admin.guru.downloadExcel', request()->query()) }}" class="btn-primary-custom">
<img src="{{ asset('images/icon/main/download.png') }}" width="18" height="18" alt="Download"> Download Excel
</a>
</div>
<form method="GET" action="{{ route('admin.guru.index') }}">

View File

@ -0,0 +1,62 @@
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<title>Daftar Guru</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: DejaVu Sans, sans-serif; font-size: 12px; color: #1e293b; padding: 24px; }
.header { text-align: center; margin-bottom: 20px; }
.header h2 { font-size: 16px; font-weight: 800; margin-bottom: 4px; }
.header p { font-size: 11px; color: #64748b; }
table { width: 100%; border-collapse: collapse; }
thead tr { background-color: #a5e6ba; }
th, td { border: 1px solid #cbd5e1; padding: 7px 9px; text-align: left; }
th { font-weight: 700; font-size: 11px; }
td { font-size: 11px; }
tr:nth-child(even) { background-color: #f8fafc; }
.footer { margin-top: 16px; font-size: 10px; color: #94a3b8; text-align: right; }
</style>
</head>
<body>
<div class="header">
<h2>DAFTAR GURU</h2>
<p>Dicetak pada {{ now()->format('d M Y, H:i') }}</p>
</div>
<table>
<thead>
<tr>
<th style="width:40px">No</th>
<th>Nama Lengkap</th>
<th>NIP</th>
<th>Mata Pelajaran</th>
<th>Kelas</th>
</tr>
</thead>
<tbody>
@foreach($gurus as $i => $guru)
<tr>
<td>{{ $i + 1 }}</td>
<td>{{ $guru->nama }}</td>
<td>{{ $guru->nip }}</td>
<td>
@foreach($guru->mengajars as $m)
<div>{{ optional($m->mapel)->nama_mapel ?? '-' }}</div>
@endforeach
</td>
<td>
@foreach($guru->mengajars as $m)
<div>{{ optional($m->kelas)->tingkat }} {{ optional($m->kelas)->nama_kelas }}</div>
@endforeach
</td>
</tr>
@endforeach
</tbody>
</table>
<div class="footer">Total: {{ count($gurus) }} guru</div>
</body>
</html>

View File

@ -111,11 +111,18 @@
<div class="custom-card">
<div class="d-flex justify-content-between align-items-center mb-3">
<div class="d-flex gap-2">
<button class="btn-primary-custom" data-bs-toggle="modal" data-bs-target="#modalTambah">
<img src="{{ asset('images/icon/main/add.png') }}" width="18">
Tambah Data
</button>
<a href="{{ route('admin.kelas.downloadPdf', request()->query()) }}" target="_blank" class="btn-primary-custom">
<img src="{{ asset('images/icon/main/download.png') }}" width="18" height="18" alt="Download"> Download PDF
</a>
<a href="{{ route('admin.kelas.downloadExcel', request()->query()) }}" class="btn-primary-custom">
<img src="{{ asset('images/icon/main/download.png') }}" width="18" height="18" alt="Download"> Download Excel
</a>
</div>
<form method="GET">
<div class="search-box">

View File

@ -0,0 +1,52 @@
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<title>Daftar Kelas</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: DejaVu Sans, sans-serif; font-size: 12px; color: #1e293b; padding: 24px; }
.header { text-align: center; margin-bottom: 20px; }
.header h2 { font-size: 16px; font-weight: 800; margin-bottom: 4px; }
.header p { font-size: 11px; color: #64748b; }
table { width: 100%; border-collapse: collapse; }
thead tr { background-color: #a5e6ba; }
th, td { border: 1px solid #cbd5e1; padding: 7px 9px; text-align: left; }
th { font-weight: 700; font-size: 11px; }
td { font-size: 11px; }
tr:nth-child(even) { background-color: #f8fafc; }
.footer { margin-top: 16px; font-size: 10px; color: #94a3b8; text-align: right; }
</style>
</head>
<body>
<div class="header">
<h2>DAFTAR KELAS</h2>
<p>Dicetak pada {{ now()->format('d M Y, H:i') }}</p>
</div>
<table>
<thead>
<tr>
<th style="width:40px">No</th>
<th>ID Kelas</th>
<th>Nama Kelas</th>
<th>Tingkat</th>
</tr>
</thead>
<tbody>
@foreach($kelass as $i => $kelas)
<tr>
<td>{{ $i + 1 }}</td>
<td>{{ $kelas->id_kelas }}</td>
<td>{{ $kelas->nama_kelas }}</td>
<td>{{ $kelas->tingkat }}</td>
</tr>
@endforeach
</tbody>
</table>
<div class="footer">Total: {{ count($kelass) }} kelas</div>
</body>
</html>

View File

@ -101,12 +101,18 @@
<div class="custom-card">
<div class="d-flex justify-content-between align-items-center mb-3">
<div class="d-flex gap-2">
<button class="btn-primary-custom" data-bs-toggle="modal" data-bs-target="#modalTambah">
<img src="{{ asset('images/icon/main/add.png') }}" width="18">
Tambah Data
</button>
<a href="{{ route('admin.mapel.downloadPdf', request()->query()) }}" target="_blank" class="btn-primary-custom">
<img src="{{ asset('images/icon/main/download.png') }}" width="18" height="18" alt="Download"> Download PDF
</a>
<a href="{{ route('admin.mapel.downloadExcel', request()->query()) }}" class="btn-primary-custom">
<img src="{{ asset('images/icon/main/download.png') }}" width="18" height="18" alt="Download"> Download Excel
</a>
</div>
<form method="GET">
<div class="search-box">
<input type="text" name="search" placeholder="Cari" value="{{ request('search') }}">

View File

@ -0,0 +1,56 @@
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<title>Daftar Mata Pelajaran</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: DejaVu Sans, sans-serif; font-size: 12px; color: #1e293b; padding: 24px; }
.header { text-align: center; margin-bottom: 20px; }
.header h2 { font-size: 16px; font-weight: 800; margin-bottom: 4px; }
.header p { font-size: 11px; color: #64748b; }
table { width: 100%; border-collapse: collapse; }
thead tr { background-color: #a5e6ba; }
th, td { border: 1px solid #cbd5e1; padding: 7px 9px; text-align: left; }
th { font-weight: 700; font-size: 11px; }
td { font-size: 11px; }
tr:nth-child(even) { background-color: #f8fafc; }
.footer { margin-top: 16px; font-size: 10px; color: #94a3b8; text-align: right; }
</style>
</head>
<body>
<div class="header">
<h2>DAFTAR MATA PELAJARAN</h2>
<p>Dicetak pada {{ now()->format('d M Y, H:i') }}</p>
</div>
<table>
<thead>
<tr>
<th style="width:40px">No</th>
<th>ID Mapel</th>
<th>Nama Mata Pelajaran</th>
<th>Kelas</th>
</tr>
</thead>
<tbody>
@foreach($mapels as $i => $mapel)
<tr>
<td>{{ $i + 1 }}</td>
<td>{{ $mapel->id_mapel }}</td>
<td>{{ $mapel->nama_mapel }}</td>
<td>
@foreach($mapel->kelas as $k)
<span>{{ $k->tingkat }} {{ $k->nama_kelas }}</span>@unless($loop->last), @endunless
@endforeach
</td>
</tr>
@endforeach
</tbody>
</table>
<div class="footer">Total: {{ count($mapels) }} mata pelajaran</div>
</body>
</html>

View File

@ -110,11 +110,18 @@
<div class="custom-card">
<div class="d-flex justify-content-between align-items-center mb-3">
<div class="d-flex gap-2">
<button class="btn-primary-custom" data-bs-toggle="modal" data-bs-target="#modalTambah">
<img src="{{ asset('images/icon/main/add.png') }}" width="18">
Tambah Data
</button>
<a href="{{ route('admin.siswa.downloadPdf', request()->query()) }}" target="_blank" class="btn-primary-custom">
<img src="{{ asset('images/icon/main/download.png') }}" width="18" height="18" alt="Download"> Download PDF
</a>
<a href="{{ route('admin.siswa.downloadExcel', request()->query()) }}" class="btn-primary-custom">
<img src="{{ asset('images/icon/main/download.png') }}" width="18" height="18" alt="Download"> Download Excel
</a>
</div>
<form method="GET">
<div class="search-box">

View File

@ -0,0 +1,56 @@
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<title>Daftar Siswa</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: DejaVu Sans, sans-serif; font-size: 12px; color: #1e293b; padding: 24px; }
.header { text-align: center; margin-bottom: 20px; }
.header h2 { font-size: 16px; font-weight: 800; margin-bottom: 4px; }
.header p { font-size: 11px; color: #64748b; }
table { width: 100%; border-collapse: collapse; }
thead tr { background-color: #a5e6ba; }
th, td { border: 1px solid #cbd5e1; padding: 7px 9px; text-align: left; }
th { font-weight: 700; font-size: 11px; }
td { font-size: 11px; }
tr:nth-child(even) { background-color: #f8fafc; }
.footer { margin-top: 16px; font-size: 10px; color: #94a3b8; text-align: right; }
</style>
</head>
<body>
<div class="header">
<h2>DAFTAR SISWA</h2>
<p>Dicetak pada {{ now()->format('d M Y, H:i') }}</p>
</div>
<table>
<thead>
<tr>
<th style="width:40px">No</th>
<th>NISN</th>
<th>Nama</th>
<th>Tempat Lahir</th>
<th>Tanggal Lahir</th>
<th>Kelas</th>
</tr>
</thead>
<tbody>
@foreach($siswas as $i => $siswa)
<tr>
<td>{{ $i + 1 }}</td>
<td>{{ $siswa->nisn }}</td>
<td>{{ $siswa->nama }}</td>
<td>{{ $siswa->tempat_lahir }}</td>
<td>{{ \Carbon\Carbon::parse($siswa->tanggal_lahir)->format('d M Y') }}</td>
<td>{{ $siswa->kelas->tingkat }} - {{ $siswa->kelas->nama_kelas }}</td>
</tr>
@endforeach
</tbody>
</table>
<div class="footer">Total: {{ count($siswas) }} siswa</div>
</body>
</html>

View File

@ -4,46 +4,29 @@
@push('styles')
<style>
/* ─────────────────────────────────────────
SEMBUNYIKAN SIDEBAR APP + TOGGLE
Hanya sembunyikan elemen navigasi,
bukan semua .sidebar agar tidak rusak
───────────────────────────────────────── */
.siswa-wrapper .sidebar,
.siswa-wrapper [class*="sidebar"]:not(.quiz-sidebar-panel),
.sidebar-toggle-btn {
display: none !important;
}
/* Paksa main content pakai full width */
/* Sembunyikan sidebar navigasi app */
.siswa-wrapper .nav-sidebar,
.siswa-wrapper aside.sidebar,
.sidebar-toggle-btn { display: none !important; }
.siswa-wrapper .main,
.siswa-wrapper .main-content,
.siswa-wrapper > div:not(.sidebar):not([class*="sidebar"]) {
.siswa-wrapper .main-content {
margin-left: 0 !important;
width: 100% !important;
max-width: 100% !important;
}
/* Paksa .content tidak punya padding aneh */
.content {
padding: 16px 20px 40px !important;
}
/* ─────────────────────────────────────────
LAYOUT UTAMA 2 KOLOM
LAYOUT
───────────────────────────────────────── */
.quiz-page {
display: grid;
grid-template-columns: 1fr 272px;
gap: 20px;
max-width: 1100px;
max-width: 1080px;
margin: 0 auto;
padding: 20px 16px 48px;
align-items: start;
}
/* ─────────────────────────────────────────
KOLOM KIRI
───────────────────────────────────────── */
.quiz-main {
display: flex;
flex-direction: column;
@ -70,7 +53,6 @@
gap: 8px;
}
.quiz-title img { width: 22px; height: 22px; object-fit: contain; }
.quiz-meta {
display: flex;
justify-content: center;
@ -91,6 +73,20 @@
}
.quiz-meta img { width: 13px; height: 13px; object-fit: contain; }
/* Durasi badge di header */
.durasi-meta-badge {
display: inline-flex;
align-items: center;
gap: 5px;
font-size: 12px;
font-weight: 700;
color: #5b21b6;
background: #ede9fe;
border: 1px solid #c4b5fd;
border-radius: 99px;
padding: 4px 12px;
}
/* Progress */
.progress-card {
background: #fff;
@ -131,7 +127,6 @@
display: none;
}
.soal-card.active { display: block; }
.soal-number {
display: inline-block;
background: linear-gradient(135deg, #667eea, #764ba2);
@ -149,8 +144,6 @@
line-height: 1.7;
margin-bottom: 18px;
}
/* Opsi */
.opsi-list { display: flex; flex-direction: column; gap: 9px; }
.opsi-item {
display: flex;
@ -174,7 +167,6 @@
font-weight: 600;
}
.opsi-item input[type="radio"] { display: none; }
.opsi-label-circle {
width: 30px; height: 30px;
border-radius: 50%;
@ -186,7 +178,7 @@
}
.opsi-item.selected .opsi-label-circle { background: #667eea; color: #fff; }
/* Tombol navigasi */
/* Nav buttons */
.nav-buttons-card {
background: #fff;
border: 1.5px solid #e2e8f0;
@ -210,7 +202,6 @@
transition: all 0.18s;
font-family: 'Poppins', sans-serif;
}
.btn-nav img { width: 15px; height: 15px; object-fit: contain; }
.btn-prev { background: #f1f5f9; color: #475569; }
.btn-prev:hover:not(:disabled) { background: #e2e8f0; }
.btn-prev:disabled { opacity: 0.4; cursor: not-allowed; }
@ -218,6 +209,7 @@
.btn-next:hover { opacity: 0.88; }
.btn-submit { background: linear-gradient(135deg, #22c55e, #16a34a); color: #fff; display: none; }
.btn-submit:hover { opacity: 0.88; }
.target-selesai{ width: 15px; height: 15px; object-fit: contain; }
/* Warning */
.warning-box {
@ -236,7 +228,7 @@
.warning-box img { width: 15px; height: 15px; object-fit: contain; flex-shrink: 0; }
/* ─────────────────────────────────────────
KOLOM KANAN SIDEBAR QUIZ
SIDEBAR QUIZ
───────────────────────────────────────── */
.quiz-sidebar-panel {
display: flex;
@ -260,13 +252,22 @@
color: #94a3b8;
text-transform: uppercase;
letter-spacing: 1px;
margin-bottom: 8px;
margin-bottom: 4px;
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
}
.timer-label img { width: 13px; height: 13px; object-fit: contain; }
/* Baris total durasi (kecil, di bawah label) */
.timer-total-info {
font-size: 11px;
color: #a78bfa;
font-weight: 600;
margin-bottom: 8px;
}
.timer-display {
font-size: 34px;
font-weight: 800;
@ -305,23 +306,18 @@
padding: 18px 20px;
}
.nav-card-label {
font-size: 10px;
font-weight: 700;
font-size: 10px; font-weight: 700;
color: #94a3b8;
text-transform: uppercase;
letter-spacing: 1px;
margin-bottom: 12px;
display: flex;
align-items: center;
gap: 6px;
display: flex; align-items: center; gap: 6px;
}
.nav-card-label img { width: 13px; height: 13px; object-fit: contain; }
.nav-summary {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
margin-bottom: 14px;
gap: 8px; margin-bottom: 14px;
}
.summary-item {
text-align: center;
@ -334,25 +330,19 @@
.summary-label { font-size: 10px; color: #94a3b8; font-weight: 500; margin-top: 2px; }
.summary-item.answered-sum .summary-num { color: #22c55e; }
.summary-item.unanswered-sum .summary-num { color: #94a3b8; }
.nav-grid {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 7px;
margin-bottom: 14px;
gap: 7px; margin-bottom: 14px;
}
.nav-btn {
aspect-ratio: 1;
border-radius: 9px;
border: 1.5px solid #e2e8f0;
background: #f8fafc;
font-size: 12px;
font-weight: 700;
color: #64748b;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px; font-weight: 700;
color: #64748b; cursor: pointer;
display: flex; align-items: center; justify-content: center;
transition: all 0.15s;
font-family: 'Poppins', sans-serif;
}
@ -360,33 +350,20 @@
.nav-btn.active { background: #667eea; border-color: #667eea; color: #fff; }
.nav-btn.answered { background: #dcfce7; border-color: #22c55e; color: #16a34a; }
.nav-btn.active.answered { background: #22c55e; border-color: #22c55e; color: #fff; }
.nav-legend {
display: flex;
flex-direction: column;
gap: 5px;
padding-top: 12px;
display: flex; flex-direction: column;
gap: 5px; padding-top: 12px;
border-top: 1px solid #f1f5f9;
}
.legend-item {
display: flex;
align-items: center;
gap: 7px;
font-size: 11px;
color: #64748b;
}
.legend-dot {
width: 13px; height: 13px;
border-radius: 4px;
flex-shrink: 0;
display: flex; align-items: center;
gap: 7px; font-size: 11px; color: #64748b;
}
.legend-dot { width: 13px; height: 13px; border-radius: 4px; flex-shrink: 0; }
.legend-dot.current { background: #667eea; }
.legend-dot.done { background: #dcfce7; border: 1.5px solid #22c55e; }
.legend-dot.undone { background: #f8fafc; border: 1.5px solid #e2e8f0; }
/* ─────────────────────────────────────────
RESPONSIVE
───────────────────────────────────────── */
@media (max-width: 768px) {
.quiz-page { grid-template-columns: 1fr; }
.quiz-sidebar-panel { position: static; order: -1; }
@ -399,29 +376,33 @@
<div class="quiz-page">
{{-- ══════════════════════════════
KOLOM KIRI Soal
══════════════════════════════ --}}
{{-- KOLOM KIRI --}}
<div class="quiz-main">
<div class="quiz-header">
<div class="quiz-title">
<img src="{{ asset('images/icon/siswac/piala.png') }}" alt="Ikon piala">
<img src="{{ asset('images/icon/siswac/piala.png') }}" alt="">
{{ $challenge->judul_challenge }}
</div>
<div class="quiz-meta">
<span>
<img src="{{ asset('images/icon/siswac/buku1.png') }}" alt="Jumlah soal">
<img src="{{ asset('images/icon/siswac/buku1.png') }}" alt="">
{{ $challenge->soal->count() }} Soal
</span>
<span>
<img src="{{ asset('images/icon/siswac/star.png') }}" alt="EXP">
<img src="{{ asset('images/icon/siswac/star.png') }}" alt="">
{{ $challenge->exp }} EXP
</span>
<span>
<img src="{{ asset('images/icon/siswac/alarm.png') }}" alt="Tenggat waktu">
<img src="{{ asset('images/icon/siswac/alarm.png') }}" alt="">
Tenggat: {{ \Carbon\Carbon::parse($challenge->tenggat_waktu)->format('d M Y, H:i') }}
</span>
{{-- Badge durasi --}}
@if($challenge->durasi_pengerjaan)
<span class="durasi-meta-badge">
{{ $challenge->durasi_pengerjaan }} menit pengerjaan
</span>
@endif
</div>
</div>
@ -460,7 +441,7 @@
@endforeach
<div class="warning-box" id="warningBox">
<img src="{{ asset('images/icon/siswac/alert.png') }}" alt="Peringatan">
<img src="{{ asset('images/icon/siswac/alert.png') }}" alt="">
Masih ada <span id="warningCount"></span> soal yang belum dijawab. Yakin ingin submit?
</div>
@ -471,7 +452,7 @@
onclick="nextSoal()">Selanjutnya </button>
<button type="submit" class="btn-nav btn-submit" id="btnSubmit"
onclick="return konfirmasiSubmit()">
<img src="{{ asset('images/icon/siswac/target.png') }}" alt="Submit">
<img src="{{ asset('images/icon/siswac/target.png') }}" class="target-selesai" alt="Submit">
Selesai &amp; Submit
</button>
</div>
@ -480,16 +461,20 @@
</div>{{-- /quiz-main --}}
{{-- ══════════════════════════════
KOLOM KANAN Sidebar Quiz
══════════════════════════════ --}}
{{-- KOLOM KANAN --}}
<div class="quiz-sidebar-panel">
<div class="timer-card">
<div class="timer-label">
<img src="{{ asset('images/icon/siswac/alarm.png') }}" alt="Timer">
<img src="{{ asset('images/icon/siswac/alarm.png') }}" alt="">
Sisa Waktu
</div>
{{-- Info total durasi --}}
@if($challenge->durasi_pengerjaan)
<div class="timer-total-info">
dari {{ $challenge->durasi_pengerjaan }} menit
</div>
@endif
<div class="timer-display" id="timerDisplay">--:--</div>
<div class="timer-bar-wrap">
<div class="timer-bar-fill" id="timerBar" style="width:100%"></div>
@ -498,10 +483,9 @@
<div class="nav-card">
<div class="nav-card-label">
<img src="{{ asset('images/icon/siswac/buku1.png') }}" alt="Navigasi soal">
<img src="{{ asset('images/icon/siswac/buku1.png') }}" alt="">
Navigasi Soal
</div>
<div class="nav-summary">
<div class="summary-item answered-sum">
<div class="summary-num" id="summaryAnswered">0</div>
@ -512,7 +496,6 @@
<div class="summary-label">Belum</div>
</div>
</div>
<div class="nav-grid" id="navGrid">
@foreach($challenge->soal as $i => $soal)
<button type="button"
@ -523,20 +506,10 @@ class="nav-btn {{ $i === 0 ? 'active' : '' }}"
</button>
@endforeach
</div>
<div class="nav-legend">
<div class="legend-item">
<div class="legend-dot current"></div>
<span>Soal aktif</span>
</div>
<div class="legend-item">
<div class="legend-dot done"></div>
<span>Sudah dijawab</span>
</div>
<div class="legend-item">
<div class="legend-dot undone"></div>
<span>Belum dijawab</span>
</div>
<div class="legend-item"><div class="legend-dot current"></div><span>Soal aktif</span></div>
<div class="legend-item"><div class="legend-dot done"></div><span>Sudah dijawab</span></div>
<div class="legend-item"><div class="legend-dot undone"></div><span>Belum dijawab</span></div>
</div>
</div>
@ -552,14 +525,37 @@ class="nav-btn {{ $i === 0 ? 'active' : '' }}"
let currentSoal = 0;
let jawaban = {};
// ══════════════════════════════════════════════════════════
// FIX TIMER — pakai timestamp detik dari server, kalikan
// 1000 di JS. Ini cara paling reliable lintas timezone.
// Carbon::parse()->timestamp = Unix timestamp dalam DETIK
// ══════════════════════════════════════════════════════════
const tenggatMs = {{ \Carbon\Carbon::parse($challenge->tenggat_waktu)->timestamp }} * 1000;
const totalDetik = Math.max(0, Math.floor((tenggatMs - Date.now()) / 1000));
let sisaDetik = totalDetik;
// ═══════════════════════════════════════════════════════════════════
// TIMER — menggunakan durasi_pengerjaan (menit) dari database
//
// Logika:
// 1. Jika ada durasi_pengerjaan → hitung mundur dari durasi tsb
// 2. Jika tidak ada durasi_pengerjaan (null) → fallback ke tenggat
// 3. Ambil waktu mulai dari sessionStorage agar konsisten
// jika halaman di-refresh (timer tidak reset)
// ═══════════════════════════════════════════════════════════════════
@if($challenge->durasi_pengerjaan)
// Durasi dalam detik
const DURASI_DETIK = {{ (int)$challenge->durasi_pengerjaan * 60 }};
// Simpan waktu mulai ke sessionStorage agar tidak reset saat refresh
const SESSION_KEY = 'quiz_start_{{ $challenge->id_challenge }}';
let startTime = parseInt(sessionStorage.getItem(SESSION_KEY) || '0');
if (!startTime) {
startTime = Date.now();
sessionStorage.setItem(SESSION_KEY, startTime);
}
// Hitung sisa detik berdasarkan waktu mulai
const totalDetik = DURASI_DETIK;
let sisaDetik = Math.max(0, DURASI_DETIK - Math.floor((Date.now() - startTime) / 1000));
@else
// Fallback: pakai tenggat waktu
const tenggatMs = {{ \Carbon\Carbon::parse($challenge->tenggat_waktu)->timestamp }} * 1000;
const totalDetik = Math.max(0, Math.floor((tenggatMs - Date.now()) / 1000));
let sisaDetik = totalDetik;
@endif
const elTimer = document.getElementById('timerDisplay');
const elTimerBar = document.getElementById('timerBar');
@ -578,15 +574,14 @@ function formatWaktu(s) {
function updateTimer() {
elTimer.textContent = formatWaktu(sisaDetik);
// Progress bar timer
const pct = totalDetik > 0 ? (sisaDetik / totalDetik) * 100 : 0;
elTimerBar.style.width = pct + '%';
// State warna
if (sisaDetik <= 60) {
elTimer.className = 'timer-display danger';
elTimerBar.className = 'timer-bar-fill danger';
} else if (sisaDetik <= 300) {
} else if (sisaDetik <= Math.floor(totalDetik * 0.25)) {
// Kuning saat sisa ≤ 25% dari total durasi
elTimer.className = 'timer-display warning';
elTimerBar.className = 'timer-bar-fill warning';
} else {
@ -597,6 +592,9 @@ function updateTimer() {
if (sisaDetik <= 0) {
clearInterval(timerInterval);
elTimer.textContent = '00:00';
@if($challenge->durasi_pengerjaan)
sessionStorage.removeItem('quiz_start_{{ $challenge->id_challenge }}');
@endif
alert('Waktu habis! Jawaban kamu akan otomatis dikumpulkan.');
document.getElementById('quizForm').submit();
return;
@ -608,18 +606,15 @@ function updateTimer() {
const timerInterval = setInterval(updateTimer, 1000);
// ══════════════════════════════════════════
// ═══════════════════════════════════════════════════════════════════
// NAVIGASI SOAL
// ══════════════════════════════════════════
// ═══════════════════════════════════════════════════════════════════
function goToSoal(index) {
document.getElementById(`soal-${currentSoal}`).classList.remove('active');
document.getElementById(`navBtn-${currentSoal}`).classList.remove('active');
currentSoal = index;
document.getElementById(`soal-${currentSoal}`).classList.add('active');
document.getElementById(`navBtn-${currentSoal}`).classList.add('active');
updateNav();
updateProgress();
}
@ -629,14 +624,12 @@ function prevSoal() { if (currentSoal > 0) goToSoal(currentSoal - 1)
function pilihJawaban(soalIndex, opsi, idSoal) {
jawaban[soalIndex] = opsi;
['A','B','C','D'].forEach(o => {
document.getElementById(`label-${soalIndex}-${o}`)?.classList.remove('selected');
});
document.getElementById(`label-${soalIndex}-${opsi}`)?.classList.add('selected');
document.getElementById(`radio-${soalIndex}-${opsi}`).checked = true;
document.getElementById(`navBtn-${soalIndex}`)?.classList.add('answered');
updateProgress();
updateSummary();
}
@ -645,9 +638,7 @@ function updateNav() {
const btnPrev = document.getElementById('btnPrev');
const btnNext = document.getElementById('btnNext');
const btnSubmit = document.getElementById('btnSubmit');
btnPrev.disabled = (currentSoal === 0);
if (currentSoal === totalSoal - 1) {
btnNext.style.display = 'none';
btnSubmit.style.display = 'inline-flex';
@ -678,6 +669,10 @@ function konfirmasiSubmit() {
document.getElementById('warningBox').style.display = 'flex';
return confirm(`Masih ada ${belum} soal yang belum dijawab. Yakin ingin submit?`);
}
@if($challenge->durasi_pengerjaan)
// Bersihkan session timer setelah submit
sessionStorage.removeItem('quiz_start_{{ $challenge->id_challenge }}');
@endif
return confirm('Yakin ingin submit jawaban? Jawaban tidak bisa diubah setelah submit.');
}

View File

@ -0,0 +1,46 @@
@if ($paginator->hasPages())
<nav>
<ul class="pagination">
{{-- Previous Page Link --}}
@if ($paginator->onFirstPage())
<li class="page-item disabled" aria-disabled="true" aria-label="@lang('pagination.previous')">
<span class="page-link" aria-hidden="true">&lsaquo;</span>
</li>
@else
<li class="page-item">
<a class="page-link" href="{{ $paginator->previousPageUrl() }}" rel="prev" aria-label="@lang('pagination.previous')">&lsaquo;</a>
</li>
@endif
{{-- Pagination Elements --}}
@foreach ($elements as $element)
{{-- "Three Dots" Separator --}}
@if (is_string($element))
<li class="page-item disabled" aria-disabled="true"><span class="page-link">{{ $element }}</span></li>
@endif
{{-- Array Of Links --}}
@if (is_array($element))
@foreach ($element as $page => $url)
@if ($page == $paginator->currentPage())
<li class="page-item active" aria-current="page"><span class="page-link">{{ $page }}</span></li>
@else
<li class="page-item"><a class="page-link" href="{{ $url }}">{{ $page }}</a></li>
@endif
@endforeach
@endif
@endforeach
{{-- Next Page Link --}}
@if ($paginator->hasMorePages())
<li class="page-item">
<a class="page-link" href="{{ $paginator->nextPageUrl() }}" rel="next" aria-label="@lang('pagination.next')">&rsaquo;</a>
</li>
@else
<li class="page-item disabled" aria-disabled="true" aria-label="@lang('pagination.next')">
<span class="page-link" aria-hidden="true">&rsaquo;</span>
</li>
@endif
</ul>
</nav>
@endif

View File

@ -0,0 +1,119 @@
@if ($paginator->hasPages())
<nav class="d-flex justify-items-center justify-content-between align-items-center mt-2">
{{-- Info teks --}}
<p style="font-size:13px;color:#64748b;margin:0">
Menampilkan
<span style="font-weight:700;color:#1e293b">{{ $paginator->firstItem() }}</span>
<span style="font-weight:700;color:#1e293b">{{ $paginator->lastItem() }}</span>
dari
<span style="font-weight:700;color:#1e293b">{{ $paginator->total() }}</span>
data
</p>
{{-- Tombol pagination --}}
<ul style="display:flex;align-items:center;gap:6px;list-style:none;margin:0;padding:0">
{{-- Prev --}}
@if ($paginator->onFirstPage())
<li>
<span style="
display:inline-flex;align-items:center;justify-content:center;
width:34px;height:34px;border-radius:10px;
background:#f1f5f9;color:#cbd5e1;
font-size:16px;font-weight:700;cursor:not-allowed;
border:1.5px solid #e2e8f0;
"></span>
</li>
@else
<li>
<a href="{{ $paginator->previousPageUrl() }}" rel="prev" style="
display:inline-flex;align-items:center;justify-content:center;
width:34px;height:34px;border-radius:10px;
background:#fff;color:#2b8ef3;
font-size:16px;font-weight:700;
border:1.5px solid #2b8ef3;
text-decoration:none;transition:all 0.2s;
"
onmouseover="this.style.background='#2b8ef3';this.style.color='#fff'"
onmouseout="this.style.background='#fff';this.style.color='#2b8ef3'"
></a>
</li>
@endif
{{-- Nomor halaman --}}
@foreach ($elements as $element)
@if (is_string($element))
<li>
<span style="
display:inline-flex;align-items:center;justify-content:center;
width:34px;height:34px;border-radius:10px;
background:#f1f5f9;color:#94a3b8;
font-size:13px;font-weight:700;
border:1.5px solid #e2e8f0;
">…</span>
</li>
@endif
@if (is_array($element))
@foreach ($element as $page => $url)
@if ($page == $paginator->currentPage())
<li>
<span style="
display:inline-flex;align-items:center;justify-content:center;
width:34px;height:34px;border-radius:10px;
background:#2b8ef3;color:#fff;
font-size:13px;font-weight:700;
border:1.5px solid #2b8ef3;
">{{ $page }}</span>
</li>
@else
<li>
<a href="{{ $url }}" style="
display:inline-flex;align-items:center;justify-content:center;
width:34px;height:34px;border-radius:10px;
background:#fff;color:#475569;
font-size:13px;font-weight:600;
border:1.5px solid #e2e8f0;
text-decoration:none;transition:all 0.2s;
"
onmouseover="this.style.background='#e6f0ff';this.style.borderColor='#2b8ef3';this.style.color='#2b8ef3'"
onmouseout="this.style.background='#fff';this.style.borderColor='#e2e8f0';this.style.color='#475569'"
>{{ $page }}</a>
</li>
@endif
@endforeach
@endif
@endforeach
{{-- Next --}}
@if ($paginator->hasMorePages())
<li>
<a href="{{ $paginator->nextPageUrl() }}" rel="next" style="
display:inline-flex;align-items:center;justify-content:center;
width:34px;height:34px;border-radius:10px;
background:#fff;color:#2b8ef3;
font-size:16px;font-weight:700;
border:1.5px solid #2b8ef3;
text-decoration:none;transition:all 0.2s;
"
onmouseover="this.style.background='#2b8ef3';this.style.color='#fff'"
onmouseout="this.style.background='#fff';this.style.color='#2b8ef3'"
></a>
</li>
@else
<li>
<span style="
display:inline-flex;align-items:center;justify-content:center;
width:34px;height:34px;border-radius:10px;
background:#f1f5f9;color:#cbd5e1;
font-size:16px;font-weight:700;cursor:not-allowed;
border:1.5px solid #e2e8f0;
"></span>
</li>
@endif
</ul>
</nav>
@endif

View File

@ -0,0 +1,46 @@
@if ($paginator->hasPages())
<nav>
<ul class="pagination">
{{-- Previous Page Link --}}
@if ($paginator->onFirstPage())
<li class="disabled" aria-disabled="true" aria-label="@lang('pagination.previous')">
<span aria-hidden="true">&lsaquo;</span>
</li>
@else
<li>
<a href="{{ $paginator->previousPageUrl() }}" rel="prev" aria-label="@lang('pagination.previous')">&lsaquo;</a>
</li>
@endif
{{-- Pagination Elements --}}
@foreach ($elements as $element)
{{-- "Three Dots" Separator --}}
@if (is_string($element))
<li class="disabled" aria-disabled="true"><span>{{ $element }}</span></li>
@endif
{{-- Array Of Links --}}
@if (is_array($element))
@foreach ($element as $page => $url)
@if ($page == $paginator->currentPage())
<li class="active" aria-current="page"><span>{{ $page }}</span></li>
@else
<li><a href="{{ $url }}">{{ $page }}</a></li>
@endif
@endforeach
@endif
@endforeach
{{-- Next Page Link --}}
@if ($paginator->hasMorePages())
<li>
<a href="{{ $paginator->nextPageUrl() }}" rel="next" aria-label="@lang('pagination.next')">&rsaquo;</a>
</li>
@else
<li class="disabled" aria-disabled="true" aria-label="@lang('pagination.next')">
<span aria-hidden="true">&rsaquo;</span>
</li>
@endif
</ul>
</nav>
@endif

View File

@ -0,0 +1,36 @@
@if ($paginator->hasPages())
<div class="ui pagination menu" role="navigation">
{{-- Previous Page Link --}}
@if ($paginator->onFirstPage())
<a class="icon item disabled" aria-disabled="true" aria-label="@lang('pagination.previous')"> <i class="left chevron icon"></i> </a>
@else
<a class="icon item" href="{{ $paginator->previousPageUrl() }}" rel="prev" aria-label="@lang('pagination.previous')"> <i class="left chevron icon"></i> </a>
@endif
{{-- Pagination Elements --}}
@foreach ($elements as $element)
{{-- "Three Dots" Separator --}}
@if (is_string($element))
<a class="icon item disabled" aria-disabled="true">{{ $element }}</a>
@endif
{{-- Array Of Links --}}
@if (is_array($element))
@foreach ($element as $page => $url)
@if ($page == $paginator->currentPage())
<a class="item active" href="{{ $url }}" aria-current="page">{{ $page }}</a>
@else
<a class="item" href="{{ $url }}">{{ $page }}</a>
@endif
@endforeach
@endif
@endforeach
{{-- Next Page Link --}}
@if ($paginator->hasMorePages())
<a class="icon item" href="{{ $paginator->nextPageUrl() }}" rel="next" aria-label="@lang('pagination.next')"> <i class="right chevron icon"></i> </a>
@else
<a class="icon item disabled" aria-disabled="true" aria-label="@lang('pagination.next')"> <i class="right chevron icon"></i> </a>
@endif
</div>
@endif

View File

@ -0,0 +1,27 @@
@if ($paginator->hasPages())
<nav>
<ul class="pagination">
{{-- Previous Page Link --}}
@if ($paginator->onFirstPage())
<li class="page-item disabled" aria-disabled="true">
<span class="page-link">@lang('pagination.previous')</span>
</li>
@else
<li class="page-item">
<a class="page-link" href="{{ $paginator->previousPageUrl() }}" rel="prev">@lang('pagination.previous')</a>
</li>
@endif
{{-- Next Page Link --}}
@if ($paginator->hasMorePages())
<li class="page-item">
<a class="page-link" href="{{ $paginator->nextPageUrl() }}" rel="next">@lang('pagination.next')</a>
</li>
@else
<li class="page-item disabled" aria-disabled="true">
<span class="page-link">@lang('pagination.next')</span>
</li>
@endif
</ul>
</nav>
@endif

View File

@ -0,0 +1,29 @@
@if ($paginator->hasPages())
<nav role="navigation" aria-label="{!! __('Pagination Navigation') !!}">
<ul class="pagination">
{{-- Previous Page Link --}}
@if ($paginator->onFirstPage())
<li class="page-item disabled" aria-disabled="true">
<span class="page-link">{!! __('pagination.previous') !!}</span>
</li>
@else
<li class="page-item">
<a class="page-link" href="{{ $paginator->previousPageUrl() }}" rel="prev">
{!! __('pagination.previous') !!}
</a>
</li>
@endif
{{-- Next Page Link --}}
@if ($paginator->hasMorePages())
<li class="page-item">
<a class="page-link" href="{{ $paginator->nextPageUrl() }}" rel="next">{!! __('pagination.next') !!}</a>
</li>
@else
<li class="page-item disabled" aria-disabled="true">
<span class="page-link">{!! __('pagination.next') !!}</span>
</li>
@endif
</ul>
</nav>
@endif

View File

@ -0,0 +1,19 @@
@if ($paginator->hasPages())
<nav>
<ul class="pagination">
{{-- Previous Page Link --}}
@if ($paginator->onFirstPage())
<li class="disabled" aria-disabled="true"><span>@lang('pagination.previous')</span></li>
@else
<li><a href="{{ $paginator->previousPageUrl() }}" rel="prev">@lang('pagination.previous')</a></li>
@endif
{{-- Next Page Link --}}
@if ($paginator->hasMorePages())
<li><a href="{{ $paginator->nextPageUrl() }}" rel="next">@lang('pagination.next')</a></li>
@else
<li class="disabled" aria-disabled="true"><span>@lang('pagination.next')</span></li>
@endif
</ul>
</nav>
@endif

View File

@ -0,0 +1,25 @@
@if ($paginator->hasPages())
<nav role="navigation" aria-label="{!! __('Pagination Navigation') !!}" class="flex justify-between">
{{-- Previous Page Link --}}
@if ($paginator->onFirstPage())
<span class="relative inline-flex items-center px-4 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 cursor-default leading-5 rounded-md dark:text-gray-600 dark:bg-gray-800 dark:border-gray-600">
{!! __('pagination.previous') !!}
</span>
@else
<a href="{{ $paginator->previousPageUrl() }}" rel="prev" class="relative inline-flex items-center px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 leading-5 rounded-md hover:text-gray-500 focus:outline-none focus:ring ring-gray-300 focus:border-blue-300 active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150 dark:bg-gray-800 dark:border-gray-600 dark:text-gray-300 dark:focus:border-blue-700 dark:active:bg-gray-700 dark:active:text-gray-300">
{!! __('pagination.previous') !!}
</a>
@endif
{{-- Next Page Link --}}
@if ($paginator->hasMorePages())
<a href="{{ $paginator->nextPageUrl() }}" rel="next" class="relative inline-flex items-center px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 leading-5 rounded-md hover:text-gray-500 focus:outline-none focus:ring ring-gray-300 focus:border-blue-300 active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150 dark:bg-gray-800 dark:border-gray-600 dark:text-gray-300 dark:focus:border-blue-700 dark:active:bg-gray-700 dark:active:text-gray-300">
{!! __('pagination.next') !!}
</a>
@else
<span class="relative inline-flex items-center px-4 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 cursor-default leading-5 rounded-md dark:text-gray-600 dark:bg-gray-800 dark:border-gray-600">
{!! __('pagination.next') !!}
</span>
@endif
</nav>
@endif

View File

@ -0,0 +1,106 @@
@if ($paginator->hasPages())
<nav role="navigation" aria-label="{{ __('Pagination Navigation') }}" class="flex items-center justify-between">
<div class="flex justify-between flex-1 sm:hidden">
@if ($paginator->onFirstPage())
<span class="relative inline-flex items-center px-4 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 cursor-default leading-5 rounded-md dark:text-gray-600 dark:bg-gray-800 dark:border-gray-600">
{!! __('pagination.previous') !!}
</span>
@else
<a href="{{ $paginator->previousPageUrl() }}" class="relative inline-flex items-center px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 leading-5 rounded-md hover:text-gray-500 focus:outline-none focus:ring ring-gray-300 focus:border-blue-300 active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150 dark:bg-gray-800 dark:border-gray-600 dark:text-gray-300 dark:focus:border-blue-700 dark:active:bg-gray-700 dark:active:text-gray-300">
{!! __('pagination.previous') !!}
</a>
@endif
@if ($paginator->hasMorePages())
<a href="{{ $paginator->nextPageUrl() }}" class="relative inline-flex items-center px-4 py-2 ml-3 text-sm font-medium text-gray-700 bg-white border border-gray-300 leading-5 rounded-md hover:text-gray-500 focus:outline-none focus:ring ring-gray-300 focus:border-blue-300 active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150 dark:bg-gray-800 dark:border-gray-600 dark:text-gray-300 dark:focus:border-blue-700 dark:active:bg-gray-700 dark:active:text-gray-300">
{!! __('pagination.next') !!}
</a>
@else
<span class="relative inline-flex items-center px-4 py-2 ml-3 text-sm font-medium text-gray-500 bg-white border border-gray-300 cursor-default leading-5 rounded-md dark:text-gray-600 dark:bg-gray-800 dark:border-gray-600">
{!! __('pagination.next') !!}
</span>
@endif
</div>
<div class="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between">
<div>
<p class="text-sm text-gray-700 leading-5 dark:text-gray-400">
{!! __('Showing') !!}
@if ($paginator->firstItem())
<span class="font-medium">{{ $paginator->firstItem() }}</span>
{!! __('to') !!}
<span class="font-medium">{{ $paginator->lastItem() }}</span>
@else
{{ $paginator->count() }}
@endif
{!! __('of') !!}
<span class="font-medium">{{ $paginator->total() }}</span>
{!! __('results') !!}
</p>
</div>
<div>
<span class="relative z-0 inline-flex rtl:flex-row-reverse shadow-sm rounded-md">
{{-- Previous Page Link --}}
@if ($paginator->onFirstPage())
<span aria-disabled="true" aria-label="{{ __('pagination.previous') }}">
<span class="relative inline-flex items-center px-2 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 cursor-default rounded-l-md leading-5 dark:bg-gray-800 dark:border-gray-600" aria-hidden="true">
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z" clip-rule="evenodd" />
</svg>
</span>
</span>
@else
<a href="{{ $paginator->previousPageUrl() }}" rel="prev" class="relative inline-flex items-center px-2 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 rounded-l-md leading-5 hover:text-gray-400 focus:z-10 focus:outline-none focus:ring ring-gray-300 focus:border-blue-300 active:bg-gray-100 active:text-gray-500 transition ease-in-out duration-150 dark:bg-gray-800 dark:border-gray-600 dark:active:bg-gray-700 dark:focus:border-blue-800" aria-label="{{ __('pagination.previous') }}">
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z" clip-rule="evenodd" />
</svg>
</a>
@endif
{{-- Pagination Elements --}}
@foreach ($elements as $element)
{{-- "Three Dots" Separator --}}
@if (is_string($element))
<span aria-disabled="true">
<span class="relative inline-flex items-center px-4 py-2 -ml-px text-sm font-medium text-gray-700 bg-white border border-gray-300 cursor-default leading-5 dark:bg-gray-800 dark:border-gray-600">{{ $element }}</span>
</span>
@endif
{{-- Array Of Links --}}
@if (is_array($element))
@foreach ($element as $page => $url)
@if ($page == $paginator->currentPage())
<span aria-current="page">
<span class="relative inline-flex items-center px-4 py-2 -ml-px text-sm font-medium text-gray-500 bg-white border border-gray-300 cursor-default leading-5 dark:bg-gray-800 dark:border-gray-600">{{ $page }}</span>
</span>
@else
<a href="{{ $url }}" class="relative inline-flex items-center px-4 py-2 -ml-px text-sm font-medium text-gray-700 bg-white border border-gray-300 leading-5 hover:text-gray-500 focus:z-10 focus:outline-none focus:ring ring-gray-300 focus:border-blue-300 active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150 dark:bg-gray-800 dark:border-gray-600 dark:text-gray-400 dark:hover:text-gray-300 dark:active:bg-gray-700 dark:focus:border-blue-800" aria-label="{{ __('Go to page :page', ['page' => $page]) }}">
{{ $page }}
</a>
@endif
@endforeach
@endif
@endforeach
{{-- Next Page Link --}}
@if ($paginator->hasMorePages())
<a href="{{ $paginator->nextPageUrl() }}" rel="next" class="relative inline-flex items-center px-2 py-2 -ml-px text-sm font-medium text-gray-500 bg-white border border-gray-300 rounded-r-md leading-5 hover:text-gray-400 focus:z-10 focus:outline-none focus:ring ring-gray-300 focus:border-blue-300 active:bg-gray-100 active:text-gray-500 transition ease-in-out duration-150 dark:bg-gray-800 dark:border-gray-600 dark:active:bg-gray-700 dark:focus:border-blue-800" aria-label="{{ __('pagination.next') }}">
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd" />
</svg>
</a>
@else
<span aria-disabled="true" aria-label="{{ __('pagination.next') }}">
<span class="relative inline-flex items-center px-2 py-2 -ml-px text-sm font-medium text-gray-500 bg-white border border-gray-300 cursor-default rounded-r-md leading-5 dark:bg-gray-800 dark:border-gray-600" aria-hidden="true">
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd" />
</svg>
</span>
</span>
@endif
</span>
</div>
</div>
</nav>
@endif

View File

@ -85,20 +85,32 @@
Route::middleware(['auth:admin'])->prefix('admin')->name('admin.')->group(function () {
// DASHBOARD
Route::get('/dashboard', [AdminController::class, 'dashboard'])->name('dashboard');
Route::get('/dashboard',[AdminController::class, 'dashboard'])->name('dashboard');
// PROFILE
Route::get('/profile', [AdminProfileController::class, 'edit'])->name('profile.edit');
Route::post('/profile', [AdminProfileController::class, 'updateAjax'])->name('profile.update');
Route::post('/profile',[AdminProfileController::class, 'updateAjax'])->name('profile.update');
//GURU
// GURU
Route::get('/guru/kelas-by-mapel', [AdminGuruController::class, 'getKelasByMapel'])
->name('guru.kelasByMapel');
Route::get('guru/download-pdf', [AdminGuruController::class, 'downloadPdf'])->name('guru.downloadPdf');
Route::get('guru/download-excel', [AdminGuruController::class, 'downloadExcel'])->name('guru.downloadExcel');
Route::resource('guru', AdminGuruController::class);
//SISWA KELAS DAN MAPEL
// SISWA
Route::get('siswa/download-pdf', [AdminSiswaController::class, 'downloadPdf'])->name('siswa.downloadPdf');
Route::get('siswa/download-excel', [AdminSiswaController::class, 'downloadExcel'])->name('siswa.downloadExcel');
Route::resource('siswa', AdminSiswaController::class);
// KELAS
Route::get('kelas/download-pdf', [AdminKelasController::class, 'downloadPdf'])->name('kelas.downloadPdf');
Route::get('kelas/download-excel', [AdminKelasController::class, 'downloadExcel'])->name('kelas.downloadExcel');
Route::resource('kelas', AdminKelasController::class);
// MAPEL
Route::get('mapel/download-pdf', [AdminMapelController::class, 'downloadPdf'])->name('mapel.downloadPdf');
Route::get('mapel/download-excel', [AdminMapelController::class, 'downloadExcel'])->name('mapel.downloadExcel');
Route::resource('mapel', AdminMapelController::class);
//HISTORI MATERI
@ -106,12 +118,12 @@
Route::delete('/materi/{id}', [AdminMateriTugasController::class, 'destroyMateri'])->name('materi.destroy');
//HISTRORY TUGAS
Route::get('/tugas/history', [AdminMateriTugasController::class, 'historyTugas'])->name('tugas.history');
Route::get('/tugas/{id}/detail', [AdminMateriTugasController::class, 'detailTugas'])->name('tugas.detail');
Route::delete('/tugas/{id}', [AdminMateriTugasController::class, 'destroyTugas'])->name('tugas.destroy');
Route::get('/tugas/history',[AdminMateriTugasController::class, 'historyTugas'])->name('tugas.history');
Route::get('/tugas/{id}/detail',[AdminMateriTugasController::class, 'detailTugas'])->name('tugas.detail');
Route::delete('/tugas/{id}',[AdminMateriTugasController::class, 'destroyTugas'])->name('tugas.destroy');
//CHALLENGE
Route::get('/challenge/{id}/edit-data', [AdminChallengeController::class, 'editData'])
Route::get('/challenge/{id}/edit-data',[AdminChallengeController::class, 'editData'])
->name('challenge.editData');
Route::resource('challenge', AdminChallengeController::class);