diff --git a/app/Http/Controllers/API/ProgressController.php b/app/Http/Controllers/API/ProgressController.php new file mode 100644 index 0000000..29563af --- /dev/null +++ b/app/Http/Controllers/API/ProgressController.php @@ -0,0 +1,288 @@ +middleware('auth:sanctum'); + } + + // Method untuk mendapatkan progres materi + public function getMateriProgress($materiId) + { + // Ambil user yang terotentikasi + $user = Auth::user(); + + // Ambil materi berdasarkan ID + $materi = Materi::findOrFail($materiId); + + // Menghitung total kategori dan submateri yang selesai + $totalSubmateri = 0; + $completedSubmateri = 0; + + // Looping untuk setiap kategori dari materi + foreach ($materi->kategori as $kategori) { + $totalSubmateri += $kategori->subMateri()->count(); + + // Menghitung jumlah submateri yang selesai untuk kategori ini + $completedSubmateri += Progress::where('user_id', $user->id) + ->where('status', 'selesai') + ->whereIn('sub_materi_id', $kategori->subMateri->pluck('id')) + ->count(); + } + + // Menghitung persentase progres + $progressPercentage = ($totalSubmateri > 0) ? ($completedSubmateri / $totalSubmateri) * 100 : 0; + + return response()->json([ + 'materi' => $materi->title, + 'total_submateri' => $totalSubmateri, + 'completed_submateri' => $completedSubmateri, + 'progress_percentage' => $progressPercentage + ]); + } + + // Method untuk update progress + public function updateProgress(Request $request, $subMateriId) + { + $user = Auth::user(); + + // Update atau buat data progres baru + $progress = Progress::updateOrCreate( + ['user_id' => $user->id, 'sub_materi_id' => $subMateriId], + ['status' => 'selesai'] + ); + + return response()->json(['message' => 'Progress updated successfully']); + } + + public function getProgressBySubMateri($subMateriId) +{ + // Ambil user yang terotentikasi + $user = Auth::user(); + + // Cek apakah ada progres untuk user ini pada submateri yang dimaksud, ambil yang terbaru + $progress = Progress::where('user_id', $user->id) + ->where('sub_materi_id', $subMateriId) + ->latest() // Mengambil data yang paling baru berdasarkan 'updated_at' + ->first(); + + // Jika progres ada, kembalikan statusnya + if ($progress) { + return response()->json([ + 'sub_materi_id' => $subMateriId, + 'status' => $progress->status, // 'selesai', 'gagal', atau status lainnya + 'message' => 'Progress found', + ]); + } + + // Jika tidak ada progres, kembalikan status belum selesai + return response()->json([ + 'sub_materi_id' => $subMateriId, + 'status' => 'belum selesai', // Default status + 'message' => 'No progress found', + ]); +} + + + public function getProgressPercentage(Request $request) + { + // Ambil user yang terotentikasi + $user = Auth::user(); + + // Ambil semua submateri yang ada di sistem + $totalSubmateri = SubMateri::count(); // Menghitung semua submateri yang ada + $completedSubmateri = Progress::where('user_id', $user->id) + ->where('status', 'selesai') + ->count(); // Menghitung submateri yang statusnya selesai + + // Menghitung persentase progres + $progressPercentage = ($totalSubmateri > 0) ? ($completedSubmateri / $totalSubmateri) * 100 : 0; + + // Return hasil persentase progres + return response()->json([ + 'user_id' => $user->id, + 'nama_lengkap' => $user->nama_lengkap, + 'total_submateri' => $totalSubmateri, + 'completed_submateri' => $completedSubmateri, + 'progress_percentage' => $progressPercentage + ]); + } + + public function getProgressPercentageById(Request $request, $user_id) +{ + // Validasi user_id jika diperlukan + if (!User::find($user_id)) { + return response()->json([ + 'message' => 'User not found', + ], 404); + } + + // Ambil user berdasarkan ID + $user = User::find($user_id); + + // Ambil semua submateri yang ada di sistem + $totalSubmateri = SubMateri::count(); // Menghitung semua submateri yang ada + $completedSubmateri = Progress::where('user_id', $user->id) + ->where('status', 'selesai') + ->count(); // Menghitung submateri yang statusnya selesai + + // Menghitung persentase progres + $progressPercentage = ($totalSubmateri > 0) ? ($completedSubmateri / $totalSubmateri) * 100 : 0; + + // Return hasil persentase progres + return response()->json([ + 'user_id' => $user->id, + 'nama_lengkap' => $user->nama_lengkap, + 'total_submateri' => $totalSubmateri, + 'completed_submateri' => $completedSubmateri, + 'progress_percentage' => $progressPercentage + ]); +} + + +public function saveRecordedAudioName(Request $request, $id_latihan) + { + // Validasi data yang masuk + $request->validate([ + 'recorded_audio' => 'required|string', // Nama file audio yang disimpan + ]); + + // Temukan latihan berdasarkan ID (tanpa memeriksa user_id) + $latihan = Latihan::find($id_latihan); + + // Jika latihan ditemukan, simpan nama file rekaman ke kolom recorder_audio + if ($latihan) { + $latihan->recorder_audio = $request->recorded_audio; + $latihan->status = 'benar'; // Status latihan diubah menjadi selesai + $latihan->save(); + + return response()->json([ + 'message' => 'Rekaman berhasil disimpan!', + 'latihan' => $latihan, + ], 200); + } else { + return response()->json([ + 'message' => 'Latihan tidak ditemukan.', + ], 404); + } + } + + // API untuk menyimpan data progres latihan yang telah diselesaikan + public function saveProgress(Request $request, $submateri_id) + { + // Validasi data yang masuk + $request->validate([ + 'latihan_ids' => 'required|array', // Array dari ID latihan yang telah diselesaikan + 'latihan_ids.*' => 'exists:latihan,id', // Pastikan ID latihan ada di tabel latihan + ]); + + // Ambil user_id dari token yang digunakan (auth) + $user_id = Auth::id(); // Mendapatkan ID pengguna yang terautentikasi + + // Ambil data submateri untuk menampilkan title + $submateri = SubMateri::find($submateri_id); + if (!$submateri) { + return response()->json(['message' => 'SubMateri tidak ditemukan'], 404); + } + + // Menyimpan progres untuk setiap latihan yang diselesaikan oleh pengguna + $savedProgress = []; + foreach ($request->latihan_ids as $latihanId) { + // Temukan latihan berdasarkan ID + $latihan = Latihan::find($latihanId); + + // Jika latihan ditemukan, simpan ke tabel progres + if ($latihan) { + $progress = Progress::updateOrCreate( + [ + 'user_id' => $user_id, // Gunakan user_id dari token + 'sub_materi_id' => $submateri_id, + 'id_latihan' => $latihan->id, + ], + [ + 'status' => 'menunggu', // Status latihan diatur sebagai 'menunggu' setelah selesai + 'nilai' => $latihan->nilai, // Nilai latihan yang diberikan oleh pengajar + ] + ); + + // Menambahkan data progres yang baru disimpan untuk ditampilkan + $savedProgress[] = [ + 'user_id' => $user_id, + 'sub_materi_id' => $submateri_id, + 'submateri_title' => $submateri->title, // Menambahkan title dari submateri + 'status' => 'menunggu', + 'id_latihan' => $latihan->id, + 'potongan_ayat' => $latihan->potongan_ayat, + 'latin_text' => $latihan->latin_text, + 'recorder_audio' => $latihan->recorder_audio, + ]; + } + } + + return response()->json([ + 'message' => 'Progres latihan berhasil disimpan.', + 'progress' => $savedProgress, + ], 200); + } + + public function getHasilPenilaianByUserAndSubMateri($submateri_id) +{ + // Ambil user_id dari token yang digunakan (auth) + $user_id = Auth::id(); // Mendapatkan ID pengguna yang terautentikasi + + // Ambil data berdasarkan user_id dan submateri_id + $progressData = Progress::with([ + 'latihan' => function ($query) { + $query->select('id', 'potongan_ayat', 'latin_text', 'recorder_audio', 'feedback_pengajar', 'status'); + } + ]) + ->where('user_id', $user_id) // Mengambil progress berdasarkan user_id yang terautentikasi + ->where('sub_materi_id', $submateri_id) // Mengambil progress berdasarkan submateri_id + ->get(['user_id', 'sub_materi_id', 'id_latihan', 'nilai']); // Pilih data yang diperlukan dari tabel progress + + // Cek apakah ada data progress + if ($progressData->isEmpty()) { + return response()->json(['message' => 'Tidak ada data progres untuk pengguna ini pada submateri ini'], 404); + } + + // Menyiapkan hasil yang akan dikembalikan + $results = $progressData->map(function ($progress) { + return [ + 'user_id' => $progress->user_id, + 'sub_materi_id' => $progress->sub_materi_id, + 'id_latihan' => $progress->id_latihan, + 'nilai' => $progress->nilai, + 'potongan_ayat' => $progress->latihan->potongan_ayat, + 'latin_text' => $progress->latihan->latin_text, + 'recorder_audio' => $progress->latihan->recorder_audio, + 'feedback_pengajar' => $progress->latihan->feedback_pengajar, + 'status' => $progress->latihan->status, + ]; + }); + + return response()->json([ + 'message' => 'Data hasil penilaian berhasil diambil.', + 'data' => $results + ], 200); +} + + +} + diff --git a/app/Http/Controllers/API/UploadAudioController.php b/app/Http/Controllers/API/UploadAudioController.php new file mode 100644 index 0000000..2b68c46 --- /dev/null +++ b/app/Http/Controllers/API/UploadAudioController.php @@ -0,0 +1,30 @@ +file('recorded_audio')); + // Validasi file + $request->validate([ + // 'recorded_audio' => 'required|mimes:m4a,mp3,wav|max:10240', // Batasi ukuran file (misalnya 10MB) + 'recorded_audio' => 'required|mimes:m4a,mp3,wav', // Batasi ukuran file (misalnya 10MB) + ]); + + // Mendapatkan file dari request + $file = $request->file('recorded_audio'); + + // Menyimpan file di disk public + $path = $file->store('audio_files', 'public'); // Menyimpan di folder `storage/app/public/audio_files` + + return response()->json([ + 'message' => 'File berhasil diupload!', + 'file_path' => $path + ], 200); + } +} diff --git a/app/Http/Controllers/API/UserControler.php b/app/Http/Controllers/API/UserControler.php index c8ed669..33f0d38 100644 --- a/app/Http/Controllers/API/UserControler.php +++ b/app/Http/Controllers/API/UserControler.php @@ -5,11 +5,13 @@ use App\Helpers\ResponseFormatter; use App\Http\Controllers\Controller; use App\Models\User; +use App\Models\Progress; use Exception; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Validator; +use Illuminate\Support\Facades\DB; class UserControler extends Controller { @@ -87,6 +89,51 @@ public function login(Request $request) } } + public function loginWithTelp(Request $request) +{ + try { + $request->validate([ + 'no_telp_wali' => 'required|string', // Validate 'no_telp_wali' + 'password' => 'required' // Validate password + ]); + + // Find the user by 'no_telp_wali' + $user = User::where('no_telp_wali', $request->no_telp_wali)->first(); + + // Check if the user exists + if (!$user) { + return ResponseFormatter::error([ + 'message' => 'No telepon salah' + ], 'Authentication Failed', 401); + } + + // Check if the password is correct + if (!Hash::check($request->password, $user->password)) { + return ResponseFormatter::error([ + 'message' => 'Password salah' + ], 'Authentication Failed', 401); + } + + // Create token for the user + $tokenResult = $user->createToken('authToken')->plainTextToken; + + return ResponseFormatter::success([ + 'access_token' => $tokenResult, + 'token_type' => 'Bearer', + 'user' => [ + 'peran' => $user->peran // Ensure 'peran' is included here + ] + ], 'Authenticated'); + } catch (Exception $error) { + return ResponseFormatter::error([ + 'message' => 'Something went wrong', + 'error' => $error->getMessage() + ], 'Authentication Failed', 500); + } +} + + + public function fetch(Request $request) { return ResponseFormatter::success($request->user(), 'Data berhasil di ambil'); @@ -102,8 +149,10 @@ public function logout(Request $request) public function getUsersByRole(Request $request) { try { - // Ambil data user yang memiliki peran 'santri' - $users = User::where('peran', 'santri')->get(); + // Ambil data user yang memiliki peran 'santri' dan urutkan berdasarkan created_at secara descending + $users = User::where('peran', 'santri') + ->orderBy('created_at', 'desc') + ->get(); // Jika tidak ada user dengan peran 'santri' if ($users->isEmpty()) { @@ -128,6 +177,7 @@ public function getUsersByRole(Request $request) } } + public function getUserInfoById($id) { // Mencari user berdasarkan ID @@ -259,55 +309,234 @@ public function tambahSantri(Request $request) ); } + public function changePassword(Request $request) +{ + // Validasi inputan password baru + $validator = Validator::make($request->all(), [ + 'new_password' => 'required|string|min:8|confirmed', // Pastikan password baru minimal 8 karakter dan dikonfirmasi + ]); -// public function importSantri(Request $request) -// { -// // Validasi input -// $validator = Validator::make($request->all(), [ -// 'santri' => 'required|array', -// 'santri.*.nama_lengkap' => 'required|string|max:255', -// 'santri.*.alamat' => 'required|string|max:255', -// 'santri.*.usia' => 'required|string|max:255', -// 'santri.*.no_telp_wali' => 'required|string|max:20', -// 'santri.*.email' => 'required|email|max:255', -// 'santri.*.jenis_kelamin' => 'required|string|in:Laki-laki,Perempuan', -// 'santri.*.jenjang_pendidikan' => 'required|string|max:255', -// ]); + // Jika validasi gagal + if ($validator->fails()) { + return response()->json([ + 'status' => 'error', + 'message' => $validator->errors(), + ], 400); + } -// // Jika validasi gagal -// if ($validator->fails()) { -// return ResponseFormatter::error( -// null, -// 'Validation Error', -// 422 -// ); -// } + // Ambil user yang terautentikasi + $user = Auth::user(); -// // Ambil data santri dari request -// $santriData = $request->input('santri'); -// $santriInserted = []; + // Update password pengguna secara manual + DB::table('users') // Gunakan query builder untuk update langsung + ->where('id', $user->id) // Menentukan user berdasarkan ID + ->update(['password' => Hash::make($request->new_password)]); // Enkripsi password baru dan simpan + + return response()->json([ + 'status' => 'success', + 'message' => 'Password berhasil diubah', + ], 200); +} + +public function getUserInfoByToken(Request $request) +{ + // Mendapatkan pengguna yang terautentikasi + $user = Auth::user(); + + // Jika pengguna tidak ditemukan (misalnya, token tidak valid) + if (!$user) { + return ResponseFormatter::error( + null, + 'User not found', + 404 + ); + } + + // Mengembalikan data pengguna dalam format JSON menggunakan ResponseFormatter + return ResponseFormatter::success( + [ + 'id' => $user->id, + 'nama_lengkap' => $user->nama_lengkap, + 'alamat' => $user->alamat, + 'usia' => $user->usia, + 'no_telp_wali' => $user->no_telp_wali, + 'email' => $user->email, + 'jenis_kelamin' => $user->jenis_kelamin, + 'jenjang_pendidikan' => $user->jenjang_pendidikan, + ], + 'User data retrieved successfully' + ); +} + +public function updateUserByToken(Request $request) +{ + // Validasi data yang diterima + $validator = Validator::make($request->all(), [ + 'nama_lengkap' => 'required|string|max:255', + 'alamat' => 'required|string|max:255', + 'usia' => 'required|string|max:255', + 'no_telp_wali' => 'required|string|max:20', + 'email' => 'required|email|max:255', + 'jenjang_pendidikan' => 'required|string|max:255', + 'jenis_kelamin' => 'required|string|in:Laki-laki,Perempuan', + ]); + + // Jika validasi gagal, kembalikan respons error + if ($validator->fails()) { + return ResponseFormatter::error( + null, + 'Validation Error', + 422 + ); + } + + // Mendapatkan pengguna yang terautentikasi + $user = Auth::user(); + + // Jika pengguna tidak ditemukan (misalnya, token tidak valid) + if (!$user) { + return ResponseFormatter::error( + null, + 'User not found', + 404 + ); + } + + // Menggunakan DB::table untuk memperbarui data pengguna secara manual + DB::table('users') + ->where('id', $user->id) + ->update([ + 'nama_lengkap' => $request->nama_lengkap, + 'alamat' => $request->alamat, + 'usia' => $request->usia, + 'no_telp_wali' => $request->no_telp_wali, + 'email' => $request->email, + 'jenjang_pendidikan' => $request->jenjang_pendidikan, + 'jenis_kelamin' => $request->jenis_kelamin, + ]); + + // Mengembalikan respons sukses + return ResponseFormatter::success( + null, + 'User updated successfully' + ); +} + + + + + + + public function importSantri(Request $request) +{ + // Validasi input + $validator = Validator::make($request->all(), [ + 'santri' => 'required|array', + 'santri.*.nama_lengkap' => 'required|string|max:255', + 'santri.*.alamat' => 'required|string|max:255', + 'santri.*.usia' => 'required|string|max:255', + 'santri.*.no_telp_wali' => 'required|string|max:20', + 'santri.*.email' => 'required|email|max:255', + 'santri.*.jenis_kelamin' => 'required|string|in:Laki-laki,Perempuan', + 'santri.*.jenjang_pendidikan' => 'required|string|max:255', + ]); + + // Jika validasi gagal + if ($validator->fails()) { + return ResponseFormatter::error( + null, + 'Validation Error', + 422 + ); + } + + // Ambil data santri dari request + $santriData = $request->input('santri'); + $santriInserted = []; + + // Simpan tiap santri ke database + foreach ($santriData as $santri) { + $santriInserted[] = User::create([ + 'nama_lengkap' => $santri['nama_lengkap'], + 'alamat' => $santri['alamat'], + 'usia' => $santri['usia'], + 'no_telp_wali' => $santri['no_telp_wali'], + 'email' => $santri['email'], + 'jenis_kelamin' => $santri['jenis_kelamin'], + 'jenjang_pendidikan' => $santri['jenjang_pendidikan'], + 'peran' => 'santri', + 'password' => bcrypt('almuhajirin'), // password = email (dihash) + ]); + } + + // Respon sukses + return ResponseFormatter::success( + $santriInserted, + 'Santri data imported successfully' + ); +} + +public function getUserProgres(Request $request) +{ + try { + // Ambil semua user dengan peran 'santri' + $santriUsers = User::where('peran', 'santri')->get(); // Ambil semua user dengan peran 'santri' + + // Jika tidak ada santri + if ($santriUsers->isEmpty()) { + return response()->json([ + 'status' => 'error', + 'message' => 'Tidak ada santri yang ditemukan.', + ], 404); // Status code 404 (Not Found) + } + + // Ambil progres untuk setiap santri dan filter yang sudah menyelesaikan submateri + $santriProgressData = $santriUsers->map(function ($santri) { + // Ambil progres latihan yang telah diselesaikan oleh santri berdasarkan user ID + $progresData = Progress::where('user_id', $santri->id) + ->where('status', 'selesai') // Menghitung submateri yang sudah selesai + ->with('submateri') // Load data submateri yang terkait + ->get(); + + // Hitung jumlah submateri yang selesai + $completedSubmateriCount = $progresData->count(); // Menghitung jumlah data progres yang selesai + + // Hanya tampilkan santri yang telah menyelesaikan submateri + if ($completedSubmateriCount > 0) { + return [ + 'user_id' => $santri->id, + 'nama_lengkap' => $santri->nama_lengkap, + 'no_telp_wali' => $santri->no_telp_wali, + 'completed_submateri' => $completedSubmateriCount, // Jumlah submateri yang telah selesai + ]; + } + })->filter(function ($santri) { + return $santri !== null; // Filter out santri that haven't completed any submateri + }); + + // Jika tidak ada santri yang menyelesaikan submateri + if ($santriProgressData->isEmpty()) { + return response()->json([ + 'status' => 'error', + 'message' => 'Tidak ada santri yang telah menyelesaikan submateri.', + ], 404); // Status code 404 (Not Found) + } + + // Kirim data progres untuk setiap santri yang sudah selesai latihan + return response()->json([ + 'status' => 'success', + 'data' => $santriProgressData, // Mengembalikan data progres yang telah diselesaikan + ], 200); + + } catch (\Exception $e) { + return response()->json([ + 'status' => 'error', + 'message' => 'Something went wrong: ' . $e->getMessage(), + ], 500); + } +} -// // Simpan tiap santri ke database -// foreach ($santriData as $santri) { -// $santriInserted[] = User::create([ -// 'nama_lengkap' => $santri['nama_lengkap'], -// 'alamat' => $santri['alamat'], -// 'usia' => $santri['usia'], -// 'no_telp_wali' => $santri['no_telp_wali'], -// 'email' => $santri['email'], -// 'jenis_kelamin' => $santri['jenis_kelamin'], -// 'jenjang_pendidikan' => $santri['jenjang_pendidikan'], -// 'peran' => 'santri', -// 'password' => bcrypt($santri['email']), // password = email (dihash) -// ]); -// } -// // Respon sukses -// return ResponseFormatter::success( -// $santriInserted, -// 'Santri data imported successfully' -// ); -// } } diff --git a/app/Http/Controllers/Auth/ConfirmPasswordController.php b/app/Http/Controllers/Auth/ConfirmPasswordController.php new file mode 100644 index 0000000..3559954 --- /dev/null +++ b/app/Http/Controllers/Auth/ConfirmPasswordController.php @@ -0,0 +1,39 @@ +middleware('auth'); + } +} diff --git a/app/Http/Controllers/Auth/ForgotPasswordController.php b/app/Http/Controllers/Auth/ForgotPasswordController.php new file mode 100644 index 0000000..465c39c --- /dev/null +++ b/app/Http/Controllers/Auth/ForgotPasswordController.php @@ -0,0 +1,22 @@ +middleware('guest')->except('logout'); + $this->middleware('auth')->only('logout'); + } +} diff --git a/app/Http/Controllers/Auth/RegisterController.php b/app/Http/Controllers/Auth/RegisterController.php new file mode 100644 index 0000000..961ea36 --- /dev/null +++ b/app/Http/Controllers/Auth/RegisterController.php @@ -0,0 +1,72 @@ +middleware('guest'); + } + + /** + * Get a validator for an incoming registration request. + * + * @param array $data + * @return \Illuminate\Contracts\Validation\Validator + */ + protected function validator(array $data) + { + return Validator::make($data, [ + 'name' => ['required', 'string', 'max:255'], + 'email' => ['required', 'string', 'email', 'max:255', 'unique:users'], + 'password' => ['required', 'string', 'min:8', 'confirmed'], + ]); + } + + /** + * Create a new user instance after a valid registration. + * + * @param array $data + * @return \App\Models\User + */ + protected function create(array $data) + { + return User::create([ + 'name' => $data['name'], + 'email' => $data['email'], + 'password' => Hash::make($data['password']), + ]); + } +} diff --git a/app/Http/Controllers/Auth/ResetPasswordController.php b/app/Http/Controllers/Auth/ResetPasswordController.php new file mode 100644 index 0000000..fe965b2 --- /dev/null +++ b/app/Http/Controllers/Auth/ResetPasswordController.php @@ -0,0 +1,29 @@ +middleware('auth'); + $this->middleware('signed')->only('verify'); + $this->middleware('throttle:6,1')->only('verify', 'resend'); + } +} diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php new file mode 100644 index 0000000..7cbc2c3 --- /dev/null +++ b/app/Http/Controllers/HomeController.php @@ -0,0 +1,28 @@ +middleware('auth'); + } + + /** + * Show the application dashboard. + * + * @return \Illuminate\Contracts\Support\Renderable + */ + public function index() + { + return view('home'); + } +} diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index c34cdcf..b6ea116 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -39,7 +39,7 @@ class Kernel extends HttpKernel ], 'api' => [ - // \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class, + \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class, \Illuminate\Routing\Middleware\ThrottleRequests::class.':api', \Illuminate\Routing\Middleware\SubstituteBindings::class, ], diff --git a/app/Models/Latihan.php b/app/Models/Latihan.php index 4382627..eedd6be 100644 --- a/app/Models/Latihan.php +++ b/app/Models/Latihan.php @@ -20,6 +20,14 @@ class Latihan extends Model 'materi_description', 'correct_audio', 'recorder_audio', + 'feedback_pengajar', + 'nilai', + 'status', ]; + public function progress() + { + return $this->hasMany(Progress::class, 'id_latihan'); // Relasi balik ke Progress + } + } diff --git a/app/Models/Progress.php b/app/Models/Progress.php new file mode 100644 index 0000000..7ee396c --- /dev/null +++ b/app/Models/Progress.php @@ -0,0 +1,31 @@ +belongsTo(User::class); + } + + // Relasi ke SubMateri + public function subMateri() + { + return $this->belongsTo(SubMateri::class); + } + + // Relasi ke Latihan + public function Latihan() + { + return $this->belongsTo(Latihan::class, 'id_latihan'); + } +} diff --git a/composer.json b/composer.json index 5b40f87..b111701 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,9 @@ "guzzlehttp/guzzle": "^7.2", "laravel/framework": "^10.0", "laravel/sanctum": "^3.2", - "laravel/tinker": "^2.8" + "laravel/tinker": "^2.8", + "laravel/ui": "^4.6", + "phpmailer/phpmailer": "^6.10" }, "require-dev": { "fakerphp/faker": "^1.9.1", diff --git a/composer.lock b/composer.lock index cf350fa..37f4621 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "bfe12996eeecb6fdc8713a9fd9d431f8", + "content-hash": "1787b3b8dd905d7a2fb5758e07bf55a0", "packages": [ { "name": "brick/math", @@ -1512,6 +1512,69 @@ }, "time": "2025-01-27T14:24:01+00:00" }, + { + "name": "laravel/ui", + "version": "v4.6.1", + "source": { + "type": "git", + "url": "https://github.com/laravel/ui.git", + "reference": "7d6ffa38d79f19c9b3e70a751a9af845e8f41d88" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/ui/zipball/7d6ffa38d79f19c9b3e70a751a9af845e8f41d88", + "reference": "7d6ffa38d79f19c9b3e70a751a9af845e8f41d88", + "shasum": "" + }, + "require": { + "illuminate/console": "^9.21|^10.0|^11.0|^12.0", + "illuminate/filesystem": "^9.21|^10.0|^11.0|^12.0", + "illuminate/support": "^9.21|^10.0|^11.0|^12.0", + "illuminate/validation": "^9.21|^10.0|^11.0|^12.0", + "php": "^8.0", + "symfony/console": "^6.0|^7.0" + }, + "require-dev": { + "orchestra/testbench": "^7.35|^8.15|^9.0|^10.0", + "phpunit/phpunit": "^9.3|^10.4|^11.5" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Laravel\\Ui\\UiServiceProvider" + ] + }, + "branch-alias": { + "dev-master": "4.x-dev" + } + }, + "autoload": { + "psr-4": { + "Laravel\\Ui\\": "src/", + "Illuminate\\Foundation\\Auth\\": "auth-backend/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Laravel UI utilities and presets.", + "keywords": [ + "laravel", + "ui" + ], + "support": { + "source": "https://github.com/laravel/ui/tree/v4.6.1" + }, + "time": "2025-01-28T15:15:29+00:00" + }, { "name": "league/commonmark", "version": "2.6.1", @@ -2390,6 +2453,87 @@ ], "time": "2024-11-21T10:36:35+00:00" }, + { + "name": "phpmailer/phpmailer", + "version": "v6.10.0", + "source": { + "type": "git", + "url": "https://github.com/PHPMailer/PHPMailer.git", + "reference": "bf74d75a1fde6beaa34a0ddae2ec5fce0f72a144" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/bf74d75a1fde6beaa34a0ddae2ec5fce0f72a144", + "reference": "bf74d75a1fde6beaa34a0ddae2ec5fce0f72a144", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-filter": "*", + "ext-hash": "*", + "php": ">=5.5.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "doctrine/annotations": "^1.2.6 || ^1.13.3", + "php-parallel-lint/php-console-highlighter": "^1.0.0", + "php-parallel-lint/php-parallel-lint": "^1.3.2", + "phpcompatibility/php-compatibility": "^9.3.5", + "roave/security-advisories": "dev-latest", + "squizlabs/php_codesniffer": "^3.7.2", + "yoast/phpunit-polyfills": "^1.0.4" + }, + "suggest": { + "decomplexity/SendOauth2": "Adapter for using XOAUTH2 authentication", + "ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses", + "ext-openssl": "Needed for secure SMTP sending and DKIM signing", + "greew/oauth2-azure-provider": "Needed for Microsoft Azure XOAUTH2 authentication", + "hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication", + "league/oauth2-google": "Needed for Google XOAUTH2 authentication", + "psr/log": "For optional PSR-3 debug logging", + "symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)", + "thenetworg/oauth2-azure": "Needed for Microsoft XOAUTH2 authentication" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPMailer\\PHPMailer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1-only" + ], + "authors": [ + { + "name": "Marcus Bointon", + "email": "phpmailer@synchromedia.co.uk" + }, + { + "name": "Jim Jagielski", + "email": "jimjag@gmail.com" + }, + { + "name": "Andy Prevost", + "email": "codeworxtech@users.sourceforge.net" + }, + { + "name": "Brent R. Matzelle" + } + ], + "description": "PHPMailer is a full-featured email creation and transfer class for PHP", + "support": { + "issues": "https://github.com/PHPMailer/PHPMailer/issues", + "source": "https://github.com/PHPMailer/PHPMailer/tree/v6.10.0" + }, + "funding": [ + { + "url": "https://github.com/Synchro", + "type": "github" + } + ], + "time": "2025-04-24T15:19:31+00:00" + }, { "name": "phpoption/phpoption", "version": "1.9.3", diff --git a/database/migrations/2014_10_12_100000_create_password_resets_table.php b/database/migrations/2014_10_12_100000_create_password_resets_table.php new file mode 100644 index 0000000..fcacb80 --- /dev/null +++ b/database/migrations/2014_10_12_100000_create_password_resets_table.php @@ -0,0 +1,32 @@ +string('email')->index(); + $table->string('token'); + $table->timestamp('created_at')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('password_resets'); + } +}; diff --git a/database/migrations/2025_06_02_184222_create_progress_table.php b/database/migrations/2025_06_02_184222_create_progress_table.php new file mode 100644 index 0000000..0f9a1d6 --- /dev/null +++ b/database/migrations/2025_06_02_184222_create_progress_table.php @@ -0,0 +1,30 @@ +id(); + $table->foreignId('user_id')->constrained('users')->onDelete('cascade'); + $table->foreignId('sub_materi_id')->constrained('sub_materi')->onDelete('cascade'); + $table->enum('status', ['selesai', 'belum selesai'])->default('belum selesai'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('progress'); + } +}; diff --git a/database/migrations/2025_06_16_092657_add_fields_to_latihan_table.php b/database/migrations/2025_06_16_092657_add_fields_to_latihan_table.php new file mode 100644 index 0000000..1ab01ee --- /dev/null +++ b/database/migrations/2025_06_16_092657_add_fields_to_latihan_table.php @@ -0,0 +1,32 @@ +text('feedback_pengajar')->nullable()->after('recorder_audio'); + $table->decimal('nilai', 5, 2)->nullable()->after('feedback_pengajar'); + $table->enum('status', ['benar', 'salah'])->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('latihan', function (Blueprint $table) { + $table->dropColumn('feedback_pengajar'); + $table->dropColumn('nilai'); + $table->dropColumn('status'); + }); + } +}; diff --git a/database/migrations/2025_06_16_093236_add_fields_to_progress_table.php b/database/migrations/2025_06_16_093236_add_fields_to_progress_table.php new file mode 100644 index 0000000..d9595e4 --- /dev/null +++ b/database/migrations/2025_06_16_093236_add_fields_to_progress_table.php @@ -0,0 +1,28 @@ +decimal('nilai', 5, 2)->nullable()->after('status'); // Kolom nilai yang didapatkan oleh santri + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('progress', function (Blueprint $table) { + $table->dropColumn('nilai'); + }); + } +}; diff --git a/database/migrations/2025_06_16_093929_update_status_field_in_progress_table.php b/database/migrations/2025_06_16_093929_update_status_field_in_progress_table.php new file mode 100644 index 0000000..5622ded --- /dev/null +++ b/database/migrations/2025_06_16_093929_update_status_field_in_progress_table.php @@ -0,0 +1,28 @@ +enum('status', ['selesai', 'menunggu', 'gagal', 'belum selesai'])->default('belum selesai')->change(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('progress', function (Blueprint $table) { + $table->enum('status', ['selesai', 'belum selesai'])->default('belum selesai')->change(); + }); + } +}; diff --git a/database/migrations/2025_06_16_095119_add_id_latihan_to_progress_table.php b/database/migrations/2025_06_16_095119_add_id_latihan_to_progress_table.php new file mode 100644 index 0000000..d5b81a9 --- /dev/null +++ b/database/migrations/2025_06_16_095119_add_id_latihan_to_progress_table.php @@ -0,0 +1,29 @@ +foreignId('id_latihan')->nullable()->constrained('latihan')->onDelete('cascade')->after('id_user'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('progress', function (Blueprint $table) { + $table->dropForeign(['id_latihan']); // Menghapus foreign key + $table->dropColumn('id_latihan'); // Menghapus kolom + }); + } +}; diff --git a/database/seeders/QuizTableSeeder.php b/database/seeders/QuizTableSeeder.php index c76c827..0b57c2b 100644 --- a/database/seeders/QuizTableSeeder.php +++ b/database/seeders/QuizTableSeeder.php @@ -15,226 +15,257 @@ class QuizTableSeeder extends Seeder public function run() { $questions = [ - // Soal 1 [ - 'id_materi' => 1, - 'question' => 'Makharijul Huruf artinya .....', - 'option_a' => 'Tempat-tempat keluarnya huruf hijaiah', - 'option_b' => 'Tempat-tempat berubahnya huruf hijaiah', - 'option_c' => 'Tempat-tempat hilangnya huruf hijaiah', - 'option_d' => 'Tempat-tempat berkumpulnya huruf hijaiah', - 'correct_option' => 'a', - 'score' => 20, - ], - // Soal 2 - [ - 'id_materi' => 1, - 'question' => 'Jumlah makhraj-makhraj huruf menurut Ibnu Jazari adalah .....', - 'option_a' => '15 makhraj', - 'option_b' => '16 makhraj', - 'option_c' => '17 makhraj', - 'option_d' => '18 makhraj', - 'correct_option' => 'c', - 'score' => 20, - ], - // Soal 3 - [ - 'id_materi' => 1, - 'question' => 'Salah satu tempat keluarnya huruf adalah Al-Halqi. Halqi artinya .....', - 'option_a' => 'Lidah', - 'option_b' => 'Tenggorokan', - 'option_c' => 'Rongga hidung', - 'option_d' => 'Bibir', - 'correct_option' => 'b', - 'score' => 20, - ], - // Soal 4 - [ - 'id_materi' => 1, - 'question' => 'Huruf ain (ع) merupakan huruf yang keluar dari .....', - 'option_a' => 'Lidah bagian tengah', - 'option_b' => 'Tenggorokan bagian tengah', - 'option_c' => 'Lidah bagian ujung', - 'option_d' => 'Tenggorokan bagian atas', - 'correct_option' => 'b', - 'score' => 20, - ], - // Soal 5 - [ - 'id_materi' => 1, - 'question' => 'Jumlah huruf yang keluar lewat tenggorokan adalah .....', - 'option_a' => '5 huruf', - 'option_b' => '6 huruf', - 'option_c' => '7 huruf', - 'option_d' => '8 huruf', - 'correct_option' => 'b', - 'score' => 20, - ], - // Soal 6 - [ - 'id_materi' => 1, - 'question' => 'Berikut ini merupakan huruf yang keluar lewat tenggorokan, kecuali .....', - 'option_a' => 'Huruf ghain (غ)', - 'option_b' => 'Huruf hamzah (ء)', - 'option_c' => 'Huruf kha (خ)', - 'option_d' => 'Huruf nun (ن)', - 'correct_option' => 'd', - 'score' => 20, - ], - // Soal 7 - [ - 'id_materi' => 1, - 'question' => 'Berikut ini merupakan huruf yang memiliki makhraj sama dengan huruf jim (ج) adalah .....', - 'option_a' => 'Huruf syin (ش)', - 'option_b' => 'Huruf fa (ف)', - 'option_c' => 'Huruf lam (ل)', - 'option_d' => 'Huruf nun (ن)', - 'correct_option' => 'a', - 'score' => 20, - ], - // Soal 8 - [ - 'id_materi' => 1, - 'question' => 'Huruf qaf (ق) merupakan huruf yang keluar dari lidah bagian ....', - 'option_a' => 'Pangkal lidah', - 'option_b' => 'Ujung lidah', - 'option_c' => 'Tengah lidah', - 'option_d' => 'Tepi lidah', - 'correct_option' => 'a', - 'score' => 20, - ], - // Soal 9 - [ - 'id_materi' => 1, - 'question' => 'Jumlah huruf yang keluar lewat lidah adalah .....', - 'option_a' => '15 huruf', - 'option_b' => '16 huruf', - 'option_c' => '17 huruf', - 'option_d' => '18 huruf', - 'correct_option' => 'd', - 'score' => 20, - ], - // Soal 10 - [ - 'id_materi' => 1, - 'question' => 'Jumlah huruf yang keluar lewat bibir adalah .....', - 'option_a' => '2 huruf', - 'option_b' => '3 huruf', - 'option_c' => '4 huruf', - 'option_d' => '5 huruf', - 'correct_option' => 'c', - 'score' => 20, - ], - // Soal 11 - [ - 'id_materi' => 1, - 'question' => 'Berikut ini merupakan kelompok huruf yang keluar dari makhraj yang sama, kecuali .....', - 'option_a' => 'Huruf ع dan ح', - 'option_b' => 'Huruf غ dan خ', - 'option_c' => 'Huruf ل dan ط', - 'option_d' => 'Huruf ث, ذ, dan ظ', - 'correct_option' => 'a', - 'score' => 20, - ], - // Soal 12 - [ - 'id_materi' => 1, - 'question' => 'Berikut ini merupakan huruf yang keluar dari pangkal lidah adalah .....', - 'option_a' => 'Huruf kaf (ك)', - 'option_b' => 'Huruf nun (ن)', - 'option_c' => 'Huruf jim (ج)', - 'option_d' => 'Huruf shad (ص)', - 'correct_option' => 'a', - 'score' => 20, - ], - // Soal 13 - [ - 'id_materi' => 1, - 'question' => 'Berikut ini merupakan huruf yang keluar dari tengah lidah adalah .....', - 'option_a' => 'Huruf kaf (ك)', - 'option_b' => 'Huruf nun (ن)', - 'option_c' => 'Huruf jim (ج)', - 'option_d' => 'Huruf shad (ص)', - 'correct_option' => 'c', - 'score' => 20, - ], - // Soal 14 - [ - 'id_materi' => 1, - 'question' => 'Huruf dhad (ض) merupakan huruf yang keluar dari .....', - 'option_a' => 'Pangkal lidah', - 'option_b' => 'Bibir dalam', - 'option_c' => 'Tepi lidah', - 'option_d' => 'Ujung lidah', - 'correct_option' => 'c', - 'score' => 20, - ], - // Soal 15 - [ - 'id_materi' => 1, - 'question' => 'Dua huruf yang keluar dari makhraj yang sama disebut .....', - 'option_a' => 'Mutamatsilain', - 'option_b' => 'Mutajanisain', - 'option_c' => 'Mutaqaribain', - 'option_d' => 'Mutabaidain', - 'correct_option' => 'c', - 'score' => 20, - ], - // Soal 16 - [ - 'id_materi' => 1, - 'question' => 'Berikut ini adalah huruf-huruf yang keluar dari ujung lidah, kecuali .....', - 'option_a' => 'Huruf dal (د)', - 'option_b' => 'Huruf tsa (ث)', - 'option_c' => 'Huruf ya (ي)', - 'option_d' => 'Huruf dha (ظ)', - 'correct_option' => 'c', - 'score' => 20, - ], - // Soal 17 - [ - 'id_materi' => 1, - 'question' => 'Huruf yang keluar dari al-Jauf adalah .....', - 'option_a' => 'Alif', - 'option_b' => 'Ba', - 'option_c' => 'Ta', - 'option_d' => 'Tsa', - 'correct_option' => 'a', - 'score' => 20, - ], - // Soal 18 - [ - 'id_materi' => 1, - 'question' => 'Berapa jumlah huruf yang keluar dari pangkal lidah? .....', - 'option_a' => '1 huruf', - 'option_b' => '2 huruf', - 'option_c' => '3 huruf', - 'option_d' => '4 huruf', - 'correct_option' => 'b', - 'score' => 20, - ], - // Soal 19 - [ - 'id_materi' => 1, - 'question' => 'Berikut ini manakah pernyataan yang benar? .....', - 'option_a' => 'Makhraj sin (س) adalah ujung lidah dengan rongga antara gigi atas dan gigi bawah yang lebih dekat dengan gigi bawah', - 'option_b' => 'Makhraj ta (ت) adalah ujung lidah menempel dengan ujung gigi atas', - 'option_c' => 'Makhraj tha (ط) adalah ujung lidah menempel dengan ujung gigi atas', - 'option_d' => 'Makhraj tsa (ث) adalah ujung lidah dengan rongga antara gigi atas dan gigi bawah yang lebih dekat dengan gigi bawah', - 'correct_option' => 'b', - 'score' => 20, - ], - // Soal 20 - [ - 'id_materi' => 1, - 'question' => 'Makhraj huruf zay (ز) adalah .....', - 'option_a' => 'Ujung lidah dengan rongga antara gigi atas dan gigi bawah yang lebih dekat dengan gigi bawah', - 'option_b' => 'Ujung lidah menempel dengan pangkal gigi atas', - 'option_c' => 'Ujung lidah menempel dengan ujung gigi atas', - 'option_d' => 'Tenggorakan bagian tengah', - 'correct_option' => 'c', - 'score' => 20, - ], + 'id_materi' => 2, + 'question' => 'Ada berapa jumlah huruf yang memiliki sifat Jahr?', + 'option_a' => '19', + 'option_b' => '20', + 'option_c' => '21', + 'option_d' => '10', + 'correct_option' => 'b', + 'score' => 20, +], +[ + 'id_materi' => 2, + 'question' => 'Ada berapa jumlah huruf yang memiliki sifat Tawassuth?', + 'option_a' => '4', + 'option_b' => '5', + 'option_c' => '10', + 'option_d' => '7', + 'correct_option' => 'b', + 'score' => 20, +], +[ + 'id_materi' => 2, + 'question' => 'Ada berapa jumlah huruf yang memiliki sifat Tafasysyi?', + 'option_a' => '1', + 'option_b' => '4', + 'option_c' => '5', + 'option_d' => '7', + 'correct_option' => 'a', + 'score' => 20, +], +[ + 'id_materi' => 2, + 'question' => 'Ada berapa jumlah huruf yang memiliki sifat Qalqalah?', + 'option_a' => '1', + 'option_b' => '4', + 'option_c' => '5', + 'option_d' => '7', + 'correct_option' => 'b', + 'score' => 20, +], +[ + 'id_materi' => 2, + 'question' => 'Ada berapa jumlah huruf yang memiliki sifat Shafir?', + 'option_a' => '1', + 'option_b' => '4', + 'option_c' => '5', + 'option_d' => '3', + 'correct_option' => 'd', + 'score' => 20, +], +[ + 'id_materi' => 2, + 'question' => 'Sifat apa yang berlawanan dari sifat Hams?', + 'option_a' => 'Syiddah', + 'option_b' => 'Tawassuth', + 'option_c' => 'Isti’la', + 'option_d' => 'Jahr', + 'correct_option' => 'd', + 'score' => 20, +], +[ + 'id_materi' => 2, + 'question' => 'Sifat apa yang berlawanan dari sifat Ishmat?', + 'option_a' => 'Infitah', + 'option_b' => 'Idzlaq', + 'option_c' => 'Isti’la', + 'option_d' => 'Tawassuth', + 'correct_option' => 'b', + 'score' => 20, +], +[ + 'id_materi' => 2, + 'question' => 'Sifat apa yang berlawanan dari sifat Isti’la?', + 'option_a' => 'Infitah', + 'option_b' => 'Istifal', + 'option_c' => 'Lin', + 'option_d' => 'Shafir', + 'correct_option' => 'b', + 'score' => 20, +], +[ + 'id_materi' => 2, + 'question' => 'Sifat apa yang berlawanan dari sifat Shafir?', + 'option_a' => 'Lin', + 'option_b' => 'Qalqalah', + 'option_c' => 'Hams', + 'option_d' => 'Tidak ada', + 'correct_option' => 'd', + 'score' => 20, +], +[ + 'id_materi' => 2, + 'question' => 'Sifat apa yang berlawanan dari sifat Infitah?', + 'option_a' => 'Ithbaq', + 'option_b' => 'Idzlaq', + 'option_c' => 'Jahr', + 'option_d' => 'Hams', + 'correct_option' => 'a', + 'score' => 20, +], +[ + 'id_materi' => 2, + 'question' => 'Berikut ini adalah sifat-sifat yang dimiliki huruf ba (ب) kecuali...', + 'option_a' => 'Qalqalah', + 'option_b' => 'Jahr', + 'option_c' => 'Syiddah', + 'option_d' => 'Isti’la', + 'correct_option' => 'd', + 'score' => 20, +], +[ + 'id_materi' => 2, + 'question' => 'Berikut ini adalah sifat-sifat yang dimiliki huruf ro (ر) kecuali...', + 'option_a' => 'Jahr', + 'option_b' => 'Syiddah', + 'option_c' => 'Takrir', + 'option_d' => 'Infitah', + 'correct_option' => 'd', + 'score' => 20, +], +[ + 'id_materi' => 2, + 'question' => 'Berikut ini adalah sifat-sifat yang dimiliki huruf syin (ش) kecuali...', + 'option_a' => 'Hams', + 'option_b' => 'Rakhawah', + 'option_c' => 'Qalqalah', + 'option_d' => 'Tafasysyi', + 'correct_option' => 'c', + 'score' => 20, +], +[ + 'id_materi' => 2, + 'question' => 'Berikut ini adalah sifat-sifat yang dimiliki huruf ta (ت) kecuali...', + 'option_a' => 'Hams', + 'option_b' => 'Syiddah', + 'option_c' => 'Istifal', + 'option_d' => 'Ithbaq', + 'correct_option' => 'd', + 'score' => 20, +], +[ + 'id_materi' => 2, + 'question' => 'Berikut ini adalah sifat-sifat yang dimiliki huruf kho (خ) kecuali...', + 'option_a' => 'Hams', + 'option_b' => 'Rakhawah', + 'option_c' => 'Idzlaq', + 'option_d' => 'Isti’la', + 'correct_option' => 'b', + 'score' => 20, +], +[ + 'id_materi' => 2, + 'question' => 'Manakah huruf berikut yang memiliki sifat Syiddah?', + 'option_a' => 'Syin', + 'option_b' => 'Qaf', + 'option_c' => 'Nun', + 'option_d' => 'Lam', + 'correct_option' => 'b', + 'score' => 20, +], +[ + 'id_materi' => 2, + 'question' => 'Manakah huruf berikut yang memiliki sifat Inhiraf?', + 'option_a' => 'Syin', + 'option_b' => 'Qaf', + 'option_c' => 'Nun', + 'option_d' => 'Lam', + 'correct_option' => 'a', + 'score' => 20, +], +[ + 'id_materi' => 2, + 'question' => 'Manakah huruf berikut yang memiliki sifat Istithalah?', + 'option_a' => 'Ro', + 'option_b' => 'Dhad', + 'option_c' => 'Mim', + 'option_d' => 'Ain', + 'correct_option' => 'b', + 'score' => 20, +], +[ + 'id_materi' => 2, + 'question' => 'Manakah huruf berikut yang memiliki sifat Ghunnah?', + 'option_a' => 'Ro', + 'option_b' => 'Dhad', + 'option_c' => 'Mim', + 'option_d' => 'Ain', + 'correct_option' => 'c', + 'score' => 20, +], +[ + 'id_materi' => 2, + 'question' => 'Manakah huruf berikut yang memiliki sifat Idzlaq?', + 'option_a' => 'Ro', + 'option_b' => 'Dhad', + 'option_c' => 'Syin', + 'option_d' => 'Ain', + 'correct_option' => 'c', + 'score' => 20, +], +[ + 'id_materi' => 2, + 'question' => 'Pilih pernyataan benar berikut ini!', + 'option_a' => 'Jahr adalah tertahannya aliran nafas ketika mengucapkan huruf', + 'option_b' => 'Syiddah adalah mengalirnya suara ketika mengucapkan huruf', + 'option_c' => 'Isti’la adalah turunnya pangkal lidah ketika mengucapkan huruf', + 'option_d' => 'Tawassuth adalah tertahannya suara ketika mengucapkan huruf', + 'correct_option' => 'b', + 'score' => 20, +], +[ + 'id_materi' => 2, + 'question' => 'Huruf ro dan lam memiliki sifat yang hampir sama, namun ada satu sifat yang membedakan keduanya. Sifat itu adalah sifat....', + 'option_a' => 'Istithalah', + 'option_b' => 'Tafasysyi', + 'option_c' => 'Takrir', + 'option_d' => 'Lin', + 'correct_option' => 'c', + 'score' => 20, +], +[ + 'id_materi' => 2, + 'question' => 'Pilih pernyataan benar berikut ini!', + 'option_a' => 'Tawassuth merupakan sifat pertengahan antara Isti’la dan Istifal', + 'option_b' => 'Sifat Qalqalah merupakan sifat yang tidak memiliki lawan', + 'option_c' => 'Ada 10 huruf yang memiliki sifat Isti’la', + 'option_d' => 'Jumlah sifat yang dimiliki huruf hamzah adalah 9 sifat.', + 'correct_option' => 'a', + 'score' => 20, +], +[ + 'id_materi' => 2, + 'question' => 'Sifat Inhiraf hanya dimiliki oleh dua huruf, yaitu', + 'option_a' => 'Lam dan Ro', + 'option_b' => 'Nun dan Mim', + 'option_c' => 'Hamzah dan Alif', + 'option_d' => 'Sin dan Shad', + 'correct_option' => 'a', + 'score' => 20, +], +[ + 'id_materi' => 2, + 'question' => 'Pilih pernyataan benar berikut ini!', + 'option_a' => 'Sifat Takrir adalah bergetarnya bibir ketika mengucapkan huruf', + 'option_b' => 'Sifat Ishmat adalah cepatnya suara ketika mengucapkan huruf', + 'option_c' => 'Sifat Tafasysyi adalah lawan kata dari sifat Inhiraf', + 'option_d' => 'Sifat Lin adalah mengucapkan huruf dengan lentur', + 'correct_option' => 'd', + 'score' => 20, +] + ]; // Insert data soal ke dalam tabel quiz diff --git a/package.json b/package.json index 3a76ed0..2055b69 100644 --- a/package.json +++ b/package.json @@ -5,8 +5,13 @@ "build": "vite build" }, "devDependencies": { + "@popperjs/core": "^2.11.6", + "@vitejs/plugin-vue": "^4.5.0", "axios": "^1.1.2", + "bootstrap": "^5.2.3", "laravel-vite-plugin": "^0.7.2", - "vite": "^4.0.0" + "sass": "^1.56.1", + "vite": "^4.0.0", + "vue": "^3.2.37" } } diff --git a/resources/js/app.js b/resources/js/app.js index e59d6a0..4158f48 100644 --- a/resources/js/app.js +++ b/resources/js/app.js @@ -1 +1,39 @@ +/** + * First we will load all of this project's JavaScript dependencies which + * includes Vue and other libraries. It is a great starting point when + * building robust, powerful web applications using Vue and Laravel. + */ + import './bootstrap'; +import { createApp } from 'vue'; + +/** + * Next, we will create a fresh Vue application instance. You may then begin + * registering components with the application instance so they are ready + * to use in your application's views. An example is included for you. + */ + +const app = createApp({}); + +import ExampleComponent from './components/ExampleComponent.vue'; +app.component('example-component', ExampleComponent); + +/** + * The following block of code may be used to automatically register your + * Vue components. It will recursively scan this directory for the Vue + * components and automatically register them with their "basename". + * + * Eg. ./components/ExampleComponent.vue -> + */ + +// Object.entries(import.meta.glob('./**/*.vue', { eager: true })).forEach(([path, definition]) => { +// app.component(path.split('/').pop().replace(/\.\w+$/, ''), definition.default); +// }); + +/** + * Finally, we will attach the application instance to a HTML element with + * an "id" attribute of "app". This element is included with the "auth" + * scaffolding. Otherwise, you will need to add an element yourself. + */ + +app.mount('#app'); diff --git a/resources/js/bootstrap.js b/resources/js/bootstrap.js index 846d350..46f7a33 100644 --- a/resources/js/bootstrap.js +++ b/resources/js/bootstrap.js @@ -1,3 +1,5 @@ +import 'bootstrap'; + /** * We'll load the axios HTTP library which allows us to easily issue requests * to our Laravel back-end. This library automatically handles sending the @@ -24,7 +26,7 @@ window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; // broadcaster: 'pusher', // key: import.meta.env.VITE_PUSHER_APP_KEY, // cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER ?? 'mt1', -// wsHost: import.meta.env.VITE_PUSHER_HOST ? import.meta.env.VITE_PUSHER_HOST : `ws-${import.meta.env.VITE_PUSHER_APP_CLUSTER}.pusher.com`, +// wsHost: import.meta.env.VITE_PUSHER_HOST ?? `ws-${import.meta.env.VITE_PUSHER_APP_CLUSTER}.pusher.com`, // wsPort: import.meta.env.VITE_PUSHER_PORT ?? 80, // wssPort: import.meta.env.VITE_PUSHER_PORT ?? 443, // forceTLS: (import.meta.env.VITE_PUSHER_SCHEME ?? 'https') === 'https', diff --git a/resources/js/components/ExampleComponent.vue b/resources/js/components/ExampleComponent.vue new file mode 100644 index 0000000..3fb9f9a --- /dev/null +++ b/resources/js/components/ExampleComponent.vue @@ -0,0 +1,23 @@ + + + diff --git a/resources/sass/_variables.scss b/resources/sass/_variables.scss new file mode 100644 index 0000000..172daaa --- /dev/null +++ b/resources/sass/_variables.scss @@ -0,0 +1,7 @@ +// Body +$body-bg: #f8fafc; + +// Typography +$font-family-sans-serif: 'Nunito', sans-serif; +$font-size-base: 0.9rem; +$line-height-base: 1.6; diff --git a/resources/sass/app.scss b/resources/sass/app.scss new file mode 100644 index 0000000..1026a0b --- /dev/null +++ b/resources/sass/app.scss @@ -0,0 +1,8 @@ +// Fonts +@import url('https://fonts.bunny.net/css?family=Nunito'); + +// Variables +@import 'variables'; + +// Bootstrap +@import 'bootstrap/scss/bootstrap'; diff --git a/resources/views/auth/login.blade.php b/resources/views/auth/login.blade.php new file mode 100644 index 0000000..ea9ac94 --- /dev/null +++ b/resources/views/auth/login.blade.php @@ -0,0 +1,73 @@ +@extends('layouts.app') + +@section('content') +
+
+
+
+
{{ __('Login') }}
+ +
+
+ @csrf + +
+ + +
+ + + @error('email') + + {{ $message }} + + @enderror +
+
+ +
+ + +
+ + + @error('password') + + {{ $message }} + + @enderror +
+
+ +
+
+
+ + + +
+
+
+ +
+
+ + + @if (Route::has('password.request')) + + {{ __('Forgot Your Password?') }} + + @endif +
+
+
+
+
+
+
+
+@endsection diff --git a/resources/views/auth/passwords/confirm.blade.php b/resources/views/auth/passwords/confirm.blade.php new file mode 100644 index 0000000..f8c8e61 --- /dev/null +++ b/resources/views/auth/passwords/confirm.blade.php @@ -0,0 +1,49 @@ +@extends('layouts.app') + +@section('content') +
+
+
+
+
{{ __('Confirm Password') }}
+ +
+ {{ __('Please confirm your password before continuing.') }} + +
+ @csrf + +
+ + +
+ + + @error('password') + + {{ $message }} + + @enderror +
+
+ +
+
+ + + @if (Route::has('password.request')) + + {{ __('Forgot Your Password?') }} + + @endif +
+
+
+
+
+
+
+
+@endsection diff --git a/resources/views/auth/passwords/email.blade.php b/resources/views/auth/passwords/email.blade.php new file mode 100644 index 0000000..d1ac783 --- /dev/null +++ b/resources/views/auth/passwords/email.blade.php @@ -0,0 +1,47 @@ +@extends('layouts.app') + +@section('content') +
+
+
+
+
{{ __('Reset Password') }}
+ +
+ @if (session('status')) + + @endif + +
+ @csrf + +
+ + +
+ + + @error('email') + + {{ $message }} + + @enderror +
+
+ +
+
+ +
+
+
+
+
+
+
+
+@endsection diff --git a/resources/views/auth/passwords/reset.blade.php b/resources/views/auth/passwords/reset.blade.php new file mode 100644 index 0000000..dccf6c6 --- /dev/null +++ b/resources/views/auth/passwords/reset.blade.php @@ -0,0 +1,65 @@ +@extends('layouts.app') + +@section('content') +
+
+
+
+
{{ __('Reset Password') }}
+ +
+
+ @csrf + + + +
+ + +
+ + + @error('email') + + {{ $message }} + + @enderror +
+
+ +
+ + +
+ + + @error('password') + + {{ $message }} + + @enderror +
+
+ +
+ + +
+ +
+
+ +
+
+ +
+
+
+
+
+
+
+
+@endsection diff --git a/resources/views/auth/register.blade.php b/resources/views/auth/register.blade.php new file mode 100644 index 0000000..12cad1a --- /dev/null +++ b/resources/views/auth/register.blade.php @@ -0,0 +1,77 @@ +@extends('layouts.app') + +@section('content') +
+
+
+
+
{{ __('Register') }}
+ +
+
+ @csrf + +
+ + +
+ + + @error('name') + + {{ $message }} + + @enderror +
+
+ +
+ + +
+ + + @error('email') + + {{ $message }} + + @enderror +
+
+ +
+ + +
+ + + @error('password') + + {{ $message }} + + @enderror +
+
+ +
+ + +
+ +
+
+ +
+
+ +
+
+
+
+
+
+
+
+@endsection diff --git a/resources/views/auth/verify.blade.php b/resources/views/auth/verify.blade.php new file mode 100644 index 0000000..9f8c1bc --- /dev/null +++ b/resources/views/auth/verify.blade.php @@ -0,0 +1,28 @@ +@extends('layouts.app') + +@section('content') +
+
+
+
+
{{ __('Verify Your Email Address') }}
+ +
+ @if (session('resent')) + + @endif + + {{ __('Before proceeding, please check your email for a verification link.') }} + {{ __('If you did not receive the email') }}, +
+ @csrf + . +
+
+
+
+
+
+@endsection diff --git a/resources/views/home.blade.php b/resources/views/home.blade.php new file mode 100644 index 0000000..1f34466 --- /dev/null +++ b/resources/views/home.blade.php @@ -0,0 +1,23 @@ +@extends('layouts.app') + +@section('content') +
+
+
+
+
{{ __('Dashboard') }}
+ +
+ @if (session('status')) + + @endif + + {{ __('You are logged in!') }} +
+
+
+
+
+@endsection diff --git a/resources/views/layouts/app.blade.php b/resources/views/layouts/app.blade.php new file mode 100644 index 0000000..a6970da --- /dev/null +++ b/resources/views/layouts/app.blade.php @@ -0,0 +1,80 @@ + + + + + + + + + + {{ config('app.name', 'Laravel') }} + + + + + + + @vite(['resources/sass/app.scss', 'resources/js/app.js']) + + +
+ + +
+ @yield('content') +
+
+ + diff --git a/routes/api.php b/routes/api.php index 88d4329..774ca17 100644 --- a/routes/api.php +++ b/routes/api.php @@ -1,13 +1,16 @@ group(function () { Route::get('user', [UserControler::class, 'fetch']); Route::post('logout', [UserControler::class, 'logout']); + Route::post('/change_password', [UserControler::class, 'changePassword']); }); Route::get('/materi', [MateriControler::class, 'getMateri']); @@ -53,5 +58,27 @@ Route::put('/updateUser/{id}', [UserControler::class, 'updateUserById']); Route::post('/tambahSantri', [UserControler::class, 'tambahSantri']); + + Route::get('/userByToken', [UserControler::class, 'getUserInfoByToken']); + + // Memperbarui data pengguna berdasarkan token + Route::post('/user/updateBytoken', [UserControler::class, 'updateUserByToken']); + Route::post('/importSantri', [UserControler::class, 'importSantri']); }); +Route::middleware('auth:sanctum')->group(function () { + Route::get('/materi/{materiId}/progress', [ProgressController::class, 'getMateriProgress']); + Route::post('/progress/{subMateriId}/update', [ProgressController::class, 'updateProgress']); + Route::get('/progress/{subMateriId}/status', [ProgressController::class, 'getProgressBySubMateri']); + Route::get('/progres/presentase', [ProgressController::class, 'getProgressPercentage']); + Route::get('/progres/{user_id}', [ProgressController::class, 'getProgressPercentageById']); + Route::put('latihan/{id_latihan}/saverecord', [ProgressController::class, 'saveRecordedAudioName']); + Route::post('progress/{submateri_id}/save', [ProgressController::class, 'saveProgress']); + Route::get('/hasil-penilaian/{submateri_id}', [ProgressController::class, 'getHasilPenilaianByUserAndSubMateri']); + Route::post('/upload_audio', [UploadAudioController::class, 'uploadAudio']); +}); + + + +Route::get('/users/progres', [UserControler::class, 'getUserProgres']); + diff --git a/routes/web.php b/routes/web.php index d259f33..3b126a0 100644 --- a/routes/web.php +++ b/routes/web.php @@ -16,3 +16,7 @@ Route::get('/', function () { return view('welcome'); }); + +Auth::routes(); + +Route::get('/home', [App\Http\Controllers\HomeController::class, 'index'])->name('home'); diff --git a/vite.config.js b/vite.config.js index 421b569..a36b23a 100644 --- a/vite.config.js +++ b/vite.config.js @@ -1,11 +1,28 @@ import { defineConfig } from 'vite'; import laravel from 'laravel-vite-plugin'; +import vue from '@vitejs/plugin-vue'; export default defineConfig({ plugins: [ laravel({ - input: ['resources/css/app.css', 'resources/js/app.js'], + input: [ + 'resources/sass/app.scss', + 'resources/js/app.js', + ], refresh: true, }), + vue({ + template: { + transformAssetUrls: { + base: null, + includeAbsolute: false, + }, + }, + }), ], + resolve: { + alias: { + vue: 'vue/dist/vue.esm-bundler.js', + }, + }, });