first commit backend

This commit is contained in:
Itzfebry 2025-07-17 14:42:29 +07:00
commit b8c9d560dd
235 changed files with 32073 additions and 0 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

18
.editorconfig Normal file
View File

@ -0,0 +1,18 @@
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
[*.{yml,yaml}]
indent_size = 2
[docker-compose.yml]
indent_size = 4

11
.gitattributes vendored Normal file
View File

@ -0,0 +1,11 @@
* text=auto eol=lf
*.blade.php diff=html
*.css diff=css
*.html diff=html
*.md diff=markdown
*.php diff=php
/.github export-ignore
CHANGELOG.md export-ignore
.styleci.yml export-ignore

23
.gitignore vendored Normal file
View File

@ -0,0 +1,23 @@
/.phpunit.cache
/node_modules
/public/build
/public/hot
/public/storage
/storage/*.key
/storage/pail
/vendor
.env
.env.backup
.env.production
.phpactor.json
.phpunit.result.cache
Homestead.json
Homestead.yaml
npm-debug.log
yarn-error.log
/auth.json
/.fleet
/.idea
/.nova
/.vscode
/.zed

View File

@ -0,0 +1,14 @@
<?php
namespace App\Exports;
use Illuminate\Contracts\View\View;
use Maatwebsite\Excel\Concerns\FromView;
class QuizExport implements FromView
{
public function view(): View
{
return view('pages.role_guru.quiz.excel_export_quiz');
}
}

View File

@ -0,0 +1,48 @@
<?php
namespace App\Exports;
use Maatwebsite\Excel\Concerns\FromCollection;
use Maatwebsite\Excel\Concerns\WithHeadings;
class RekapExport implements FromCollection, WithHeadings
{
protected $rekap;
public function __construct($rekap)
{
$this->rekap = $rekap;
}
public function collection()
{
return collect($this->rekap)->map(function ($item) {
return [
$item['matapelajaran'],
$item['judul_quiz'],
$item['nama_siswa'],
$item['tahun_ajaran'],
$item['kelas'],
$item['total_skor'],
$item['kkm'],
$item['persentase'],
$item['persentase'] >= $item['kkm'] ? 'Lulus' : 'Tidak Lulus',
];
});
}
public function headings(): array
{
return [
'Mata Pelajaran',
'Judul Quiz',
'Nama Siswa',
'Tahun Ajaran',
'Kelas',
'Total Skor',
'KKM',
'Nilai (%)',
'Status',
];
}
}

View File

@ -0,0 +1,107 @@
<?php
namespace App\Exports;
use App\Models\SubmitTugas;
use Illuminate\Support\Facades\Auth;
use Maatwebsite\Excel\Concerns\FromCollection;
use Maatwebsite\Excel\Concerns\WithHeadings;
use Maatwebsite\Excel\Concerns\WithMapping;
use Maatwebsite\Excel\Concerns\WithStyles;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class TugasDetailExport implements FromCollection, WithHeadings, WithMapping, WithStyles
{
protected $selectedKelas;
protected $selectedMatpel;
public function __construct($selectedKelas = null, $selectedMatpel = null)
{
$this->selectedKelas = $selectedKelas;
$this->selectedMatpel = $selectedMatpel;
}
/**
* @return \Illuminate\Support\Collection
*/
public function collection()
{
$guruNip = Auth::user()->guru->nip;
$query = SubmitTugas::with(['siswa', 'tugas.mataPelajaran'])
->whereHas('tugas', function ($q) use ($guruNip) {
$q->where('guru_nip', $guruNip);
});
if ($this->selectedKelas) {
$query->whereHas('tugas', function ($q) {
$q->where('kelas', $this->selectedKelas);
});
}
if ($this->selectedMatpel) {
$query->whereHas('tugas', function ($q) {
$q->where('matapelajaran_id', $this->selectedMatpel);
});
}
return $query->orderBy('created_at', 'desc')->get();
}
public function headings(): array
{
return [
'No',
'Nama Tugas',
'Mata Pelajaran',
'Kelas',
'Nama Siswa',
'NISN',
'Tanggal Submit',
'Nilai',
'Status Nilai',
'Tenggat Waktu',
'Status Tenggat',
'Deskripsi Tugas'
];
}
public function map($submitTugas): array
{
static $no = 1;
$tugas = $submitTugas->tugas;
$siswa = $submitTugas->siswa;
$statusNilai = $submitTugas->nilai !== null ? 'Sudah Dinilai' : 'Belum Dinilai';
$statusTenggat = \Carbon\Carbon::parse($tugas->tenggat)->isPast() ? 'Lewat Tenggat' : 'Masih Aktif';
return [
$no++,
$tugas->nama,
$tugas->mataPelajaran->nama ?? '-',
$tugas->kelas,
$siswa->nama ?? 'Siswa tidak ditemukan',
$submitTugas->nisn,
\Carbon\Carbon::parse($submitTugas->tanggal)->format('d/m/Y H:i'),
$submitTugas->nilai ?? '-',
$statusNilai,
\Carbon\Carbon::parse($tugas->tenggat)->format('d/m/Y'),
$statusTenggat,
$tugas->deskripsi ?? '-'
];
}
public function styles(Worksheet $sheet)
{
return [
1 => [
'font' => ['bold' => true],
'fill' => [
'fillType' => \PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID,
'startColor' => ['rgb' => 'E2E8F0']
]
]
];
}
}

100
app/Exports/TugasExport.php Normal file
View File

@ -0,0 +1,100 @@
<?php
namespace App\Exports;
use App\Models\Tugas;
use Illuminate\Support\Facades\Auth;
use Maatwebsite\Excel\Concerns\FromCollection;
use Maatwebsite\Excel\Concerns\WithHeadings;
use Maatwebsite\Excel\Concerns\WithMapping;
use Maatwebsite\Excel\Concerns\WithStyles;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class TugasExport implements FromCollection, WithHeadings, WithMapping, WithStyles
{
protected $selectedKelas;
protected $selectedMatpel;
public function __construct($selectedKelas = null, $selectedMatpel = null)
{
$this->selectedKelas = $selectedKelas;
$this->selectedMatpel = $selectedMatpel;
}
public function collection()
{
$guruNip = Auth::user()->guru->nip;
$query = Tugas::with(['mataPelajaran', 'submitTugas.siswa'])
->where('guru_nip', $guruNip);
if ($this->selectedKelas) {
$query->where('kelas', $this->selectedKelas);
}
if ($this->selectedMatpel) {
$query->where('matapelajaran_id', $this->selectedMatpel);
}
return $query->orderBy('created_at', 'desc')->get();
}
public function headings(): array
{
return [
'No',
'Nama Tugas',
'Mata Pelajaran',
'Kelas',
'Tanggal Dibuat',
'Tenggat Waktu',
'Jumlah Submit',
'Sudah Dinilai',
'Belum Dinilai',
'Rata-rata Nilai',
'Status Tenggat'
];
}
public function map($tugas): array
{
static $no = 1;
$submitTugas = $tugas->submitTugas ?: collect();
$sudahDinilai = $submitTugas->whereNotNull('nilai')->count();
$totalSubmit = $submitTugas->count();
$belumDinilai = $totalSubmit - $sudahDinilai;
$rataRataNilai = $submitTugas->whereNotNull('nilai')->count() > 0
? number_format($submitTugas->whereNotNull('nilai')->avg('nilai'), 1)
: '-';
$statusTenggat = \Carbon\Carbon::parse($tugas->tenggat)->isPast() ? 'Lewat Tenggat' : 'Masih Aktif';
return [
$no++,
$tugas->nama,
$tugas->mataPelajaran->nama ?? '-',
$tugas->kelas,
\Carbon\Carbon::parse($tugas->tanggal)->format('d/m/Y'),
\Carbon\Carbon::parse($tugas->tenggat)->format('d/m/Y'),
$totalSubmit,
$sudahDinilai,
$belumDinilai,
$rataRataNilai,
$statusTenggat
];
}
public function styles(Worksheet $sheet)
{
return [
1 => [
'font' => ['bold' => true],
'fill' => [
'fillType' => \PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID,
'startColor' => ['rgb' => 'E2E8F0']
]
]
];
}
}

View File

@ -0,0 +1,193 @@
<?php
namespace App\Http\Controllers;
use App\Models\Admin;
use App\Repositories\AdminRepository;
use App\Repositories\UserRepository;
use Illuminate\Database\QueryException;
use Illuminate\Http\Request;
use RealRashid\SweetAlert\Facades\Alert;
use App\Models\MataPelajaran;
use App\Models\Guru;
use App\Models\Tugas;
use App\Models\Quizzes;
use App\Models\AuditLog;
class AdminController extends Controller
{
protected $param;
protected $paramUser;
public function __construct(AdminRepository $admin, UserRepository $user)
{
$this->param = $admin;
$this->paramUser = $user;
}
public function index(Request $request)
{
$limit = $request->has('page_length') ? $request->get('page_length') : 10;
$search = $request->has('search') ? $request->get('search') : null;
$admin = $this->param->getData($search, $limit);
return view("pages.role_admin.admin.index", compact("admin"));
}
/**
* Show the form for creating a new resource.
*/
public function create()
{
return view("pages.role_admin.admin.create");
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
try {
$dataUser = $request->validate([
'email' => 'required',
'role' => 'required',
]);
$data = $request->validate([
'nip' => 'required|string|size:18|unique:guru,nip',
'nama' => 'required|string',
'jk' => 'required',
]);
if (Admin::where('nip', $data['nip'])->exists()) {
Alert::error("Terjadi Kesalahan", "NIP sudah terdaftar.");
return back()->withInput();
}
$dataUser['pass'] = $request->nip;
$user = $this->paramUser->store($dataUser);
$data["user_id"] = $user->id;
$this->param->store($data);
Alert::success("Berhasil", "Data Berhasil di simpan.");
return redirect()->route("admin");
} catch (\Exception $e) {
Alert::error("Terjadi Kesalahan", $e->getMessage());
return back()->withInput();
} catch (QueryException $e) {
Alert::error("Terjadi Kesalahan", $e->getMessage());
return back()->withInput();
}
}
/**
* Display the specified resource.
*/
public function show(string $id)
{
//
}
/**
* Show the form for editing the specified resource.
*/
public function edit(string $id)
{
$admin = $this->param->find($id);
return view("pages.role_admin.admin.edit", compact("admin"));
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, string $id)
{
try {
$dataUser = $request->validate([
'email' => 'required',
]);
$data = $request->validate([
'nip' => 'required|string|size:18',
'nama' => 'required|string',
'jk' => 'required',
]);
$this->paramUser->update($dataUser, $request->user_id);
$this->param->update($data, $id);
Alert::success("Berhasil", "Data Berhasil di ubah.");
return redirect()->route("admin");
} catch (\Exception $e) {
Alert::error("Terjadi Kesalahan", $e->getMessage());
return back()->withInput();
} catch (QueryException $e) {
Alert::error("Terjadi Kesalahan", $e->getMessage());
return back()->withInput();
}
}
/**
* Remove the specified resource from storage.
*/
public function destroy(Request $request)
{
try {
$this->param->destroy($request->formid);
$this->paramUser->destroy($request->user_id);
Alert::success("Berhasil", "Data Berhasil di Hapus.");
return redirect()->route("admin");
} catch (\Exception $e) {
Alert::error("Terjadi Kesalahan", $e->getMessage());
return back();
}
}
// Menampilkan halaman form pemindahan data
public function pindahDataPage()
{
$mataPelajaran = MataPelajaran::all();
$guru = Guru::all();
return view('pages.role_admin.pindah_data', compact('mataPelajaran', 'guru'));
}
// Proses pemindahan data guru
public function pindahGuruMatpel(Request $request)
{
$request->validate([
'mata_pelajaran_id' => 'required|exists:matapelajaran,id',
'guru_lama_nip' => 'required|exists:guru,nip',
'guru_baru_nip' => 'required|exists:guru,nip',
]);
if ($request->guru_lama_nip == $request->guru_baru_nip) {
Alert::error('Gagal', 'Guru lama dan guru baru tidak boleh sama.');
return back()->withInput();
}
$mapel = MataPelajaran::where('id', $request->mata_pelajaran_id)
->where('guru_nip', $request->guru_lama_nip)
->first();
if (!$mapel) {
Alert::error('Gagal', 'Guru lama bukan pengampu mata pelajaran yang dipilih.');
return back()->withInput();
}
// Update tugas
Tugas::where('guru_nip', $request->guru_lama_nip)
->where('matapelajaran_id', $request->mata_pelajaran_id)
->update(['guru_nip' => $request->guru_baru_nip]);
// Update guru pengampu pada tabel matapelajaran
MataPelajaran::where('id', $request->mata_pelajaran_id)
->where('guru_nip', $request->guru_lama_nip)
->update(['guru_nip' => $request->guru_baru_nip]);
Alert::success('Berhasil', 'Data guru berhasil dipindahkan.');
return redirect()->route('admin.pindah-data');
}
public function auditLogPage()
{
$logs = AuditLog::with('user')->orderBy('created_at', 'desc')->paginate(30);
return view('pages.role_admin.audit_log', compact('logs'));
}
}

View File

@ -0,0 +1,87 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Http\Controllers\Traits\ApiResponse;
use App\Models\Admin;
use App\Models\Guru;
use App\Models\Siswa;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
class AuthController extends Controller
{
use ApiResponse;
public function login(Request $request)
{
$request->validate([
"login" => "required|string",
"password" => "required|string",
]);
// Temukan user berdasarkan email atau NIP
$login = $request->login;
$user = User::where('email', $login)->first();
if (!$user) {
$siswa = Siswa::where('nisn', $login)->first();
if ($siswa)
$user = $siswa->user;
if (!$user) {
$guru = Guru::where('nip', $login)->first();
if ($guru)
$user = $guru->user;
}
}
// $token = DB::table('personal_access_tokens')->where('tokenable_id', $user->id)->first();
// if (!is_null($token)) {
// return $this->errorApiResponse("User sudah login di device lain. Harap logout terlebih dahulu.");
// }
// Jika user ditemukan dan password cocok
if ($user && Hash::check($request->password, $user->password)) {
$token = $user->createToken('Mobile')->plainTextToken;
$user = $user->role == "siswa" ? $siswa : $guru;
return $this->okApiResponse([
'token' => $token,
'user' => $user,
]);
}
return $this->errorApiResponse('Login gagal. Cek kembali kredensial Anda.');
}
public function logout(Request $request)
{
$request->user()->currentAccessToken()->delete();
return $this->okApiResponse([], 'Berhsasil Logout.');
}
public function user(Request $request)
{
$data = [];
$user = $request->user();
if ($user->role == 'siswa') {
$data = Siswa::where('user_id', $user->id);
} else {
$data = Guru::where('user_id', $user->id);
}
$data = $data->with('user')->first();
return $this->okApiResponse([
'user' => $data,
]);
}
}

View File

@ -0,0 +1,18 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Http\Controllers\Traits\ApiResponse;
use App\Models\Kelas;
use Illuminate\Http\Request;
class KelasContoller extends Controller
{
use ApiResponse;
public function index(Request $request)
{
$kelas = Kelas::orderBy("nama", "ASC")->get();
return $this->okApiResponse($kelas);
}
}

View File

@ -0,0 +1,90 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Http\Controllers\Traits\ApiResponse;
use App\Models\Guru;
use App\Models\MataPelajaran;
use App\Models\Materi;
use App\Models\Siswa;
use Illuminate\Http\Request;
class MataPelajaranController extends Controller
{
use ApiResponse;
public function getMatpel(Request $request)
{
$data = [];
$query = [];
$user = $request->user();
if ($user->role == 'siswa') {
$data = Siswa::where('user_id', $user->id);
} else {
$data = Guru::where('user_id', $user->id);
}
$data = $data->with('user')->first();
if ($data->user->role == "siswa") {
$query = MataPelajaran::where(function ($query) use ($data) {
$query->where('kelas', $data->kelas)
->where('tahun_ajaran', $data->tahun_ajaran);
});
} else {
$query = MataPelajaran::where(function ($query) use ($data, $request) {
$query->where('guru_nip', $data->nip)
->where("kelas", $request->kelas)
->where("tahun_ajaran", $request->tahun_ajaran);
});
}
$query = $query->with(['guru', 'materi'])->get();
$result = $query->map(function ($item) {
$materi = $item->materi ?? collect();
$jumlahBuku = $materi->where('type', 'buku')->count();
$jumlahVideo = $materi->where('type', 'video')->count();
return array_merge(
$item->toArray(),
[
'jumlah_buku' => $jumlahBuku,
'jumlah_video' => $jumlahVideo,
]
);
});
return $this->okApiResponse($result);
}
public function getMatpelSimple(Request $request)
{
$data = [];
$query = [];
$user = $request->user();
if ($user->role == 'siswa') {
$data = Siswa::where('user_id', $user->id);
} else {
$data = Guru::where('user_id', $user->id);
}
$data = $data->with('user')->first();
if ($data->user->role == "siswa") {
$query = MataPelajaran::where(function ($query) use ($data) {
$query->where('kelas', $data->kelas)
->where('tahun_ajaran', $data->tahun_ajaran);
});
} else {
$query = MataPelajaran::where(function ($query) use ($data) {
$query->where('guru_nip', $data->nip);
});
}
$query = $query->with('guru')->get();
return $this->okApiResponse($query);
}
}

View File

@ -0,0 +1,42 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Http\Controllers\Traits\ApiResponse;
use App\Models\Guru;
use App\Models\Siswa;
use App\Repositories\MateriRepository;
use Exception;
use Illuminate\Http\Request;
class MateriController extends Controller
{
protected $param;
use ApiResponse;
public function __construct(MateriRepository $materi)
{
$this->param = $materi;
}
public function getMateri(Request $request)
{
try {
$data = [];
$user = $request->user();
if ($user->role == 'siswa') {
$data = Siswa::where('user_id', $user->id);
} else {
$data = Guru::where('user_id', $user->id);
}
$data = $data->with('user')->first();
$result = $this->param->getDataApi($request->id_matpel, $request->semester, $request->type, $data);
return $this->okApiResponse($result);
} catch (Exception $e) {
return $this->errorApiResponse("Terjadi Kesalahan " . $e->getMessage());
}
}
}

View File

@ -0,0 +1,67 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\QuizAttempts;
use App\Models\QuizLevelSetting;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class QuizAnalysisController extends Controller
{
static function getSkor($first)
{
$quizId = $first->quiz_id;
$quizLevelSettings = QuizLevelSetting::where('quiz_id', $quizId)->first();
$jumlahSoalPerLevel = json_decode($quizLevelSettings->jumlah_soal_per_level, true);
$totalSkorLevel = 0;
foreach (json_decode($quizLevelSettings->skor_level) as $key => $value) {
$totalSkorLevel += $value * (int) $jumlahSoalPerLevel[$key];
}
return $totalSkorLevel;
}
public function analisis(Request $request)
{
$user = Auth::user()->siswa;
// Ambil semua attempt dengan relasi quizzes dan mata pelajaran
$attempts = QuizAttempts::where('nisn', $user->nisn)
->with(['quizzes.mataPelajaran'])
->get();
// Kelompokkan berdasarkan mata_pelajaran_id dan hitung rata-rata skor
$grouped = $attempts->groupBy(function ($item) {
return $item->quizzes->mataPelajaran->id;
});
$avgScores = $grouped->map(function ($items) {
$first = $items->first(); // untuk ambil info nama mapel
$avg = $items->avg('skor');
return [
'mapel_id' => $first->quizzes->mataPelajaran->id,
'mapel' => $first->quizzes->mataPelajaran->nama,
'rata_rata_skor' => $avg,
'persentase' => round(($avg / $this->getSkor($first)) * 100),
];
});
// Urutkan berdasarkan skor rata-rata (desc dan asc)
$kelebihan = $avgScores->sortByDesc('rata_rata_skor')->take(2);
$kelebihanMapelIds = $kelebihan->pluck('mapel_id')->toArray();
$kekurangan = $avgScores->whereNotIn('mapel_id', $kelebihanMapelIds)
->sortBy('rata_rata_skor')
->take(2);
return response()->json([
'kelebihan' => array_values($kelebihan->toArray()),
'kekurangan' => array_values($kekurangan->toArray())
]);
}
}

View File

@ -0,0 +1,222 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Http\Controllers\Traits\ApiResponse;
use App\Models\QuizAttemptAnswers;
use App\Models\QuizAttempts;
use App\Models\QuizLevelSetting;
use App\Models\QuizQuestions;
use App\Repositories\QuizRepository;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class QuizController extends Controller
{
use ApiResponse;
protected $param;
public function __construct(QuizRepository $quizzes)
{
$this->param = $quizzes;
}
public function index(Request $request)
{
$data = $this->param->apiGetQuizzes($request->matapelajaran_id);
return $this->okApiResponse($data);
}
public function quizGuru(Request $request)
{
$data = $this->param->apiGetQuizzesGuru($request, $request->matapelajaran_id);
return $this->okApiResponse($data);
}
public function start(Request $request)
{
$request->validate([
'quiz_id' => 'required|exists:quizzes,id',
'nisn' => 'required|string',
]);
$quizLevelSettings = QuizLevelSetting::where('quiz_id', $request->quiz_id)->first();
$level = [];
foreach (json_decode($quizLevelSettings->jumlah_soal_per_level) as $key => $value) {
$lvlNumber = preg_replace('/[^0-9]/', '', $key);
$level['fase' . $lvlNumber] = 0;
}
$attempt = QuizAttempts::create([
'quiz_id' => $request->quiz_id,
'nisn' => $request->nisn,
'skor' => 0,
'level_akhir' => $quizLevelSettings->level_awal,
'fase' => $quizLevelSettings->level_awal,
'benar' => json_encode($level),
]);
return response()->json([
'attempt_id' => $attempt->id,
'message' => 'Quiz dimulai.',
]);
}
public function nextQuestion($attempt_id)
{
try {
$data = $this->param->nextQuestion($attempt_id);
return $this->okApiResponse($data);
} catch (\Exception $e) {
return $this->errorApiResponse($e->getMessage(), 500);
}
}
public function answer(Request $request, $attempt_id)
{
try {
$data = $this->param->answer($request, $attempt_id);
return $this->okApiResponse($data);
} catch (\Illuminate\Validation\ValidationException $e) {
return $this->errorApiResponse('Validation error: ' . $e->getMessage(), 422);
} catch (\Exception $e) {
return $this->errorApiResponse($e->getMessage(), 400);
}
}
public function getFinishQuiz(Request $request)
{
try {
return $this->param->getFinishQuiz($request->quiz_id);
} catch (\Exception $e) {
return $this->errorApiResponse($e->getMessage(), 500);
}
}
public function getTopFive(Request $request)
{
$query = QuizAttempts::with('siswa')
->where('quiz_id', $request->quiz_id)
->orderByDesc('skor')
->take(5)
->get();
$skorMe = QuizAttempts::select('skor')
->where('nisn', Auth::user()->siswa->nisn)
->where('quiz_id', $request->quiz_id)
->orderByDesc('skor')->first();
return response()->json([
'skor_me' => $skorMe,
'data' => $query,
]);
}
static function getNilai($first)
{
$quizId = $first->quiz_id;
$quizLevelSettings = QuizLevelSetting::where('quiz_id', $quizId)->first();
$jumlahSoalPerLevel = json_decode($quizLevelSettings->jumlah_soal_per_level, true);
$totalSkorLevel = 0;
foreach (json_decode($quizLevelSettings->skor_level) as $key => $value) {
$totalSkorLevel += $value * (int) $jumlahSoalPerLevel[$key];
}
return [
"total_skor_level" => $totalSkorLevel,
"kkm" => $quizLevelSettings->kkm,
];
}
public function getApiQuizGuru(Request $request)
{
$query = QuizAttempts::with(['quizzes', 'siswa'])
->where(function ($q) use ($request) {
$q->where('quiz_id', $request->quiz_id);
})->whereHas('siswa', function ($q) use ($request) {
$q->where('kelas', $request->kelas)
->where('tahun_ajaran', $request->tahun_ajaran);
})->get();
$avgScores = $query->map(function ($items) {
$first = $items->first(); // untuk ambil info nama mapel
$avg = $items->avg('skor');
return [
'nama' => $first->siswa->nama,
'mapel_id' => $first->quizzes->mataPelajaran->id,
'mapel' => $first->quizzes->mataPelajaran->nama,
'skor' => $first->skor,
'persentase' => round(($avg / $this->getNilai($first)['total_skor_level']) * 100),
'kkm' => $this->getNilai($first)['kkm'],
];
});
return $this->okApiResponse($avgScores);
}
public function debugQuiz($attempt_id)
{
try {
$attempt = QuizAttempts::findOrFail($attempt_id);
$questions = QuizQuestions::where('quiz_id', $attempt->quiz_id)->get();
$answeredQuestions = QuizAttemptAnswers::where('attempt_id', $attempt_id)->get();
return response()->json([
'attempt' => $attempt,
'total_questions' => $questions->count(),
'answered_questions' => $answeredQuestions->count(),
'questions_by_level' => $questions->groupBy('level'),
'answered_question_ids' => $answeredQuestions->pluck('question_id'),
'current_level' => $attempt->level_akhir,
'current_fase' => $attempt->fase,
]);
} catch (\Exception $e) {
return $this->errorApiResponse($e->getMessage(), 500);
}
}
public function autoFinish($attempt_id)
{
$attempt = \App\Models\QuizAttempts::findOrFail($attempt_id);
// Ambil semua soal quiz
$allQuestions = \App\Models\QuizQuestions::where('quiz_id', $attempt->quiz_id)->pluck('id')->toArray();
// Ambil semua question_id yang sudah dijawab
$answered = \App\Models\QuizAttemptAnswers::where('attempt_id', $attempt_id)->pluck('question_id')->toArray();
// Cari soal yang belum dijawab
$unanswered = array_diff($allQuestions, $answered);
foreach ($unanswered as $questionId) {
\App\Models\QuizAttemptAnswers::create([
'attempt_id' => $attempt->id,
'question_id' => $questionId,
'jawaban_siswa' => '',
'benar' => 0,
]);
}
// Hitung penilaian berdasarkan jawaban yang benar-benar dikerjakan
$jawaban_benar = \App\Models\QuizAttemptAnswers::where('attempt_id', $attempt->id)->where('benar', 1)->count();
$jawaban_salah = \App\Models\QuizAttemptAnswers::where('attempt_id', $attempt->id)->where('benar', 0)->count();
$total_dikerjakan = $jawaban_benar + $jawaban_salah;
$skor = $attempt->skor;
$persentase = $total_dikerjakan > 0 ? round(($jawaban_benar / $total_dikerjakan) * 100) : 0;
return response()->json([
'status' => true,
'message' => 'Quiz auto-finished successfully',
'data' => [
'jawaban_benar' => $jawaban_benar,
'jawaban_salah' => $jawaban_salah,
'total_dikerjakan' => $total_dikerjakan,
'skor' => $skor,
'persentase' => $persentase,
'attempt' => $attempt
]
]);
}
}

View File

@ -0,0 +1,49 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Http\Controllers\Traits\ApiResponse;
use App\Http\Resources\SubmitTugasResource;
use App\Repositories\ApiSubmitTugasRepository;
use Illuminate\Http\Request;
class SubmitTugasController extends Controller
{
protected $param;
use ApiResponse;
public function __construct(ApiSubmitTugasRepository $submitTugas)
{
$this->param = $submitTugas;
}
public function store(Request $request)
{
$data = $this->param->store($request);
return $this->okApiResponse(new SubmitTugasResource($data), "Submit Tugas Berhasil");
}
public function detail(Request $request)
{
$data = $this->param->detail($request);
return $this->okApiResponse(new SubmitTugasResource($data), "Berhasil get submit tugas");
}
public function update(Request $request)
{
$data = $this->param->update($request);
return $this->okApiResponse(new SubmitTugasResource($data), "Update Tugas Berhasil");
}
public function updateNilai(Request $request)
{
$request->validate([
'id' => 'required|exists:submit_tugas,id',
'nilai' => 'required|integer|min:0|max:100'
]);
$data = $this->param->update($request);
return $this->okApiResponse(new SubmitTugasResource($data), "Nilai berhasil diperbarui");
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Http\Controllers\Traits\ApiResponse;
use App\Models\TahunAjaran;
use Illuminate\Http\Request;
class TahunAjaranController extends Controller
{
use ApiResponse;
public function getTahunAjaran(Request $request)
{
$data = TahunAjaran::where("status", "aktif")->get();
return $this->okApiResponse($data);
}
}

View File

@ -0,0 +1,47 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Http\Controllers\Traits\ApiResponse;
use App\Models\Guru;
use App\Models\Siswa;
use App\Repositories\ApiTugasRepository;
use Illuminate\Http\Request;
class TugasController extends Controller
{
protected $param;
use ApiResponse;
public function __construct(ApiTugasRepository $tugas)
{
$this->param = $tugas;
}
public function getTugas(Request $request)
{
try {
$data = [];
$user = $request->user();
if ($user->role == 'siswa') {
$data = Siswa::where('user_id', $user->id);
} else {
$data = Guru::where('user_id', $user->id);
}
$data = $data->with('user')->first();
$result = $this->param->getDataApi($data, $request);
return $this->okApiResponse($result);
} catch (\Exception $e) {
return $this->errorApiResponse("Error : " . $e->getMessage());
}
}
public function getSubmitTugasSiswa(Request $request)
{
$result = $this->param->getSubmitTugasSiswa($request);
return $this->okApiResponse($result);
}
}

View File

@ -0,0 +1,79 @@
<?php
namespace App\Http\Controllers;
use App\Models\Admin;
use App\Models\Guru;
use App\Models\User;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Log;
class AuthController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index()
{
return view("pages.auth.index");
}
public function login(Request $request)
{
try {
$request->validate([
"login" => "required|string",
"password" => "required|string",
]);
$login = $request->login;
$user = User::where('email', $login)->first();
if (!$user) {
$admin = Admin::where('nip', $login)->first();
if ($admin) {
$user = $admin->user;
}
if (!$user) {
$guru = Guru::where('nip', $login)->first();
if ($guru) {
$user = $guru->user;
}
}
}
if ($user && Hash::check($request->password, $user->password)) {
Auth::login($user);
$request->session()->regenerate();
if ($user->role == 'admin') {
return redirect()->route('/');
} elseif ($user->role == 'guru') {
return redirect()->route('dashboard.guru');
}
}
return redirect()->route('login')->with('error', "Nip atau Password anda salah!");
} catch (Exception $e) {
Log::error("Error saat login: " . $e->getMessage());
return redirect()->route("login")->with("error", "Terjadi kesalahan sistem. Silahkan coba lagi. $e");
}
}
public function logout(Request $request)
{
try {
Auth::logout();
$request->session()->invalidate();
// DB::table('sessions')->where('user_id', Auth::user()->nip)->delete();
$request->session()->regenerateToken();
return redirect()->route('login');
} catch (Exception $e) {
Log::error("Error saat login: " . $e->getMessage());
}
}
}

View File

@ -0,0 +1,8 @@
<?php
namespace App\Http\Controllers;
abstract class Controller
{
//
}

View File

@ -0,0 +1,78 @@
<?php
namespace App\Http\Controllers;
use App\Repositories\DashboardRepository;
use Illuminate\Http\Request;
class DashboardController extends Controller
{
protected $param;
public function __construct(DashboardRepository $dashboard)
{
$this->param = $dashboard;
}
public function index(Request $request)
{
$tahunAjaran = $request->get('tahun_ajaran');
$kelas = $request->get('kelas');
$dashboard = $this->param->getData($tahunAjaran, $kelas);
return view('pages.role_admin.admin_dashboard.index', compact('dashboard'));
}
public function indexAdmin()
{
$dashboard = $this->param->getData();
return view("pages.role_guru.dashboard.index", compact("dashboard"));
}
/**
* 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,160 @@
<?php
namespace App\Http\Controllers;
use App\Models\Guru;
use App\Models\MataPelajaran;
use App\Models\Kelas;
use App\Models\AuditLog;
use App\Repositories\GuruRepository;
use App\Repositories\UserRepository;
use Illuminate\Database\QueryException;
use Illuminate\Http\Request;
use RealRashid\SweetAlert\Facades\Alert;
use Illuminate\Support\Facades\Auth;
class GuruController extends Controller
{
protected $param;
protected $param2;
public function __construct(GuruRepository $guru, UserRepository $userRepository)
{
$this->param = $guru;
$this->param2 = $userRepository;
}
public function index(Request $request)
{
$limit = $request->has('page_length') ? $request->get('page_length') : 10;
$search = $request->has('search') ? $request->get('search') : null;
$guru = $this->param->getData($search, $limit);
return view("pages.role_admin.guru.index", compact("guru"));
}
/**
* Show the form for creating a new resource.
*/
public function create()
{
return view("pages.role_admin.guru.create");
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
try {
$dataUser = $request->validate([
'email' => 'required',
'role' => 'required',
]);
$data = $request->validate([
'nip' => 'required|string|size:18|unique:guru,nip',
'nama' => 'required|string',
'jk' => 'required',
]);
if (Guru::where('nip', $data['nip'])->exists()) {
Alert::error("Terjadi Kesalahan", "NIP sudah terdaftar.");
return back()->withInput();
}
$dataUser['pass'] = $request->nip;
$user = $this->param2->store($dataUser);
$data["user_id"] = $user->id;
$this->param->store($data);
Alert::success("Berhasil", "Data Berhasil di simpan.");
return redirect()->route("guru");
} catch (\Exception $e) {
Alert::error("Terjadi Kesalahan", $e->getMessage());
return back()->withInput();
} catch (QueryException $e) {
Alert::error("Terjadi Kesalahan", $e->getMessage());
return back()->withInput();
}
}
/**
* Display the specified resource.
*/
public function show(string $id)
{
//
}
/**
* Show the form for editing the specified resource.
*/
public function edit(string $id)
{
$guru = $this->param->find($id);
return view("pages.role_admin.guru.edit", compact("guru"));
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, string $id)
{
try {
$dataUser = $request->validate([
'email' => 'required',
]);
$data = $request->validate([
'nip' => 'required|string|size:18',
'nama' => 'required|string',
'jk' => 'required',
]);
$this->param2->update($dataUser, $request->user_id);
$this->param->update($data, $id);
Alert::success("Berhasil", "Data Berhasil di ubah.");
return redirect()->route("guru");
} catch (\Exception $e) {
Alert::error("Terjadi Kesalahan", $e->getMessage());
return back()->withInput();
} catch (QueryException $e) {
Alert::error("Terjadi Kesalahan", $e->getMessage());
return back()->withInput();
}
}
/**
* Remove the specified resource from storage.
*/
public function destroy(Request $request)
{
try {
$guru = Guru::find($request->formid);
if (!$guru) {
Alert::error('Gagal', 'Guru tidak ditemukan.');
return back();
}
$nip = trim(strtoupper($guru->nip));
// Cek apakah guru masih menjadi wali kelas (robust, ignore case & space)
$isWali = Kelas::whereRaw('TRIM(UPPER(nip_wali)) = ?', [$nip])->exists();
if ($isWali) {
Alert::error('Gagal', 'Tidak dapat menghapus guru karena masih menjadi wali kelas. Silakan ganti wali kelas terlebih dahulu.');
return back();
}
$this->param->destroy($request->formid);
$this->param2->destroy($request->user_id);
// Audit log
AuditLog::create([
'user_id' => Auth::id(),
'action' => 'delete_guru',
'description' => 'Menghapus guru: ' . $guru->nama . ' (' . $guru->nip . ')',
]);
Alert::success("Berhasil", "Data Berhasil di hapus data.");
return redirect()->route("guru");
} catch (\Exception $e) {
Alert::error("Terjadi Kesalahan", $e->getMessage());
return back();
}
}
}

View File

@ -0,0 +1,27 @@
<?php
namespace App\Http\Controllers;
use App\Repositories\GuruDashboardRepository;
use Illuminate\Http\Request;
class GuruDashboardController extends Controller
{
protected $guruDashboardRepository;
public function __construct(GuruDashboardRepository $guruDashboardRepository)
{
$this->guruDashboardRepository = $guruDashboardRepository;
}
public function index(Request $request)
{
$tahunAjaran = $request->get('tahun_ajaran', 'all');
$kelas = $request->get('kelas', 'all');
$quizStatus = $request->get('quiz_status', 'all');
$dashboard = $this->guruDashboardRepository->getDashboardData($tahunAjaran, $kelas, $quizStatus);
return view('pages.role_admin.guru_dashboard.index', compact('dashboard'));
}
}

View File

@ -0,0 +1,113 @@
<?php
namespace App\Http\Controllers;
use App\Models\Kelas;
use App\Repositories\KelasRepository;
use Illuminate\Database\QueryException;
use Illuminate\Http\Request;
use RealRashid\SweetAlert\Facades\Alert;
class KelasContoller extends Controller
{
protected $param;
protected $paramUser;
public function __construct(KelasRepository $kelas)
{
$this->param = $kelas;
}
public function index(Request $request)
{
$limit = $request->has('page_length') ? $request->get('page_length') : 10;
$search = $request->has('search') ? $request->get('search') : null;
$kelas = $this->param->getData($search, $limit);
return view("pages.role_admin.kelas.index", compact("kelas"));
}
/**
* Show the form for creating a new resource.
*/
public function create()
{
return view("pages.role_admin.kelas.create");
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
try {
$data = $request->validate([
'nama' => 'required|string',
]);
$this->param->store($data);
Alert::success("Berhasil", "Data Berhasil di simpan.");
return redirect()->route("kelas");
} catch (\Exception $e) {
Alert::error("Terjadi Kesalahan", $e->getMessage());
return back()->withInput();
} catch (QueryException $e) {
Alert::error("Terjadi Kesalahan", $e->getMessage());
return back()->withInput();
}
}
/**
* Display the specified resource.
*/
public function show(string $id)
{
//
}
/**
* Show the form for editing the specified resource.
*/
public function edit(string $id)
{
// $kelas = Kelas::find($id);
// return view("pages.role_admin.kelas.edit", compact("kelas"));
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, string $id)
{
// try {
// $data = $request->validate([
// 'nama' => 'required|string',
// ]);
// $this->param->update($data, $id);
// Alert::success("Berhasil", "Data Berhasil di ubah.");
// return redirect()->route("kelas");
// } catch (\Exception $e) {
// Alert::error("Terjadi Kesalahan", $e->getMessage());
// return back()->withInput();
// } catch (QueryException $e) {
// Alert::error("Terjadi Kesalahan", $e->getMessage());
// return back()->withInput();
// }
}
/**
* Remove the specified resource from storage.
*/
public function destroy(Request $request)
{
try {
$this->param->destroy($request->formkelas);
Alert::success("Berhasil", "Data Berhasil di Hapus.");
return redirect()->route("kelas");
} catch (\Exception $e) {
Alert::error("Terjadi Kesalahan", $e->getMessage());
return back();
}
}
}

View File

@ -0,0 +1,131 @@
<?php
namespace App\Http\Controllers;
use App\Models\Guru;
use App\Models\Kelas;
use App\Models\TahunAjaran;
use App\Models\AuditLog;
use Illuminate\Support\Facades\Auth;
use App\Repositories\MataPelajaranRepository;
use Illuminate\Database\QueryException;
use Illuminate\Http\Request;
use RealRashid\SweetAlert\Facades\Alert;
class MataPelajaranController extends Controller
{
protected $param;
public function __construct(MataPelajaranRepository $mataPelajaran)
{
$this->param = $mataPelajaran;
}
public function index(Request $request)
{
$limit = $request->has('page_length') ? $request->get('page_length') : 10;
$search = $request->has('search') ? $request->get('search') : null;
$mataPelajaran = $this->param->getData($search, $limit);
return view("pages.role_admin.mata_pelajaran.index", compact("mataPelajaran"));
}
/**
* Show the form for creating a new resource.
*/
public function create()
{
$guru = Guru::all();
$kelas = Kelas::all();
$tahunAjaran = TahunAjaran::where('status', 'aktif')->get();
return view("pages.role_admin.mata_pelajaran.create", compact("guru", "kelas", "tahunAjaran"));
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
try {
$data = $request->validate([
'nama' => 'required',
'guru_nip' => 'required',
'kelas' => 'required',
'tahun_ajaran' => 'required',
]);
$this->param->store($data);
Alert::success("Berhasil", "Data Berhasil di Tambahkan.");
return redirect()->route("mata-pelajaran");
} catch (\Exception $e) {
Alert::error("Terjadi Kesalahan", $e->getMessage());
return back()->withInput();
} catch (QueryException $e) {
Alert::error("Terjadi Kesalahan", $e->getMessage());
return back()->withInput();
}
}
/**
* Display the specified resource.
*/
public function show(string $id)
{
//
}
/**
* Show the form for editing the specified resource.
*/
public function edit(string $id)
{
$mataPelajaran = $this->param->find($id);
$guru = Guru::all();
$kelas = Kelas::all();
$tahunAjaran = TahunAjaran::where('status', 'aktif')->get();
return view("pages.role_admin.mata_pelajaran.edit", compact(["mataPelajaran", "guru", "kelas", "tahunAjaran"]));
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, string $id)
{
try {
$data = $request->validate([
'nama' => 'required',
'guru_nip' => 'required',
'kelas' => 'required',
'tahun_ajaran' => 'required',
]);
$old = $this->param->find($id);
$isManual = $old->guru_nip !== $data['guru_nip'];
$this->param->update($data, $id);
// Audit log
AuditLog::create([
'user_id' => Auth::id(),
'action' => 'update_pengampu_mapel',
'description' => 'Update pengampu mapel: ' . $old->nama . ' (' . $old->id . ') dari ' . $old->guru_nip . ' ke ' . $data['guru_nip'],
]);
if ($isManual) {
Alert::warning('Perhatian', 'Anda mengganti pengampu mata pelajaran tanpa fitur pemindahan data. Data tugas dan submit tugas lama tetap milik guru sebelumnya.');
} else {
Alert::success("Berhasil", "Data Berhasil di Ubah.");
}
return redirect()->route("mata-pelajaran");
} catch (\Exception $e) {
Alert::error("Terjadi Kesalahan", $e->getMessage());
return back()->withInput();
} catch (QueryException $e) {
Alert::error("Terjadi Kesalahan", $e->getMessage());
return back()->withInput();
}
}
/**
* Remove the specified resource from storage.
*/
public function destroy(string $id)
{
//
}
}

View File

@ -0,0 +1,232 @@
<?php
namespace App\Http\Controllers\RoleGuru;
use App\Http\Controllers\Controller;
use App\Models\MataPelajaran;
use App\Models\Materi;
use App\Models\TahunAjaran;
use App\Repositories\MateriRepository;
use Illuminate\Database\QueryException;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Storage;
use RealRashid\SweetAlert\Facades\Alert;
use Illuminate\Support\Str;
class MateriController extends Controller
{
protected $param;
public function __construct(MateriRepository $materi)
{
$this->param = $materi;
}
public function index(Request $request)
{
$limit = $request->has('page_length') ? $request->get('page_length') : 10;
$search = $request->has('search') ? $request->get('search') : null;
$materi = $this->param->getData($search, $limit);
return view("pages.role_guru.materi.index", compact("materi"));
}
public function detail($id)
{
$materiDetail = Materi::find($id);
return view("pages.role_guru.materi.detail", compact("materiDetail"));
}
/**
* Show the form for creating a new resource.
*/
public function create()
{
$tahunAjaran = TahunAjaran::where('status', 'aktif')->get();
$matpel = MataPelajaran::where('guru_nip', Auth::user()->guru->nip)->get();
return view("pages.role_guru.materi.create", compact('matpel', 'tahunAjaran'));
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
try {
$tanggal = $request->input('tanggal');
$guruNip = $request->user()->guru->nip;
// dd($request->file('path'));
if ($request->type == "buku") {
// Validasi untuk buku (file PDF)
$validated = $request->validate([
'matapelajaran_id' => 'required',
'semester' => 'required',
'type' => 'required',
'judul_materi' => 'required',
'deskripsi' => 'required',
'path' => 'required|file|mimes:pdf|max:5120',
'tahun_ajaran' => 'required',
]);
$judul = Str::slug(Str::limit($request->judul_materi, 50));
$ext = $request->file('path')->getClientOriginalExtension();
$namaFile = "{$guruNip}_{$tanggal}_{$judul}.{$ext}";
$path = $request->file('path')->storeAs('materi', $namaFile, 'public');
$validated['path'] = $path;
} else {
// Validasi untuk video (link video)
$validated = $request->validate([
'matapelajaran_id' => 'required',
'semester' => 'required',
'type' => 'required',
'judul_materi' => 'required',
'deskripsi' => 'required',
'path' => 'required|string', // path link video
'tahun_ajaran' => 'required',
]);
}
$validated['tanggal'] = $tanggal;
$this->param->store($validated);
Alert::success("Berhasil", "Data Berhasil di simpan.");
return redirect()->route("materi");
} catch (\Exception $e) {
if ($e instanceof \Illuminate\Validation\ValidationException) {
$errors = $e->validator->errors()->all();
Alert::error("Validasi Gagal", implode("\n", $errors));
} else {
Alert::error("Terjadi Kesalahan", $e->getMessage());
}
return back()->withInput();
} catch (QueryException $e) {
Alert::error("Terjadi Kesalahan", $e->getMessage());
return back()->withInput();
}
}
/**
* Display the specified resource.
*/
public function show(string $id)
{
//
}
/**
* Show the form for editing the specified resource.
*/
public function edit(string $id)
{
$tahunAjaran = TahunAjaran::where('status', 'aktif')->get();
$matpel = MataPelajaran::where('guru_nip', Auth::user()->guru->nip)->get();
$materi = $this->param->find($id);
return view("pages.role_guru.materi.edit", compact(["tahunAjaran", "matpel", "materi"]));
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, $id)
{
try {
$tanggal = $request->input('tanggal');
$guruNip = $request->user()->guru->nip;
$materi = $this->param->find($id);
if (!$materi) {
Alert::error("Tidak ditemukan", "Data materi tidak ditemukan.");
return redirect()->route("materi");
}
if ($request->type == "buku") {
$validated = $request->validate([
'matapelajaran_id' => 'required',
'semester' => 'required',
'type' => 'required',
'judul_materi' => 'required',
'deskripsi' => 'required',
'path' => 'nullable|file|mimes:pdf|max:5120', // Boleh kosong saat update
'tahun_ajaran' => 'required',
]);
// Jika ada file baru diunggah
if ($request->hasFile('path')) {
// Hapus file lama jika ada
if ($materi->path && Storage::disk('public')->exists($materi->path)) {
Storage::disk('public')->delete($materi->path);
}
$judul = Str::slug(Str::limit($request->judul_materi, 50));
$ext = $request->file('path')->getClientOriginalExtension();
$namaFile = "{$guruNip}_{$tanggal}_{$judul}.{$ext}";
$path = $request->file('path')->storeAs('materi', $namaFile, 'public');
$validated['path'] = $path;
} else {
$validated['path'] = $materi->path;
}
} else {
$validated = $request->validate([
'matapelajaran_id' => 'required',
'semester' => 'required',
'type' => 'required',
'judul_materi' => 'required',
'deskripsi' => 'required',
'path' => 'required|string', // Link video
'tahun_ajaran' => 'required',
]);
}
$validated['tanggal'] = $tanggal;
$this->param->update($validated, $id);
Alert::success("Berhasil", "Data berhasil diperbarui.");
return redirect()->route("materi");
} catch (\Exception $e) {
if ($e instanceof \Illuminate\Validation\ValidationException) {
$errors = $e->validator->errors()->all();
Alert::error("Validasi Gagal", implode("\n", $errors));
} else {
Alert::error("Terjadi Kesalahan", $e->getMessage());
}
return back()->withInput();
} catch (QueryException $e) {
Alert::error("Terjadi Kesalahan", $e->getMessage());
return back()->withInput();
}
}
/**
* Remove the specified resource from storage.
*/
public function destroy(Request $request)
{
try {
$materi = $this->param->find($request->formid);
if ($materi->type === 'buku' && $materi->path) {
// Hapus file PDF dari storage/public/materi
Storage::disk('public')->delete($materi->path);
}
$this->param->destroy($materi->id);
Alert::success("Berhasil", "Data berhasil dihapus.");
return redirect()->route("materi"); // sesuaikan dengan route kamu
} catch (\Exception $e) {
Alert::error("Terjadi Kesalahan", $e->getMessage());
return back();
}
}
}

View File

@ -0,0 +1,503 @@
<?php
namespace App\Http\Controllers\RoleGuru;
use App\Exports\QuizExport;
use App\Http\Controllers\Controller;
use App\Models\Kelas;
use App\Models\MataPelajaran;
use App\Models\QuizLevelSetting;
use App\Models\QuizQuestions;
use App\Models\Quizzes;
use App\Models\Siswa;
use App\Models\TahunAjaran;
use App\Notifications\QuizBaruNotification;
use App\Repositories\QuizRepository;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Session;
use Maatwebsite\Excel\Concerns\FromView;
use Maatwebsite\Excel\Facades\Excel;
use RealRashid\SweetAlert\Facades\Alert;
class QuizController extends Controller
{
protected $param;
public function __construct(QuizRepository $quiz)
{
$this->param = $quiz;
}
public function index(Request $request)
{
$nip = Auth::user()->guru->nip;
$matpel = MataPelajaran::where('guru_nip', $nip)->get();
$kelas = Kelas::all();
$tahunAjaran = TahunAjaran::where('status', 'aktif')->get();
// Get quizzes created by this teacher
$quiz = Quizzes::with(['mataPelajaran', 'quizLevelSetting'])
->whereHas('mataPelajaran', function ($q) use ($nip) {
$q->where('guru_nip', $nip);
})
->orderBy('created_at', 'desc')
->get();
return view("pages.role_guru.quiz.index", compact(['matpel', 'kelas', 'tahunAjaran', 'quiz']));
}
public function getQuizByMatpel($id)
{
$nip = Auth::user()->guru->nip;
$quizzes = Quizzes::where('matapelajaran_id', $id)
->whereHas('mataPelajaran', function ($q) use ($nip) {
$q->where('guru_nip', $nip);
})
->get();
return response()->json($quizzes);
}
public function excelDownload()
{
return Excel::download(new QuizExport, "format_excel_untuk_quiz.xlsx");
}
public function preview(Request $request)
{
$request->validate([
'file' => 'required|mimes:xlsx,xls'
]);
$data = Excel::toArray([], $request->file('file'));
// Ambil sheet pertama
$rows = $data[0];
$filteredRows = [];
$jumlahSoalPerLevel = [];
$totalSoalPeLevel = [];
$batasNaikLevel = [];
$skorLevel = [];
foreach ($rows as $index => $row) {
if ($index == 0 || !empty($row[1])) {
$filteredRows[] = $row;
$level = $row[3];
$skor = $row[8];
if (!empty($level) && $index != 0) {
$key = 'level' . $level;
if (!isset($jumlahSoalPerLevel[$key])) {
$jumlahSoalPerLevel[$key] = 0;
$totalSoalPeLevel[$key] = 0;
}
$jumlahSoalPerLevel[$key]++;
$totalSoalPeLevel[$key]++;
$skorLevel[$key] = $skor;
}
}
}
// Hitung batas naik level = 50% dari jumlah soal di level tersebut
foreach ($jumlahSoalPerLevel as $key => $jumlah) {
// Ambil level dari key, contoh: 'level2' → 2
$level = str_replace('level', '', $key);
$keyFase = 'fase' . $level;
// Hitung 50% lalu dibulatkan ke atas (ceil)
$batasNaikLevel[$keyFase] = (int) ceil($jumlah * 0.5);
$jumlahSoalPerLevel[$key] = (int) ceil($jumlah * 0.5);
}
$soalCount = count($filteredRows) - 1;
// Simpan preview dan jumlah soal ke session
Session::put('judul', $request->judul);
Session::put('deskripsi', $request->deskripsi);
Session::put('matapelajaran_id', $request->matapelajaran_id);
Session::put('waktu', $request->waktu);
Session::put('preview_soal', $filteredRows);
Session::put('total_soal', $soalCount);
Session::put('total_soal_tampil', $request->total_soal_tampil ?? 20);
Session::put('uploaded_filename', $request->file('file')->getClientOriginalName());
// quiz level settings
Session::put('total_soal_per_level', $totalSoalPeLevel);
Session::put('jumlah_soal_per_level', $jumlahSoalPerLevel);
Session::put('level_awal', $request->level_awal);
Session::put('batas_naik_level', $batasNaikLevel);
Session::put('skor_level', $skorLevel);
Session::put('kkm', $request->kkm);
return redirect()->back();
}
protected function removeSession()
{
session()->forget('judul');
session()->forget('deskripsi');
session()->forget('matapelajaran_id');
session()->forget('waktu');
session()->forget('preview_soal');
session()->forget('total_soal');
session()->forget('total_soal_tampil');
session()->forget('uploaded_filename');
// quiz level settings
session()->forget('total_soal_per_level');
session()->forget('jumlah_soal_per_level');
session()->forget('level_awal');
session()->forget('batas_naik_level');
session()->forget('skor_level');
session()->forget('kkm');
}
public function resetPreview()
{
$this->removeSession();
return redirect()->back()->with('success', 'Data preview berhasil direset.');
}
/**
* Show the form for creating a new resource.
*/
public function create()
{
$nip = Auth::user()->guru->nip;
$matpel = MataPelajaran::where("guru_nip", $nip)->get();
$naik_level = json_encode(session('batas_naik_level') ?? []);
return view("pages.role_guru.quiz.create", compact(['matpel', 'naik_level']));
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
$preview = session('preview_soal');
if (!is_array($preview)) {
return redirect()->back()->with('validation_error', [
'title' => 'Error',
'message' => 'Data soal tidak ditemukan atau session sudah habis. Silakan ulangi proses import/preview quiz.'
]);
}
array_shift($preview);
if (!$preview || count($preview) <= 1) {
\Log::error('Quiz Import Error: Tidak ada data untuk disimpan');
Alert::error("Terjadi Kesalahan", "Tidak ada data untuk disimpan.");
return redirect()->back();
}
if ($request->total_soal_tampil < 10) {
\Log::error('Quiz Import Error: Jumlah soal minimal 10');
Alert::error("Terjadi Kesalahan", "Jumlah soal minimal 10.");
return redirect()->back();
}
// ========================================
// VALIDASI KETAT UNTUK MENCEGAH BUG QUIZ
// ========================================
// 1. VALIDASI: Total soal per level harus sama dengan total soal tampil
$totalSoalPerLevel = 0;
foreach ($request->jumlah_soal_per_level as $key => $value) {
$totalSoalPerLevel += (int) $value;
}
if ($totalSoalPerLevel != $request->total_soal_tampil) {
\Log::error('Quiz Import Error: Total soal per level tidak sama dengan total soal tampil');
\Log::error('Detail: Total soal per level = ' . $totalSoalPerLevel . ', Total soal tampil = ' . $request->total_soal_tampil);
\Log::error('Detail per level: ' . json_encode($request->jumlah_soal_per_level));
return redirect()->back()->with('validation_error', [
'title' => 'Error Validasi',
'message' => "Total soal per level ($totalSoalPerLevel) harus sama dengan total soal tampil ({$request->total_soal_tampil}).<br><br><strong>POTENSI BUG:</strong> Jika tidak sama, siswa bisa stuck di level tertentu karena soal tidak cukup.<br><strong>CATATAN:</strong> Pastikan jumlah soal per level dijumlahkan sama dengan total soal tampil."
]);
}
// 2. VALIDASI: Hitung jumlah soal yang tersedia per level dari data import
$levelCounts = [];
foreach ($preview as $row) {
if (!empty($row[3])) { // level
$level = $row[3];
$levelCounts[$level] = ($levelCounts[$level] ?? 0) + 1;
}
}
// 3. VALIDASI: Jumlah soal per level tidak boleh melebihi soal yang tersedia
foreach ($request->jumlah_soal_per_level as $key => $value) {
$level = str_replace('level', '', $key);
$availableInLevel = $levelCounts[$level] ?? 0;
if ($value > $availableInLevel) {
\Log::error('Quiz Import Error: Jumlah soal setting melebihi soal yang tersedia');
\Log::error('Detail: Level ' . $level . ' - Setting: ' . $value . ', Tersedia: ' . $availableInLevel);
return redirect()->back()->with('validation_error', [
'title' => 'Error Validasi',
'message' => "Level $level: Setting $value soal, tapi hanya ada $availableInLevel soal tersedia.<br><br><strong>POTENSI BUG:</strong> Sistem akan stuck karena tidak ada cukup soal di level tersebut.<br><strong>CATATAN:</strong> Kurangi jumlah soal setting atau tambah soal di level tersebut."
]);
}
}
// 4. VALIDASI: Batas naik level tidak boleh melebihi jumlah soal di level tersebut
foreach ($request->batas_naik_level as $key => $value) {
$level = str_replace('fase', '', $key);
$soalInLevel = $request->jumlah_soal_per_level["level$level"] ?? 0;
// VALIDASI BARU: Batas naik level tidak boleh sama atau lebih besar dari jumlah soal di level
if ($value >= $soalInLevel) {
return redirect()->back()->with('validation_error', [
'title' => 'Error Validasi',
'message' => "Level $level: Syarat naik level ($value) tidak boleh sama atau lebih besar dari jumlah soal ($soalInLevel).<br><br><strong>POTENSI BUG:</strong> Jika siswa salah satu saja, quiz akan stuck di level ini.<br><strong>CATATAN:</strong> Kurangi syarat naik level atau tambah jumlah soal di level ini."
]);
}
if ($value > $soalInLevel) {
\Log::error('Quiz Import Error: Batas naik level melebihi jumlah soal di level');
\Log::error('Detail: Level ' . $level . ' - Batas naik: ' . $value . ', Soal di level: ' . $soalInLevel);
return redirect()->back()->with('validation_error', [
'title' => 'Error Validasi',
'message' => "Level $level: Batas naik level ($value) tidak boleh melebihi jumlah soal ($soalInLevel).<br><br><strong>POTENSI BUG:</strong> Siswa tidak akan pernah naik level karena batas terlalu tinggi.<br><strong>CATATAN:</strong> Batas naik level harus ≤ jumlah soal di level tersebut."
]);
}
}
// 5. VALIDASI: Pastikan ada soal di setiap level yang di-setting
foreach ($request->jumlah_soal_per_level as $key => $value) {
$level = str_replace('level', '', $key);
$availableInLevel = $levelCounts[$level] ?? 0;
if ($availableInLevel == 0) {
\Log::error('Quiz Import Error: Tidak ada soal di level yang di-setting');
\Log::error('Detail: Level ' . $level . ' tidak memiliki soal di data import');
return redirect()->back()->with('validation_error', [
'title' => 'Error Validasi',
'message' => "Level $level: Tidak ada soal tersedia di level ini.<br><br><strong>POTENSI BUG:</strong> Sistem akan stuck karena tidak ada soal di level tersebut.<br><strong>CATATAN:</strong> Pastikan data import memiliki soal dengan level $level atau hapus setting level ini."
]);
}
}
// 6. VALIDASI: Level harus berurutan (1, 2, 3, dst)
$levels = array_keys($request->jumlah_soal_per_level);
sort($levels);
$expectedLevels = [];
for ($i = 1; $i <= count($levels); $i++) {
$expectedLevels[] = "level$i";
}
if ($levels !== $expectedLevels) {
\Log::error('Quiz Import Error: Level tidak berurutan');
\Log::error('Detail: Level yang ada = ' . json_encode($levels) . ', Level yang diharapkan = ' . json_encode($expectedLevels));
return redirect()->back()->with('validation_error', [
'title' => 'Error Validasi',
'message' => "Level harus berurutan (Level 1, Level 2, Level 3, dst).<br><br><strong>POTENSI BUG:</strong> Sistem adaptive learning akan bingung dengan level yang tidak berurutan.<br><strong>CATATAN:</strong> Pastikan level di data import berurutan dari 1, 2, 3, dst."
]);
}
// Update session dengan nilai total_soal_tampil yang baru
Session::put('total_soal_tampil', $request->total_soal_tampil);
// VALIDASI: Jumlah soal yang harus dikerjakan per level tidak boleh melebihi jumlah soal di bank soal
$totalSoalPerLevel = 0;
foreach ($request->jumlah_soal_per_level as $key => $value) {
$level = str_replace('level', '', $key);
$soalTersedia = $request->total_soal_per_level["level$level"] ?? 0;
if ($value > $soalTersedia) {
return redirect()->back()->with('validation_error', [
'title' => 'Error Validasi',
'message' => "Level $level: Jumlah soal yang dikerjakan ($value) tidak boleh lebih besar dari jumlah soal di bank soal ($soalTersedia)."
]);
}
$totalSoalPerLevel += (int) $value;
}
// VALIDASI: Total soal yang harus dikerjakan (semua level) harus sama dengan total soal tampil
if ($totalSoalPerLevel != $request->total_soal_tampil) {
return redirect()->back()->with('validation_error', [
'title' => 'Error Validasi',
'message' => "Total soal yang harus dikerjakan ($totalSoalPerLevel) tidak sama dengan total soal tampil ({$request->total_soal_tampil}).<br><br><strong>POTENSI BUG:</strong> Quiz bisa stuck atau soal tidak cukup.<br><strong>CATATAN:</strong> Sesuaikan jumlah soal per level agar totalnya sama dengan total soal tampil."
]);
}
// VALIDASI: Cegah quiz stuck jika syarat naik level tidak tercapai dan soal habis
foreach ($request->batas_naik_level as $key => $value) {
$level = str_replace('fase', '', $key);
$jumlah_soal = $request->jumlah_soal_per_level["level$level"] ?? 0;
$batas_naik = $value;
// Jika syarat naik lebih besar dari jumlah soal, mustahil
if ($batas_naik > $jumlah_soal) {
return redirect()->back()->with('validation_error', [
'title' => 'Error Validasi',
'message' => "Level $level: Syarat naik level ($batas_naik) tidak boleh lebih besar dari jumlah soal yang dikerjakan ($jumlah_soal)."
]);
}
// Jika syarat naik level terlalu rendah, tetap valid, tapi cek kemungkinan stuck
// Simulasi: Jika siswa menjawab semua soal tapi benar kurang dari syarat naik, quiz stuck
// Contoh: syarat naik 3 dari 10, jika siswa hanya benar 2, quiz stuck
// Validasi: syarat naik level harus bisa dicapai dengan minimal 1 benar di setiap soal
// (tidak perlu, karena sudah dicegah oleh logika di atas)
// Namun, jika syarat naik terlalu rendah, warning saja (tidak error)
// Jika syarat naik terlalu tinggi, error
// Jika syarat naik level tidak tercapai dan soal habis, quiz stuck
// (Sudah dicegah oleh validasi di atas)
}
// VALIDASI: Cegah quiz stuck jika siswa gagal naik level dan soal habis
/*
$levelKeys = array_keys($request->jumlah_soal_per_level);
$levelCount = count($levelKeys);
for ($i = 0; $i < $levelCount - 1; $i++) { // Kecuali level terakhir
$currentLevelKey = $levelKeys[$i];
$nextLevelKeys = array_slice($levelKeys, $i + 1);
$soalDiLevelIni = (int) $request->jumlah_soal_per_level[$currentLevelKey];
$soalDiLevelBerikutnya = 0;
foreach ($nextLevelKeys as $k) {
$soalDiLevelBerikutnya += (int) $request->jumlah_soal_per_level[$k];
}
$totalSoalTampil = (int) $request->total_soal_tampil;
$batasNaik = (int) ($request->batas_naik_level['fase' . ($i + 1)] ?? 0);
// Jika semua soal di level ini habis, dan siswa gagal naik (benar < batas naik), quiz stuck
// Kondisi: soal di level ini = total soal tampil - soal di level berikutnya
if ($soalDiLevelIni === $totalSoalTampil - $soalDiLevelBerikutnya && $soalDiLevelIni > 0 && $batasNaik > 0) {
return redirect()->back()->with('validation_error', [
'title' => 'Konfigurasi Quiz Tidak Valid',
'message' => "Quiz tidak bisa disimpan karena pada fase " . ($i + 1) . ", siswa yang gagal naik ke level berikutnya akan kehabisan soal. Mohon ulangi konfigurasi quiz agar lebih seimbang dan baik."
]);
}
}
*/
try {
// Simpan quiz dan soalnya ke DB
$quiz = Quizzes::create([
'judul' => $request->judul,
'deskripsi' => $request->deskripsi,
'matapelajaran_id' => $request->matapelajaran_id,
'total_soal' => $request->total_soal,
'total_soal_tampil' => $request->total_soal_tampil,
'waktu' => $request->waktu,
]);
QuizLevelSetting::create([
'quiz_id' => $quiz->id,
'jumlah_soal_per_level' => json_encode($request->jumlah_soal_per_level),
'level_awal' => session('level_awal') ?? 1,
'batas_naik_level' => json_encode($request->batas_naik_level),
'skor_level' => json_encode(session('skor_level')),
'kkm' => session('kkm') ?? 75,
]);
// Menampung data dalam array sebelum disimpan
$quizQuestionsData = [];
foreach ($preview as $row) {
$jawabanBenar = strtolower(trim($row[2]));
// Menyiapkan data untuk disimpan
$quizQuestionsData[] = [
'quiz_id' => $quiz->id,
'pertanyaan' => $row[1],
'opsi_a' => $row[4],
'opsi_b' => $row[5],
'opsi_c' => $row[6],
'opsi_d' => $row[7],
'jawaban_benar' => $jawabanBenar,
'level' => $row[3],
'skor' => $row[8],
'created_at' => now(),
'updated_at' => now(),
];
}
// Simpan semua data sekali gus menggunakan insert
if (!empty($quizQuestionsData)) {
QuizQuestions::insert($quizQuestionsData);
}
// Hapus session
$this->removeSession();
$matpel = MataPelajaran::findOrFail($request->matapelajaran_id);
// Cari siswa berdasarkan kelas dan tahun ajaran
$siswas = Siswa::where('kelas', $matpel['kelas'])
->where('tahun_ajaran', $matpel['tahun_ajaran'])
->get();
// Kirim notifikasi ke setiap siswa
foreach ($siswas as $siswa) {
$siswa->notify(new QuizBaruNotification($quiz));
}
\Log::info('Quiz berhasil diimport: ' . $request->judul);
return redirect()->route('quiz')->with('success_message', [
'title' => 'Berhasil',
'message' => 'Data quiz berhasil disimpan dan notifikasi telah dikirim ke siswa.'
]);
} catch (\Exception $e) {
\Log::error('Quiz Import Error: ' . $e->getMessage());
return redirect()->back()->with('validation_error', [
'title' => 'Terjadi Kesalahan',
'message' => 'Gagal menyimpan data quiz: ' . $e->getMessage()
]);
} catch (\Illuminate\Database\QueryException $e) {
\Log::error('Quiz Import Error: Database error - ' . $e->getMessage());
return redirect()->back()->with('validation_error', [
'title' => 'Terjadi Kesalahan',
'message' => 'Gagal menyimpan data quiz. Silakan coba lagi.'
]);
}
}
/**
* 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(Request $request)
{
try {
$quiz = Quizzes::findOrFail($request->formid);
$quiz->delete();
return redirect()->back()->with('success_message', [
'title' => 'Berhasil',
'message' => 'Data quiz berhasil dihapus.'
]);
} catch (\Throwable $th) {
return redirect()->back()->with('validation_error', [
'title' => 'Terjadi Kesalahan',
'message' => 'Gagal menghapus data quiz: ' . $th->getMessage()
]);
}
}
}

View File

@ -0,0 +1,62 @@
<?php
namespace App\Http\Controllers\RoleGuru;
use App\Http\Controllers\Controller;
use App\Models\QuizAttempts;
use App\Models\Siswa;
use Illuminate\Http\Request;
class QuizRankingController extends Controller
{
// GET /quiz/{quizId}/ranking
public function ranking($quizId)
{
// Ambil attempt terbaru per siswa untuk quiz ini
$latestAttempts = QuizAttempts::where('quiz_id', $quizId)
->selectRaw('MAX(id) as id')
->groupBy('nisn')
->pluck('id');
$ranking = QuizAttempts::with('siswa')
->whereIn('id', $latestAttempts)
->get()
->map(function($attempt) {
return [
'nama' => $attempt->siswa->nama ?? '-',
'nisn' => $attempt->nisn,
'skor' => (int) $attempt->skor,
'waktu_selesai' => $attempt->updated_at,
];
})
->sortByDesc('skor')
->values()
->take(5);
return response()->json([
'success' => true,
'ranking' => $ranking
]);
}
// GET /quiz/{quizId}/skor-saya
public function skorSaya($quizId)
{
$nisn = auth()->user()->siswa->nisn ?? null;
if (!$nisn) {
return response()->json(['success' => false, 'message' => 'User bukan siswa.']);
}
$attempt = QuizAttempts::where('quiz_id', $quizId)
->where('nisn', $nisn)
->orderByDesc('id')
->first();
if (!$attempt) {
return response()->json(['success' => false, 'message' => 'Belum ada attempt.']);
}
return response()->json([
'success' => true,
'skor' => $attempt->skor,
'waktu_selesai' => $attempt->updated_at,
]);
}
}

View File

@ -0,0 +1,76 @@
<?php
namespace App\Http\Controllers\RoleGuru;
use App\Exports\RekapExport;
use App\Http\Controllers\Controller;
use App\Models\Kelas;
use App\Models\MataPelajaran;
use App\Models\QuizAttempts;
use App\Models\QuizLevelSetting;
use App\Models\Quizzes;
use App\Models\TahunAjaran;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Maatwebsite\Excel\Facades\Excel;
class RekapQuizController extends Controller
{
static function getNilai($first)
{
$quizId = $first->quiz_id;
$quizLevelSettings = QuizLevelSetting::where('quiz_id', $quizId)->first();
$jumlahSoalPerLevel = json_decode($quizLevelSettings->jumlah_soal_per_level, true);
$totalSkorLevel = 0;
foreach (json_decode($quizLevelSettings->skor_level) as $key => $value) {
$totalSkorLevel += $value * (int) $jumlahSoalPerLevel[$key];
}
return [
"total_skor_level" => $totalSkorLevel,
"kkm" => $quizLevelSettings->kkm,
];
}
public function index(Request $request)
{
$nip = Auth::user()->guru->nip;
$matpel = MataPelajaran::where('guru_nip', $nip)->get();
$judulQuiz = Quizzes::where('judul', $request->judul)->first();
$kelas = Kelas::all();
$tahunAjaran = TahunAjaran::where('status', 'aktif')->get();
$query = QuizAttempts::with(['quizzes', 'siswa'])
->whereHas('quizzes', function ($q) use ($request) {
$q->where('judul', $request->judul);
})->whereHas('siswa', function ($q) use ($request) {
$q->where('kelas', $request->kelas)
->where('tahun_ajaran', $request->tahun_ajaran);
})->get();
$rekap = $query->map(function ($item) use ($request) {
$nilaiData = $this->getNilai($item);
return [
'matapelajaran' => $item->quizzes->mataPelajaran->nama,
'judul_quiz' => $item->quizzes->judul,
'nama_siswa' => $item->siswa->nama,
'tahun_ajaran' => $request->tahun_ajaran,
'kelas' => $request->kelas,
'mapel_id' => $item->quizzes->mataPelajaran->id,
'total_skor' => $item->skor,
'persentase' => round(($item->skor / $nilaiData['total_skor_level']) * 100),
'kkm' => $nilaiData['kkm'],
];
});
if ($request->input('action') === 'download') {
return Excel::download(new RekapExport($rekap), 'rekap-quiz' . $request->kelas . '.xlsx');
}
// Convert $rekap to array to prevent automatic JSON conversion
$rekapArray = $rekap->toArray();
return view("pages.role_guru.quiz.rekap-quiz", compact(['matpel', 'judulQuiz', 'kelas', 'tahunAjaran', 'rekapArray']));
}
}

View File

@ -0,0 +1,161 @@
<?php
namespace App\Http\Controllers\RoleGuru;
use App\Exports\TugasDetailExport;
use App\Exports\TugasExport;
use App\Http\Controllers\Controller;
use App\Models\Kelas;
use App\Models\MataPelajaran;
use App\Models\SubmitTugas;
use App\Models\Tugas;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Maatwebsite\Excel\Facades\Excel;
use RealRashid\SweetAlert\Facades\Alert;
class RekapTugasController extends Controller
{
public function index(Request $request)
{
$guruNip = Auth::user()->guru->nip;
// Get mata pelajaran yang diajar oleh guru
$mataPelajaran = MataPelajaran::where('guru_nip', $guruNip)->get();
// Get kelas yang diajar oleh guru
$kelasList = Kelas::whereIn('nama', $mataPelajaran->pluck('kelas'))->get();
// Filter parameters
$selectedKelas = $request->get('kelas');
$selectedMatpel = $request->get('matpel');
$search = $request->get('search');
// Query untuk mendapatkan tugas yang dibuat oleh guru
$query = Tugas::with(['mataPelajaran', 'submitTugas.siswa'])
->where('guru_nip', $guruNip);
// Apply filters
if ($selectedKelas) {
$query->where('kelas', $selectedKelas);
}
if ($selectedMatpel) {
$query->where('matapelajaran_id', $selectedMatpel);
}
if ($search) {
$query->where('nama', 'like', '%' . $search . '%');
}
$tugas = $query->orderBy('created_at', 'desc')->paginate(10);
// Ensure submitTugas is always a collection
$tugas->getCollection()->transform(function ($t) {
if (!$t->submitTugas) {
$t->setRelation('submitTugas', collect());
}
return $t;
});
return view('pages.role_guru.rekap_tugas.index', compact(
'tugas',
'kelasList',
'mataPelajaran',
'selectedKelas',
'selectedMatpel',
'search'
));
}
public function detail($id)
{
$tugas = Tugas::with(['mataPelajaran', 'submitTugas.siswa'])
->where('guru_nip', Auth::user()->guru->nip)
->findOrFail($id);
$submitTugas = SubmitTugas::with('siswa')
->where('tugas_id', $id)
->get();
return view('pages.role_guru.rekap_tugas.detail', compact('tugas', 'submitTugas'));
}
public function updateNilai(Request $request, $id)
{
try {
$request->validate([
'nilai' => 'required|numeric|min:0|max:100'
]);
$submitTugas = SubmitTugas::findOrFail($id);
// Check if the task belongs to the teacher
$tugas = Tugas::where('id', $submitTugas->tugas_id)
->where('guru_nip', Auth::user()->guru->nip)
->first();
if (!$tugas) {
if ($request->ajax()) {
return response()->json([
'success' => false,
'message' => 'Anda tidak memiliki akses untuk menilai tugas ini.'
]);
}
Alert::error("Error", "Anda tidak memiliki akses untuk menilai tugas ini.");
return back();
}
$submitTugas->update([
'nilai' => $request->nilai
]);
if ($request->ajax()) {
return response()->json([
'success' => true,
'message' => 'Nilai berhasil diperbarui.'
]);
}
Alert::success("Berhasil", "Nilai berhasil diperbarui.");
return back();
} catch (\Exception $e) {
if ($request->ajax()) {
return response()->json([
'success' => false,
'message' => 'Terjadi kesalahan: ' . $e->getMessage()
]);
}
Alert::error("Error", "Terjadi kesalahan: " . $e->getMessage());
return back();
}
}
public function export(Request $request)
{
$selectedKelas = $request->get('kelas');
$selectedMatpel = $request->get('matpel');
$filename = 'rekap_tugas_' . date('Y-m-d_H-i-s') . '.xlsx';
return Excel::download(
new TugasExport($selectedKelas, $selectedMatpel),
$filename
);
}
public function exportDetail(Request $request)
{
$selectedKelas = $request->get('kelas');
$selectedMatpel = $request->get('matpel');
$filename = 'detail_tugas_' . date('Y-m-d_H-i-s') . '.xlsx';
return Excel::download(
new TugasDetailExport($selectedKelas, $selectedMatpel),
$filename
);
}
}

View File

@ -0,0 +1,154 @@
<?php
namespace App\Http\Controllers\RoleGuru;
use App\Http\Controllers\Controller;
use App\Models\Kelas;
use App\Models\MataPelajaran;
use App\Models\TahunAjaran;
use App\Models\Tugas;
use App\Repositories\TugasRepository;
use Illuminate\Database\QueryException;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use RealRashid\SweetAlert\Facades\Alert;
class TugasController extends Controller
{
protected $param;
public function __construct(TugasRepository $tugas)
{
$this->param = $tugas;
}
public function dateFormat($date)
{
return date('Y-m-d H:i:s', strtotime($date));
}
public function dataRequirement()
{
$mataPelajaran = MataPelajaran::where('guru_nip', Auth::user()->guru->nip)->get();
$kelas = Kelas::all();
$tahunAjaran = TahunAjaran::where('status', 'aktif')->get();
return [$mataPelajaran, $kelas, $tahunAjaran];
}
public function index(Request $request)
{
$limit = $request->has('page_length') ? $request->get('page_length') : 10;
$search = $request->has('search') ? $request->get('search') : null;
$tugas = $this->param->getData($search, $limit);
return view("pages.role_guru.tugas.index", compact("tugas"));
}
/**
* Show the form for creating a new resource.
*/
public function create()
{
$mataPelajaran = MataPelajaran::where('guru_nip', Auth::user()->guru->nip)->get();
$kelas = Kelas::all();
$tahunAjaran = TahunAjaran::where('status', 'aktif')->get();
return view("pages.role_guru.tugas.create", compact(["mataPelajaran", "kelas", "tahunAjaran"]));
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
try {
$request['tanggal'] = $this->dateFormat($request->input('tanggal'));
$request['tenggat'] = $this->dateFormat($request->input('tenggat'));
$data = $request->validate([
'tanggal' => 'required',
'tenggat' => 'required',
'nama' => 'required',
'matapelajaran_id' => 'required',
'kelas' => 'required',
'tahun_ajaran' => 'required',
'deskripsi' => 'nullable|string',
]);
$this->param->store($data);
Alert::success("Berhasil", "Data Berhasil di simpan.");
return redirect()->route("tugas");
} catch (\Exception $e) {
Alert::error("Terjadi Kesalahan", $e->getMessage());
return back()->withInput();
} catch (QueryException $e) {
Alert::error("Terjadi Kesalahan", $e->getMessage());
return back()->withInput();
}
}
/**
* Display the specified resource.
*/
public function show(string $id)
{
//
}
/**
* Show the form for editing the specified resource.
*/
public function edit(string $id)
{
$tugas = $this->param->find($id);
$mataPelajaran = MataPelajaran::where('guru_nip', Auth::user()->guru->nip)->get();
$kelas = Kelas::all();
$tahunAjaran = TahunAjaran::where('status', 'aktif')->get();
return view("pages.role_guru.tugas.edit", compact(["tugas", "mataPelajaran", "kelas", "tahunAjaran"]));
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, string $id)
{
try {
$request['tanggal'] = $this->dateFormat($request->input('tanggal'));
$request['tenggat'] = $this->dateFormat($request->input('tenggat'));
$data = $request->validate([
'tanggal' => 'required',
'tenggat' => 'required',
'nama' => 'required',
'matapelajaran_id' => 'required',
'kelas' => 'required',
'tahun_ajaran' => 'required',
'deskripsi' => 'nullable|string',
]);
$this->param->update($data, $id);
Alert::success("Berhasil", "Data Berhasil di ubah.");
return redirect()->route("tugas");
} catch (\Exception $e) {
Alert::error("Terjadi Kesalahan", $e->getMessage());
return back()->withInput();
} catch (QueryException $e) {
Alert::error("Terjadi Kesalahan", $e->getMessage());
return back()->withInput();
}
}
/**
* Remove the specified resource from storage.
*/
public function destroy(Request $request)
{
try {
$this->param->destroy($request->formid);
Alert::success("Berhasil", "Data Berhasil di hapus.");
return redirect()->route("tugas");
} catch (\Exception $e) {
Alert::error("Terjadi Kesalahan", $e->getMessage());
return back();
}
}
}

View File

@ -0,0 +1,147 @@
<?php
namespace App\Http\Controllers;
use App\Models\Kelas;
use App\Models\Siswa;
use App\Models\TahunAjaran;
use App\Repositories\SiswaRepository;
use App\Repositories\UserRepository;
use Illuminate\Database\QueryException;
use Illuminate\Http\Request;
use RealRashid\SweetAlert\Facades\Alert;
class SiswaController extends Controller
{
protected $param;
protected $paramUser;
public function __construct(SiswaRepository $siswa, UserRepository $user)
{
$this->param = $siswa;
$this->paramUser = $user;
}
public function index(Request $request)
{
$limit = $request->has('page_length') ? $request->get('page_length') : 10;
$search = $request->has('search') ? $request->get('search') : null;
$siswa = $this->param->getData($search, $limit);
return view("pages.role_admin.siswa.index", compact("siswa"));
}
/**
* Show the form for creating a new resource.
*/
public function create()
{
$kelas = Kelas::get();
$tahunAjaran = TahunAjaran::get();
return view("pages.role_admin.siswa.create", compact("kelas", 'tahunAjaran'));
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
try {
$dataUser = $request->validate([
'email' => 'required',
'role' => 'required',
]);
$data = $request->validate([
'nisn' => 'required|string|size:10|unique:siswa,nisn',
'nama' => 'required|string',
'jk' => 'required',
'kelas' => 'required',
'tahun_ajaran' => 'required',
]);
if (Siswa::where('nisn', $data['nisn'])->exists()) {
Alert::error("Terjadi Kesalahan", "NISN sudah terdaftar.");
return back()->withInput();
}
$dataUser['pass'] = $request->nisn;
$user = $this->paramUser->store($dataUser);
$data["user_id"] = $user->id;
$this->param->store($data);
Alert::success("Berhasil", "Data Berhasil di simpan.");
return redirect()->route("siswa");
} catch (\Exception $e) {
Alert::error("Terjadi Kesalahan", $e->getMessage());
return back()->withInput();
} catch (QueryException $e) {
Alert::error("Terjadi Kesalahan", $e->getMessage());
return back()->withInput();
}
}
/**
* Display the specified resource.
*/
public function show(string $id)
{
//
}
/**
* Show the form for editing the specified resource.
*/
public function edit(string $id)
{
$kelas = Kelas::get();
$tahunAjaran = TahunAjaran::get();
$siswa = Siswa::find($id);
return view("pages.role_admin.siswa.edit", compact(["kelas", "siswa", "tahunAjaran"]));
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, string $id)
{
try {
$dataUser = $request->validate([
'email' => 'required',
]);
$data = $request->validate([
'nisn' => 'required|string|size:10',
'nama' => 'required|string',
'jk' => 'required',
'kelas' => 'required',
'tahun_ajaran' => 'required',
]);
$this->paramUser->update($dataUser, $request->user_id);
$this->param->update($data, $id);
Alert::success("Berhasil", "Data Berhasil di ubah.");
return redirect()->route("siswa");
} catch (\Exception $e) {
Alert::error("Terjadi Kesalahan", $e->getMessage());
return back()->withInput();
} catch (QueryException $e) {
Alert::error("Terjadi Kesalahan", $e->getMessage());
return back()->withInput();
}
}
/**
* Remove the specified resource from storage.
*/
public function destroy(Request $request)
{
try {
$this->param->destroy($request->formid);
$this->paramUser->destroy($request->user_id);
Alert::success("Berhasil", "Data Berhasil di Hapus.");
return redirect()->route("siswa");
} catch (\Exception $e) {
Alert::error("Terjadi Kesalahan", $e->getMessage());
return back()->withInput();
}
}
}

View File

@ -0,0 +1,51 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class SiswaNotifikasiController extends Controller
{
public function notifCount(Request $request)
{
$siswa = $request->user()->siswa;
return response()->json([
'unread_count' => $siswa->unreadNotifications->count(),
]);
}
public function index(Request $request)
{
$siswa = $request->user()->siswa;
return response()->json([
'notifications' => $siswa->notifications->map(function ($notif) {
return [
'id' => $notif->id,
'type' => $notif->data['type'],
'judul' => $notif->data['judul'] ?? '-',
'tenggat' => $notif->data['tenggat'] ?? null,
'matapelajaran_id' => $notif->data['matapelajaran_id'] ?? null,
'matapelajaran_nama' => $notif->data['matapelajaran_nama'] ?? null,
'tugas_id' => $notif->data['tugas_id'] ?? null,
'quiz_id' => $notif->data['quiz_id'] ?? null,
'read_at' => $notif->read_at,
'created_at' => $notif->created_at->toDateTimeString(),
'is_active' => $notif->read_at == null,
];
})
]);
}
// Tandai notifikasi sebagai sudah dibaca
public function markAsRead(Request $request, $id)
{
$notif = $request->user()->siswa->notifications()->findOrFail($id);
$notif->markAsRead();
return response()->json([
'message' => 'Notifikasi ditandai sebagai dibaca.',
'id' => $id,
]);
}
}

View File

@ -0,0 +1,115 @@
<?php
namespace App\Http\Controllers;
use App\Models\TahunAjaran;
use App\Repositories\TahunAjaranRepository;
use Illuminate\Database\QueryException;
use Illuminate\Http\Request;
use RealRashid\SweetAlert\Facades\Alert;
class TahunAjaranController extends Controller
{
protected $param;
public function __construct(TahunAjaranRepository $tahunAjaran)
{
$this->param = $tahunAjaran;
}
public function index(Request $request)
{
$limit = $request->has('page_length') ? $request->get('page_length') : 10;
$search = $request->has('search') ? $request->get('search') : null;
$tahunAjaran = $this->param->getData($search, $limit);
return view("pages.role_admin.tahun_ajaran.index", compact("tahunAjaran"));
}
/**
* Show the form for creating a new resource.
*/
public function create()
{
return view("pages.role_admin.tahun_ajaran.create");
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
try {
$data = $request->validate([
'tahun' => 'required',
]);
$this->param->store($data);
Alert::success("Berhasil", "Data Berhasil di Tambahkan.");
return redirect()->route("tahun-ajaran");
} catch (\Exception $e) {
Alert::error("Terjadi Kesalahan", $e->getMessage());
return back()->withInput();
} catch (QueryException $e) {
Alert::error("Terjadi Kesalahan", $e->getMessage());
return back()->withInput();
}
}
/**
* Display the specified resource.
*/
public function show(string $id)
{
//
}
/**
* Show the form for editing the specified resource.
*/
public function edit(Request $request)
{
$tahun = urldecode($request->tahun_ajaran);
$tahunAjaran = TahunAjaran::where('tahun', $tahun)->firstOrFail();
return view('pages.role_admin.tahun_ajaran.edit', compact('tahunAjaran'));
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request)
{
try {
$tahun = urldecode($request->tahun_ajaran);
$data = $request->validate([
'status' => 'required',
]);
$this->param->update($data, $tahun);
Alert::success("Berhasil", "Data Berhasil di Ubah.");
return redirect()->route("tahun-ajaran");
} catch (\Exception $e) {
Alert::error("Terjadi Kesalahan", $e->getMessage());
return back()->withInput();
} catch (QueryException $e) {
Alert::error("Terjadi Kesalahan", $e->getMessage());
return back()->withInput();
}
}
/**
* Remove the specified resource from storage.
*/
public function destroy(Request $request)
{
try {
$tahun = $request->input('tahun');
$this->param->destroy($tahun);
Alert::success("Berhasil", "Data Berhasil di Hapus.");
return redirect()->route("tahun-ajaran");
} catch (\Exception $e) {
Alert::error("Terjadi Kesalahan", $e->getMessage());
return back();
}
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Http\Controllers\Traits;
use Illuminate\Http\JsonResponse;
trait ApiResponse
{
protected function okApiResponse($data, $message = 'Success'): JsonResponse
{
return response()->json([
'status' => true,
'message' => $message,
'data' => $data
], 200);
}
/**
* Response error (400/500 Internal Server Error)
*/
protected function errorApiResponse($message = 'Something went wrong', $statusCode = 500): JsonResponse
{
return response()->json([
'status' => false,
'message' => $message,
], $statusCode);
}
}

View File

@ -0,0 +1,74 @@
<?php
namespace App\Http\Controllers;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use RealRashid\SweetAlert\Facades\Alert;
class UserController extends Controller
{
public function change()
{
return view("pages.auth.change_password");
}
public function changePassword(Request $request)
{
try {
$request->validate([
'old_password' => ['required'],
'new_password' => ['required', 'min:8', 'confirmed'],
]);
$user = Auth::user();
if (!Hash::check($request->old_password, $user->password)) {
Alert::error("Terjadi Kesalahan", "Password lama salah.");
return redirect()->back();
}
User::where('id', $user->id)->update(['password' => Hash::make($request->new_password)]);
Alert::success('Berhasil', 'Password berhasil diubah.');
if ($user->role == "guru") {
return redirect()->route("dashboard.guru");
} else {
return redirect()->route("/");
}
} catch (\Throwable $th) {
Alert::error($th->getMessage());
return redirect()->back();
}
}
public function changePasswordApi(Request $request)
{
try {
$request->validate([
'old_password' => ['required'],
'new_password' => ['required', 'min:8', 'confirmed'],
]);
$user = Auth::user();
if (!Hash::check($request->old_password, $user->password)) {
return response()->json([
'status' => false,
'message' => "Password lama salah.",
]);
}
User::where('id', $user->id)->update(['password' => Hash::make($request->new_password)]);
return response()->json([
'status' => true,
'message' => "Password berhasil diubah.",
]);
} catch (\Throwable $th) {
return response()->json([
"status" => false,
'message' => $th->getMessage(),
]);
}
}
}

View File

@ -0,0 +1,142 @@
<?php
namespace App\Http\Controllers;
use App\Models\Guru;
use App\Models\Kelas;
use App\Models\TahunAjaran;
use App\Models\AuditLog;
use Illuminate\Support\Facades\Auth;
use App\Repositories\WaliKelasRepository;
use Illuminate\Database\QueryException;
use Illuminate\Http\Request;
use RealRashid\SweetAlert\Facades\Alert;
class WaliKelasController extends Controller
{
protected $param;
public function __construct(WaliKelasRepository $waliKelas)
{
$this->param = $waliKelas;
}
public function index(Request $request)
{
$limit = $request->has('page_length') ? $request->get('page_length') : 10;
$search = $request->has('search') ? $request->get('search') : null;
$waliKelas = $this->param->getData($search, $limit);
return view('pages.role_admin.wali_kelas.index', compact('waliKelas'));
}
/**
* Show the form for creating a new resource.
*/
public function create()
{
$kelas = Kelas::get();
$guru = Guru::get();
$tahunAjaran = TahunAjaran::where('status', 'aktif')->get();
return view('pages.role_admin.wali_kelas.create', compact(['kelas', 'guru', 'tahunAjaran']));
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
try {
$data = $request->validate([
'kelas' => 'required',
'tahun_ajaran' => 'required',
'wali_nip' => 'required',
]);
$this->param->store($data);
// Update kolom nip_wali pada tabel kelas
Kelas::where('nama', $data['kelas'])->update(['nip_wali' => $data['wali_nip']]);
Alert::success("Berhasil", "Data Berhasil di Tambahkan.");
return redirect()->route("wali-kelas");
} catch (\Exception $e) {
Alert::error("Terjadi Kesalahan", $e->getMessage());
return back()->withInput();
} catch (QueryException $e) {
Alert::error("Terjadi Kesalahan", $e->getMessage());
return back()->withInput();
}
}
/**
* Display the specified resource.
*/
public function show(string $id)
{
//
}
/**
* Show the form for editing the specified resource.
*/
public function edit(string $id)
{
$waliKelas = $this->param->find($id);
$kelas = Kelas::get();
$guru = Guru::get();
$tahunAjaran = TahunAjaran::where('status', 'aktif')->get();
return view('pages.role_admin.wali_kelas.edit', compact(['waliKelas', 'kelas', 'guru', 'tahunAjaran']));
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, string $id)
{
try {
$data = $request->validate([
'kelas' => 'required',
'tahun_ajaran' => 'required',
'wali_nip' => 'required',
]);
$this->param->update($data, $id);
// Update kolom nip_wali pada tabel kelas
Kelas::where('nama', $data['kelas'])->update(['nip_wali' => $data['wali_nip']]);
Alert::success("Berhasil", "Data Berhasil di ubah.");
return redirect()->route("wali-kelas");
} catch (\Exception $e) {
Alert::error("Terjadi Kesalahan", $e->getMessage());
return back()->withInput();
} catch (QueryException $e) {
Alert::error("Terjadi Kesalahan", $e->getMessage());
return back()->withInput();
}
}
/**
* Remove the specified resource from storage.
*/
public function destroy(Request $request)
{
try {
$wali = $this->param->find($request->formid);
// Cek apakah wali kelas masih aktif di tabel kelas
$isWali = Kelas::where('nip_wali', $wali->wali_nip)->exists();
if ($isWali) {
Alert::error('Gagal', 'Tidak dapat menghapus wali kelas karena masih aktif di kelas. Silakan ganti wali kelas terlebih dahulu.');
return back();
}
$this->param->destroy($request->formid);
// Audit log
AuditLog::create([
'user_id' => Auth::id(),
'action' => 'delete_wali_kelas',
'description' => 'Menghapus wali kelas: ' . $wali->wali_nip,
]);
Alert::success("Berhasil", "Data Berhasil di hapus.");
return redirect()->route("wali-kelas");
} catch (\Exception $e) {
Alert::error("Terjadi Kesalahan", $e->getMessage());
return back();
}
}
}

View File

@ -0,0 +1,32 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Laravel\Sanctum\PersonalAccessToken;
class CheckApiToken
{
public function handle(Request $request, Closure $next)
{
$header = $request->header('Authorization');
if (!$header || !str_starts_with($header, 'Bearer ')) {
return response()->json(['message' => 'Token tidak ditemukan.'], 401);
}
$accessToken = str_replace('Bearer ', '', $header);
$token = PersonalAccessToken::findToken($accessToken);
if (!$token) {
return response()->json(['message' => 'Token tidak valid.'], 401);
}
// Set user yang sedang login (penting)
$request->merge(['user' => $token->tokenable]);
return $next($request);
}
}

View File

@ -0,0 +1,26 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Symfony\Component\HttpFoundation\Response;
class RoleMiddleware
{
public function handle(Request $request, Closure $next, ...$roles): Response
{
if (!Auth::check()) {
return redirect('/login');
}
$user = Auth::user();
if (in_array($user->role, $roles)) {
return $next($request);
}
abort(403, 'Unauthorized');
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class ChangePasswordRequest extends FormRequest
{
public function rules()
{
return [
'old_password' => ['required'],
'new_password' => ['required', 'min:8', 'confirmed'],
];
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
use Carbon\Carbon;
class SubmitTugasResource extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->id,
'tanggal' => $this->tanggal ? Carbon::parse($this->tanggal)->format('Y-m-d H:i:s') : null,
'nisn' => $this->nisn,
'tugas_id' => $this->tugas_id,
'text' => $this->text,
'file' => $this->file,
'nilai' => $this->nilai,
'created_at' => $this->created_at ? $this->created_at->format('Y-m-d H:i:s') : null,
'updated_at' => $this->updated_at ? $this->updated_at->format('Y-m-d H:i:s') : null,
'siswa' => $this->whenLoaded('siswa', function () {
return [
'nisn' => $this->siswa->nisn,
'nama' => $this->siswa->nama,
];
}),
];
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
use Carbon\Carbon;
class TugasResource extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->id,
'nama' => $this->nama,
'deskripsi' => $this->deskripsi,
'tanggal' => $this->tanggal ? Carbon::parse($this->tanggal)->format('Y-m-d H:i:s') : null,
'tenggat' => $this->tenggat ? Carbon::parse($this->tenggat)->format('Y-m-d H:i:s') : null,
'guru_nip' => $this->guru_nip,
'matapelajaran_id' => $this->matapelajaran_id,
'kelas' => $this->kelas,
'tahun_ajaran' => $this->tahun_ajaran,
'mata_pelajaran' => $this->whenLoaded('mataPelajaran', function () {
return [
'id' => $this->mataPelajaran->id,
'nama' => $this->mataPelajaran->nama,
];
}),
'submit_tugas' => SubmitTugasResource::collection($this->whenLoaded('submitTugas')),
];
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace App\Imports;
use App\Models\QuizQuestions;
use Maatwebsite\Excel\Concerns\ToModel;
class QuizImport implements ToModel
{
/**
* @param array $row
*
* @return \Illuminate\Database\Eloquent\Model|null
*/
public function model(array $row)
{
return new QuizQuestions([
//
]);
}
}

23
app/Models/Admin.php Normal file
View File

@ -0,0 +1,23 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Admin extends Model
{
protected $table = 'admin';
protected $fillable = [
'id',
'user_id',
'nip',
'nama',
'jk',
];
public function user()
{
return $this->belongsTo(User::class, 'user_id', 'id');
}
}

18
app/Models/AuditLog.php Normal file
View File

@ -0,0 +1,18 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class AuditLog extends Model
{
protected $table = 'audit_logs';
protected $fillable = [
'user_id', 'action', 'description'
];
public function user()
{
return $this->belongsTo(User::class, 'user_id', 'id');
}
}

33
app/Models/Guru.php Normal file
View File

@ -0,0 +1,33 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Guru extends Model
{
protected $table = 'guru';
protected $fillable = [
'id',
'user_id',
'nip',
'nama',
'jk',
];
// public function kelas()
// {
// return $this->hasOne(Kelas::class, 'nip_wali', 'nip');
// }
public function mataPelajaran()
{
return $this->hasOne(MataPelajaran::class, 'guru_nip', 'nip');
}
public function user()
{
return $this->belongsTo(User::class, 'user_id', 'id');
}
}

28
app/Models/Kelas.php Normal file
View File

@ -0,0 +1,28 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Kelas extends Model
{
protected $table = "kelas";
protected $fillable = [
"nama",
];
public function siswa()
{
return $this->hasMany(Siswa::class, 'kelas', 'nama');
}
// public function guru()
// {
// return $this->belongsTo(Guru::class, 'nip_wali', 'nip');
// }
public function mataPelajaran()
{
return $this->hasMany(MataPelajaran::class, 'kelas', 'nama');
}
}

View File

@ -0,0 +1,34 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class MataPelajaran extends Model
{
protected $table = 'matapelajaran';
protected $fillable = [
"nama",
"guru_nip",
"kelas",
"tahun_ajaran",
];
public function materi()
{
return $this->hasMany(Materi::class, "matapelajaran_id", "id");
}
public function guru()
{
return $this->belongsTo(Guru::class, "guru_nip", "nip");
}
public function kelas()
{
return $this->belongsTo(Kelas::class, "kelas", "nama");
}
public function tahunAjaran()
{
return $this->belongsTo(TahunAjaran::class, "tahun_ajaran", "tahun");
}
}

30
app/Models/Materi.php Normal file
View File

@ -0,0 +1,30 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Materi extends Model
{
protected $table = "materi";
protected $fillable = [
"tanggal",
"matapelajaran_id",
"semester",
"type",
"judul_materi",
"deskripsi",
"path",
"tahun_ajaran"
];
public function mataPelajaran()
{
return $this->belongsTo(MataPelajaran::class, "matapelajaran_id", "id");
}
public function tahunAjaran()
{
return $this->belongsTo(TahunAjaran::class, "tahun_ajaran", "tahun");
}
}

View File

@ -0,0 +1,27 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class QuizAttemptAnswers extends Model
{
protected $table = "quiz_attempt_answers";
protected $fillable = [
"attempt_id",
"question_id",
"jawaban_siswa",
"benar",
];
public function quizAttempt()
{
return $this->belongsTo(QuizAttempts::class, "attempt_id", "id");
}
public function quizQuestion()
{
return $this->belongsTo(QuizQuestions::class, "question_id", "id");
}
}

View File

@ -0,0 +1,38 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class QuizAttempts extends Model
{
protected $table = "quiz_attempts";
protected $fillable = [
"quiz_id",
"nisn",
"skor",
"level_akhir",
"jumlah_soal_dijawab",
"benar",
];
public function attemptAnswers()
{
return $this->hasMany(QuizAttemptAnswers::class, 'attempt_id');
}
public function jumlahJawaban()
{
return $this->hasMany(QuizAttemptAnswers::class, 'attempt_id')->count();
}
public function quizzes()
{
return $this->belongsTo(Quizzes::class, "quiz_id", "id");
}
public function siswa()
{
return $this->belongsTo(Siswa::class, "nisn", "nisn");
}
}

View File

@ -0,0 +1,24 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class QuizLevelSetting extends Model
{
protected $table = "quiz_level_settings";
protected $fillable = [
'quiz_id',
'jumlah_soal_per_level',
'level_awal',
'batas_naik_level',
'skor_level',
'kkm',
];
public function quiz()
{
return $this->belongsTo(Quizzes::class);
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class QuizQuestions extends Model
{
protected $table = "quiz_questions";
protected $fillable = [
"quiz_id",
"pertanyaan",
"opsi_a",
"opsi_b",
"opsi_c",
"opsi_d",
"jawaban_bener",
"level",
];
public function attemptAnswers()
{
return $this->hasMany(QuizAttemptAnswers::class, 'question_id');
}
public function quiz()
{
return $this->belongsTo(Quizzes::class, "quiz_id", "id");
}
}

39
app/Models/Quizzes.php Normal file
View File

@ -0,0 +1,39 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Quizzes extends Model
{
protected $table = "quizzes";
protected $fillable = [
"judul",
"deskripsi",
"total_soal",
"total_soal_tampil",
"waktu",
"matapelajaran_id",
];
public function levelSetting()
{
return $this->hasOne(QuizLevelSetting::class);
}
public function quizAttempt()
{
return $this->hasOne(QuizAttempts::class, "quiz_id", "id");
}
public function quizLevelSetting()
{
return $this->hasOne(QuizLevelSetting::class, 'quiz_id', 'id');
}
public function mataPelajaran()
{
return $this->belongsTo(MataPelajaran::class, "matapelajaran_id", "id");
}
}

40
app/Models/Siswa.php Normal file
View File

@ -0,0 +1,40 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Notifications\Notifiable;
class Siswa extends Model
{
use Notifiable;
protected $table = "siswa";
protected $fillable = [
"user_id",
"nisn",
"nama",
"jk",
"kelas",
"tahun_ajaran",
];
public function submitTugas()
{
return $this->hasOne(SubmitTugas::class, "nisn", "nisn");
}
public function user()
{
return $this->belongsTo(User::class, "user_id", "id");
}
public function kelas()
{
return $this->belongsTo(Kelas::class, "kelas", "nama");
}
public function tahunAjaran()
{
return $this->belongsTo(TahunAjaran::class, "tahun_ajaran", "tahun");
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class SubmitTugas extends Model
{
protected $table = "submit_tugas";
protected $fillable = [
"id",
"tanggal",
"nisn",
"tugas_id",
"text",
"file",
"nilai"
];
public function siswa()
{
return $this->belongsTo(Siswa::class, "nisn", "nisn");
}
public function tugas()
{
return $this->belongsTo(Tugas::class, "tugas_id", "id");
}
}

View File

@ -0,0 +1,15 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class TahunAjaran extends Model
{
protected $table = "tahun_ajaran";
protected $fillable = [
"tahun",
"status",
];
}

38
app/Models/Tugas.php Normal file
View File

@ -0,0 +1,38 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Tugas extends Model
{
protected $table = "tugas";
protected $fillable = [
"tanggal",
"tenggat",
"guru_nip",
"nama",
"matapelajaran_id",
"kelas",
"tahun_ajaran",
'deskripsi',
];
public function submitTugas()
{
return $this->hasMany(SubmitTugas::class, "tugas_id", "id");
}
public function guru()
{
return $this->belongsTo(Guru::class, "guru_nip", "nip");
}
public function mataPelajaran()
{
return $this->belongsTo(MataPelajaran::class, "matapelajaran_id", "id");
}
public function tahunAjaran()
{
return $this->belongsTo(TahunAjaran::class, "tahun_ajaran", "id");
}
}

50
app/Models/User.php Normal file
View File

@ -0,0 +1,50 @@
<?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;
use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable
{
use HasFactory, Notifiable, HasApiTokens;
protected $table = 'users';
protected $fillable = [
'id',
'email',
'password',
'role',
];
protected $hidden = [
'password',
];
protected function casts(): array
{
return [
'password' => 'hashed',
];
}
public function admin()
{
return $this->hasOne(Admin::class, 'user_id', 'id');
}
public function guru()
{
return $this->hasOne(Guru::class, 'user_id', 'id');
}
public function siswa()
{
return $this->hasOne(Siswa::class, 'user_id', 'id');
}
}

31
app/Models/WaliKelas.php Normal file
View File

@ -0,0 +1,31 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class WaliKelas extends Model
{
protected $table = "riwayat_kelas";
protected $fillable = [
"kelas",
"tahun_ajaran",
"wali_nip",
];
public function kelas()
{
return $this->belongsTo(Kelas::class, 'kelas', 'nama');
}
public function guru()
{
return $this->belongsTo(Guru::class, 'wali_nip', 'nip');
}
public function tahunAjaran()
{
return $this->belongsTo(TahunAjaran::class, 'tahun_ajaran', 'tahun');
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace App\Notifications;
use App\Models\Materi;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
class MateriBaruNotification extends Notification
{
use Queueable;
protected $materi;
public function __construct(Materi $materi)
{
$this->materi = $materi;
}
public function via(object $notifiable): array
{
return ['database'];
}
public function toDatabase($notifiable): array
{
$this->materi->loadMissing('mataPelajaran');
return [
'judul' => $this->materi->judul_materi,
'type' => "Materi",
'matapelajaran_id' => $this->materi->matapelajaran_id,
'matapelajaran_nama' => $this->materi->mataPelajaran->nama ?? null,
];
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace App\Notifications;
use App\Models\Quizzes;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
class QuizBaruNotification extends Notification
{
use Queueable;
protected $param;
public function __construct(Quizzes $quizzes)
{
$this->param = $quizzes;
}
public function via(object $notifiable): array
{
return ['database'];
}
public function toDatabase($notifiable): array
{
$this->param->loadMissing('mataPelajaran');
return [
'judul' => $this->param->judul,
'type' => "Quiz",
'matapelajaran_id' => $this->param->matapelajaran_id,
'matapelajaran_nama' => $this->param->mataPelajaran->nama ?? null,
];
}
}

View File

@ -0,0 +1,38 @@
<?php
namespace App\Notifications;
use App\Models\Tugas;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
class TugasBaruNotification extends Notification
{
use Queueable;
protected $tugas;
public function __construct(Tugas $tugas)
{
$this->tugas = $tugas;
}
public function via(object $notifiable): array
{
return ['database'];
}
public function toDatabase($notifiable): array
{
// Pastikan relasi sudah di-load
$this->tugas->loadMissing('mataPelajaran');
return [
'judul' => $this->tugas->nama,
'type' => "Tugas",
'matapelajaran_id' => $this->tugas->matapelajaran_id,
'matapelajaran_nama' => $this->tugas->mataPelajaran->nama ?? null,
];
}
}

View 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
{
//
}
}

View File

@ -0,0 +1,61 @@
<?php
namespace App\Repositories;
use App\Models\Admin;
class AdminRepository
{
protected $model;
public function __construct(Admin $guru)
{
$this->model = $guru;
}
public function find($id)
{
return $this->model->with('user')->find($id);
}
public function getData($search, $limit = 10)
{
$search = strtolower($search);
$query = $this->model
->where(function ($query) use ($search) {
$query->where("nama", "like", "%" . $search . "%")
->orWhere("nip", "like", "%" . $search . "%");
})
->orWhereHas("user", function ($query) use ($search) {
$query->where("email", "like", "%" . $search . "%");
})
->paginate($limit);
return $query;
}
public function store($data)
{
return $this->model->create([
"user_id" => $data["user_id"],
"nip" => $data["nip"],
"nama" => $data["nama"],
"jk" => $data["jk"],
]);
}
public function update($data, $id)
{
return $this->model->where('id', $id)->update([
"nip" => $data["nip"],
"nama" => $data["nama"],
"jk" => $data["jk"],
]);
}
public function destroy($id)
{
return $this->model->where('id', $id)->delete();
}
}

View File

@ -0,0 +1,99 @@
<?php
namespace App\Repositories;
use App\Models\SubmitTugas;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
class ApiSubmitTugasRepository
{
protected $model;
public function __construct(SubmitTugas $submitTugas)
{
$this->model = $submitTugas;
}
public function store($request)
{
$tanggal = now(); // Ubah agar menyimpan datetime lengkap
$request->validate([
'tugas_id' => 'required|exists:tugas,id',
'nisn' => 'required|string|max:12',
'text' => 'nullable|required_without:file|string',
'file' => 'nullable|required_without:text|file|mimes:pdf,jpg,png',
'nilai' => 'nullable|integer|min:0|max:100'
]);
$filePath = null;
if ($request->hasFile('file')) {
$filename = Str::uuid() . '.' . $request->file->extension();
$filePath = $request->file->storeAs('tugas/submit_tugas', $filename, 'public');
}
$submit = SubmitTugas::create([
'tugas_id' => $request->tugas_id,
'nisn' => $request->nisn,
'tanggal' => $tanggal, // simpan datetime lengkap
'text' => $request->text,
'file' => $filePath,
'nilai' => $request->nilai
]);
return $submit;
}
public function detail($request)
{
// Cek jika ada parameter submit_id, maka ambil berdasarkan id
if ($request->has('submit_id')) {
$query = $this->model->with('siswa')->find($request->submit_id);
return $query;
}
// Default: cari berdasarkan nisn dan tugas_id
$query = $this->model->with('siswa')->where(function ($q) use ($request) {
$q->where('nisn', $request->nisn)
->where('tugas_id', $request->tugas_id);
})->first();
return $query;
}
public function update($request)
{
$tanggal = now(); // Ubah agar menyimpan datetime lengkap
$request->validate([
'id' => 'required|exists:submit_tugas,id',
'nisn' => 'required|string|max:12',
'text' => 'nullable|string',
'nilai' => 'nullable|integer|min:0|max:100'
]);
$submit = $this->model->findOrFail($request->id);
$filePath = $submit->file;
if ($request->hasFile('file')) {
if ($filePath && Storage::disk('public')->exists($filePath)) {
Storage::disk('public')->delete($filePath);
}
$filename = Str::uuid() . '.' . $request->file->extension();
$filePath = $request->file->storeAs('tugas/submit_tugas', $filename, 'public');
}
$submit->update([
'nisn' => $request->nisn,
'tanggal' => $tanggal, // simpan datetime lengkap
'text' => $request->text,
'file' => $filePath,
'nilai' => $request->nilai
]);
return $submit;
}
}

View File

@ -0,0 +1,81 @@
<?php
namespace App\Repositories;
use App\Models\Siswa;
use App\Models\SubmitTugas;
use App\Models\Tugas;
use App\Http\Resources\TugasResource;
use GuzzleHttp\Psr7\Request;
use Illuminate\Support\Facades\Auth;
class ApiTugasRepository
{
protected $model;
public function __construct(Tugas $tugas)
{
$this->model = $tugas;
}
public function getDataApi($request, $param)
{
$query = $this->model->with(['mataPelajaran', 'submitTugas']);
if ($request->user->role == "siswa") {
if ($param->type_tugas == "selesai") {
$query->withWhereHas("submitTugas", function ($q) use ($request) {
$q->where('nisn', $request->nisn);
});
} else {
$query->whereDoesntHave("submitTugas", function ($q) use ($request) {
$q->where('nisn', $request->nisn);
});
}
$query->where(function ($q) use ($request) {
$q->where("kelas", $request->kelas)
->where("tahun_ajaran", $request->tahun_ajaran);
});
} else {
$query->where(function ($q) use ($request, $param) {
$q->where("guru_nip", $request->nip)
->where('kelas', $param->kelas)
->where('tahun_ajaran', $param->tahun_ajaran);
});
}
$query->where('matapelajaran_id', $param->id_matpel);
$data = $query->get();
return TugasResource::collection($data);
}
public function getSubmitTugasSiswa($request)
{
$query = Siswa::where(function ($q) use ($request) {
$q->where("kelas", $request->kelas)
->where("tahun_ajaran", $request->tahun_ajaran);
});
if ($request->type_tugas == "selesai") {
$query->withWhereHas("submitTugas", function ($q) use ($request) {
$q->where("tugas_id", $request->tugas_id);
})->with([
'submitTugas' => function ($q) use ($request) {
$q->where("tugas_id", $request->tugas_id)
->with('tugas');
}
]);
} else {
$query->with('submitTugas')
->whereDoesntHave("submitTugas", function ($q) use ($request) {
$q->where("tugas_id", $request->tugas_id);
});
}
$data = $query->get();
return $data;
}
}

View File

@ -0,0 +1,73 @@
<?php
namespace App\Repositories;
use App\Models\User;
use App\Models\Kelas;
use App\Models\MataPelajaran;
use App\Models\TahunAjaran;
use App\Models\WaliKelas;
use App\Models\Siswa;
class DashboardRepository
{
public function getData($tahunAjaran = null, $kelas = null)
{
$data = [];
// Get active academic year if not specified
if (!$tahunAjaran) {
$tahunAjaran = TahunAjaran::where('status', 'aktif')->first();
if ($tahunAjaran) {
$tahunAjaran = $tahunAjaran->tahun;
}
}
// Get all academic years for dropdown
$data['tahun_ajaran_list'] = TahunAjaran::select('tahun')->get()->pluck('tahun');
// Get all classes for dropdown
$data['kelas_list'] = Kelas::select('nama')->get()->pluck('nama');
// User statistics (not affected by academic year or class)
$data['admin'] = User::where('role', "admin")->count();
$data['guru'] = User::where('role', "guru")->count();
// Academic statistics with year and class filter
$siswaQuery = Siswa::query();
$mataPelajaranQuery = MataPelajaran::query();
$waliKelasQuery = WaliKelas::query();
if ($tahunAjaran && $tahunAjaran !== 'all') {
$siswaQuery->where('tahun_ajaran', $tahunAjaran);
$mataPelajaranQuery->where('tahun_ajaran', $tahunAjaran);
$waliKelasQuery->where('tahun_ajaran', $tahunAjaran);
}
if ($kelas && $kelas !== 'all') {
$siswaQuery->where('kelas', $kelas);
$mataPelajaranQuery->where('kelas', $kelas);
$waliKelasQuery->where('kelas', $kelas);
}
$data['siswa'] = $siswaQuery->count();
// Kelas count needs special handling as it's not directly tied to siswa by year/class but to the existing classes
// If filtering by class, we only count that specific class, otherwise all classes
$data['kelas'] = ($kelas && $kelas !== 'all') ? 1 : Kelas::count();
$data['mata_pelajaran'] = $mataPelajaranQuery->count();
$data['wali_kelas'] = $waliKelasQuery->count();
// Get active academic year info
$activeYear = TahunAjaran::where('status', 'aktif')->first();
if ($activeYear) {
$data['tahun_ajaran_aktif'] = $activeYear->tahun;
} else {
$data['tahun_ajaran_aktif'] = 'Belum ada tahun ajaran aktif';
}
// Store selected year and class
$data['selected_year'] = $tahunAjaran;
$data['selected_kelas'] = $kelas;
return $data;
}
}

View File

@ -0,0 +1,243 @@
<?php
namespace App\Repositories;
use App\Models\Kelas;
use App\Models\MataPelajaran;
use App\Models\Materi;
use App\Models\QuizAttempts;
use App\Models\QuizLevelSetting;
use App\Models\Quizzes;
use App\Models\Siswa;
use App\Models\SubmitTugas;
use App\Models\TahunAjaran;
use App\Models\Tugas;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
class GuruDashboardRepository
{
public function getDashboardData($tahunAjaran = 'all', $kelas = 'all', $quizStatus = 'all')
{
$guru = Auth::user()->guru;
// Get all academic years for filter based on guru's subjects
$tahunAjaranList = MataPelajaran::where('guru_nip', $guru->nip)
->distinct('tahun_ajaran')
->pluck('tahun_ajaran')
->toArray();
// Get all classes for filter based on guru's subjects
$kelasList = MataPelajaran::where('guru_nip', $guru->nip)
->distinct('kelas')
->pluck('kelas')
->toArray();
// Get active academic year
$tahunAjaranAktif = TahunAjaran::where('status', 'aktif')->first();
// Get relevant MataPelajaran first based on guru_nip and tahun_ajaran
$guruMataPelajaranQuery = MataPelajaran::where('guru_nip', $guru->nip);
if ($tahunAjaran !== 'all') {
$guruMataPelajaranQuery->where('tahun_ajaran', $tahunAjaran);
}
if ($kelas !== 'all') {
$guruMataPelajaranQuery->where('kelas', $kelas);
}
$guruMataPelajaranIds = $guruMataPelajaranQuery->pluck('id')->toArray();
$guruMataPelajaranClasses = $guruMataPelajaranQuery->pluck('kelas')->unique()->toArray();
// Now build the kelasQuery based on these class names
$kelasQuery = Kelas::whereIn('nama', $guruMataPelajaranClasses);
// Get subjects taught by this teacher (this query already correctly uses guru_nip)
$mataPelajaranQuery = MataPelajaran::where('guru_nip', $guru->nip);
// Apply filters if not 'all'
if ($tahunAjaran !== 'all') {
$mataPelajaranQuery->where('tahun_ajaran', $tahunAjaran);
}
if ($kelas !== 'all') {
$kelasQuery->where('nama', $kelas);
$mataPelajaranQuery->where('kelas', $kelas);
}
// Get counts
$kelasCount = $kelasQuery->count();
$mataPelajaranCount = $mataPelajaranQuery->count();
// Get total students from these classes
$siswaCount = Siswa::whereIn('kelas', $kelasQuery->pluck('nama'))->count();
// New features: Total Tugas, Materi, Quiz
$totalTugas = Tugas::where('guru_nip', $guru->nip)
->when($tahunAjaran !== 'all', function ($query) use ($tahunAjaran) {
return $query->where('tahun_ajaran', $tahunAjaran);
})
->when($kelas !== 'all', function ($query) use ($kelas) {
return $query->where('kelas', $kelas);
})
->count();
$totalMateri = Materi::whereIn('matapelajaran_id', $guruMataPelajaranIds)
->when($tahunAjaran !== 'all', function ($query) use ($tahunAjaran) {
return $query->where('tahun_ajaran', $tahunAjaran);
})
->count();
$totalQuiz = Quizzes::whereIn('matapelajaran_id', $guruMataPelajaranIds)
->count();
// Quiz Results (Passed/Failed)
$passedQuizzes = 0;
$failedQuizzes = 0;
$quizAttempts = QuizAttempts::whereHas('quizzes.mataPelajaran', function ($query) use ($guru, $tahunAjaran, $kelas) {
$query->where('guru_nip', $guru->nip);
if ($tahunAjaran !== 'all') {
$query->where('tahun_ajaran', $tahunAjaran);
}
if ($kelas !== 'all') {
$query->where('kelas', $kelas);
}
})->get();
foreach ($quizAttempts as $attempt) {
$quizLevelSettings = QuizLevelSetting::where('quiz_id', $attempt->quiz_id)->first();
if ($quizLevelSettings) {
$jumlahSoalPerLevel = json_decode($quizLevelSettings->jumlah_soal_per_level, true);
$totalSkorLevel = 0;
foreach (json_decode($quizLevelSettings->skor_level) as $key => $value) {
$totalSkorLevel += $value * (int) $jumlahSoalPerLevel[$key];
}
$persentase = ($totalSkorLevel > 0) ? round(($attempt->skor / $totalSkorLevel) * 100) : 0;
$kkm = $quizLevelSettings->kkm;
if ($persentase >= $kkm) {
$passedQuizzes++;
} else {
$failedQuizzes++;
}
}
}
// Prepare data for Mata Pelajaran Distribution Chart
$mataPelajaranChartLabels = [];
$mataPelajaranChartData = [];
$mataPelajaranWithClasses = MataPelajaran::where('guru_nip', $guru->nip)
->when($tahunAjaran !== 'all', function ($query) use ($tahunAjaran) {
return $query->where('tahun_ajaran', $tahunAjaran);
})
->when($kelas !== 'all', function ($query) use ($kelas) {
return $query->where('kelas', $kelas);
})
->get();
foreach ($mataPelajaranWithClasses as $mp) {
$studentsInClass = Siswa::where('kelas', $mp->kelas)->count();
$mataPelajaranChartLabels[] = $mp->nama; // Assuming 'nama' is the subject name
$mataPelajaranChartData[] = $studentsInClass; // Count students in the associated class
}
// Prepare data for Siswa per Kelas Chart
$kelasChartLabels = [];
$kelasChartData = [];
$guruClasses = Kelas::whereIn('nama', $guruMataPelajaranClasses)
->when($kelas !== 'all', function ($query) use ($kelas) {
return $query->where('nama', $kelas);
})
->get();
foreach ($guruClasses as $k) {
$siswaInKelasCount = Siswa::where('kelas', $k->nama)->count();
$kelasChartLabels[] = $k->nama;
$kelasChartData[] = $siswaInKelasCount;
}
// Prepare data for Nilai Rata-rata Tugas per Mata Pelajaran dan Kelas Chart
$tugasChartLabels = [];
$tugasChartData = [];
$tugasChartDataPerTugas = [];
// Get all mata pelajaran that the teacher teaches with filters
$mataPelajaranQuery = MataPelajaran::where('guru_nip', $guru->nip)
->when($tahunAjaran !== 'all', function ($query) use ($tahunAjaran) {
return $query->where('tahun_ajaran', $tahunAjaran);
})
->when($kelas !== 'all', function ($query) use ($kelas) {
return $query->where('kelas', $kelas);
})
->orderBy('nama', 'asc');
$mataPelajaranList = $mataPelajaranQuery->get();
foreach ($mataPelajaranList as $matpel) {
// Get all tasks for this mata pelajaran and class
$tugasForMatpel = Tugas::where('guru_nip', $guru->nip)
->where('matapelajaran_id', $matpel->id)
->where('kelas', $matpel->kelas)
->when($tahunAjaran !== 'all', function ($query) use ($tahunAjaran) {
return $query->where('tahun_ajaran', $tahunAjaran);
})
->orderBy('created_at', 'desc')
->get();
foreach ($tugasForMatpel as $tugas) {
// Get average score for this task
$avgNilai = SubmitTugas::where('tugas_id', $tugas->id)
->whereNotNull('nilai')
->avg('nilai');
if ($avgNilai !== null) {
$tugasChartLabels[] = "{$matpel->nama} ({$matpel->kelas}) - {$tugas->nama}";
$tugasChartData[] = round($avgNilai, 1);
$tugasChartDataPerTugas[] = [
'id_tugas' => $tugas->id,
'nama_tugas' => $tugas->nama,
'mata_pelajaran' => $matpel->nama,
'kelas' => $matpel->kelas,
'rata_rata' => round($avgNilai, 1),
'jumlah_submit' => SubmitTugas::where('tugas_id', $tugas->id)->count(),
'jumlah_dinilai' => SubmitTugas::where('tugas_id', $tugas->id)->whereNotNull('nilai')->count(),
'tanggal_tugas' => $tugas->tanggal,
'tenggat_waktu' => $tugas->tenggat
];
}
}
}
return [
'selected_year' => $tahunAjaran,
'selected_kelas' => $kelas,
'selected_quiz_status' => $quizStatus,
'tahun_ajaran_list' => $tahunAjaranList,
'kelas_list' => $kelasList,
'tahun_ajaran_aktif' => $tahunAjaranAktif ? $tahunAjaranAktif->tahun : '-',
'kelas' => $kelasCount,
'mata_pelajaran' => $mataPelajaranCount,
'siswa' => $siswaCount,
'total_tugas' => $totalTugas,
'total_materi' => $totalMateri,
'total_quiz' => $totalQuiz,
'passed_quizzes' => $passedQuizzes,
'failed_quizzes' => $failedQuizzes,
'mata_pelajaran_chart' => [
'labels' => $mataPelajaranChartLabels,
'data' => $mataPelajaranChartData,
],
'kelas_chart' => [
'labels' => $kelasChartLabels,
'data' => $kelasChartData,
],
'tugas_chart' => [
'labels' => $tugasChartLabels,
'data' => $tugasChartData,
'data_per_tugas' => $tugasChartDataPerTugas,
],
];
}
}

View File

@ -0,0 +1,60 @@
<?php
namespace App\Repositories;
use App\Models\Guru;
class GuruRepository
{
protected $model;
public function __construct(Guru $guru)
{
$this->model = $guru;
}
public function find($id)
{
return $this->model->with('user')->find($id);
}
public function getData($search, $limit = 10)
{
$search = strtolower($search);
$query = $this->model
->where(function ($query) use ($search) {
$query->where("nama", "like", "%" . $search . "%");
})
->orWhereHas("user", function ($query) use ($search) {
$query->where("email", "like", "%" . $search . "%");
})
->paginate($limit);
return $query;
}
public function store($data)
{
return $this->model->create([
"user_id" => $data["user_id"],
"nip" => $data["nip"],
"nama" => $data["nama"],
"jk" => $data["jk"],
]);
}
public function update($data, $id)
{
return $this->model->where('id', $id)->update([
"nip" => $data["nip"],
"nama" => $data["nama"],
"jk" => $data["jk"],
]);
}
public function destroy($id)
{
return $this->model->where('id', $id)->delete();
}
}

View File

@ -0,0 +1,47 @@
<?php
namespace App\Repositories;
use App\Models\Kelas;
class KelasRepository
{
protected $model;
public function __construct(Kelas $kelas)
{
$this->model = $kelas;
}
public function getData($search, $limit = 10)
{
$search = strtolower($search);
$query = $this->model
->where(function ($query) use ($search) {
$query->where("nama", "like", "%" . $search . "%");
})
->paginate($limit);
return $query;
}
public function store($data)
{
return $this->model->create([
"nama" => $data["nama"],
]);
}
public function update($data, $id)
{
// return $this->model->where('id', $id)->update([
// "nama" => $data["nama"],
// ]);
}
public function destroy($kelas)
{
return $this->model->where('nama', $kelas)->delete();
}
}

View File

@ -0,0 +1,64 @@
<?php
namespace App\Repositories;
use App\Models\Admin;
use App\Models\MataPelajaran;
class MataPelajaranRepository
{
protected $model;
public function __construct(MataPelajaran $mataPelajaran)
{
$this->model = $mataPelajaran;
}
public function find($id)
{
return $this->model->with(["guru", "kelas", "tahunAjaran"])->find($id);
}
public function getData($search, $limit = 10)
{
$search = strtolower($search);
$query = $this->model
->where(function ($query) use ($search) {
$query->where("nama", "like", "%" . $search . "%")
->orWhere("kelas", "like", "%" . $search . "%")
->orWhere("tahun_ajaran", "like", "%" . $search . "%");
})
->orWhereHas("guru", function ($query) use ($search) {
$query->where("nama", "like", "%" . $search . "%");
})
->paginate($limit);
return $query;
}
public function store($data)
{
return $this->model->create([
"nama" => $data["nama"],
"guru_nip" => $data["guru_nip"],
"kelas" => $data["kelas"],
"tahun_ajaran" => $data["tahun_ajaran"],
]);
}
public function update($data, $id)
{
return $this->model->where('id', $id)->update([
"nama" => $data["nama"],
"guru_nip" => $data["guru_nip"],
"kelas" => $data["kelas"],
"tahun_ajaran" => $data["tahun_ajaran"],
]);
}
public function destroy($id)
{
// return $this->model->where('id', $id)->delete();
}
}

View File

@ -0,0 +1,115 @@
<?php
namespace App\Repositories;
use App\Models\Materi;
use App\Models\Siswa;
use App\Notifications\MateriBaruNotification;
use Illuminate\Support\Facades\Auth;
class MateriRepository
{
protected $model;
public function __construct(Materi $materi)
{
$this->model = $materi;
}
public function find($id)
{
return $this->model->with('mataPelajaran')->find($id);
}
public function getDataApi($matapelajaranId, $semester = 1, $type = "buku", $request)
{
$query = $this->model
->where(function ($query) use ($matapelajaranId, $semester, $type) {
$query->where("matapelajaran_id", $matapelajaranId)
->where("semester", $semester)
->where("type", $type);
})
->with("mataPelajaran");
if ($request->user->role == "siswa") {
$query->where(function ($query) use ($request) {
$query->where("tahun_ajaran", $request->tahun_ajaran);
});
} else {
$query->whereHas("mataPelajaran", function ($query) use ($request) {
$query->where("guru_nip", $request->nip);
});
}
$query = $query->get();
return $query;
}
public function getData($search, $limit = 10)
{
$search = strtolower($search);
$guru = Auth::user()->guru;
$query = $this->model->with('mataPelajaran')
->whereHas('mataPelajaran', function ($query) use ($guru) {
$query->where('guru_nip', $guru->nip);
})
->where(function ($query) use ($search) {
$query->where("semester", "like", "%" . $search . "%")
->orWhere("type", "like", "%" . $search . "%")
->orWhere("judul_materi", "like", "%" . $search . "%")
->orWhere("tahun_ajaran", "like", "%" . $search . "%")
->orWhereHas("mataPelajaran", function ($query) use ($search) {
$query->where("nama", "like", "%" . $search . "%");
});
})
->paginate($limit);
return $query;
}
public function store($data)
{
$materi = $this->model->create([
"tanggal" => $data["tanggal"],
"matapelajaran_id" => $data["matapelajaran_id"],
"semester" => $data["semester"],
"type" => $data["type"],
"judul_materi" => $data["judul_materi"],
"deskripsi" => $data["deskripsi"],
"path" => $data["path"],
"tahun_ajaran" => $data["tahun_ajaran"],
]);
// Cari siswa berdasarkan kelas dan tahun ajaran
$siswas = Siswa::where('tahun_ajaran', $data['tahun_ajaran'])->get();
// Kirim notifikasi ke setiap siswa
$materi->load('mataPelajaran');
foreach ($siswas as $siswa) {
$siswa->notify(new MateriBaruNotification($materi));
}
return $materi;
}
public function update($data, $id)
{
return $this->model->where('id', $id)->update([
"tanggal" => $data["tanggal"],
"matapelajaran_id" => $data["matapelajaran_id"],
"semester" => $data["semester"],
"type" => $data["type"],
"judul_materi" => $data["judul_materi"],
"deskripsi" => $data["deskripsi"],
"path" => $data["path"],
"tahun_ajaran" => $data["tahun_ajaran"],
]);
}
public function destroy($id)
{
return $this->model->where('id', $id)->delete();
}
}

View File

@ -0,0 +1,219 @@
<?php
namespace App\Repositories;
use App\Http\Controllers\Traits\ApiResponse;
use App\Models\QuizAttemptAnswers;
use App\Models\QuizAttempts;
use App\Models\QuizLevelSetting;
use App\Models\QuizQuestions;
use App\Models\Quizzes;
use Illuminate\Support\Facades\Auth;
class QuizRepository
{
protected $model;
protected $modelQuizzes;
use ApiResponse;
public function __construct(QuizQuestions $quizQuestions, Quizzes $quizzes)
{
$this->model = $quizQuestions;
$this->modelQuizzes = $quizzes;
}
public function apiGetQuizzes($id)
{
$nisn = Auth::user()->siswa->nisn;
$query = Quizzes::where('matapelajaran_id', $id)
->with([
'quizAttempt' => function ($q) use ($nisn) {
$q->where('nisn', $nisn);
}
])
->get();
return $query;
}
public function apiGetQuizzesGuru($request, $id)
{
$query = Quizzes::where('matapelajaran_id', $id)
->whereHas('mataPelajaran', function ($q) use ($request) {
$q->where('kelas', $request->kelas)
->where('tahun_ajaran', $request->tahun_ajaran);
})
->get();
return $query;
}
public function getData($request)
{
$query = $this->model->with('quiz')
->whereHas('quiz', function ($q) use ($request) {
$q->where('matapelajaran_id', $request->matapelajaran_id)
->where('judul', $request->judul);
})
->whereHas('quiz.mataPelajaran', function ($q) use ($request) {
$q->where('kelas', $request->kelas)
->where('tahun_ajaran', $request->tahun_ajaran);
})->get();
return $query;
}
public function nextQuestion($attempt_id)
{
$attempt = QuizAttempts::findOrFail((int) $attempt_id);
// Ambil total soal tampil dari quiz
$quiz = Quizzes::findOrFail((int) $attempt->quiz_id);
// Kalau sudah mencapai total soal tampil, jangan kasih soal lagi
if ($attempt->jumlahJawaban() >= $quiz->total_soal_tampil) {
return response()->json([
'message' => 'Semua soal sudah dijawab.',
], 200);
}
// Kalau belum, ambil 1 soal random di level sekarang
// Find the attempt by given ID
$question = QuizQuestions::where('quiz_id', $attempt->quiz_id)
->where('level', $attempt->level_akhir)
// Get the total soal tampil from the quiz
->whereDoesntHave('attemptAnswers', function ($q) use ($attempt_id) {
$q->where('attempt_id', $attempt_id);
})
->inRandomOrder()
->first();
$quizAttemptAnswer = QuizAttemptAnswers::where('attempt_id', $attempt_id)->count();
if (!$question) {
return response()->json([
'message' => 'Tidak ada soal lagi di level ini.',
], 404);
}
$question['pertanyaan'] = $quizAttemptAnswer + 1 . ". " . $question->pertanyaan;
return $question;
}
public function answer($request, $attempt_id)
{
$request->validate([
'question_id' => 'required|integer',
// Hilangkan validasi in:a,b,c,d agar jawaban_siswa boleh kosong
'jawaban_siswa' => 'nullable',
]);
// Validasi attempt
$attempt = QuizAttempts::findOrFail($attempt_id);
// Validasi question exists dan belongs to the correct quiz
$question = QuizQuestions::where('id', $request->question_id)
->where('quiz_id', $attempt->quiz_id)
->first();
if (!$question) {
throw new \Exception('The selected question is invalid or does not belong to this quiz.');
}
$quizzes = Quizzes::findOrFail($attempt->quiz_id);
$quizLevelSettings = QuizLevelSetting::where('quiz_id', $attempt->quiz_id)->first();
$jumlahSoalPerLevel = json_decode($quizLevelSettings->jumlah_soal_per_level, true);
$batasNaikLevel = json_decode($quizLevelSettings->batas_naik_level, true);
// Jika jawaban kosong, otomatis salah
$isCorrect = ($request->jawaban_siswa && $request->jawaban_siswa == $question->jawaban_benar) ? 1 : 0;
// Hitung skor tambahan
$skor_tambahan = 0;
if ($isCorrect) {
$skor_tambahan = $question->skor;
}
// Tambahkan skor ke attempt
$attempt->skor += $skor_tambahan;
// Simpan jawaban (jika kosong, simpan string kosong)
QuizAttemptAnswers::create([
'attempt_id' => $attempt->id,
'question_id' => $question->id,
'jawaban_siswa' => $request->jawaban_siswa ?? '',
'benar' => $isCorrect,
]);
// Update jumlah soal dijawab
$attempt->jumlah_soal_dijawab++;
// cek fase
foreach ($jumlahSoalPerLevel as $key => $value) {
$lvlNumber = preg_replace('/[^0-9]/', '', $key);
if ($attempt->fase == $lvlNumber) {
$benar = json_decode($attempt->benar, true) ?? [];
$jumlahFase = count($jumlahSoalPerLevel);
$isLastFase = $attempt->fase == $jumlahFase;
if (!$isLastFase && $isCorrect) {
$benar['fase' . $lvlNumber] = ($benar['fase' . $lvlNumber] ?? 0) + ($isCorrect ? 1 : 0);
}
$attempt->benar = json_encode($benar);
if ($attempt->jumlah_soal_dijawab == $value) {
if ($benar['fase' . $lvlNumber] >= $batasNaikLevel['fase' . $lvlNumber]) {
// if ($attempt->level_akhir < $lvlNumber) {
$attempt->level_akhir += 1;
// }
} else {
$attempt->level_akhir;
}
$attempt->fase += 1;
$attempt->jumlah_soal_dijawab = 0;
}
break;
}
}
$attempt->save();
$jumlah_jawaban = QuizAttemptAnswers::where('attempt_id', $attempt->id)->count();
$total_soal_tampil = $quizzes->total_soal_tampil;
$selesai = $jumlah_jawaban >= $total_soal_tampil;
return [
'quiz_id' => $attempt->quiz_id,
'correct' => $isCorrect,
'fase' => $attempt->fase,
'new_level' => $attempt->level_akhir,
'skor_sementara' => $attempt->skor,
'selesai' => $selesai,
];
}
public function getFinishQuiz($quizId)
{
$attempt = QuizAttempts::where('quiz_id', $quizId)
->where('nisn', Auth::user()->siswa->nisn)
->orderByDesc('id') // ambil attempt terbaru
->first();
if (!$attempt) {
return $this->errorApiResponse('Data tidak ditemukan', 404);
}
$attempt->jumlah_soal = $attempt->quizzes()->first()->total_soal_tampil;
$attempt->jawaban_benar = (string) QuizAttemptAnswers::where('attempt_id', $attempt->id)->where('benar', 1)->count();
$attempt->jawaban_salah = (string) QuizAttemptAnswers::where('attempt_id', $attempt->id)->where('benar', "0")->count();
return $this->okApiResponse($attempt);
}
}

View File

@ -0,0 +1,65 @@
<?php
namespace App\Repositories;
use App\Models\Siswa;
class SiswaRepository
{
protected $model;
public function __construct(Siswa $siswa)
{
$this->model = $siswa;
}
public function find($id)
{
return $this->model->with('user')->find($id);
}
public function getData($search, $limit = 10)
{
$search = strtolower($search);
$query = $this->model
->where(function ($query) use ($search) {
$query->where("nama", "like", "%" . $search . "%")
->orWhere("nisn", "like", "%" . $search . "%");
})
->orWhereHas("user", function ($query) use ($search) {
$query->where("email", "like", "%" . $search . "%");
})
->paginate($limit);
return $query;
}
public function store($data)
{
return $this->model->create([
"user_id" => $data["user_id"],
"nisn" => $data["nisn"],
"nama" => $data["nama"],
"jk" => $data["jk"],
"kelas" => $data["kelas"],
"tahun_ajaran" => $data["tahun_ajaran"],
]);
}
public function update($data, $id)
{
return $this->model->where('id', $id)->update([
"nisn" => $data["nisn"],
"nama" => $data["nama"],
"jk" => $data["jk"],
"kelas" => $data["kelas"],
"tahun_ajaran" => $data["tahun_ajaran"],
]);
}
public function destroy($id)
{
return $this->model->where('id', $id)->delete();
}
}

View File

@ -0,0 +1,60 @@
<?php
namespace App\Repositories;
use App\Models\Guru;
class SubmitTugasRepository
{
protected $model;
public function __construct(Guru $guru)
{
$this->model = $guru;
}
public function find($id)
{
return $this->model->with('user')->find($id);
}
public function getData($search, $limit = 10)
{
$search = strtolower($search);
$query = $this->model
->where(function ($query) use ($search) {
$query->where("nama", "like", "%" . $search . "%");
})
->orWhereHas("user", function ($query) use ($search) {
$query->where("email", "like", "%" . $search . "%");
})
->paginate($limit);
return $query;
}
public function store($data)
{
return $this->model->create([
"user_id" => $data["user_id"],
"nip" => $data["nip"],
"nama" => $data["nama"],
"jk" => $data["jk"],
]);
}
public function update($data, $id)
{
return $this->model->where('id', $id)->update([
"nip" => $data["nip"],
"nama" => $data["nama"],
"jk" => $data["jk"],
]);
}
public function destroy($id)
{
return $this->model->where('id', $id)->delete();
}
}

View File

@ -0,0 +1,54 @@
<?php
namespace App\Repositories;
use App\Models\TahunAjaran;
class TahunAjaranRepository
{
protected $model;
public function __construct(TahunAjaran $tahunAjaran)
{
$this->model = $tahunAjaran;
}
public function find($id)
{
return $this->model->find($id);
}
public function getData($search, $limit = 10)
{
$search = strtolower($search);
$query = $this->model
->where(function ($query) use ($search) {
$query->where("tahun", "like", "%" . $search . "%")
->orWhere("status", "like", "%" . $search . "%");
})
->paginate($limit);
return $query;
}
public function store($data)
{
return $this->model->create([
"tahun" => $data["tahun"],
"status" => "aktif",
]);
}
public function update($data, $tahun)
{
return $this->model->where('tahun', $tahun)->update([
"status" => $data["status"],
]);
}
public function destroy($tahun)
{
return $this->model->where('tahun', $tahun)->delete();
}
}

View File

@ -0,0 +1,111 @@
<?php
namespace App\Repositories;
use App\Models\Siswa;
use App\Models\Tugas;
use App\Notifications\TugasBaruNotification;
use GuzzleHttp\Psr7\Request;
use Illuminate\Support\Facades\Auth;
class TugasRepository
{
protected $model;
protected $nipUser;
public function __construct(Tugas $tugas)
{
$this->model = $tugas;
$this->nipUser = Auth::user()->guru->nip;
}
public function getDataApi($request)
{
$query = $this->model->with('mataPelajaran');
if ($request->user->role == "siswa") {
$query->where(function ($query) use ($request) {
$query->where("kelas", $request->kelas)
->where("tahun_ajaran", $request->tahun_ajaran);
});
} else {
$query->where(function ($query) use ($request) {
$query->where("guru_nip", $request->nip)
->orWhere('kelas', $request->kelas)
->orWhere('kelas', $request->tahun_ajaran);
});
}
$query = $query->get();
return $query;
}
public function find($id)
{
return $this->model->with('mataPelajaran')->find($id);
}
public function getData($search, $limit = 10)
{
$search = strtolower($search);
$nipGuru = Auth::user()->guru->nip;
$query = $this->model->where('guru_nip', $nipGuru)
->where(function ($query) use ($search, $nipGuru) {
$query->where("nama", "like", "%" . $search . "%")
->orWhere("kelas", "like", "%" . $search . "%")
->orWhere("tahun_ajaran", "like", "%" . $search . "%")
->orWhereHas("mataPelajaran", function ($query) use ($search) {
$query->where("nama", "like", "%" . $search . "%");
});
})
->paginate($limit);
return $query;
}
public function store($data)
{
$tugas = $this->model->create([
"tanggal" => $data["tanggal"],
"tenggat" => $data["tenggat"],
"guru_nip" => $this->nipUser,
"nama" => $data["nama"],
"matapelajaran_id" => $data["matapelajaran_id"],
"kelas" => $data["kelas"],
"tahun_ajaran" => $data["tahun_ajaran"],
"deskripsi" => $data["deskripsi"] ?? null,
]);
// Cari siswa berdasarkan kelas dan tahun ajaran
$siswas = Siswa::where('kelas', $data['kelas'])
->where('tahun_ajaran', $data['tahun_ajaran'])
->get();
// Kirim notifikasi ke setiap siswa
foreach ($siswas as $siswa) {
$siswa->notify(new TugasBaruNotification($tugas));
}
return $tugas;
}
public function update($data, $id)
{
return $this->model->where('id', $id)->update([
"tanggal" => $data["tanggal"],
"tenggat" => $data["tenggat"],
"guru_nip" => $this->nipUser,
"nama" => $data["nama"],
"matapelajaran_id" => $data["matapelajaran_id"],
"kelas" => $data["kelas"],
"tahun_ajaran" => $data["tahun_ajaran"],
"deskripsi" => $data["deskripsi"] ?? null,
]);
}
public function destroy($id)
{
return $this->model->where('id', $id)->delete();
}
}

View File

@ -0,0 +1,42 @@
<?php
namespace App\Repositories;
use App\Models\User;
class UserRepository
{
protected $model;
public function __construct(User $user)
{
$this->model = $user;
}
public function find($id)
{
}
public function store($data)
{
return $this->model->create([
"email" => $data["email"],
"password" => $data["pass"],
"role" => $data["role"],
]);
}
public function update($data, $id)
{
return $this->model->where('id', $id)->update([
"email" => $data["email"],
]);
}
public function destroy($id)
{
return $this->model->where('id', $id)->delete();
}
}

View File

@ -0,0 +1,60 @@
<?php
namespace App\Repositories;
use App\Models\WaliKelas;
class WaliKelasRepository
{
protected $model;
public function __construct(WaliKelas $waliKelas)
{
$this->model = $waliKelas;
}
public function find($id)
{
return $this->model->with(["guru", "kelas"])->find($id);
}
public function getData($search, $limit = 10)
{
$search = strtolower($search);
$query = $this->model
->where(function ($query) use ($search) {
$query->where("tahun_ajaran", "like", "%" . $search . "%")
->orWhere("kelas", "like", "%" . $search . "%");
})
->orWhereHas("guru", function ($query) use ($search) {
$query->where("nama", "like", "%" . $search . "%");
})
->paginate($limit);
return $query;
}
public function store($data)
{
return $this->model->create([
"kelas" => $data["kelas"],
"tahun_ajaran" => $data["tahun_ajaran"],
"wali_nip" => $data["wali_nip"],
]);
}
public function update($data, $id)
{
return $this->model->where('id', $id)->update([
"kelas" => $data["kelas"],
"tahun_ajaran" => $data["tahun_ajaran"],
"wali_nip" => $data["wali_nip"],
]);
}
public function destroy($id)
{
return $this->model->where('id', $id)->delete();
}
}

View File

@ -0,0 +1,26 @@
<?php
namespace App\View\Components;
use Closure;
use Illuminate\Contracts\View\View;
use Illuminate\View\Component;
class Icon extends Component
{
/**
* Create a new component instance.
*/
public function __construct()
{
//
}
/**
* Get the view / contents that represent the component.
*/
public function render(): View|Closure|string
{
return view('components.icon');
}
}

15
artisan Normal file
View File

@ -0,0 +1,15 @@
#!/usr/bin/env php
<?php
use Symfony\Component\Console\Input\ArgvInput;
define('LARAVEL_START', microtime(true));
// Register the Composer autoloader...
require __DIR__.'/vendor/autoload.php';
// Bootstrap Laravel and handle the command...
$status = (require_once __DIR__.'/bootstrap/app.php')
->handleCommand(new ArgvInput);
exit($status);

21
bootstrap/app.php Normal file
View File

@ -0,0 +1,21 @@
<?php
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__ . '/../routes/web.php',
api: __DIR__ . '/../routes/api.php',
commands: __DIR__ . '/../routes/console.php',
health: '/up',
)
->withMiddleware(function (Middleware $middleware) {
$middleware->alias([
'role' => \App\Http\Middleware\RoleMiddleware::class,
]);
})
->withExceptions(function (Exceptions $exceptions) {
//
})->create();

2
bootstrap/cache/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*
!.gitignore

5
bootstrap/providers.php Normal file
View File

@ -0,0 +1,5 @@
<?php
return [
App\Providers\AppServiceProvider::class,
];

77
composer.json Normal file
View File

@ -0,0 +1,77 @@
{
"$schema": "https://getcomposer.org/schema.json",
"name": "laravel/laravel",
"type": "project",
"description": "The skeleton application for the Laravel framework.",
"keywords": [
"laravel",
"framework"
],
"license": "MIT",
"require": {
"php": "^8.2",
"laravel/framework": "^11.31",
"laravel/sanctum": "^4.0",
"laravel/tinker": "^2.9",
"maatwebsite/excel": "^3.1",
"realrashid/sweet-alert": "^7.2"
},
"require-dev": {
"fakerphp/faker": "^1.23",
"laravel/pail": "^1.1",
"laravel/pint": "^1.13",
"laravel/sail": "^1.26",
"mockery/mockery": "^1.6",
"nunomaduro/collision": "^8.1",
"phpunit/phpunit": "^11.0.1"
},
"autoload": {
"psr-4": {
"App\\": "app/",
"Database\\Factories\\": "database/factories/",
"Database\\Seeders\\": "database/seeders/"
}
},
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/"
}
},
"scripts": {
"post-autoload-dump": [
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
"@php artisan package:discover --ansi"
],
"post-update-cmd": [
"@php artisan vendor:publish --tag=laravel-assets --ansi --force"
],
"post-root-package-install": [
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
],
"post-create-project-cmd": [
"@php artisan key:generate --ansi",
"@php -r \"file_exists('database/database.sqlite') || touch('database/database.sqlite');\"",
"@php artisan migrate --graceful --ansi"
],
"dev": [
"Composer\\Config::disableProcessTimeout",
"npx concurrently -c \"#93c5fd,#c4b5fd,#fb7185,#fdba74\" \"php artisan serve\" \"php artisan queue:listen --tries=1\" \"php artisan pail --timeout=0\" \"npm run dev\" --names=server,queue,logs,vite"
]
},
"extra": {
"laravel": {
"dont-discover": []
}
},
"config": {
"optimize-autoloader": true,
"preferred-install": "dist",
"sort-packages": true,
"allow-plugins": {
"pestphp/pest-plugin": true,
"php-http/discovery": true
}
},
"minimum-stability": "stable",
"prefer-stable": true
}

8832
composer.lock generated Normal file

File diff suppressed because it is too large Load Diff

126
config/app.php Normal file
View File

@ -0,0 +1,126 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Application Name
|--------------------------------------------------------------------------
|
| This value is the name of your application, which will be used when the
| framework needs to place the application's name in a notification or
| other UI elements where an application name needs to be displayed.
|
*/
'name' => env('APP_NAME', 'Laravel'),
/*
|--------------------------------------------------------------------------
| Application Environment
|--------------------------------------------------------------------------
|
| This value determines the "environment" your application is currently
| running in. This may determine how you prefer to configure various
| services the application utilizes. Set this in your ".env" file.
|
*/
'env' => env('APP_ENV', 'production'),
/*
|--------------------------------------------------------------------------
| Application Debug Mode
|--------------------------------------------------------------------------
|
| When your application is in debug mode, detailed error messages with
| stack traces will be shown on every error that occurs within your
| application. If disabled, a simple generic error page is shown.
|
*/
'debug' => (bool) env('APP_DEBUG', false),
/*
|--------------------------------------------------------------------------
| Application URL
|--------------------------------------------------------------------------
|
| This URL is used by the console to properly generate URLs when using
| the Artisan command line tool. You should set this to the root of
| the application so that it's available within Artisan commands.
|
*/
'url' => env('APP_URL', 'http://localhost'),
/*
|--------------------------------------------------------------------------
| Application Timezone
|--------------------------------------------------------------------------
|
| Here you may specify the default timezone for your application, which
| will be used by the PHP date and date-time functions. The timezone
| is set to "UTC" by default as it is suitable for most use cases.
|
*/
'timezone' => env('APP_TIMEZONE', 'UTC'),
/*
|--------------------------------------------------------------------------
| Application Locale Configuration
|--------------------------------------------------------------------------
|
| The application locale determines the default locale that will be used
| by Laravel's translation / localization methods. This option can be
| set to any locale for which you plan to have translation strings.
|
*/
'locale' => env('APP_LOCALE', 'en'),
'fallback_locale' => env('APP_FALLBACK_LOCALE', 'en'),
'faker_locale' => env('APP_FAKER_LOCALE', 'en_US'),
/*
|--------------------------------------------------------------------------
| Encryption Key
|--------------------------------------------------------------------------
|
| This key is utilized by Laravel's encryption services and should be set
| to a random, 32 character string to ensure that all encrypted values
| are secure. You should do this prior to deploying the application.
|
*/
'cipher' => 'AES-256-CBC',
'key' => env('APP_KEY'),
'previous_keys' => [
...array_filter(
explode(',', env('APP_PREVIOUS_KEYS', ''))
),
],
/*
|--------------------------------------------------------------------------
| Maintenance Mode Driver
|--------------------------------------------------------------------------
|
| These configuration options determine the driver used to determine and
| manage Laravel's "maintenance mode" status. The "cache" driver will
| allow maintenance mode to be controlled across multiple machines.
|
| Supported drivers: "file", "cache"
|
*/
'maintenance' => [
'driver' => env('APP_MAINTENANCE_DRIVER', 'file'),
'store' => env('APP_MAINTENANCE_STORE', 'database'),
],
];

115
config/auth.php Normal file
View File

@ -0,0 +1,115 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Authentication Defaults
|--------------------------------------------------------------------------
|
| This option defines the default authentication "guard" and password
| reset "broker" for your application. You may change these values
| as required, but they're a perfect start for most applications.
|
*/
'defaults' => [
'guard' => env('AUTH_GUARD', 'web'),
'passwords' => env('AUTH_PASSWORD_BROKER', 'users'),
],
/*
|--------------------------------------------------------------------------
| Authentication Guards
|--------------------------------------------------------------------------
|
| Next, you may define every authentication guard for your application.
| Of course, a great default configuration has been defined for you
| which utilizes session storage plus the Eloquent user provider.
|
| All authentication guards have a user provider, which defines how the
| users are actually retrieved out of your database or other storage
| system used by the application. Typically, Eloquent is utilized.
|
| Supported: "session"
|
*/
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
],
/*
|--------------------------------------------------------------------------
| User Providers
|--------------------------------------------------------------------------
|
| All authentication guards have a user provider, which defines how the
| users are actually retrieved out of your database or other storage
| system used by the application. Typically, Eloquent is utilized.
|
| If you have multiple user tables or models you may configure multiple
| providers to represent the model / table. These providers may then
| be assigned to any extra authentication guards you have defined.
|
| Supported: "database", "eloquent"
|
*/
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => env('AUTH_MODEL', App\Models\User::class),
],
// 'users' => [
// 'driver' => 'database',
// 'table' => 'users',
// ],
],
/*
|--------------------------------------------------------------------------
| Resetting Passwords
|--------------------------------------------------------------------------
|
| These configuration options specify the behavior of Laravel's password
| reset functionality, including the table utilized for token storage
| and the user provider that is invoked to actually retrieve users.
|
| The expiry time is the number of minutes that each reset token will be
| considered valid. This security feature keeps tokens short-lived so
| they have less time to be guessed. You may change this as needed.
|
| The throttle setting is the number of seconds a user must wait before
| generating more password reset tokens. This prevents the user from
| quickly generating a very large amount of password reset tokens.
|
*/
'passwords' => [
'users' => [
'provider' => 'users',
'table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_reset_tokens'),
'expire' => 60,
'throttle' => 60,
],
],
/*
|--------------------------------------------------------------------------
| Password Confirmation Timeout
|--------------------------------------------------------------------------
|
| Here you may define the amount of seconds before a password confirmation
| window expires and users are asked to re-enter their password via the
| confirmation screen. By default, the timeout lasts for three hours.
|
*/
'password_timeout' => env('AUTH_PASSWORD_TIMEOUT', 10800),
];

108
config/cache.php Normal file
View File

@ -0,0 +1,108 @@
<?php
use Illuminate\Support\Str;
return [
/*
|--------------------------------------------------------------------------
| Default Cache Store
|--------------------------------------------------------------------------
|
| This option controls the default cache store that will be used by the
| framework. This connection is utilized if another isn't explicitly
| specified when running a cache operation inside the application.
|
*/
'default' => env('CACHE_STORE', 'database'),
/*
|--------------------------------------------------------------------------
| Cache Stores
|--------------------------------------------------------------------------
|
| Here you may define all of the cache "stores" for your application as
| well as their drivers. You may even define multiple stores for the
| same cache driver to group types of items stored in your caches.
|
| Supported drivers: "array", "database", "file", "memcached",
| "redis", "dynamodb", "octane", "null"
|
*/
'stores' => [
'array' => [
'driver' => 'array',
'serialize' => false,
],
'database' => [
'driver' => 'database',
'connection' => env('DB_CACHE_CONNECTION'),
'table' => env('DB_CACHE_TABLE', 'cache'),
'lock_connection' => env('DB_CACHE_LOCK_CONNECTION'),
'lock_table' => env('DB_CACHE_LOCK_TABLE'),
],
'file' => [
'driver' => 'file',
'path' => storage_path('framework/cache/data'),
'lock_path' => storage_path('framework/cache/data'),
],
'memcached' => [
'driver' => 'memcached',
'persistent_id' => env('MEMCACHED_PERSISTENT_ID'),
'sasl' => [
env('MEMCACHED_USERNAME'),
env('MEMCACHED_PASSWORD'),
],
'options' => [
// Memcached::OPT_CONNECT_TIMEOUT => 2000,
],
'servers' => [
[
'host' => env('MEMCACHED_HOST', '127.0.0.1'),
'port' => env('MEMCACHED_PORT', 11211),
'weight' => 100,
],
],
],
'redis' => [
'driver' => 'redis',
'connection' => env('REDIS_CACHE_CONNECTION', 'cache'),
'lock_connection' => env('REDIS_CACHE_LOCK_CONNECTION', 'default'),
],
'dynamodb' => [
'driver' => 'dynamodb',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
'table' => env('DYNAMODB_CACHE_TABLE', 'cache'),
'endpoint' => env('DYNAMODB_ENDPOINT'),
],
'octane' => [
'driver' => 'octane',
],
],
/*
|--------------------------------------------------------------------------
| Cache Key Prefix
|--------------------------------------------------------------------------
|
| When utilizing the APC, database, memcached, Redis, and DynamoDB cache
| stores, there might be other applications using the same cache. For
| that reason, you may prefix every cache key to avoid collisions.
|
*/
'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache_'),
];

173
config/database.php Normal file
View File

@ -0,0 +1,173 @@
<?php
use Illuminate\Support\Str;
return [
/*
|--------------------------------------------------------------------------
| Default Database Connection Name
|--------------------------------------------------------------------------
|
| Here you may specify which of the database connections below you wish
| to use as your default connection for database operations. This is
| the connection which will be utilized unless another connection
| is explicitly specified when you execute a query / statement.
|
*/
'default' => env('DB_CONNECTION', 'sqlite'),
/*
|--------------------------------------------------------------------------
| Database Connections
|--------------------------------------------------------------------------
|
| Below are all of the database connections defined for your application.
| An example configuration is provided for each database system which
| is supported by Laravel. You're free to add / remove connections.
|
*/
'connections' => [
'sqlite' => [
'driver' => 'sqlite',
'url' => env('DB_URL'),
'database' => env('DB_DATABASE', database_path('database.sqlite')),
'prefix' => '',
'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
'busy_timeout' => null,
'journal_mode' => null,
'synchronous' => null,
],
'mysql' => [
'driver' => 'mysql',
'url' => env('DB_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'),
'database' => env('DB_DATABASE', 'laravel'),
'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''),
'unix_socket' => env('DB_SOCKET', ''),
'charset' => env('DB_CHARSET', 'utf8mb4'),
'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
'prefix' => '',
'prefix_indexes' => true,
'strict' => true,
'engine' => null,
'options' => extension_loaded('pdo_mysql') ? array_filter([
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
]) : [],
],
'mariadb' => [
'driver' => 'mariadb',
'url' => env('DB_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'),
'database' => env('DB_DATABASE', 'laravel'),
'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''),
'unix_socket' => env('DB_SOCKET', ''),
'charset' => env('DB_CHARSET', 'utf8mb4'),
'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
'prefix' => '',
'prefix_indexes' => true,
'strict' => true,
'engine' => null,
'options' => extension_loaded('pdo_mysql') ? array_filter([
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
]) : [],
],
'pgsql' => [
'driver' => 'pgsql',
'url' => env('DB_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '5432'),
'database' => env('DB_DATABASE', 'laravel'),
'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''),
'charset' => env('DB_CHARSET', 'utf8'),
'prefix' => '',
'prefix_indexes' => true,
'search_path' => 'public',
'sslmode' => 'prefer',
],
'sqlsrv' => [
'driver' => 'sqlsrv',
'url' => env('DB_URL'),
'host' => env('DB_HOST', 'localhost'),
'port' => env('DB_PORT', '1433'),
'database' => env('DB_DATABASE', 'laravel'),
'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''),
'charset' => env('DB_CHARSET', 'utf8'),
'prefix' => '',
'prefix_indexes' => true,
// 'encrypt' => env('DB_ENCRYPT', 'yes'),
// 'trust_server_certificate' => env('DB_TRUST_SERVER_CERTIFICATE', 'false'),
],
],
/*
|--------------------------------------------------------------------------
| Migration Repository Table
|--------------------------------------------------------------------------
|
| This table keeps track of all the migrations that have already run for
| your application. Using this information, we can determine which of
| the migrations on disk haven't actually been run on the database.
|
*/
'migrations' => [
'table' => 'migrations',
'update_date_on_publish' => true,
],
/*
|--------------------------------------------------------------------------
| Redis Databases
|--------------------------------------------------------------------------
|
| Redis is an open source, fast, and advanced key-value store that also
| provides a richer body of commands than a typical key-value system
| such as Memcached. You may define your connection settings here.
|
*/
'redis' => [
'client' => env('REDIS_CLIENT', 'phpredis'),
'options' => [
'cluster' => env('REDIS_CLUSTER', 'redis'),
'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'),
],
'default' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'username' => env('REDIS_USERNAME'),
'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', '6379'),
'database' => env('REDIS_DB', '0'),
],
'cache' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'username' => env('REDIS_USERNAME'),
'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', '6379'),
'database' => env('REDIS_CACHE_DB', '1'),
],
],
];

380
config/excel.php Normal file
View File

@ -0,0 +1,380 @@
<?php
use Maatwebsite\Excel\Excel;
use PhpOffice\PhpSpreadsheet\Reader\Csv;
return [
'exports' => [
/*
|--------------------------------------------------------------------------
| Chunk size
|--------------------------------------------------------------------------
|
| When using FromQuery, the query is automatically chunked.
| Here you can specify how big the chunk should be.
|
*/
'chunk_size' => 1000,
/*
|--------------------------------------------------------------------------
| Pre-calculate formulas during export
|--------------------------------------------------------------------------
*/
'pre_calculate_formulas' => false,
/*
|--------------------------------------------------------------------------
| Enable strict null comparison
|--------------------------------------------------------------------------
|
| When enabling strict null comparison empty cells ('') will
| be added to the sheet.
*/
'strict_null_comparison' => false,
/*
|--------------------------------------------------------------------------
| CSV Settings
|--------------------------------------------------------------------------
|
| Configure e.g. delimiter, enclosure and line ending for CSV exports.
|
*/
'csv' => [
'delimiter' => ',',
'enclosure' => '"',
'line_ending' => PHP_EOL,
'use_bom' => false,
'include_separator_line' => false,
'excel_compatibility' => false,
'output_encoding' => '',
'test_auto_detect' => true,
],
/*
|--------------------------------------------------------------------------
| Worksheet properties
|--------------------------------------------------------------------------
|
| Configure e.g. default title, creator, subject,...
|
*/
'properties' => [
'creator' => '',
'lastModifiedBy' => '',
'title' => '',
'description' => '',
'subject' => '',
'keywords' => '',
'category' => '',
'manager' => '',
'company' => '',
],
],
'imports' => [
/*
|--------------------------------------------------------------------------
| Read Only
|--------------------------------------------------------------------------
|
| When dealing with imports, you might only be interested in the
| data that the sheet exists. By default we ignore all styles,
| however if you want to do some logic based on style data
| you can enable it by setting read_only to false.
|
*/
'read_only' => true,
/*
|--------------------------------------------------------------------------
| Ignore Empty
|--------------------------------------------------------------------------
|
| When dealing with imports, you might be interested in ignoring
| rows that have null values or empty strings. By default rows
| containing empty strings or empty values are not ignored but can be
| ignored by enabling the setting ignore_empty to true.
|
*/
'ignore_empty' => false,
/*
|--------------------------------------------------------------------------
| Heading Row Formatter
|--------------------------------------------------------------------------
|
| Configure the heading row formatter.
| Available options: none|slug|custom
|
*/
'heading_row' => [
'formatter' => 'slug',
],
/*
|--------------------------------------------------------------------------
| CSV Settings
|--------------------------------------------------------------------------
|
| Configure e.g. delimiter, enclosure and line ending for CSV imports.
|
*/
'csv' => [
'delimiter' => null,
'enclosure' => '"',
'escape_character' => '\\',
'contiguous' => false,
'input_encoding' => Csv::GUESS_ENCODING,
],
/*
|--------------------------------------------------------------------------
| Worksheet properties
|--------------------------------------------------------------------------
|
| Configure e.g. default title, creator, subject,...
|
*/
'properties' => [
'creator' => '',
'lastModifiedBy' => '',
'title' => '',
'description' => '',
'subject' => '',
'keywords' => '',
'category' => '',
'manager' => '',
'company' => '',
],
/*
|--------------------------------------------------------------------------
| Cell Middleware
|--------------------------------------------------------------------------
|
| Configure middleware that is executed on getting a cell value
|
*/
'cells' => [
'middleware' => [
//\Maatwebsite\Excel\Middleware\TrimCellValue::class,
//\Maatwebsite\Excel\Middleware\ConvertEmptyCellValuesToNull::class,
],
],
],
/*
|--------------------------------------------------------------------------
| Extension detector
|--------------------------------------------------------------------------
|
| Configure here which writer/reader type should be used when the package
| needs to guess the correct type based on the extension alone.
|
*/
'extension_detector' => [
'xlsx' => Excel::XLSX,
'xlsm' => Excel::XLSX,
'xltx' => Excel::XLSX,
'xltm' => Excel::XLSX,
'xls' => Excel::XLS,
'xlt' => Excel::XLS,
'ods' => Excel::ODS,
'ots' => Excel::ODS,
'slk' => Excel::SLK,
'xml' => Excel::XML,
'gnumeric' => Excel::GNUMERIC,
'htm' => Excel::HTML,
'html' => Excel::HTML,
'csv' => Excel::CSV,
'tsv' => Excel::TSV,
/*
|--------------------------------------------------------------------------
| PDF Extension
|--------------------------------------------------------------------------
|
| Configure here which Pdf driver should be used by default.
| Available options: Excel::MPDF | Excel::TCPDF | Excel::DOMPDF
|
*/
'pdf' => Excel::DOMPDF,
],
/*
|--------------------------------------------------------------------------
| Value Binder
|--------------------------------------------------------------------------
|
| PhpSpreadsheet offers a way to hook into the process of a value being
| written to a cell. In there some assumptions are made on how the
| value should be formatted. If you want to change those defaults,
| you can implement your own default value binder.
|
| Possible value binders:
|
| [x] Maatwebsite\Excel\DefaultValueBinder::class
| [x] PhpOffice\PhpSpreadsheet\Cell\StringValueBinder::class
| [x] PhpOffice\PhpSpreadsheet\Cell\AdvancedValueBinder::class
|
*/
'value_binder' => [
'default' => Maatwebsite\Excel\DefaultValueBinder::class,
],
'cache' => [
/*
|--------------------------------------------------------------------------
| Default cell caching driver
|--------------------------------------------------------------------------
|
| By default PhpSpreadsheet keeps all cell values in memory, however when
| dealing with large files, this might result into memory issues. If you
| want to mitigate that, you can configure a cell caching driver here.
| When using the illuminate driver, it will store each value in the
| cache store. This can slow down the process, because it needs to
| store each value. You can use the "batch" store if you want to
| only persist to the store when the memory limit is reached.
|
| Drivers: memory|illuminate|batch
|
*/
'driver' => 'memory',
/*
|--------------------------------------------------------------------------
| Batch memory caching
|--------------------------------------------------------------------------
|
| When dealing with the "batch" caching driver, it will only
| persist to the store when the memory limit is reached.
| Here you can tweak the memory limit to your liking.
|
*/
'batch' => [
'memory_limit' => 60000,
],
/*
|--------------------------------------------------------------------------
| Illuminate cache
|--------------------------------------------------------------------------
|
| When using the "illuminate" caching driver, it will automatically use
| your default cache store. However if you prefer to have the cell
| cache on a separate store, you can configure the store name here.
| You can use any store defined in your cache config. When leaving
| at "null" it will use the default store.
|
*/
'illuminate' => [
'store' => null,
],
/*
|--------------------------------------------------------------------------
| Cache Time-to-live (TTL)
|--------------------------------------------------------------------------
|
| The TTL of items written to cache. If you want to keep the items cached
| indefinitely, set this to null. Otherwise, set a number of seconds,
| a \DateInterval, or a callable.
|
| Allowable types: callable|\DateInterval|int|null
|
*/
'default_ttl' => 10800,
],
/*
|--------------------------------------------------------------------------
| Transaction Handler
|--------------------------------------------------------------------------
|
| By default the import is wrapped in a transaction. This is useful
| for when an import may fail and you want to retry it. With the
| transactions, the previous import gets rolled-back.
|
| You can disable the transaction handler by setting this to null.
| Or you can choose a custom made transaction handler here.
|
| Supported handlers: null|db
|
*/
'transactions' => [
'handler' => 'db',
'db' => [
'connection' => null,
],
],
'temporary_files' => [
/*
|--------------------------------------------------------------------------
| Local Temporary Path
|--------------------------------------------------------------------------
|
| When exporting and importing files, we use a temporary file, before
| storing reading or downloading. Here you can customize that path.
| permissions is an array with the permission flags for the directory (dir)
| and the create file (file).
|
*/
'local_path' => storage_path('framework/cache/laravel-excel'),
/*
|--------------------------------------------------------------------------
| Local Temporary Path Permissions
|--------------------------------------------------------------------------
|
| Permissions is an array with the permission flags for the directory (dir)
| and the create file (file).
| If omitted the default permissions of the filesystem will be used.
|
*/
'local_permissions' => [
// 'dir' => 0755,
// 'file' => 0644,
],
/*
|--------------------------------------------------------------------------
| Remote Temporary Disk
|--------------------------------------------------------------------------
|
| When dealing with a multi server setup with queues in which you
| cannot rely on having a shared local temporary path, you might
| want to store the temporary file on a shared disk. During the
| queue executing, we'll retrieve the temporary file from that
| location instead. When left to null, it will always use
| the local path. This setting only has effect when using
| in conjunction with queued imports and exports.
|
*/
'remote_disk' => null,
'remote_prefix' => null,
/*
|--------------------------------------------------------------------------
| Force Resync
|--------------------------------------------------------------------------
|
| When dealing with a multi server setup as above, it's possible
| for the clean up that occurs after entire queue has been run to only
| cleanup the server that the last AfterImportJob runs on. The rest of the server
| would still have the local temporary file stored on it. In this case your
| local storage limits can be exceeded and future imports won't be processed.
| To mitigate this you can set this config value to be true, so that after every
| queued chunk is processed the local temporary file is deleted on the server that
| processed it.
|
*/
'force_resync_remote' => null,
],
];

80
config/filesystems.php Normal file
View File

@ -0,0 +1,80 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Default Filesystem Disk
|--------------------------------------------------------------------------
|
| Here you may specify the default filesystem disk that should be used
| by the framework. The "local" disk, as well as a variety of cloud
| based disks are available to your application for file storage.
|
*/
'default' => env('FILESYSTEM_DISK', 'local'),
/*
|--------------------------------------------------------------------------
| Filesystem Disks
|--------------------------------------------------------------------------
|
| Below you may configure as many filesystem disks as necessary, and you
| may even configure multiple disks for the same driver. Examples for
| most supported storage drivers are configured here for reference.
|
| Supported drivers: "local", "ftp", "sftp", "s3"
|
*/
'disks' => [
'local' => [
'driver' => 'local',
'root' => storage_path('app/private'),
'serve' => true,
'throw' => false,
'report' => false,
],
'public' => [
'driver' => 'local',
'root' => storage_path('app/public'),
'url' => env('APP_URL').'/storage',
'visibility' => 'public',
'throw' => false,
'report' => false,
],
's3' => [
'driver' => 's3',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION'),
'bucket' => env('AWS_BUCKET'),
'url' => env('AWS_URL'),
'endpoint' => env('AWS_ENDPOINT'),
'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false),
'throw' => false,
'report' => false,
],
],
/*
|--------------------------------------------------------------------------
| Symbolic Links
|--------------------------------------------------------------------------
|
| Here you may configure the symbolic links that will be created when the
| `storage:link` Artisan command is executed. The array keys should be
| the locations of the links and the values should be their targets.
|
*/
'links' => [
public_path('storage') => storage_path('app/public'),
],
];

132
config/logging.php Normal file
View File

@ -0,0 +1,132 @@
<?php
use Monolog\Handler\NullHandler;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\SyslogUdpHandler;
use Monolog\Processor\PsrLogMessageProcessor;
return [
/*
|--------------------------------------------------------------------------
| Default Log Channel
|--------------------------------------------------------------------------
|
| This option defines the default log channel that is utilized to write
| messages to your logs. The value provided here should match one of
| the channels present in the list of "channels" configured below.
|
*/
'default' => env('LOG_CHANNEL', 'stack'),
/*
|--------------------------------------------------------------------------
| Deprecations Log Channel
|--------------------------------------------------------------------------
|
| This option controls the log channel that should be used to log warnings
| regarding deprecated PHP and library features. This allows you to get
| your application ready for upcoming major versions of dependencies.
|
*/
'deprecations' => [
'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'),
'trace' => env('LOG_DEPRECATIONS_TRACE', false),
],
/*
|--------------------------------------------------------------------------
| Log Channels
|--------------------------------------------------------------------------
|
| Here you may configure the log channels for your application. Laravel
| utilizes the Monolog PHP logging library, which includes a variety
| of powerful log handlers and formatters that you're free to use.
|
| Available drivers: "single", "daily", "slack", "syslog",
| "errorlog", "monolog", "custom", "stack"
|
*/
'channels' => [
'stack' => [
'driver' => 'stack',
'channels' => explode(',', env('LOG_STACK', 'single')),
'ignore_exceptions' => false,
],
'single' => [
'driver' => 'single',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
'replace_placeholders' => true,
],
'daily' => [
'driver' => 'daily',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
'days' => env('LOG_DAILY_DAYS', 14),
'replace_placeholders' => true,
],
'slack' => [
'driver' => 'slack',
'url' => env('LOG_SLACK_WEBHOOK_URL'),
'username' => env('LOG_SLACK_USERNAME', 'Laravel Log'),
'emoji' => env('LOG_SLACK_EMOJI', ':boom:'),
'level' => env('LOG_LEVEL', 'critical'),
'replace_placeholders' => true,
],
'papertrail' => [
'driver' => 'monolog',
'level' => env('LOG_LEVEL', 'debug'),
'handler' => env('LOG_PAPERTRAIL_HANDLER', SyslogUdpHandler::class),
'handler_with' => [
'host' => env('PAPERTRAIL_URL'),
'port' => env('PAPERTRAIL_PORT'),
'connectionString' => 'tls://'.env('PAPERTRAIL_URL').':'.env('PAPERTRAIL_PORT'),
],
'processors' => [PsrLogMessageProcessor::class],
],
'stderr' => [
'driver' => 'monolog',
'level' => env('LOG_LEVEL', 'debug'),
'handler' => StreamHandler::class,
'formatter' => env('LOG_STDERR_FORMATTER'),
'with' => [
'stream' => 'php://stderr',
],
'processors' => [PsrLogMessageProcessor::class],
],
'syslog' => [
'driver' => 'syslog',
'level' => env('LOG_LEVEL', 'debug'),
'facility' => env('LOG_SYSLOG_FACILITY', LOG_USER),
'replace_placeholders' => true,
],
'errorlog' => [
'driver' => 'errorlog',
'level' => env('LOG_LEVEL', 'debug'),
'replace_placeholders' => true,
],
'null' => [
'driver' => 'monolog',
'handler' => NullHandler::class,
],
'emergency' => [
'path' => storage_path('logs/laravel.log'),
],
],
];

116
config/mail.php Normal file
View File

@ -0,0 +1,116 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Default Mailer
|--------------------------------------------------------------------------
|
| This option controls the default mailer that is used to send all email
| messages unless another mailer is explicitly specified when sending
| the message. All additional mailers can be configured within the
| "mailers" array. Examples of each type of mailer are provided.
|
*/
'default' => env('MAIL_MAILER', 'log'),
/*
|--------------------------------------------------------------------------
| Mailer Configurations
|--------------------------------------------------------------------------
|
| Here you may configure all of the mailers used by your application plus
| their respective settings. Several examples have been configured for
| you and you are free to add your own as your application requires.
|
| Laravel supports a variety of mail "transport" drivers that can be used
| when delivering an email. You may specify which one you're using for
| your mailers below. You may also add additional mailers if needed.
|
| Supported: "smtp", "sendmail", "mailgun", "ses", "ses-v2",
| "postmark", "resend", "log", "array",
| "failover", "roundrobin"
|
*/
'mailers' => [
'smtp' => [
'transport' => 'smtp',
'scheme' => env('MAIL_SCHEME'),
'url' => env('MAIL_URL'),
'host' => env('MAIL_HOST', '127.0.0.1'),
'port' => env('MAIL_PORT', 2525),
'username' => env('MAIL_USERNAME'),
'password' => env('MAIL_PASSWORD'),
'timeout' => null,
'local_domain' => env('MAIL_EHLO_DOMAIN', parse_url(env('APP_URL', 'http://localhost'), PHP_URL_HOST)),
],
'ses' => [
'transport' => 'ses',
],
'postmark' => [
'transport' => 'postmark',
// 'message_stream_id' => env('POSTMARK_MESSAGE_STREAM_ID'),
// 'client' => [
// 'timeout' => 5,
// ],
],
'resend' => [
'transport' => 'resend',
],
'sendmail' => [
'transport' => 'sendmail',
'path' => env('MAIL_SENDMAIL_PATH', '/usr/sbin/sendmail -bs -i'),
],
'log' => [
'transport' => 'log',
'channel' => env('MAIL_LOG_CHANNEL'),
],
'array' => [
'transport' => 'array',
],
'failover' => [
'transport' => 'failover',
'mailers' => [
'smtp',
'log',
],
],
'roundrobin' => [
'transport' => 'roundrobin',
'mailers' => [
'ses',
'postmark',
],
],
],
/*
|--------------------------------------------------------------------------
| Global "From" Address
|--------------------------------------------------------------------------
|
| You may wish for all emails sent by your application to be sent from
| the same address. Here you may specify a name and address that is
| used globally for all emails that are sent by your application.
|
*/
'from' => [
'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'),
'name' => env('MAIL_FROM_NAME', 'Example'),
],
];

112
config/queue.php Normal file
View File

@ -0,0 +1,112 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Default Queue Connection Name
|--------------------------------------------------------------------------
|
| Laravel's queue supports a variety of backends via a single, unified
| API, giving you convenient access to each backend using identical
| syntax for each. The default queue connection is defined below.
|
*/
'default' => env('QUEUE_CONNECTION', 'database'),
/*
|--------------------------------------------------------------------------
| Queue Connections
|--------------------------------------------------------------------------
|
| Here you may configure the connection options for every queue backend
| used by your application. An example configuration is provided for
| each backend supported by Laravel. You're also free to add more.
|
| Drivers: "sync", "database", "beanstalkd", "sqs", "redis", "null"
|
*/
'connections' => [
'sync' => [
'driver' => 'sync',
],
'database' => [
'driver' => 'database',
'connection' => env('DB_QUEUE_CONNECTION'),
'table' => env('DB_QUEUE_TABLE', 'jobs'),
'queue' => env('DB_QUEUE', 'default'),
'retry_after' => (int) env('DB_QUEUE_RETRY_AFTER', 90),
'after_commit' => false,
],
'beanstalkd' => [
'driver' => 'beanstalkd',
'host' => env('BEANSTALKD_QUEUE_HOST', 'localhost'),
'queue' => env('BEANSTALKD_QUEUE', 'default'),
'retry_after' => (int) env('BEANSTALKD_QUEUE_RETRY_AFTER', 90),
'block_for' => 0,
'after_commit' => false,
],
'sqs' => [
'driver' => 'sqs',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'),
'queue' => env('SQS_QUEUE', 'default'),
'suffix' => env('SQS_SUFFIX'),
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
'after_commit' => false,
],
'redis' => [
'driver' => 'redis',
'connection' => env('REDIS_QUEUE_CONNECTION', 'default'),
'queue' => env('REDIS_QUEUE', 'default'),
'retry_after' => (int) env('REDIS_QUEUE_RETRY_AFTER', 90),
'block_for' => null,
'after_commit' => false,
],
],
/*
|--------------------------------------------------------------------------
| Job Batching
|--------------------------------------------------------------------------
|
| The following options configure the database and table that store job
| batching information. These options can be updated to any database
| connection and table which has been defined by your application.
|
*/
'batching' => [
'database' => env('DB_CONNECTION', 'sqlite'),
'table' => 'job_batches',
],
/*
|--------------------------------------------------------------------------
| Failed Queue Jobs
|--------------------------------------------------------------------------
|
| These options configure the behavior of failed queue job logging so you
| can control how and where failed jobs are stored. Laravel ships with
| support for storing failed jobs in a simple file or in a database.
|
| Supported drivers: "database-uuids", "dynamodb", "file", "null"
|
*/
'failed' => [
'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'),
'database' => env('DB_CONNECTION', 'sqlite'),
'table' => 'failed_jobs',
],
];

83
config/sanctum.php Normal file
View File

@ -0,0 +1,83 @@
<?php
use Laravel\Sanctum\Sanctum;
return [
/*
|--------------------------------------------------------------------------
| Stateful Domains
|--------------------------------------------------------------------------
|
| Requests from the following domains / hosts will receive stateful API
| authentication cookies. Typically, these should include your local
| and production domains which access your API via a frontend SPA.
|
*/
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
'%s%s',
'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1',
Sanctum::currentApplicationUrlWithPort()
))),
/*
|--------------------------------------------------------------------------
| Sanctum Guards
|--------------------------------------------------------------------------
|
| This array contains the authentication guards that will be checked when
| Sanctum is trying to authenticate a request. If none of these guards
| are able to authenticate the request, Sanctum will use the bearer
| token that's present on an incoming request for authentication.
|
*/
'guard' => ['web'],
/*
|--------------------------------------------------------------------------
| Expiration Minutes
|--------------------------------------------------------------------------
|
| This value controls the number of minutes until an issued token will be
| considered expired. This will override any values set in the token's
| "expires_at" attribute, but first-party sessions are not affected.
|
*/
'expiration' => null,
/*
|--------------------------------------------------------------------------
| Token Prefix
|--------------------------------------------------------------------------
|
| Sanctum can prefix new tokens in order to take advantage of numerous
| security scanning initiatives maintained by open source platforms
| that notify developers if they commit tokens into repositories.
|
| See: https://docs.github.com/en/code-security/secret-scanning/about-secret-scanning
|
*/
'token_prefix' => env('SANCTUM_TOKEN_PREFIX', ''),
/*
|--------------------------------------------------------------------------
| Sanctum Middleware
|--------------------------------------------------------------------------
|
| When authenticating your first-party SPA with Sanctum you may need to
| customize some of the middleware Sanctum uses while processing the
| request. You may change the middleware listed below as required.
|
*/
'middleware' => [
'authenticate_session' => Laravel\Sanctum\Http\Middleware\AuthenticateSession::class,
'encrypt_cookies' => Illuminate\Cookie\Middleware\EncryptCookies::class,
'validate_csrf_token' => Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class,
],
];

38
config/services.php Normal file
View File

@ -0,0 +1,38 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Third Party Services
|--------------------------------------------------------------------------
|
| This file is for storing the credentials for third party services such
| as Mailgun, Postmark, AWS and more. This file provides the de facto
| location for this type of information, allowing packages to have
| a conventional file to locate the various service credentials.
|
*/
'postmark' => [
'token' => env('POSTMARK_TOKEN'),
],
'ses' => [
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
],
'resend' => [
'key' => env('RESEND_KEY'),
],
'slack' => [
'notifications' => [
'bot_user_oauth_token' => env('SLACK_BOT_USER_OAUTH_TOKEN'),
'channel' => env('SLACK_BOT_USER_DEFAULT_CHANNEL'),
],
],
];

217
config/session.php Normal file
View File

@ -0,0 +1,217 @@
<?php
use Illuminate\Support\Str;
return [
/*
|--------------------------------------------------------------------------
| Default Session Driver
|--------------------------------------------------------------------------
|
| This option determines the default session driver that is utilized for
| incoming requests. Laravel supports a variety of storage options to
| persist session data. Database storage is a great default choice.
|
| Supported: "file", "cookie", "database", "apc",
| "memcached", "redis", "dynamodb", "array"
|
*/
'driver' => env('SESSION_DRIVER', 'database'),
/*
|--------------------------------------------------------------------------
| Session Lifetime
|--------------------------------------------------------------------------
|
| Here you may specify the number of minutes that you wish the session
| to be allowed to remain idle before it expires. If you want them
| to expire immediately when the browser is closed then you may
| indicate that via the expire_on_close configuration option.
|
*/
'lifetime' => (int) env('SESSION_LIFETIME', 120),
'expire_on_close' => env('SESSION_EXPIRE_ON_CLOSE', false),
/*
|--------------------------------------------------------------------------
| Session Encryption
|--------------------------------------------------------------------------
|
| This option allows you to easily specify that all of your session data
| should be encrypted before it's stored. All encryption is performed
| automatically by Laravel and you may use the session like normal.
|
*/
'encrypt' => env('SESSION_ENCRYPT', false),
/*
|--------------------------------------------------------------------------
| Session File Location
|--------------------------------------------------------------------------
|
| When utilizing the "file" session driver, the session files are placed
| on disk. The default storage location is defined here; however, you
| are free to provide another location where they should be stored.
|
*/
'files' => storage_path('framework/sessions'),
/*
|--------------------------------------------------------------------------
| Session Database Connection
|--------------------------------------------------------------------------
|
| When using the "database" or "redis" session drivers, you may specify a
| connection that should be used to manage these sessions. This should
| correspond to a connection in your database configuration options.
|
*/
'connection' => env('SESSION_CONNECTION'),
/*
|--------------------------------------------------------------------------
| Session Database Table
|--------------------------------------------------------------------------
|
| When using the "database" session driver, you may specify the table to
| be used to store sessions. Of course, a sensible default is defined
| for you; however, you're welcome to change this to another table.
|
*/
'table' => env('SESSION_TABLE', 'sessions'),
/*
|--------------------------------------------------------------------------
| Session Cache Store
|--------------------------------------------------------------------------
|
| When using one of the framework's cache driven session backends, you may
| define the cache store which should be used to store the session data
| between requests. This must match one of your defined cache stores.
|
| Affects: "apc", "dynamodb", "memcached", "redis"
|
*/
'store' => env('SESSION_STORE'),
/*
|--------------------------------------------------------------------------
| Session Sweeping Lottery
|--------------------------------------------------------------------------
|
| Some session drivers must manually sweep their storage location to get
| rid of old sessions from storage. Here are the chances that it will
| happen on a given request. By default, the odds are 2 out of 100.
|
*/
'lottery' => [2, 100],
/*
|--------------------------------------------------------------------------
| Session Cookie Name
|--------------------------------------------------------------------------
|
| Here you may change the name of the session cookie that is created by
| the framework. Typically, you should not need to change this value
| since doing so does not grant a meaningful security improvement.
|
*/
'cookie' => env(
'SESSION_COOKIE',
Str::slug(env('APP_NAME', 'laravel'), '_').'_session'
),
/*
|--------------------------------------------------------------------------
| Session Cookie Path
|--------------------------------------------------------------------------
|
| The session cookie path determines the path for which the cookie will
| be regarded as available. Typically, this will be the root path of
| your application, but you're free to change this when necessary.
|
*/
'path' => env('SESSION_PATH', '/'),
/*
|--------------------------------------------------------------------------
| Session Cookie Domain
|--------------------------------------------------------------------------
|
| This value determines the domain and subdomains the session cookie is
| available to. By default, the cookie will be available to the root
| domain and all subdomains. Typically, this shouldn't be changed.
|
*/
'domain' => env('SESSION_DOMAIN'),
/*
|--------------------------------------------------------------------------
| HTTPS Only Cookies
|--------------------------------------------------------------------------
|
| By setting this option to true, session cookies will only be sent back
| to the server if the browser has a HTTPS connection. This will keep
| the cookie from being sent to you when it can't be done securely.
|
*/
'secure' => env('SESSION_SECURE_COOKIE'),
/*
|--------------------------------------------------------------------------
| HTTP Access Only
|--------------------------------------------------------------------------
|
| Setting this value to true will prevent JavaScript from accessing the
| value of the cookie and the cookie will only be accessible through
| the HTTP protocol. It's unlikely you should disable this option.
|
*/
'http_only' => env('SESSION_HTTP_ONLY', true),
/*
|--------------------------------------------------------------------------
| Same-Site Cookies
|--------------------------------------------------------------------------
|
| This option determines how your cookies behave when cross-site requests
| take place, and can be used to mitigate CSRF attacks. By default, we
| will set this value to "lax" to permit secure cross-site requests.
|
| See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value
|
| Supported: "lax", "strict", "none", null
|
*/
'same_site' => env('SESSION_SAME_SITE', 'lax'),
/*
|--------------------------------------------------------------------------
| Partitioned Cookies
|--------------------------------------------------------------------------
|
| Setting this value to true will tie the cookie to the top-level site for
| a cross-site context. Partitioned cookies are accepted by the browser
| when flagged "secure" and the Same-Site attribute is set to "none".
|
*/
'partitioned' => env('SESSION_PARTITIONED_COOKIE', false),
];

267
config/sweetalert.php Normal file
View File

@ -0,0 +1,267 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Theme
|--------------------------------------------------------------------------
| The theme to use for SweetAlert2 popups.
| Available themes: dark, minimal, borderless, bootstrap-4, material-ui, wordpress-admin, bulma.
|
*/
'theme' => env('SWEET_ALERT_THEME', 'default'),
/*
|--------------------------------------------------------------------------
| CDN LINK
|--------------------------------------------------------------------------
| By default SweetAlert2 use its local sweetalert.all.js
| file.
| However, you can use its cdn if you want.
|
*/
'cdn' => env('SWEET_ALERT_CDN'),
/*
|--------------------------------------------------------------------------
| Always load the sweetalert.all.js
|--------------------------------------------------------------------------
| There might be situations where you will always want the sweet alert
| js package to be there for you. (for eg. you might use it heavily to
| show notifications or you might want to use the native js) then this
| might be handy.
|
*/
'alwaysLoadJS' => env('SWEET_ALERT_ALWAYS_LOAD_JS', false),
/*
|--------------------------------------------------------------------------
| Never load the sweetalert.all.js
|--------------------------------------------------------------------------
| If you want to handle the sweet alert js package by yourself
| (for eg. you might want to use laravel mix) then this can be
| handy.
|
| alwaysLoadJs = true & neverLoadJs = true => js will not be loaded
| alwaysLoadJs = true & neverLoadJs = false => js will be loaded
| alwaysLoadJs = false & neverLoadJs = false => js will be loaded when
| you set alert/toast by using the facade/helper functions.
*/
'neverLoadJS' => env('SWEET_ALERT_NEVER_LOAD_JS', false),
/*
|--------------------------------------------------------------------------
| AutoClose Timer
|--------------------------------------------------------------------------
|
| This is for the all Modal windows.
| For specific modal just use the autoClose() helper method.
|
*/
'timer' => env('SWEET_ALERT_TIMER', 5000),
/*
|--------------------------------------------------------------------------
| Width
|--------------------------------------------------------------------------
|
| Modal window width, including paddings (box-sizing: border-box).
| Can be in px or %.
| The default width is 32rem.
| This is for the all Modal windows.
| for particular modal just use the width() helper method.
*/
'width' => env('SWEET_ALERT_WIDTH', '32rem'),
/*
|--------------------------------------------------------------------------
| Height Auto
|--------------------------------------------------------------------------
| By default, SweetAlert2 sets html's and body's CSS height to auto !important.
| If this behavior isn't compatible with your project's layout,
| set heightAuto to false.
|
*/
'height_auto' => env('SWEET_ALERT_HEIGHT_AUTO', true),
/*
|--------------------------------------------------------------------------
| Padding
|--------------------------------------------------------------------------
|
| Modal window padding.
| Can be in px or %.
| The default padding is 1.25rem.
| This is for the all Modal windows.
| for particular modal just use the padding() helper method.
*/
'padding' => env('SWEET_ALERT_PADDING', '1.25rem'),
/*
|--------------------------------------------------------------------------
| Background
|--------------------------------------------------------------------------
|
| Modal window background
| (CSS background property).
| The default background is '#fff'.
*/
'background' => env('SWEET_ALERT_BACKGROUND', '#fff'),
/*
|--------------------------------------------------------------------------
| Animation
|--------------------------------------------------------------------------
| Custom animation with [Animate.css](https://daneden.github.io/animate.css/)
| If set to false, modal CSS animation will be use default ones.
| For specific modal just use the animation() helper method.
|
*/
'animation' => [
'enable' => env('SWEET_ALERT_ANIMATION_ENABLE', false),
],
'animatecss' => env('SWEET_ALERT_ANIMATECSS', 'https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css'),
/*
|--------------------------------------------------------------------------
| ShowConfirmButton
|--------------------------------------------------------------------------
| If set to false, a "Confirm"-button will not be shown.
| It can be useful when you're using custom HTML description.
| This is for the all Modal windows.
| For specific modal just use the showConfirmButton() helper method.
|
*/
'show_confirm_button' => env('SWEET_ALERT_CONFIRM_BUTTON', true),
/*
|--------------------------------------------------------------------------
| ShowCloseButton
|--------------------------------------------------------------------------
| If set to true, a "Close"-button will be shown,
| which the user can click on to dismiss the modal.
| This is for the all Modal windows.
| For specific modal just use the showCloseButton() helper method.
|
*/
'show_close_button' => env('SWEET_ALERT_CLOSE_BUTTON', false),
/*
|-----------------------------------------------------------------------
| Confirm/Cancel Button Text
|-----------------------------------------------------------------------
| Change the default text of the modal buttons.
| The texts translations will be handled by Laravel at runtime.
| This is for the all Modal windows.
| For specific modal just use the confirmButtonText() and
| cancelButtonText() helper methods.
*/
'button_text' => [
'confirm' => env('SWEET_ALERT_CONFIRM_BUTTON_TEXT', 'OK'),
'cancel' => env('SWEET_ALERT_CANCEL_BUTTON_TEXT', 'Cancel'),
],
/*
|--------------------------------------------------------------------------
| Toast position
|--------------------------------------------------------------------------
| Modal window or toast position, can be 'top',
| 'top-start', 'top-end', 'center', 'center-start',
| 'center-end', 'bottom', 'bottom-start', or 'bottom-end'.
| For specific modal just use the position() helper method.
|
*/
'toast_position' => env('SWEET_ALERT_TOAST_POSITION', 'top-end'),
/*
|--------------------------------------------------------------------------
| Progress Bar
|--------------------------------------------------------------------------
| If set to true, a progress bar at the bottom of a popup will be shown.
| It can be useful with toasts.
|
*/
'timer_progress_bar' => env('SWEET_ALERT_TIMER_PROGRESS_BAR', false),
/*
|--------------------------------------------------------------------------
| Middleware
|--------------------------------------------------------------------------
| Modal window or toast, config for the Middleware
|
*/
'middleware' => [
'autoClose' => env('SWEET_ALERT_MIDDLEWARE_AUTO_CLOSE', false),
'toast_position' => env('SWEET_ALERT_MIDDLEWARE_TOAST_POSITION', 'top-end'),
'toast_close_button' => env('SWEET_ALERT_MIDDLEWARE_TOAST_CLOSE_BUTTON', true),
'timer' => env('SWEET_ALERT_MIDDLEWARE_ALERT_CLOSE_TIME', 6000),
'auto_display_error_messages' => env('SWEET_ALERT_AUTO_DISPLAY_ERROR_MESSAGES', true),
],
/*
|--------------------------------------------------------------------------
| Custom Class
|--------------------------------------------------------------------------
| A custom CSS class for the modal:
|
*/
'customClass' => [
'container' => env('SWEET_ALERT_CONTAINER_CLASS'),
'popup' => env('SWEET_ALERT_POPUP_CLASS'),
'header' => env('SWEET_ALERT_HEADER_CLASS'),
'title' => env('SWEET_ALERT_TITLE_CLASS'),
'closeButton' => env('SWEET_ALERT_CLOSE_BUTTON_CLASS'),
'icon' => env('SWEET_ALERT_ICON_CLASS'),
'image' => env('SWEET_ALERT_IMAGE_CLASS'),
'content' => env('SWEET_ALERT_CONTENT_CLASS'),
'input' => env('SWEET_ALERT_INPUT_CLASS'),
'actions' => env('SWEET_ALERT_ACTIONS_CLASS'),
'confirmButton' => env('SWEET_ALERT_CONFIRM_BUTTON_CLASS'),
'cancelButton' => env('SWEET_ALERT_CANCEL_BUTTON_CLASS'),
'footer' => env('SWEET_ALERT_FOOTER_CLASS'),
],
/*
|--------------------------------------------------------------------------
| confirmDelete
|--------------------------------------------------------------------------
| customize the configuration options of the confirmation popup.
|
*/
'confirm_delete_confirm_button_text' => env('SWEET_ALERT_CONFIRM_DELETE_CONFIRM_BUTTON_TEXT', 'Yes, delete it!'),
'confirm_delete_confirm_button_color' => env('SWEET_ALERT_CONFIRM_DELETE_CONFIRM_BUTTON_COLOR'),
'confirm_delete_cancel_button_color' => env('SWEET_ALERT_CONFIRM_DELETE_CANCEL_BUTTON_COLOR', '#d33'),
'confirm_delete_cancel_button_text' => env('SWEET_ALERT_CONFIRM_DELETE_CANCEL_BUTTON_TEXT', 'Cancel'),
'confirm_delete_show_cancel_button' => env('SWEET_ALERT_CONFIRM_DELETE_SHOW_CANCEL_BUTTON', true),
'confirm_delete_show_close_button' => env('SWEET_ALERT_CONFIRM_DELETE_SHOW_CLOSE_BUTTON', false),
'confirm_delete_icon' => env('SWEET_ALERT_CONFIRM_DELETE_ICON', 'warning'),
'confirm_delete_show_loader_on_confirm' => env('SWEET_ALERT_CONFIRM_DELETE_SHOW_LOADER_ON_CONFIRM', true),
];

Some files were not shown because too many files have changed in this diff Show More