parent
e4cae10aa5
commit
d5eedb16ae
|
|
@ -100,7 +100,9 @@ public function sendOtp(Request $request)
|
||||||
// Simpan email ke session untuk prefill form login
|
// Simpan email ke session untuk prefill form login
|
||||||
session(['email' => $user->email]);
|
session(['email' => $user->email]);
|
||||||
|
|
||||||
return back()->with('otp_sent', 'OTP berhasil dikirim!');
|
return back()
|
||||||
|
->with('otp_sent', 'OTP berhasil dikirim!')
|
||||||
|
->with('need_otp', true); // ← tambahkan ini
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -108,82 +110,116 @@ public function sendOtp(Request $request)
|
||||||
// Dipanggil dari form Langkah 2 di halaman login
|
// Dipanggil dari form Langkah 2 di halaman login
|
||||||
public function loginOtp(Request $request)
|
public function loginOtp(Request $request)
|
||||||
{
|
{
|
||||||
|
\Log::info('=== LOGIN OTP CALLED - AuthController ===');
|
||||||
$request->validate([
|
$request->validate([
|
||||||
'email' => 'required|email',
|
'email' => 'required|email',
|
||||||
'password' => 'required',
|
'password' => 'required',
|
||||||
'otp' => 'nullable|digits:6',
|
'otp' => 'nullable|digits:6',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// ✅ FIX: tambah with('guru') agar relasi ter-load
|
$user = User::with(['guru', 'siswa'])->where('email', $request->email)->first();
|
||||||
$user = User::with('guru')->where('email', $request->email)->first();
|
|
||||||
|
|
||||||
if (!$user) {
|
if (!$user) {
|
||||||
return back()->with('error', 'Email tidak ditemukan.');
|
return back()->with('error', 'Email tidak ditemukan.');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cek password
|
|
||||||
if (!Hash::check($request->password, $user->password)) {
|
if (!Hash::check($request->password, $user->password)) {
|
||||||
return back()->with('error', 'Password salah.');
|
return back()->with('error', 'Password salah.');
|
||||||
}
|
}
|
||||||
|
|
||||||
// =================================================
|
// ADMIN → langsung login tanpa OTP
|
||||||
// ADMIN → LANGSUNG LOGIN TANPA OTP
|
|
||||||
// =================================================
|
|
||||||
if ($user->role === 'admin') {
|
if ($user->role === 'admin') {
|
||||||
|
|
||||||
Auth::login($user);
|
Auth::login($user);
|
||||||
$request->session()->regenerate();
|
$request->session()->regenerate();
|
||||||
|
|
||||||
return redirect()->route('admin.dashboard');
|
return redirect()->route('admin.dashboard');
|
||||||
}
|
}
|
||||||
|
|
||||||
// =================================================
|
// Cek status nonaktif
|
||||||
// GURU / SISWA WAJIB OTP
|
if ($user->role === 'guru' && (!$user->guru || $user->guru->status === 'nonaktif')) {
|
||||||
// =================================================
|
return back()->with('error', 'Akun Anda telah dinonaktifkan.');
|
||||||
if (in_array($user->role, ['guru', 'siswa'])) {
|
}
|
||||||
|
if ($user->role === 'siswa' && (!$user->siswa || $user->siswa->status === 'nonaktif')) {
|
||||||
|
return back()->with('error', 'Akun Anda telah dinonaktifkan.');
|
||||||
|
}
|
||||||
|
|
||||||
// ✅ FIX: cek status nonaktif sebelum proses OTP
|
// ── Cek cookie trusted device (per user per device) ──
|
||||||
if ($user->role === 'guru') {
|
$cookieKey = 'trusted_device_' . $user->id;
|
||||||
$user->load('guru'); // reload fresh dari DB
|
// SESUDAH
|
||||||
if (!$user->guru || $user->guru->status === 'nonaktif') {
|
$cookieValue = \Illuminate\Support\Facades\Cookie::get($cookieKey);
|
||||||
return back()->with('error', 'Akun Anda telah dinonaktifkan. Hubungi administrator.');
|
$trustedDevices = $user->trusted_devices ?? [];
|
||||||
}
|
$isDeviceTrusted = $cookieValue && in_array($cookieValue, $trustedDevices);
|
||||||
}
|
|
||||||
|
// DEBUG SEMENTARA — hapus setelah fix
|
||||||
|
\Log::info('COOKIE CHECK', [
|
||||||
|
'cookie_key' => $cookieKey,
|
||||||
|
'cookie_value' => $cookieValue,
|
||||||
|
'trusted_list' => $trustedDevices,
|
||||||
|
'is_trusted' => $isDeviceTrusted,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Device BARU → wajib OTP
|
||||||
|
if (!$isDeviceTrusted) {
|
||||||
|
|
||||||
if (empty($request->otp)) {
|
if (empty($request->otp)) {
|
||||||
return back()->with('error', 'OTP wajib diisi.');
|
return back()
|
||||||
|
->withInput()
|
||||||
|
->with('error', 'Perangkat baru terdeteksi! Silakan minta OTP terlebih dahulu.')
|
||||||
|
->with('need_otp', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$user->otp) {
|
if (!$user->otp) {
|
||||||
return back()->with('error', 'OTP belum diminta.');
|
return back()->withInput()
|
||||||
|
->with('error', 'OTP belum diminta.')
|
||||||
|
->with('need_otp', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((string)$request->otp !== (string)$user->otp) {
|
if ((string)$request->otp !== (string)$user->otp) {
|
||||||
return back()->with('error', 'Kode OTP salah.');
|
return back()->withInput()
|
||||||
|
->with('error', 'Kode OTP salah.')
|
||||||
|
->with('need_otp', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$user->otp_expired_at || now()->gt($user->otp_expired_at)) {
|
if (!$user->otp_expired_at || now()->gt($user->otp_expired_at)) {
|
||||||
return back()->with('error', 'OTP sudah kadaluarsa.');
|
return back()->withInput()
|
||||||
|
->with('error', 'OTP sudah kadaluarsa.')
|
||||||
|
->with('need_otp', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Login
|
// Generate token unik untuk device ini
|
||||||
Auth::login($user);
|
$deviceToken = \Illuminate\Support\Str::random(60);
|
||||||
$request->session()->regenerate();
|
|
||||||
|
// Simpan ke trusted_devices (maks 10)
|
||||||
|
$trustedDevices[] = $deviceToken;
|
||||||
|
if (count($trustedDevices) > 10) array_shift($trustedDevices);
|
||||||
|
|
||||||
// Hapus OTP
|
|
||||||
$user->update([
|
$user->update([
|
||||||
'otp' => null,
|
'otp' => null,
|
||||||
'otp_expired_at' => null,
|
'otp_expired_at' => null,
|
||||||
|
'trusted_devices' => $trustedDevices,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
Auth::login($user);
|
||||||
|
$request->session()->regenerate();
|
||||||
|
|
||||||
|
// Simpan cookie selama 365 hari
|
||||||
|
$cookie = cookie($cookieKey, $deviceToken, 60 * 24 * 365, '/', null, false, true);
|
||||||
|
|
||||||
|
return match ($user->role) {
|
||||||
|
'guru' => redirect()->route('guru.dashboard')->withCookie($cookie),
|
||||||
|
'siswa' => redirect()->route('siswa.dashboard')->withCookie($cookie),
|
||||||
|
default => redirect('/')->withCookie($cookie),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Device LAMA → langsung login
|
||||||
|
Auth::login($user);
|
||||||
|
$request->session()->regenerate();
|
||||||
|
|
||||||
return match ($user->role) {
|
return match ($user->role) {
|
||||||
'guru' => redirect()->route('guru.dashboard'),
|
'guru' => redirect()->route('guru.dashboard'),
|
||||||
'siswa' => redirect()->route('siswa.dashboard'),
|
'siswa' => redirect()->route('siswa.dashboard'),
|
||||||
default => redirect('/'),
|
default => redirect('/'),
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
return back()->with('error', 'Role tidak dikenali.');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,15 @@ public function create()
|
||||||
// ================= STORE =================
|
// ================= STORE =================
|
||||||
public function store(Request $request)
|
public function store(Request $request)
|
||||||
{
|
{
|
||||||
|
if ($request->has('telepon')) {
|
||||||
|
$telepon = $request->telepon;
|
||||||
|
$telepon = ltrim($telepon, '+');
|
||||||
|
if (strpos($telepon, '62') === 0) {
|
||||||
|
$telepon = '0' . substr($telepon, 2);
|
||||||
|
}
|
||||||
|
$request->merge(['telepon' => $telepon]);
|
||||||
|
}
|
||||||
|
|
||||||
$request->validate([
|
$request->validate([
|
||||||
'nama' => 'required',
|
'nama' => 'required',
|
||||||
'nip' => 'required|unique:gurus,nip',
|
'nip' => 'required|unique:gurus,nip',
|
||||||
|
|
@ -42,7 +51,7 @@ public function store(Request $request)
|
||||||
'email' => 'required|email|unique:users,email'
|
'email' => 'required|email|unique:users,email'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// 🔥 CEK JIKA SUDAH ADA (ANTI DOUBLE INSERT)
|
// 🔥 CEK JIKA SUDAH ADA (ANTI DOUBLE INSERT)
|
||||||
if (Guru::where('nip', $request->nip)->exists()) {
|
if (Guru::where('nip', $request->nip)->exists()) {
|
||||||
return back()->withErrors(['nip' => 'NIP sudah ada'])->withInput();
|
return back()->withErrors(['nip' => 'NIP sudah ada'])->withInput();
|
||||||
}
|
}
|
||||||
|
|
@ -83,6 +92,15 @@ public function edit($id)
|
||||||
// ================= UPDATE =================
|
// ================= UPDATE =================
|
||||||
public function update(Request $request, Guru $guru)
|
public function update(Request $request, Guru $guru)
|
||||||
{
|
{
|
||||||
|
if ($request->has('telepon')) {
|
||||||
|
$telepon = $request->telepon;
|
||||||
|
$telepon = ltrim($telepon, '+');
|
||||||
|
if (strpos($telepon, '62') === 0) {
|
||||||
|
$telepon = '0' . substr($telepon, 2);
|
||||||
|
}
|
||||||
|
$request->merge(['telepon' => $telepon]);
|
||||||
|
}
|
||||||
|
|
||||||
$request->validate([
|
$request->validate([
|
||||||
'nama' => 'required',
|
'nama' => 'required',
|
||||||
'nip' => 'required|unique:gurus,nip,' . $guru->id,
|
'nip' => 'required|unique:gurus,nip,' . $guru->id,
|
||||||
|
|
@ -94,18 +112,18 @@ public function update(Request $request, Guru $guru)
|
||||||
$guru->update([
|
$guru->update([
|
||||||
'nama' => $request->nama,
|
'nama' => $request->nama,
|
||||||
'nip' => $request->nip,
|
'nip' => $request->nip,
|
||||||
'mapel_id' => $request->mapel_id, // 🔥 INI WAJIB
|
'mapel_id' => $request->mapel_id, // 🔥 INI WAJIB
|
||||||
'alamat' => $request->alamat,
|
'alamat' => $request->alamat,
|
||||||
'telepon' => $request->telepon,
|
'telepon' => $request->telepon,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// 🔥 UPDATE USER (TELEPON JUGA UPDATE)
|
// 🔥 UPDATE USER (TELEPON JUGA UPDATE)
|
||||||
$user = User::where('guru_id', $guru->id)->first();
|
$user = User::where('guru_id', $guru->id)->first();
|
||||||
if ($user) {
|
if ($user) {
|
||||||
$user->update([
|
$user->update([
|
||||||
'name' => $request->nama,
|
'name' => $request->nama,
|
||||||
'mapel_id' => $request->mapel_id,
|
'mapel_id' => $request->mapel_id,
|
||||||
'telepon' => $request->telepon // ✅ WAJIB ADA
|
'telepon' => $request->telepon // ✅ WAJIB ADA
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,11 @@ public function store(Request $request)
|
||||||
'jam_selesai' => 'required',
|
'jam_selesai' => 'required',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// ✅ Validasi jam selesai harus lebih besar dari jam mulai
|
||||||
|
if ($request->jam_selesai <= $request->jam_mulai) {
|
||||||
|
return back()->withInput()->with('error', 'Jam selesai harus lebih besar dari jam mulai!');
|
||||||
|
}
|
||||||
|
|
||||||
// 🔥 CEK BENTROK KELAS
|
// 🔥 CEK BENTROK KELAS
|
||||||
$cekKelas = Jadwal::where('kelas_id', $request->kelas_id)
|
$cekKelas = Jadwal::where('kelas_id', $request->kelas_id)
|
||||||
->where('hari', $request->hari)
|
->where('hari', $request->hari)
|
||||||
|
|
@ -120,6 +125,11 @@ public function update(Request $request, $id)
|
||||||
'jam_selesai' => 'required',
|
'jam_selesai' => 'required',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// ✅ Validasi jam selesai harus lebih besar dari jam mulai
|
||||||
|
if ($request->jam_selesai <= $request->jam_mulai) {
|
||||||
|
return back()->withInput()->with('error', 'Jam selesai harus lebih besar dari jam mulai!');
|
||||||
|
}
|
||||||
|
|
||||||
$jadwal->update([
|
$jadwal->update([
|
||||||
'kelas_id' => $request->kelas_id,
|
'kelas_id' => $request->kelas_id,
|
||||||
'mata_pelajaran_id' => $request->mata_pelajaran_id,
|
'mata_pelajaran_id' => $request->mata_pelajaran_id,
|
||||||
|
|
|
||||||
|
|
@ -14,28 +14,49 @@ public function index()
|
||||||
}
|
}
|
||||||
|
|
||||||
public function update(Request $request)
|
public function update(Request $request)
|
||||||
{
|
{
|
||||||
$user = Auth::user();
|
$user = Auth::user();
|
||||||
|
|
||||||
$request->validate([
|
$rules = [
|
||||||
'name' => 'required',
|
'name' => 'required',
|
||||||
'photo' => 'nullable|image|mimes:jpg,jpeg,png|max:2048'
|
'photo' => 'nullable|image|mimes:jpg,jpeg,png,webp|max:2048',
|
||||||
]);
|
];
|
||||||
|
|
||||||
// update nama
|
// Tambahan validasi khusus siswa
|
||||||
$user->name = $request->name;
|
if ($user->role == 'siswa') {
|
||||||
|
$rules['nama_ortu'] = 'required';
|
||||||
|
$rules['alamat'] = 'required';
|
||||||
|
$rules['telepon'] = 'required';
|
||||||
|
}
|
||||||
|
|
||||||
// upload foto
|
$request->validate($rules);
|
||||||
|
|
||||||
|
// Update foto
|
||||||
if ($request->hasFile('photo')) {
|
if ($request->hasFile('photo')) {
|
||||||
$file = $request->file('photo');
|
$file = $request->file('photo');
|
||||||
$filename = time() . '.' . $file->getClientOriginalExtension();
|
$namaFile = time() . '.' . $file->extension();
|
||||||
$file->move(public_path('uploads'), $filename);
|
$file->move(public_path('uploads'), $namaFile);
|
||||||
|
$user->photo = $namaFile;
|
||||||
$user->photo = $filename;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$user->name = $request->name;
|
||||||
$user->save();
|
$user->save();
|
||||||
|
|
||||||
return back()->with('success', 'Profil berhasil diupdate');
|
// Update tabel siswa jika role siswa
|
||||||
|
if ($user->role == 'siswa' && $user->siswa) {
|
||||||
|
$telepon = ltrim($request->telepon, '+');
|
||||||
|
if (strpos($telepon, '62') === 0) {
|
||||||
|
$telepon = '0' . substr($telepon, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$user->siswa->update([
|
||||||
|
'nama' => $request->name,
|
||||||
|
'nama_ortu' => $request->nama_ortu,
|
||||||
|
'alamat' => $request->alamat,
|
||||||
|
'telepon' => $telepon,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return back()->with('success', 'Profile berhasil diupdate');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -41,6 +41,18 @@ public function store(Request $request)
|
||||||
|
|
||||||
\Log::info('STORE DIPANGGIL');
|
\Log::info('STORE DIPANGGIL');
|
||||||
|
|
||||||
|
if ($request->has('telepon')) {
|
||||||
|
$telepon = $request->telepon;
|
||||||
|
// Hapus tanda plus jika ada (+62 -> 62)
|
||||||
|
$telepon = ltrim($telepon, '+');
|
||||||
|
// Jika tipenya string diawali dengan 62, ganti jadi 0
|
||||||
|
if (strpos($telepon, '62') === 0) {
|
||||||
|
$telepon = '0' . substr($telepon, 2);
|
||||||
|
}
|
||||||
|
// Masukkan kembali nilai yang sudah rapi ke dalam request
|
||||||
|
$request->merge(['telepon' => $telepon]);
|
||||||
|
}
|
||||||
|
|
||||||
$request->validate([
|
$request->validate([
|
||||||
'nama' => 'required',
|
'nama' => 'required',
|
||||||
'jenis_kelamin' => 'required',
|
'jenis_kelamin' => 'required',
|
||||||
|
|
@ -72,10 +84,10 @@ public function store(Request $request)
|
||||||
$user = User::create([
|
$user = User::create([
|
||||||
'name' => $request->nama,
|
'name' => $request->nama,
|
||||||
'email' => $request->email,
|
'email' => $request->email,
|
||||||
'password' => Hash::make('12345678'), // 🔥 default
|
'password' => Hash::make('12345678'), // 🔥 default
|
||||||
'role' => 'siswa', // atau guru
|
'role' => 'siswa', // atau guru
|
||||||
'siswa_id' => $siswa->id,
|
'siswa_id' => $siswa->id,
|
||||||
'is_default_password' => true // 🔥 WAJIB
|
'is_default_password' => true // 🔥 WAJIB
|
||||||
]);
|
]);
|
||||||
|
|
||||||
/** @var \App\Models\User $user */
|
/** @var \App\Models\User $user */
|
||||||
|
|
@ -95,6 +107,19 @@ public function edit(Siswa $siswa)
|
||||||
// ================= UPDATE =================
|
// ================= UPDATE =================
|
||||||
public function update(Request $request, Siswa $siswa)
|
public function update(Request $request, Siswa $siswa)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
if ($request->has('telepon')) {
|
||||||
|
$telepon = $request->telepon;
|
||||||
|
// Hapus tanda plus jika ada (+62 -> 62)
|
||||||
|
$telepon = ltrim($telepon, '+');
|
||||||
|
// Jika tipenya string diawali dengan 62, ganti jadi 0
|
||||||
|
if (strpos($telepon, '62') === 0) {
|
||||||
|
$telepon = '0' . substr($telepon, 2);
|
||||||
|
}
|
||||||
|
// Masukkan kembali nilai yang sudah rapi ke dalam request
|
||||||
|
$request->merge(['telepon' => $telepon]);
|
||||||
|
}
|
||||||
|
|
||||||
$request->validate([
|
$request->validate([
|
||||||
'nama' => 'required',
|
'nama' => 'required',
|
||||||
'jenis_kelamin' => 'required',
|
'jenis_kelamin' => 'required',
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ class OtpController extends Controller
|
||||||
// ================= KIRIM OTP =================
|
// ================= KIRIM OTP =================
|
||||||
public function sendOtp(Request $request)
|
public function sendOtp(Request $request)
|
||||||
{
|
{
|
||||||
|
\Log::info('=== LOGIN OTP CALLED - OtpController ===');
|
||||||
$method = $request->input('method', 'email');
|
$method = $request->input('method', 'email');
|
||||||
|
|
||||||
if ($method === 'email') {
|
if ($method === 'email') {
|
||||||
|
|
@ -65,7 +66,9 @@ public function sendOtp(Request $request)
|
||||||
|
|
||||||
// ================= LOGIN DENGAN OTP =================
|
// ================= LOGIN DENGAN OTP =================
|
||||||
public function loginOtp(Request $request)
|
public function loginOtp(Request $request)
|
||||||
{
|
{
|
||||||
|
\Log::info('=== LOGIN OTP CALLED - OtpController ===');
|
||||||
|
|
||||||
$request->validate([
|
$request->validate([
|
||||||
'email' => 'required|email',
|
'email' => 'required|email',
|
||||||
'password' => 'required',
|
'password' => 'required',
|
||||||
|
|
@ -75,63 +78,112 @@ public function loginOtp(Request $request)
|
||||||
$user = User::with(['guru', 'siswa'])->where('email', $request->email)->first();
|
$user = User::with(['guru', 'siswa'])->where('email', $request->email)->first();
|
||||||
|
|
||||||
if (!$user) {
|
if (!$user) {
|
||||||
return back()->with('error', 'User tidak ditemukan');
|
return back()->with('error', 'Email tidak ditemukan.');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!\Hash::check($request->password, $user->password)) {
|
if (!\Hash::check($request->password, $user->password)) {
|
||||||
return back()->with('error', 'Password salah');
|
return back()->with('error', 'Password salah.');
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ FIX: cek status nonaktif untuk guru
|
// ADMIN → langsung login tanpa OTP
|
||||||
if ($user->role === 'guru') {
|
if ($user->role === 'admin') {
|
||||||
$user->load('guru');
|
\Auth::login($user);
|
||||||
if (!$user->guru || $user->guru->status === 'nonaktif') {
|
$request->session()->regenerate();
|
||||||
return back()->with('error', 'Akun Anda telah dinonaktifkan. Hubungi administrator.');
|
return redirect()->route('admin.dashboard');
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ FIX BARU: cek status nonaktif untuk siswa
|
// Cek status nonaktif
|
||||||
if ($user->role === 'siswa') {
|
if ($user->role === 'guru' && (!$user->guru || $user->guru->status === 'nonaktif')) {
|
||||||
$user->load('siswa');
|
return back()->with('error', 'Akun Anda telah dinonaktifkan.');
|
||||||
if (!$user->siswa || $user->siswa->status === 'nonaktif') {
|
|
||||||
return back()->with('error', 'Akun Anda telah dinonaktifkan. Hubungi administrator.');
|
|
||||||
}
|
}
|
||||||
|
if ($user->role === 'siswa' && (!$user->siswa || $user->siswa->status === 'nonaktif')) {
|
||||||
|
return back()->with('error', 'Akun Anda telah dinonaktifkan.');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Guru & Siswa wajib OTP
|
// ── Cek cookie trusted device ──
|
||||||
if (in_array($user->role, ['guru', 'siswa'])) {
|
$cookieKey = 'trusted_device_' . $user->id;
|
||||||
|
$cookieValue = $request->cookie($cookieKey);
|
||||||
|
$trustedDevices = $user->trusted_devices ?? [];
|
||||||
|
$isDeviceTrusted = $cookieValue && in_array($cookieValue, $trustedDevices);
|
||||||
|
|
||||||
if (!$request->otp) {
|
\Log::info('=== DEVICE CHECK ===', [
|
||||||
return back()->with('error', 'OTP wajib diisi');
|
'user_id' => $user->id,
|
||||||
|
'cookie_key' => $cookieKey,
|
||||||
|
'cookie_value' => $cookieValue,
|
||||||
|
'trusted_list' => $trustedDevices,
|
||||||
|
'is_trusted' => $isDeviceTrusted,
|
||||||
|
'all_cookies' => array_keys($request->cookies->all()),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Device BARU → wajib OTP
|
||||||
|
if (!$isDeviceTrusted) {
|
||||||
|
|
||||||
|
if (empty($request->otp)) {
|
||||||
|
return back()
|
||||||
|
->withInput()
|
||||||
|
->with('error', 'Perangkat baru terdeteksi! Silakan minta OTP terlebih dahulu.')
|
||||||
|
->with('need_otp', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$user->otp) {
|
if (!$user->otp) {
|
||||||
return back()->with('error', 'OTP belum diminta');
|
return back()->withInput()
|
||||||
|
->with('error', 'OTP belum diminta.')
|
||||||
|
->with('need_otp', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((string) $request->otp !== (string) $user->otp) {
|
if ((string)$request->otp !== (string)$user->otp) {
|
||||||
return back()->with('error', 'OTP salah');
|
return back()->withInput()
|
||||||
|
->with('error', 'Kode OTP salah.')
|
||||||
|
->with('need_otp', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$user->otp_expired_at || now()->gt($user->otp_expired_at)) {
|
if (!$user->otp_expired_at || now()->gt($user->otp_expired_at)) {
|
||||||
return back()->with('error', 'OTP sudah kadaluarsa');
|
return back()->withInput()
|
||||||
}
|
->with('error', 'OTP sudah kadaluarsa.')
|
||||||
|
->with('need_otp', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Generate token unik untuk device ini
|
||||||
|
$deviceToken = \Illuminate\Support\Str::random(60);
|
||||||
|
|
||||||
|
// Simpan ke trusted_devices (maks 10)
|
||||||
|
$trustedDevices[] = $deviceToken;
|
||||||
|
if (count($trustedDevices) > 10) array_shift($trustedDevices);
|
||||||
|
|
||||||
|
$user->update([
|
||||||
|
'otp' => null,
|
||||||
|
'otp_expired_at' => null,
|
||||||
|
'trusted_devices' => $trustedDevices,
|
||||||
|
]);
|
||||||
|
|
||||||
\Auth::login($user);
|
\Auth::login($user);
|
||||||
$request->session()->regenerate();
|
$request->session()->regenerate();
|
||||||
|
|
||||||
if (in_array($user->role, ['guru', 'siswa'])) {
|
// Simpan cookie 365 hari
|
||||||
$user->update(['otp' => null, 'otp_expired_at' => null]);
|
$cookie = cookie($cookieKey, $deviceToken, 60 * 24 * 365, '/', null, false, true);
|
||||||
}
|
|
||||||
|
|
||||||
session()->forget('email');
|
\Log::info('=== DEVICE TRUSTED SAVED ===', [
|
||||||
|
'token' => $deviceToken,
|
||||||
|
'cookie_key' => $cookieKey,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return match ($user->role) {
|
||||||
|
'guru' => redirect()->route('guru.dashboard')->withCookie($cookie),
|
||||||
|
'siswa' => redirect()->route('siswa.dashboard')->withCookie($cookie),
|
||||||
|
default => redirect('/')->withCookie($cookie),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Device LAMA → langsung login
|
||||||
|
\Log::info('=== DEVICE ALREADY TRUSTED - SKIP OTP ===');
|
||||||
|
|
||||||
|
\Auth::login($user);
|
||||||
|
$request->session()->regenerate();
|
||||||
|
|
||||||
return match ($user->role) {
|
return match ($user->role) {
|
||||||
'admin' => redirect()->route('admin.dashboard'),
|
|
||||||
'guru' => redirect()->route('guru.dashboard'),
|
'guru' => redirect()->route('guru.dashboard'),
|
||||||
'siswa' => redirect()->route('siswa.dashboard'),
|
'siswa' => redirect()->route('siswa.dashboard'),
|
||||||
default => redirect('/'),
|
default => redirect('/'),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Http\Controllers\guru;
|
namespace App\Http\Controllers\Guru;
|
||||||
|
|
||||||
use App\Models\Jadwal;
|
use App\Models\Jadwal;
|
||||||
use App\Models\Absensi;
|
use App\Models\Absensi;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Http\Controllers\guru;
|
namespace App\Http\Controllers\Guru;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Models\Jadwal;
|
use App\Models\Jadwal;
|
||||||
|
|
|
||||||
|
|
@ -200,6 +200,28 @@ public function masukkanNilaiUjian(Request $request)
|
||||||
return back()->with('success', 'Nilai ' . strtoupper($jenis) . ' berhasil dikirim ke menu nilai!');
|
return back()->with('success', 'Nilai ' . strtoupper($jenis) . ' berhasil dikirim ke menu nilai!');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── KIRIM NILAI KE SISWA ─────────────────────────────────────────
|
||||||
|
public function kirimKeSiswa(Request $request)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'kelas_id' => 'required',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$user = auth()->user();
|
||||||
|
$mapel_id = $user->mapel_id;
|
||||||
|
|
||||||
|
$nilaiList = Nilai::with('siswa')
|
||||||
|
->where('mapel_id', $mapel_id)
|
||||||
|
->whereHas('siswa', fn($q) => $q->where('kelas_id', $request->kelas_id))
|
||||||
|
->get();
|
||||||
|
|
||||||
|
foreach ($nilaiList as $n) {
|
||||||
|
$n->update(['sudah_kirim' => true]); // ✅ ganti is_published → sudah_kirim
|
||||||
|
}
|
||||||
|
|
||||||
|
return back()->with('success', 'Nilai berhasil dikirim ke siswa!');
|
||||||
|
}
|
||||||
|
|
||||||
// ── EDIT ─────────────────────────────────────────────────────────
|
// ── EDIT ─────────────────────────────────────────────────────────
|
||||||
public function edit($id)
|
public function edit($id)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -215,4 +215,52 @@ public function nilai(Request $request, $id)
|
||||||
|
|
||||||
return back()->with('success', 'Nilai berhasil disimpan');
|
return back()->with('success', 'Nilai berhasil disimpan');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function create()
|
||||||
|
{
|
||||||
|
$guru = auth()->user()->guru;
|
||||||
|
$kelas = Kelas::all();
|
||||||
|
|
||||||
|
// Ambil satu mapel pertama yang diajarkan guru ini
|
||||||
|
$mapel = Jadwal::where('guru_id', $guru->id)
|
||||||
|
->join('mata_pelajarans', 'jadwals.mata_pelajaran_id', '=', 'mata_pelajarans.id')
|
||||||
|
->select('mata_pelajarans.id', 'mata_pelajarans.nama_mapel')
|
||||||
|
->first();
|
||||||
|
|
||||||
|
return view('guru.tugas.create', compact('kelas', 'guru', 'mapel'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(Request $request)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'judul' => 'required',
|
||||||
|
'kelas_id' => 'required',
|
||||||
|
'mapel_id' => 'required',
|
||||||
|
'deadline' => 'required',
|
||||||
|
'file' => 'nullable|max:10240',
|
||||||
|
], [
|
||||||
|
'mapel_id.required' => 'Mata pelajaran belum diset, hubungi admin untuk mengatur jadwal.',
|
||||||
|
'file.max' => 'Ukuran file maksimal 10MB.',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$guru = auth()->user()->guru;
|
||||||
|
|
||||||
|
$filePath = null;
|
||||||
|
if ($request->hasFile('file')) {
|
||||||
|
$filePath = $request->file('file')->store('tugas_file', 'public');
|
||||||
|
}
|
||||||
|
|
||||||
|
Tugas::create([
|
||||||
|
'judul' => $request->judul,
|
||||||
|
'deskripsi' => $request->deskripsi,
|
||||||
|
'kelas_id' => $request->kelas_id,
|
||||||
|
'mapel_id' => $request->mapel_id, // ✅ sesuaikan nama kolom
|
||||||
|
'guru_id' => $guru->id,
|
||||||
|
'deadline' => $request->deadline,
|
||||||
|
'file' => $filePath,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return redirect()->route('guru.tugas.index')
|
||||||
|
->with('success', 'Tugas berhasil dibuat');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Illuminate\Cookie\Middleware\EncryptCookies as Middleware;
|
||||||
|
|
||||||
|
class EncryptCookies extends Middleware
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Cookie yang tidak dienkripsi
|
||||||
|
*/
|
||||||
|
protected $except = [
|
||||||
|
//
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
@ -29,8 +29,9 @@ class User extends Authenticatable
|
||||||
'photo',
|
'photo',
|
||||||
'otp',
|
'otp',
|
||||||
'otp_expired_at',
|
'otp_expired_at',
|
||||||
'telepon', // 🔥 WAJIB TAMBAH INI
|
'telepon',
|
||||||
'is_default_password'
|
'is_default_password',
|
||||||
|
'trusted_devices', // ← tambahkan ini
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -47,6 +48,7 @@ class User extends Authenticatable
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
'email_verified_at' => 'datetime',
|
'email_verified_at' => 'datetime',
|
||||||
'password' => 'hashed',
|
'password' => 'hashed',
|
||||||
|
'trusted_devices' => 'array',
|
||||||
];
|
];
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -104,7 +106,7 @@ public function mapel()
|
||||||
|
|
||||||
// Ambil nomor WA dengan fallback
|
// Ambil nomor WA dengan fallback
|
||||||
public function getTeleponLengkap()
|
public function getTeleponLengkap()
|
||||||
{
|
{
|
||||||
if ($this->telepon) {
|
if ($this->telepon) {
|
||||||
return $this->telepon;
|
return $this->telepon;
|
||||||
}
|
}
|
||||||
|
|
@ -119,5 +121,5 @@ public function getTeleponLengkap()
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::table('users', function (Blueprint $table) {
|
||||||
|
$table->json('trusted_devices')->nullable()->after('otp_expired_at');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('users', function (Blueprint $table) {
|
||||||
|
//
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 1.0 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.4 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.7 MiB |
|
|
@ -151,16 +151,46 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{-- JAM --}}
|
{{-- JAM --}}
|
||||||
<div class="field-row">
|
<div class="field-row">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="field-label">Jam Mulai</label>
|
<label class="field-label">Jam Mulai</label>
|
||||||
<input type="time" name="jam_mulai" value="{{ old('jam_mulai') }}" class="field-input">
|
<div class="select-wrap">
|
||||||
|
<select name="jam_mulai" class="field-select">
|
||||||
|
<option value="">-- Pilih Jam Mulai --</option>
|
||||||
|
@php
|
||||||
|
$slots = [];
|
||||||
|
$start = strtotime('07:00');
|
||||||
|
$end = strtotime('15:10');
|
||||||
|
for ($t = $start; $t <= $end; $t += 10 * 60) {
|
||||||
|
$slots[] = date('H:i', $t);
|
||||||
|
if (($t + 5 * 60) <= $end) {
|
||||||
|
$slots[] = date('H:i', $t + 5 * 60);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort($slots);
|
||||||
|
@endphp
|
||||||
|
@foreach($slots as $slot)
|
||||||
|
<option value="{{ $slot }}" {{ old('jam_mulai') == $slot ? 'selected' : '' }}>
|
||||||
|
{{ $slot }}
|
||||||
|
</option>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="field-label">Jam Selesai</label>
|
<label class="field-label">Jam Selesai</label>
|
||||||
<input type="time" name="jam_selesai" value="{{ old('jam_selesai') }}" class="field-input">
|
<div class="select-wrap">
|
||||||
|
<select name="jam_selesai" class="field-select">
|
||||||
|
<option value="">-- Pilih Jam Selesai --</option>
|
||||||
|
@foreach($slots as $slot)
|
||||||
|
<option value="{{ $slot }}" {{ old('jam_selesai') == $slot ? 'selected' : '' }}>
|
||||||
|
{{ $slot }}
|
||||||
|
</option>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="form-footer">
|
<div class="form-footer">
|
||||||
<button type="submit" class="btn-submit"><i class="fas fa-save"></i> Simpan</button>
|
<button type="submit" class="btn-submit"><i class="fas fa-save"></i> Simpan</button>
|
||||||
|
|
|
||||||
|
|
@ -172,16 +172,46 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{-- JAM --}}
|
{{-- JAM --}}
|
||||||
<div class="field-row">
|
<div class="field-row">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="field-label">Jam Mulai</label>
|
<label class="field-label">Jam Mulai</label>
|
||||||
<input type="time" name="jam_mulai" value="{{ $jadwal->jam_mulai }}" class="field-input" required>
|
<div class="select-wrap">
|
||||||
|
<select name="jam_mulai" class="field-select" required>
|
||||||
|
<option value="">-- Pilih Jam Mulai --</option>
|
||||||
|
@php
|
||||||
|
$slots = [];
|
||||||
|
$start = strtotime('07:00');
|
||||||
|
$end = strtotime('15:10');
|
||||||
|
for ($t = $start; $t <= $end; $t += 10 * 60) {
|
||||||
|
$slots[] = date('H:i', $t);
|
||||||
|
if (($t + 5 * 60) <= $end) {
|
||||||
|
$slots[] = date('H:i', $t + 5 * 60);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort($slots);
|
||||||
|
@endphp
|
||||||
|
@foreach($slots as $slot)
|
||||||
|
<option value="{{ $slot }}" {{ old('jam_mulai', \Illuminate\Support\Str::substr($jadwal->jam_mulai, 0, 5)) == $slot ? 'selected' : '' }}>
|
||||||
|
{{ $slot }}
|
||||||
|
</option>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="field-label">Jam Selesai</label>
|
<label class="field-label">Jam Selesai</label>
|
||||||
<input type="time" name="jam_selesai" value="{{ $jadwal->jam_selesai }}" class="field-input" required>
|
<div class="select-wrap">
|
||||||
|
<select name="jam_selesai" class="field-select" required>
|
||||||
|
<option value="">-- Pilih Jam Selesai --</option>
|
||||||
|
@foreach($slots as $slot)
|
||||||
|
<option value="{{ $slot }}" {{ old('jam_selesai', \Illuminate\Support\Str::substr($jadwal->jam_selesai, 0, 5)) == $slot ? 'selected' : '' }}>
|
||||||
|
{{ $slot }}
|
||||||
|
</option>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="form-footer">
|
<div class="form-footer">
|
||||||
<button type="submit" class="btn-submit"><i class="fas fa-save"></i> Update</button>
|
<button type="submit" class="btn-submit"><i class="fas fa-save"></i> Update</button>
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,6 @@
|
||||||
--primary-light: #EEF2FF;
|
--primary-light: #EEF2FF;
|
||||||
--success: #059669;
|
--success: #059669;
|
||||||
--success-light: #ECFDF5;
|
--success-light: #ECFDF5;
|
||||||
--danger: #DC2626;
|
|
||||||
--neutral-light: #F9FAFB;
|
|
||||||
--bg: #F3F4F8;
|
--bg: #F3F4F8;
|
||||||
--surface: #FFFFFF;
|
--surface: #FFFFFF;
|
||||||
--border: #E5E7EB;
|
--border: #E5E7EB;
|
||||||
|
|
@ -25,10 +23,11 @@
|
||||||
|
|
||||||
* { font-family: 'Plus Jakarta Sans', sans-serif; box-sizing: border-box; }
|
* { font-family: 'Plus Jakarta Sans', sans-serif; box-sizing: border-box; }
|
||||||
|
|
||||||
|
/* ── PAGE ── */
|
||||||
.sw-page { padding: 28px 32px; background: var(--bg); min-height: 100vh; }
|
.sw-page { padding: 28px 32px; background: var(--bg); min-height: 100vh; }
|
||||||
|
|
||||||
/* ── TOPBAR ── */
|
/* ── TOPBAR ── */
|
||||||
.sw-topbar { margin-bottom: 28px; }
|
.sw-topbar { margin-bottom: 24px; }
|
||||||
.sw-topbar h3 { font-size: 21px; font-weight: 800; color: var(--text-dark); margin: 0 0 4px; letter-spacing: -.3px; }
|
.sw-topbar h3 { font-size: 21px; font-weight: 800; color: var(--text-dark); margin: 0 0 4px; letter-spacing: -.3px; }
|
||||||
.sw-topbar p { font-size: 13px; color: var(--text-soft); margin: 0; }
|
.sw-topbar p { font-size: 13px; color: var(--text-soft); margin: 0; }
|
||||||
|
|
||||||
|
|
@ -40,7 +39,7 @@
|
||||||
font-size: 13px; color: var(--success); margin-bottom: 24px; font-weight: 500;
|
font-size: 13px; color: var(--success); margin-bottom: 24px; font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── GRID LAYOUT ── */
|
/* ── GRID ── */
|
||||||
.sw-grid {
|
.sw-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 300px 1fr;
|
grid-template-columns: 300px 1fr;
|
||||||
|
|
@ -53,24 +52,15 @@
|
||||||
background: var(--surface);
|
background: var(--surface);
|
||||||
border-radius: var(--radius-lg);
|
border-radius: var(--radius-lg);
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
box-shadow: 0 1px 3px rgba(0,0,0,.05);
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── AVATAR CARD ── */
|
/* ── AVATAR CARD ── */
|
||||||
.avatar-card { text-align: center; }
|
|
||||||
|
|
||||||
.avatar-banner {
|
.avatar-banner {
|
||||||
height: 90px;
|
height: 90px;
|
||||||
background: linear-gradient(135deg, #1E3A6E 0%, #4F46E5 60%, #7C3AED 100%);
|
background: linear-gradient(135deg, #1E3A6E 0%, #4F46E5 60%, #7C3AED 100%);
|
||||||
position: relative;
|
|
||||||
}
|
}
|
||||||
.avatar-banner::after {
|
.avatar-body { padding: 0 24px 28px; text-align: center; }
|
||||||
content: '';
|
|
||||||
position: absolute; inset: 0;
|
|
||||||
background: url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%23ffffff' fill-opacity='0.04'%3E%3Ccircle cx='30' cy='30' r='20'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatar-wrap {
|
.avatar-wrap {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
|
@ -79,81 +69,70 @@
|
||||||
}
|
}
|
||||||
.avatar-img {
|
.avatar-img {
|
||||||
width: 100px; height: 100px;
|
width: 100px; height: 100px;
|
||||||
border-radius: 50%;
|
border-radius: 50%; object-fit: cover;
|
||||||
object-fit: cover;
|
border: 4px solid var(--surface); display: block;
|
||||||
border: 4px solid var(--surface);
|
|
||||||
box-shadow: 0 4px 16px rgba(0,0,0,.12);
|
|
||||||
display: block;
|
|
||||||
}
|
}
|
||||||
.avatar-online {
|
.avatar-online {
|
||||||
position: absolute; bottom: 6px; right: 6px;
|
position: absolute; bottom: 6px; right: 6px;
|
||||||
width: 14px; height: 14px;
|
width: 14px; height: 14px;
|
||||||
background: var(--success);
|
background: var(--success); border: 2px solid var(--surface); border-radius: 50%;
|
||||||
border: 2px solid var(--surface);
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
}
|
||||||
|
.avatar-name { font-size: 17px; font-weight: 800; color: var(--text-dark); margin-bottom: 6px; }
|
||||||
.avatar-body { padding: 0 24px 28px; }
|
|
||||||
.avatar-name { font-size: 17px; font-weight: 800; color: var(--text-dark); margin-bottom: 4px; }
|
|
||||||
.avatar-role {
|
.avatar-role {
|
||||||
display: inline-flex; align-items: center; gap: 5px;
|
display: inline-flex; align-items: center; gap: 5px;
|
||||||
background: var(--primary-light); border: 1px solid #C7D2FE;
|
background: var(--primary-light); border: 1px solid #C7D2FE;
|
||||||
border-radius: 99px; padding: 4px 12px;
|
border-radius: 99px; padding: 4px 12px;
|
||||||
font-size: 11.5px; font-weight: 700; color: var(--primary);
|
font-size: 11.5px; font-weight: 700; color: var(--primary); margin-bottom: 20px;
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
}
|
||||||
.avatar-role span { width: 6px; height: 6px; background: var(--primary); border-radius: 50%; }
|
.avatar-role span { width: 6px; height: 6px; background: var(--primary); border-radius: 50%; }
|
||||||
|
|
||||||
.avatar-stats {
|
.avatar-stats {
|
||||||
display: flex; gap: 0;
|
display: flex;
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border); border-radius: var(--radius-md); overflow: hidden;
|
||||||
border-radius: var(--radius-md);
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
.avatar-stat {
|
|
||||||
flex: 1; padding: 12px 8px; text-align: center;
|
|
||||||
border-right: 1px solid var(--border);
|
|
||||||
}
|
}
|
||||||
|
.avatar-stat { flex: 1; padding: 12px 8px; text-align: center; border-right: 1px solid var(--border); }
|
||||||
.avatar-stat:last-child { border-right: none; }
|
.avatar-stat:last-child { border-right: none; }
|
||||||
.avatar-stat-val { font-size: 16px; font-weight: 800; color: var(--primary); }
|
.avatar-stat-val { display: flex; justify-content: center; margin-bottom: 4px; }
|
||||||
.avatar-stat-lbl { font-size: 10.5px; color: var(--text-soft); font-weight: 600; margin-top: 2px; }
|
.avatar-stat-lbl { font-size: 10.5px; color: var(--text-soft); font-weight: 600; }
|
||||||
|
|
||||||
/* ── FORM CARD ── */
|
/* ── INFO LIST (siswa) ── */
|
||||||
|
.avatar-info-list { margin-top: 18px; display: flex; flex-direction: column; gap: 10px; text-align: left; }
|
||||||
|
.avatar-info-item {
|
||||||
|
display: flex; align-items: flex-start; gap: 10px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
background: #F9FAFB; border: 1px solid var(--border); border-radius: var(--radius-md);
|
||||||
|
}
|
||||||
|
.avatar-info-item svg { color: var(--primary); flex-shrink: 0; margin-top: 1px; }
|
||||||
|
.avatar-info-item .lbl { font-size: 10.5px; font-weight: 700; color: var(--text-soft); text-transform: uppercase; letter-spacing: .5px; margin-bottom: 2px; }
|
||||||
|
.avatar-info-item .val { font-size: 13px; font-weight: 600; color: var(--text-dark); }
|
||||||
|
|
||||||
|
/* ── CARD HEADER ── */
|
||||||
.sw-card-header {
|
.sw-card-header {
|
||||||
display: flex; align-items: center; gap: 12px;
|
display: flex; align-items: center; gap: 12px;
|
||||||
padding: 20px 24px;
|
padding: 20px 24px; border-bottom: 1px solid var(--border);
|
||||||
border-bottom: 1px solid var(--border);
|
background: #F5F3FF;
|
||||||
background: linear-gradient(135deg, #F5F3FF 0%, #EEF2FF 100%);
|
|
||||||
}
|
}
|
||||||
.sw-card-header-icon {
|
.sw-card-header-icon {
|
||||||
width: 38px; height: 38px;
|
width: 38px; height: 38px;
|
||||||
background: var(--primary); border-radius: 10px;
|
background: var(--primary); border-radius: 10px;
|
||||||
display: flex; align-items: center; justify-content: center;
|
display: flex; align-items: center; justify-content: center;
|
||||||
color: #fff; flex-shrink: 0;
|
color: #fff; flex-shrink: 0;
|
||||||
box-shadow: 0 2px 8px rgba(79,70,229,.3);
|
|
||||||
}
|
}
|
||||||
.sw-card-header h5 { font-size: 15px; font-weight: 700; color: #3730A3; margin: 0; }
|
.sw-card-header h5 { font-size: 15px; font-weight: 700; color: #3730A3; margin: 0; }
|
||||||
.sw-card-header p { font-size: 12.5px; color: #6D6AA4; margin: 2px 0 0; }
|
.sw-card-header p { font-size: 12.5px; color: #6D6AA4; margin: 2px 0 0; }
|
||||||
|
|
||||||
.sw-card-body { padding: 28px 24px; }
|
.sw-card-body { padding: 28px 24px; }
|
||||||
|
|
||||||
/* ── SECTION ── */
|
/* ── SECTION DIVIDER ── */
|
||||||
.sw-section {
|
.sw-section { display: flex; align-items: center; gap: 10px; margin-bottom: 20px; }
|
||||||
display: flex; align-items: center; gap: 10px;
|
.sw-section-label { font-size: 11.5px; font-weight: 700; color: var(--primary); text-transform: uppercase; letter-spacing: .8px; white-space: nowrap; }
|
||||||
margin: 0 0 20px;
|
.sw-section-line { flex: 1; height: 1px; background: linear-gradient(to right, #C7D2FE, transparent); }
|
||||||
}
|
|
||||||
.sw-section-label {
|
|
||||||
font-size: 11.5px; font-weight: 700; color: var(--primary);
|
|
||||||
text-transform: uppercase; letter-spacing: .8px; white-space: nowrap;
|
|
||||||
}
|
|
||||||
.sw-section-line {
|
|
||||||
flex: 1; height: 1px;
|
|
||||||
background: linear-gradient(to right, #C7D2FE, transparent);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── FORM ELEMENTS ── */
|
/* ── FORM ── */
|
||||||
.sw-form-group { display: flex; flex-direction: column; gap: 6px; margin-bottom: 20px; }
|
.sw-row { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
|
||||||
|
.sw-form-group { display: flex; flex-direction: column; gap: 6px; margin-bottom: 18px; }
|
||||||
.sw-label { font-size: 12.5px; font-weight: 600; color: var(--text-mid); }
|
.sw-label { font-size: 12.5px; font-weight: 600; color: var(--text-mid); }
|
||||||
|
.sw-input-note { font-size: 11px; color: var(--text-soft); }
|
||||||
|
|
||||||
.sw-input-wrap { position: relative; }
|
.sw-input-wrap { position: relative; }
|
||||||
.sw-input-wrap svg {
|
.sw-input-wrap svg {
|
||||||
|
|
@ -161,38 +140,41 @@
|
||||||
transform: translateY(-50%); color: #9CA3AF; pointer-events: none;
|
transform: translateY(-50%); color: #9CA3AF; pointer-events: none;
|
||||||
}
|
}
|
||||||
.sw-input {
|
.sw-input {
|
||||||
width: 100%;
|
width: 100%; padding: 11px 14px 11px 42px;
|
||||||
padding: 11px 14px 11px 42px;
|
border: 1.5px solid var(--border); border-radius: var(--radius-md);
|
||||||
border: 1.5px solid var(--border);
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
font-size: 14px; font-family: 'Plus Jakarta Sans', sans-serif;
|
font-size: 14px; font-family: 'Plus Jakarta Sans', sans-serif;
|
||||||
color: var(--text-dark); background: var(--neutral-light);
|
color: var(--text-dark); background: #F9FAFB;
|
||||||
outline: none; transition: all .15s;
|
outline: none; transition: all .15s;
|
||||||
}
|
}
|
||||||
.sw-input:focus {
|
.sw-input:focus { border-color: var(--primary); background: #fff; box-shadow: 0 0 0 3px rgba(79,70,229,.1); }
|
||||||
border-color: var(--primary);
|
.sw-input[readonly] { background: #F3F4F8; color: var(--text-soft); cursor: not-allowed; }
|
||||||
background: #fff;
|
|
||||||
box-shadow: 0 0 0 3px rgba(79,70,229,.1);
|
.sw-textarea {
|
||||||
}
|
width: 100%; padding: 11px 14px;
|
||||||
|
border: 1.5px solid var(--border); border-radius: var(--radius-md);
|
||||||
|
font-size: 14px; font-family: 'Plus Jakarta Sans', sans-serif;
|
||||||
|
color: var(--text-dark); background: #F9FAFB;
|
||||||
|
outline: none; resize: vertical; min-height: 80px; transition: all .15s;
|
||||||
|
}
|
||||||
|
.sw-textarea:focus { border-color: var(--primary); background: #fff; box-shadow: 0 0 0 3px rgba(79,70,229,.1); }
|
||||||
|
|
||||||
|
/* ── PHOTO ── */
|
||||||
|
.sw-photo-preview {
|
||||||
|
display: flex; align-items: center; gap: 14px;
|
||||||
|
background: #F9FAFB; border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius-md); padding: 14px 16px; margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
.sw-photo-preview img { width: 52px; height: 52px; border-radius: 50%; object-fit: cover; border: 2px solid #C7D2FE; }
|
||||||
|
.sw-photo-preview-info .title { font-size: 13px; font-weight: 700; color: var(--text-dark); }
|
||||||
|
.sw-photo-preview-info .sub { font-size: 11.5px; color: var(--text-soft); margin-top: 2px; }
|
||||||
|
|
||||||
/* ── PHOTO UPLOAD ZONE ── */
|
|
||||||
.sw-upload-zone {
|
.sw-upload-zone {
|
||||||
border: 2px dashed var(--border);
|
border: 2px dashed var(--border); border-radius: var(--radius-md);
|
||||||
border-radius: var(--radius-md);
|
padding: 24px 20px; text-align: center;
|
||||||
padding: 24px 20px;
|
background: #F9FAFB; cursor: pointer; transition: all .2s; position: relative;
|
||||||
text-align: center;
|
|
||||||
background: var(--neutral-light);
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all .2s;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.sw-upload-zone:hover {
|
|
||||||
border-color: var(--primary);
|
|
||||||
background: var(--primary-light);
|
|
||||||
}
|
|
||||||
.sw-upload-zone input[type="file"] {
|
|
||||||
position: absolute; inset: 0; opacity: 0; cursor: pointer; width: 100%; height: 100%;
|
|
||||||
}
|
}
|
||||||
|
.sw-upload-zone:hover { border-color: var(--primary); background: var(--primary-light); }
|
||||||
|
.sw-upload-zone input[type="file"] { position: absolute; inset: 0; opacity: 0; cursor: pointer; width: 100%; height: 100%; }
|
||||||
.sw-upload-icon {
|
.sw-upload-icon {
|
||||||
width: 44px; height: 44px;
|
width: 44px; height: 44px;
|
||||||
background: var(--primary-light); border-radius: 10px;
|
background: var(--primary-light); border-radius: 10px;
|
||||||
|
|
@ -208,40 +190,24 @@
|
||||||
font-size: 11px; font-weight: 600; color: var(--primary);
|
font-size: 11px; font-weight: 600; color: var(--primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* current photo preview */
|
|
||||||
.sw-photo-preview {
|
|
||||||
display: flex; align-items: center; gap: 14px;
|
|
||||||
background: var(--neutral-light); border: 1px solid var(--border);
|
|
||||||
border-radius: var(--radius-md); padding: 14px 16px;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
.sw-photo-preview img {
|
|
||||||
width: 52px; height: 52px;
|
|
||||||
border-radius: 50%; object-fit: cover;
|
|
||||||
border: 2px solid var(--primary-light);
|
|
||||||
}
|
|
||||||
.sw-photo-preview-info .title { font-size: 13px; font-weight: 700; color: var(--text-dark); }
|
|
||||||
.sw-photo-preview-info .sub { font-size: 11.5px; color: var(--text-soft); margin-top: 2px; }
|
|
||||||
|
|
||||||
/* ── FOOTER ── */
|
/* ── FOOTER ── */
|
||||||
.sw-footer {
|
.sw-footer {
|
||||||
display: flex; justify-content: flex-end; align-items: center;
|
display: flex; justify-content: flex-end;
|
||||||
padding-top: 24px; border-top: 1px solid var(--border); margin-top: 8px;
|
padding-top: 24px; border-top: 1px solid var(--border); margin-top: 8px;
|
||||||
}
|
}
|
||||||
.sw-btn {
|
.sw-btn-primary {
|
||||||
display: inline-flex; align-items: center; gap: 7px;
|
display: inline-flex; align-items: center; gap: 7px;
|
||||||
padding: 11px 22px; border-radius: var(--radius-md);
|
padding: 11px 22px; border-radius: var(--radius-md);
|
||||||
font-size: 14px; font-weight: 700; border: none;
|
font-size: 14px; font-weight: 700; border: none; cursor: pointer;
|
||||||
cursor: pointer; text-decoration: none;
|
background: var(--primary); color: #fff;
|
||||||
transition: all .18s ease; line-height: 1;
|
font-family: 'Plus Jakarta Sans', sans-serif; transition: all .18s;
|
||||||
font-family: 'Plus Jakarta Sans', sans-serif;
|
|
||||||
}
|
}
|
||||||
.sw-btn-primary { background: var(--primary); color: #fff; box-shadow: 0 4px 14px rgba(79,70,229,.3); }
|
.sw-btn-primary:hover { background: var(--primary-dark); transform: translateY(-1px); }
|
||||||
.sw-btn-primary:hover { background: var(--primary-dark); transform: translateY(-1px); box-shadow: 0 6px 20px rgba(79,70,229,.38); }
|
|
||||||
|
|
||||||
@media (max-width: 900px) {
|
@media (max-width: 900px) {
|
||||||
.sw-grid { grid-template-columns: 1fr; }
|
.sw-grid { grid-template-columns: 1fr; }
|
||||||
.sw-page { padding: 16px; }
|
.sw-page { padding: 16px; }
|
||||||
|
.sw-row { grid-template-columns: 1fr; }
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
@ -263,10 +229,11 @@
|
||||||
|
|
||||||
<div class="sw-grid">
|
<div class="sw-grid">
|
||||||
|
|
||||||
{{-- LEFT: AVATAR CARD --}}
|
{{-- KIRI: AVATAR CARD --}}
|
||||||
<div class="sw-card avatar-card">
|
<div class="sw-card">
|
||||||
<div class="avatar-banner"></div>
|
<div class="avatar-banner"></div>
|
||||||
<div class="avatar-body">
|
<div class="avatar-body">
|
||||||
|
|
||||||
<div class="avatar-wrap">
|
<div class="avatar-wrap">
|
||||||
@if(auth()->user()->photo)
|
@if(auth()->user()->photo)
|
||||||
<img src="{{ asset('uploads/' . auth()->user()->photo) }}" class="avatar-img" alt="Foto Profil">
|
<img src="{{ asset('uploads/' . auth()->user()->photo) }}" class="avatar-img" alt="Foto Profil">
|
||||||
|
|
@ -278,7 +245,11 @@
|
||||||
|
|
||||||
<div class="avatar-name">{{ auth()->user()->name }}</div>
|
<div class="avatar-name">{{ auth()->user()->name }}</div>
|
||||||
<div class="avatar-role">
|
<div class="avatar-role">
|
||||||
<span></span> Administrator
|
<span></span>
|
||||||
|
@if(auth()->user()->role == 'siswa') Siswa
|
||||||
|
@elseif(auth()->user()->role == 'guru') Guru
|
||||||
|
@else Administrator
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="avatar-stats">
|
<div class="avatar-stats">
|
||||||
|
|
@ -301,11 +272,40 @@
|
||||||
<div class="avatar-stat-lbl">Aktif</div>
|
<div class="avatar-stat-lbl">Aktif</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{{-- Info ringkas khusus siswa --}}
|
||||||
|
@if(auth()->user()->role == 'siswa' && auth()->user()->siswa)
|
||||||
|
@php $siswa = auth()->user()->siswa; @endphp
|
||||||
|
<div class="avatar-info-list">
|
||||||
|
<div class="avatar-info-item">
|
||||||
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="7" width="20" height="14" rx="2"/><path d="M16 3H8L2 7h20l-6-4z"/></svg>
|
||||||
|
<div><div class="lbl">NIS</div><div class="val">{{ $siswa->nis }}</div></div>
|
||||||
|
</div>
|
||||||
|
<div class="avatar-info-item">
|
||||||
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>
|
||||||
|
<div><div class="lbl">Kelas</div><div class="val">{{ $siswa->kelas->nama_kelas ?? '-' }}</div></div>
|
||||||
|
</div>
|
||||||
|
<div class="avatar-info-item">
|
||||||
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>
|
||||||
|
<div><div class="lbl">Orang Tua</div><div class="val">{{ $siswa->nama_ortu }}</div></div>
|
||||||
|
</div>
|
||||||
|
<div class="avatar-info-item">
|
||||||
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07A19.5 19.5 0 0 1 4.69 13a19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 3.6 2.18h3a2 2 0 0 1 2 1.72c.127.96.361 1.903.7 2.81a2 2 0 0 1-.45 2.11L7.91 9.91a16 16 0 0 0 6.06 6.06l.94-.93a2 2 0 0 1 2.11-.45c.907.339 1.85.573 2.81.7A2 2 0 0 1 22 16.92z"/></svg>
|
||||||
|
<div><div class="lbl">Telepon</div><div class="val">{{ $siswa->telepon }}</div></div>
|
||||||
|
</div>
|
||||||
|
<div class="avatar-info-item">
|
||||||
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/><circle cx="12" cy="10" r="3"/></svg>
|
||||||
|
<div><div class="lbl">Alamat</div><div class="val">{{ $siswa->alamat }}</div></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{-- RIGHT: FORM CARD --}}
|
{{-- KANAN: FORM CARD --}}
|
||||||
<div class="sw-card">
|
<div class="sw-card">
|
||||||
|
|
||||||
<div class="sw-card-header">
|
<div class="sw-card-header">
|
||||||
<div class="sw-card-header-icon">
|
<div class="sw-card-header-icon">
|
||||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
|
||||||
|
|
@ -320,7 +320,7 @@
|
||||||
<form action="{{ route('admin.profile.update') }}" method="POST" enctype="multipart/form-data">
|
<form action="{{ route('admin.profile.update') }}" method="POST" enctype="multipart/form-data">
|
||||||
@csrf
|
@csrf
|
||||||
|
|
||||||
{{-- SECTION: INFO --}}
|
{{-- Informasi Akun --}}
|
||||||
<div class="sw-section">
|
<div class="sw-section">
|
||||||
<span class="sw-section-label">Informasi Akun</span>
|
<span class="sw-section-label">Informasi Akun</span>
|
||||||
<div class="sw-section-line"></div>
|
<div class="sw-section-line"></div>
|
||||||
|
|
@ -330,18 +330,68 @@
|
||||||
<label class="sw-label">Nama Lengkap</label>
|
<label class="sw-label">Nama Lengkap</label>
|
||||||
<div class="sw-input-wrap">
|
<div class="sw-input-wrap">
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>
|
||||||
<input type="text" name="name" class="sw-input"
|
<input type="text" name="name" class="sw-input" value="{{ auth()->user()->name }}" required>
|
||||||
value="{{ auth()->user()->name }}" required>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{-- SECTION: FOTO --}}
|
{{-- Data Diri (khusus siswa) --}}
|
||||||
<div class="sw-section" style="margin-top:28px;">
|
@if(auth()->user()->role == 'siswa' && auth()->user()->siswa)
|
||||||
|
@php $siswa = auth()->user()->siswa; @endphp
|
||||||
|
|
||||||
|
<div class="sw-section" style="margin-top: 8px;">
|
||||||
|
<span class="sw-section-label">Data Diri</span>
|
||||||
|
<div class="sw-section-line"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sw-row">
|
||||||
|
<div class="sw-form-group">
|
||||||
|
<label class="sw-label">NIS</label>
|
||||||
|
<div class="sw-input-wrap">
|
||||||
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="7" width="20" height="14" rx="2"/><path d="M16 3H8L2 7h20l-6-4z"/></svg>
|
||||||
|
<input type="text" class="sw-input" value="{{ $siswa->nis }}" readonly>
|
||||||
|
</div>
|
||||||
|
<span class="sw-input-note">NIS tidak dapat diubah</span>
|
||||||
|
</div>
|
||||||
|
<div class="sw-form-group">
|
||||||
|
<label class="sw-label">Kelas</label>
|
||||||
|
<div class="sw-input-wrap">
|
||||||
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/></svg>
|
||||||
|
<input type="text" class="sw-input" value="{{ $siswa->kelas->nama_kelas ?? '-' }}" readonly>
|
||||||
|
</div>
|
||||||
|
<span class="sw-input-note">Ditentukan oleh admin</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sw-row">
|
||||||
|
<div class="sw-form-group">
|
||||||
|
<label class="sw-label">Nama Orang Tua</label>
|
||||||
|
<div class="sw-input-wrap">
|
||||||
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>
|
||||||
|
<input type="text" name="nama_ortu" class="sw-input" value="{{ $siswa->nama_ortu }}" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="sw-form-group">
|
||||||
|
<label class="sw-label">Nomor Telepon</label>
|
||||||
|
<div class="sw-input-wrap">
|
||||||
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07A19.5 19.5 0 0 1 4.69 13a19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 3.6 2.18h3a2 2 0 0 1 2 1.72c.127.96.361 1.903.7 2.81a2 2 0 0 1-.45 2.11L7.91 9.91a16 16 0 0 0 6.06 6.06l.94-.93a2 2 0 0 1 2.11-.45c.907.339 1.85.573 2.81.7A2 2 0 0 1 22 16.92z"/></svg>
|
||||||
|
<input type="text" name="telepon" class="sw-input" value="{{ $siswa->telepon }}" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sw-form-group">
|
||||||
|
<label class="sw-label">Alamat Lengkap</label>
|
||||||
|
<textarea name="alamat" class="sw-textarea" required>{{ $siswa->alamat }}</textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@endif
|
||||||
|
|
||||||
|
{{-- Foto Profil --}}
|
||||||
|
<div class="sw-section" style="margin-top: 28px;">
|
||||||
<span class="sw-section-label">Foto Profil</span>
|
<span class="sw-section-label">Foto Profil</span>
|
||||||
<div class="sw-section-line"></div>
|
<div class="sw-section-line"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{-- Current photo preview --}}
|
|
||||||
<div class="sw-photo-preview">
|
<div class="sw-photo-preview">
|
||||||
@if(auth()->user()->photo)
|
@if(auth()->user()->photo)
|
||||||
<img src="{{ asset('uploads/' . auth()->user()->photo) }}" alt="Foto saat ini">
|
<img src="{{ asset('uploads/' . auth()->user()->photo) }}" alt="Foto saat ini">
|
||||||
|
|
@ -354,20 +404,18 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{-- Upload zone --}}
|
|
||||||
<div class="sw-upload-zone">
|
<div class="sw-upload-zone">
|
||||||
<input type="file" name="photo" accept="image/*">
|
<input type="file" name="photo" accept="image/*">
|
||||||
<div class="sw-upload-icon">
|
<div class="sw-upload-icon">
|
||||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
|
||||||
</div>
|
</div>
|
||||||
<div class="sw-upload-title">Klik untuk unggah foto baru</div>
|
<div class="sw-upload-title">Klik untuk unggah foto baru</div>
|
||||||
<div class="sw-upload-sub">atau seret & lepas file ke sini</div>
|
<div class="sw-upload-sub">atau seret & lepas file ke sini</div>
|
||||||
<div class="sw-upload-badge">Pilih File</div>
|
<div class="sw-upload-badge">Pilih File</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{-- FOOTER --}}
|
|
||||||
<div class="sw-footer">
|
<div class="sw-footer">
|
||||||
<button type="submit" class="sw-btn sw-btn-primary">
|
<button type="submit" class="sw-btn-primary">
|
||||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.3"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/><polyline points="17 21 17 13 7 13 7 21"/><polyline points="7 3 7 8 15 8"/></svg>
|
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.3"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/><polyline points="17 21 17 13 7 13 7 21"/><polyline points="7 3 7 8 15 8"/></svg>
|
||||||
Update Profil
|
Update Profil
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -381,7 +429,6 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// Live preview when file is selected
|
|
||||||
const fileInput = document.querySelector('input[type="file"]');
|
const fileInput = document.querySelector('input[type="file"]');
|
||||||
const previewImg = document.querySelector('.sw-photo-preview img');
|
const previewImg = document.querySelector('.sw-photo-preview img');
|
||||||
const uploadTitle = document.querySelector('.sw-upload-title');
|
const uploadTitle = document.querySelector('.sw-upload-title');
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,10 @@
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap');
|
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap');
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--primary: #4F46E5;
|
--primary: #4F46E5;
|
||||||
--primary-light: #EEF2FF;
|
--primary-light: #EEF2FF;
|
||||||
--primary-dark: #3730A3;
|
--primary-dark: #3730A3;
|
||||||
|
|
@ -27,37 +27,46 @@
|
||||||
--text-soft: #6B7280;
|
--text-soft: #6B7280;
|
||||||
--radius-md: 10px;
|
--radius-md: 10px;
|
||||||
--radius-lg: 14px;
|
--radius-lg: 14px;
|
||||||
--shadow-sm: 0 1px 3px rgba(0,0,0,.06), 0 1px 2px rgba(0,0,0,.04);
|
--shadow-sm: 0 1px 3px rgba(0, 0, 0, .06), 0 1px 2px rgba(0, 0, 0, .04);
|
||||||
--shadow-md: 0 4px 16px rgba(0,0,0,.07);
|
--shadow-md: 0 4px 16px rgba(0, 0, 0, .07);
|
||||||
}
|
}
|
||||||
|
|
||||||
* { font-family: 'Plus Jakarta Sans', sans-serif; box-sizing: border-box; }
|
* {
|
||||||
|
font-family: 'Plus Jakarta Sans', sans-serif;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
/* ── PAGE WRAPPER ── */
|
/* ── PAGE WRAPPER ── */
|
||||||
.sw-page { padding: 28px 32px; background: var(--bg); min-height: 100vh; }
|
.sw-page {
|
||||||
|
padding: 28px 32px;
|
||||||
|
background: var(--bg);
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
/* ── TOPBAR ── */
|
/* ── TOPBAR ── */
|
||||||
.sw-topbar {
|
.sw-topbar {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin-bottom: 24px;
|
margin-bottom: 24px;
|
||||||
}
|
}
|
||||||
.sw-topbar-left h3 {
|
|
||||||
|
.sw-topbar-left h3 {
|
||||||
font-size: 22px;
|
font-size: 22px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: var(--text-dark);
|
color: var(--text-dark);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
letter-spacing: -.3px;
|
letter-spacing: -.3px;
|
||||||
}
|
}
|
||||||
.sw-topbar-left p {
|
|
||||||
|
.sw-topbar-left p {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: var(--text-soft);
|
color: var(--text-soft);
|
||||||
margin: 2px 0 0;
|
margin: 2px 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── BUTTON ── */
|
/* ── BUTTON ── */
|
||||||
.sw-btn {
|
.sw-btn {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 7px;
|
gap: 7px;
|
||||||
|
|
@ -70,21 +79,72 @@
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
transition: all .18s ease;
|
transition: all .18s ease;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
}
|
}
|
||||||
.sw-btn-primary { background: var(--primary); color: #fff; }
|
|
||||||
.sw-btn-primary:hover { background: var(--primary-dark); color: #fff; transform: translateY(-1px); box-shadow: 0 4px 12px rgba(79,70,229,.35); }
|
|
||||||
.sw-btn-secondary { background: var(--surface); color: var(--text-mid); border: 1px solid var(--border); }
|
|
||||||
.sw-btn-secondary:hover { background: var(--neutral-light); color: var(--text-mid); }
|
|
||||||
.sw-btn-danger { background: var(--danger-light); color: var(--danger); border: 1px solid #FECACA; }
|
|
||||||
.sw-btn-danger:hover { background: var(--danger); color: #fff; }
|
|
||||||
.sw-btn-warning { background: var(--warning-light); color: var(--warning); border: 1px solid #FDE68A; }
|
|
||||||
.sw-btn-warning:hover { background: var(--warning); color: #fff; }
|
|
||||||
.sw-btn-success { background: var(--success-light); color: var(--success); border: 1px solid #A7F3D0; }
|
|
||||||
.sw-btn-success:hover { background: var(--success); color: #fff; }
|
|
||||||
.sw-btn-sm { padding: 6px 12px; font-size: 12.5px; border-radius: 8px; }
|
|
||||||
|
|
||||||
/* ── ALERT ── */
|
.sw-btn-primary {
|
||||||
.sw-alert {
|
background: var(--primary);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-btn-primary:hover {
|
||||||
|
background: var(--primary-dark);
|
||||||
|
color: #fff;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 4px 12px rgba(79, 70, 229, .35);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-btn-secondary {
|
||||||
|
background: var(--surface);
|
||||||
|
color: var(--text-mid);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-btn-secondary:hover {
|
||||||
|
background: var(--neutral-light);
|
||||||
|
color: var(--text-mid);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-btn-danger {
|
||||||
|
background: var(--danger-light);
|
||||||
|
color: var(--danger);
|
||||||
|
border: 1px solid #FECACA;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-btn-danger:hover {
|
||||||
|
background: var(--danger);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-btn-warning {
|
||||||
|
background: var(--warning-light);
|
||||||
|
color: var(--warning);
|
||||||
|
border: 1px solid #FDE68A;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-btn-warning:hover {
|
||||||
|
background: var(--warning);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-btn-success {
|
||||||
|
background: var(--success-light);
|
||||||
|
color: var(--success);
|
||||||
|
border: 1px solid #A7F3D0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-btn-success:hover {
|
||||||
|
background: var(--success);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-btn-sm {
|
||||||
|
padding: 6px 12px;
|
||||||
|
font-size: 12.5px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── ALERT ── */
|
||||||
|
.sw-alert {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
|
|
@ -96,23 +156,40 @@
|
||||||
background: var(--success-light);
|
background: var(--success-light);
|
||||||
color: var(--success);
|
color: var(--success);
|
||||||
border: 1px solid #A7F3D0;
|
border: 1px solid #A7F3D0;
|
||||||
}
|
}
|
||||||
.sw-alert svg { flex-shrink: 0; }
|
|
||||||
|
|
||||||
/* ── FILTER CARD ── */
|
.sw-alert svg {
|
||||||
.sw-card {
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── FILTER CARD ── */
|
||||||
|
.sw-card {
|
||||||
background: var(--surface);
|
background: var(--surface);
|
||||||
border-radius: var(--radius-lg);
|
border-radius: var(--radius-lg);
|
||||||
box-shadow: var(--shadow-sm);
|
box-shadow: var(--shadow-sm);
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
}
|
}
|
||||||
.sw-filter {
|
|
||||||
|
.sw-filter {
|
||||||
padding: 16px 20px;
|
padding: 16px 20px;
|
||||||
margin-bottom: 18px;
|
margin-bottom: 18px;
|
||||||
}
|
}
|
||||||
.sw-filter form { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
|
|
||||||
.sw-filter label { font-size: 13px; font-weight: 600; color: var(--text-mid); white-space: nowrap; }
|
.sw-filter form {
|
||||||
.sw-filter .form-select {
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-filter label {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-mid);
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-filter .form-select {
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
padding: 8px 14px;
|
padding: 8px 14px;
|
||||||
|
|
@ -122,32 +199,42 @@
|
||||||
font-family: 'Plus Jakarta Sans', sans-serif;
|
font-family: 'Plus Jakarta Sans', sans-serif;
|
||||||
outline: none;
|
outline: none;
|
||||||
transition: border-color .15s;
|
transition: border-color .15s;
|
||||||
}
|
}
|
||||||
.sw-filter .form-select:focus { border-color: var(--primary); box-shadow: 0 0 0 3px rgba(79,70,229,.1); }
|
|
||||||
|
|
||||||
/* ── BULK BAR ── */
|
.sw-filter .form-select:focus {
|
||||||
.sw-bulk-bar {
|
border-color: var(--primary);
|
||||||
|
box-shadow: 0 0 0 3px rgba(79, 70, 229, .1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── BULK BAR ── */
|
||||||
|
.sw-bulk-bar {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
margin-bottom: 14px;
|
margin-bottom: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── TABLE CARD ── */
|
/* ── TABLE CARD ── */
|
||||||
.sw-table-card { overflow: hidden; }
|
.sw-table-card {
|
||||||
.sw-table-wrap { overflow-x: auto; }
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
table.sw-table {
|
.sw-table-wrap {
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.sw-table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
font-size: 13.5px;
|
font-size: 13.5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
table.sw-table thead tr {
|
table.sw-table thead tr {
|
||||||
background: linear-gradient(135deg, #F5F3FF 0%, #EEF2FF 100%);
|
background: linear-gradient(135deg, #F5F3FF 0%, #EEF2FF 100%);
|
||||||
border-bottom: 2px solid #DDD6FE;
|
border-bottom: 2px solid #DDD6FE;
|
||||||
}
|
}
|
||||||
table.sw-table thead th {
|
|
||||||
|
table.sw-table thead th {
|
||||||
padding: 13px 16px;
|
padding: 13px 16px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
|
@ -156,31 +243,47 @@
|
||||||
letter-spacing: .6px;
|
letter-spacing: .6px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
table.sw-table tbody tr {
|
|
||||||
|
table.sw-table tbody tr {
|
||||||
border-bottom: 1px solid var(--border);
|
border-bottom: 1px solid var(--border);
|
||||||
transition: background .15s;
|
transition: background .15s;
|
||||||
}
|
}
|
||||||
table.sw-table tbody tr:last-child { border-bottom: none; }
|
|
||||||
table.sw-table tbody tr:hover { background: #FAFAFF; }
|
table.sw-table tbody tr:last-child {
|
||||||
table.sw-table td {
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.sw-table tbody tr:hover {
|
||||||
|
background: #FAFAFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.sw-table td {
|
||||||
padding: 13px 16px;
|
padding: 13px 16px;
|
||||||
color: var(--text-mid);
|
color: var(--text-mid);
|
||||||
border: none;
|
border: none;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
table.sw-table td.center { text-align: center; }
|
|
||||||
table.sw-table td.name-cell { font-weight: 600; color: var(--text-dark); }
|
|
||||||
|
|
||||||
/* ── CUSTOM CHECKBOX ── */
|
table.sw-table td.center {
|
||||||
.sw-check {
|
text-align: center;
|
||||||
width: 16px; height: 16px;
|
}
|
||||||
|
|
||||||
|
table.sw-table td.name-cell {
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── CUSTOM CHECKBOX ── */
|
||||||
|
.sw-check {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
accent-color: var(--primary);
|
accent-color: var(--primary);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── BADGES ── */
|
/* ── BADGES ── */
|
||||||
.sw-badge {
|
.sw-badge {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
|
|
@ -189,20 +292,41 @@
|
||||||
font-size: 11.5px;
|
font-size: 11.5px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
letter-spacing: .1px;
|
letter-spacing: .1px;
|
||||||
}
|
}
|
||||||
.sw-badge-success { background: var(--success-light); color: var(--success); }
|
|
||||||
.sw-badge-danger { background: var(--danger-light); color: var(--danger); }
|
|
||||||
.sw-badge-primary { background: var(--primary-light); color: var(--primary); }
|
|
||||||
.sw-badge-neutral { background: #F3F4F6; color: #6B7280; }
|
|
||||||
.sw-badge-info { background: var(--info-light); color: var(--info); }
|
|
||||||
|
|
||||||
/* ── NONAKTIF INLINE FORM ── */
|
.sw-badge-success {
|
||||||
.sw-nonaktif-form {
|
background: var(--success-light);
|
||||||
|
color: var(--success);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-badge-danger {
|
||||||
|
background: var(--danger-light);
|
||||||
|
color: var(--danger);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-badge-primary {
|
||||||
|
background: var(--primary-light);
|
||||||
|
color: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-badge-neutral {
|
||||||
|
background: #F3F4F6;
|
||||||
|
color: #6B7280;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-badge-info {
|
||||||
|
background: var(--info-light);
|
||||||
|
color: var(--info);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── NONAKTIF INLINE FORM ── */
|
||||||
|
.sw-nonaktif-form {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
.sw-nonaktif-input {
|
|
||||||
|
.sw-nonaktif-input {
|
||||||
width: 110px;
|
width: 110px;
|
||||||
padding: 6px 10px;
|
padding: 6px 10px;
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
|
|
@ -213,34 +337,62 @@
|
||||||
background: var(--neutral-light);
|
background: var(--neutral-light);
|
||||||
outline: none;
|
outline: none;
|
||||||
transition: border-color .15s;
|
transition: border-color .15s;
|
||||||
}
|
}
|
||||||
.sw-nonaktif-input:focus { border-color: var(--danger); box-shadow: 0 0 0 3px rgba(220,38,38,.08); }
|
|
||||||
.sw-nonaktif-input::placeholder { color: #9CA3AF; }
|
|
||||||
|
|
||||||
/* ── ACTION CELL ── */
|
.sw-nonaktif-input:focus {
|
||||||
.sw-action-cell { display: flex; gap: 6px; align-items: center; justify-content: center; }
|
border-color: var(--danger);
|
||||||
|
box-shadow: 0 0 0 3px rgba(220, 38, 38, .08);
|
||||||
|
}
|
||||||
|
|
||||||
/* ── EMPTY STATE ── */
|
.sw-nonaktif-input::placeholder {
|
||||||
.sw-empty {
|
color: #9CA3AF;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── ACTION CELL ── */
|
||||||
|
.sw-action-cell {
|
||||||
|
display: flex;
|
||||||
|
gap: 6px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── EMPTY STATE ── */
|
||||||
|
.sw-empty {
|
||||||
padding: 60px 20px;
|
padding: 60px 20px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: var(--text-soft);
|
color: var(--text-soft);
|
||||||
}
|
}
|
||||||
.sw-empty-icon { font-size: 40px; margin-bottom: 12px; opacity: .4; }
|
|
||||||
.sw-empty p { font-size: 14px; margin: 0; }
|
|
||||||
|
|
||||||
/* ── DOT INDICATOR ── */
|
.sw-empty-icon {
|
||||||
.sw-dot {
|
font-size: 40px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
opacity: .4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-empty p {
|
||||||
|
font-size: 14px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── DOT INDICATOR ── */
|
||||||
|
.sw-dot {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 7px; height: 7px;
|
width: 7px;
|
||||||
|
height: 7px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
}
|
}
|
||||||
.sw-dot-success { background: var(--success); }
|
|
||||||
.sw-dot-danger { background: var(--danger); }
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<div class="sw-page">
|
.sw-dot-success {
|
||||||
|
background: var(--success);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-dot-danger {
|
||||||
|
background: var(--danger);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="sw-page">
|
||||||
|
|
||||||
{{-- TOPBAR --}}
|
{{-- TOPBAR --}}
|
||||||
<div class="sw-topbar">
|
<div class="sw-topbar">
|
||||||
|
|
@ -249,7 +401,9 @@
|
||||||
<p>Kelola seluruh data dan status siswa</p>
|
<p>Kelola seluruh data dan status siswa</p>
|
||||||
</div>
|
</div>
|
||||||
<a href="{{ route('admin.siswa.create') }}" class="sw-btn sw-btn-primary">
|
<a href="{{ route('admin.siswa.create') }}" class="sw-btn sw-btn-primary">
|
||||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M12 5v14M5 12h14"/></svg>
|
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
|
||||||
|
<path d="M12 5v14M5 12h14" />
|
||||||
|
</svg>
|
||||||
Tambah Siswa
|
Tambah Siswa
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -257,7 +411,10 @@
|
||||||
{{-- ALERT --}}
|
{{-- ALERT --}}
|
||||||
@if(session('success'))
|
@if(session('success'))
|
||||||
<div class="sw-alert">
|
<div class="sw-alert">
|
||||||
<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>
|
<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2">
|
||||||
|
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14" />
|
||||||
|
<polyline points="22 4 12 14.01 9 11.01" />
|
||||||
|
</svg>
|
||||||
{{ session('success') }}
|
{{ session('success') }}
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
|
@ -275,7 +432,9 @@
|
||||||
@endforeach
|
@endforeach
|
||||||
</select>
|
</select>
|
||||||
<button type="submit" class="sw-btn sw-btn-primary">
|
<button type="submit" class="sw-btn sw-btn-primary">
|
||||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"/></svg>
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2">
|
||||||
|
<polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3" />
|
||||||
|
</svg>
|
||||||
Filter
|
Filter
|
||||||
</button>
|
</button>
|
||||||
<a href="{{ route('admin.siswa.index') }}" class="sw-btn sw-btn-secondary">Reset</a>
|
<a href="{{ route('admin.siswa.index') }}" class="sw-btn sw-btn-secondary">Reset</a>
|
||||||
|
|
@ -289,7 +448,10 @@
|
||||||
<div class="sw-bulk-bar">
|
<div class="sw-bulk-bar">
|
||||||
<button type="submit" class="sw-btn sw-btn-danger"
|
<button type="submit" class="sw-btn sw-btn-danger"
|
||||||
onclick="return confirm('Nonaktifkan siswa yang dipilih?')">
|
onclick="return confirm('Nonaktifkan siswa yang dipilih?')">
|
||||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><circle cx="12" cy="12" r="10"/><line x1="8" y1="12" x2="16" y2="12"/></svg>
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2">
|
||||||
|
<circle cx="12" cy="12" r="10" />
|
||||||
|
<line x1="8" y1="12" x2="16" y2="12" />
|
||||||
|
</svg>
|
||||||
Nonaktifkan Terpilih
|
Nonaktifkan Terpilih
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -340,7 +502,8 @@
|
||||||
</td>
|
</td>
|
||||||
<td>{{ $item->nama_ortu }}</td>
|
<td>{{ $item->nama_ortu }}</td>
|
||||||
<td style="font-size:12.5px; color:var(--text-soft);">{{ $item->user->email ?? '-' }}</td>
|
<td style="font-size:12.5px; color:var(--text-soft);">{{ $item->user->email ?? '-' }}</td>
|
||||||
<td style="max-width:160px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;" title="{{ $item->alamat }}">
|
<td style="max-width:160px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;"
|
||||||
|
title="{{ $item->alamat }}">
|
||||||
{{ $item->alamat }}
|
{{ $item->alamat }}
|
||||||
</td>
|
</td>
|
||||||
<td class="center" style="font-size:12.5px;">{{ $item->telepon }}</td>
|
<td class="center" style="font-size:12.5px;">{{ $item->telepon }}</td>
|
||||||
|
|
@ -359,22 +522,52 @@
|
||||||
<div class="sw-action-cell">
|
<div class="sw-action-cell">
|
||||||
<a href="{{ route('admin.siswa.edit', $item->id) }}"
|
<a href="{{ route('admin.siswa.edit', $item->id) }}"
|
||||||
class="sw-btn sw-btn-warning sw-btn-sm">
|
class="sw-btn sw-btn-warning sw-btn-sm">
|
||||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none"
|
||||||
|
stroke="currentColor" stroke-width="2.2">
|
||||||
|
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" />
|
||||||
|
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" />
|
||||||
|
</svg>
|
||||||
Edit
|
Edit
|
||||||
</a>
|
</a>
|
||||||
@if($item->status == 'aktif')
|
|
||||||
<form action="{{ route('admin.siswa.nonaktif', $item->id) }}" method="POST" class="sw-nonaktif-form">
|
<form action="{{ route('admin.siswa.destroy', $item->id) }}" method="POST"
|
||||||
|
onsubmit="return confirm('Yakin hapus data {{ $item->nama }}? Akun login juga akan terhapus!')">
|
||||||
@csrf
|
@csrf
|
||||||
<input type="text" name="alasan" placeholder="Alasan..." required class="sw-nonaktif-input">
|
@method('DELETE')
|
||||||
<button type="submit" class="sw-btn sw-btn-danger sw-btn-sm">
|
<button type="submit" class="sw-btn sw-btn-danger sw-btn-sm">
|
||||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><circle cx="12" cy="12" r="10"/><line x1="8" y1="12" x2="16" y2="12"/></svg>
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none"
|
||||||
|
stroke="currentColor" stroke-width="2.2">
|
||||||
|
<polyline points="3 6 5 6 21 6" />
|
||||||
|
<path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6" />
|
||||||
|
<path d="M10 11v6M14 11v6" />
|
||||||
|
<path d="M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2" />
|
||||||
|
</svg>
|
||||||
|
Hapus
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
@if($item->status == 'aktif')
|
||||||
|
<form action="{{ route('admin.siswa.nonaktif', $item->id) }}" method="POST"
|
||||||
|
class="sw-nonaktif-form">
|
||||||
|
@csrf
|
||||||
|
<input type="text" name="alasan" placeholder="Alasan..." required
|
||||||
|
class="sw-nonaktif-input">
|
||||||
|
<button type="submit" class="sw-btn sw-btn-danger sw-btn-sm">
|
||||||
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none"
|
||||||
|
stroke="currentColor" stroke-width="2.2">
|
||||||
|
<circle cx="12" cy="12" r="10" />
|
||||||
|
<line x1="8" y1="12" x2="16" y2="12" />
|
||||||
|
</svg>
|
||||||
Nonaktif
|
Nonaktif
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
@else
|
@else
|
||||||
<a href="{{ route('admin.siswa.aktifkan', $item->id) }}"
|
<a href="{{ route('admin.siswa.aktifkan', $item->id) }}"
|
||||||
class="sw-btn sw-btn-success sw-btn-sm">
|
class="sw-btn sw-btn-success sw-btn-sm">
|
||||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><polyline points="20 6 9 17 4 12"/></svg>
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none"
|
||||||
|
stroke="currentColor" stroke-width="2.2">
|
||||||
|
<polyline points="20 6 9 17 4 12" />
|
||||||
|
</svg>
|
||||||
Aktifkan
|
Aktifkan
|
||||||
</a>
|
</a>
|
||||||
@endif
|
@endif
|
||||||
|
|
@ -397,12 +590,12 @@ class="sw-btn sw-btn-success sw-btn-sm">
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
document.getElementById('checkAll').addEventListener('change', function () {
|
document.getElementById('checkAll').addEventListener('change', function () {
|
||||||
document.querySelectorAll('input[name="ids[]"]').forEach(cb => cb.checked = this.checked);
|
document.querySelectorAll('input[name="ids[]"]').forEach(cb => cb.checked = this.checked);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@endsection
|
@endsection
|
||||||
|
|
@ -562,9 +562,15 @@
|
||||||
LANGKAH 2: Login
|
LANGKAH 2: Login
|
||||||
Field OTP disembunyikan otomatis untuk admin
|
Field OTP disembunyikan otomatis untuk admin
|
||||||
============================================================ --}}
|
============================================================ --}}
|
||||||
<form method="POST" action="{{ route('login.otp') }}">
|
{{-- ============================================================
|
||||||
|
LANGKAH 2: Login
|
||||||
|
============================================================ --}}
|
||||||
|
<form method="POST" action="{{ route('login.otp') }}">
|
||||||
@csrf
|
@csrf
|
||||||
|
|
||||||
|
{{-- Tampilkan OTP jika: device baru ATAU OTP baru dikirim --}}
|
||||||
|
<input type="hidden" id="has-otp-error" value="{{ (session('need_otp') || session('otp_sent')) ? '1' : '0' }}">
|
||||||
|
|
||||||
{{-- Email --}}
|
{{-- Email --}}
|
||||||
<div class="input-wrap">
|
<div class="input-wrap">
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
|
|
@ -576,7 +582,7 @@
|
||||||
name="email"
|
name="email"
|
||||||
id="login-email"
|
id="login-email"
|
||||||
placeholder="Email"
|
placeholder="Email"
|
||||||
value="{{ session('email') ?? old('email') }}"
|
value="{{ old('email', session('email')) }}"
|
||||||
oninput="checkAdminEmail(this.value)"
|
oninput="checkAdminEmail(this.value)"
|
||||||
required
|
required
|
||||||
autocomplete="email"
|
autocomplete="email"
|
||||||
|
|
@ -592,8 +598,8 @@
|
||||||
<input type="password" name="password" placeholder="Password" required autocomplete="current-password">
|
<input type="password" name="password" placeholder="Password" required autocomplete="current-password">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{-- OTP — disembunyikan untuk admin --}}
|
{{-- OTP — muncul hanya jika device baru --}}
|
||||||
<div class="input-wrap" id="wrap-otp-field">
|
<div class="input-wrap" id="wrap-otp-field" style="display:none;">
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
<polyline points="9 11 12 14 22 4"/>
|
<polyline points="9 11 12 14 22 4"/>
|
||||||
<path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/>
|
<path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/>
|
||||||
|
|
@ -602,14 +608,19 @@
|
||||||
type="text"
|
type="text"
|
||||||
name="otp"
|
name="otp"
|
||||||
id="otp-input"
|
id="otp-input"
|
||||||
placeholder="Masukkan OTP"
|
placeholder="Masukkan OTP 6 digit"
|
||||||
maxlength="6"
|
maxlength="6"
|
||||||
inputmode="numeric"
|
inputmode="numeric"
|
||||||
autocomplete="one-time-code"
|
autocomplete="one-time-code"
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{-- Badge info admin --}}
|
{{-- Info device baru --}}
|
||||||
|
<div id="new-device-banner" style="display:none; font-size:12px; color:rgba(255,255,255,.7); background:rgba(255,255,255,.1); border:1px solid rgba(255,255,255,.2); border-radius:10px; padding:10px 13px; margin-bottom:12px;">
|
||||||
|
🔐 Perangkat baru terdeteksi. Silakan minta OTP di Langkah 1 lalu masukkan di atas.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Badge admin --}}
|
||||||
<div class="admin-info-badge" id="admin-info-badge">
|
<div class="admin-info-badge" id="admin-info-badge">
|
||||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2">
|
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2">
|
||||||
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>
|
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>
|
||||||
|
|
@ -618,7 +629,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="submit" class="btn-login">Masuk ke Portal</button>
|
<button type="submit" class="btn-login">Masuk ke Portal</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<a href="{{ route('password.request') }}" class="forgot-link">Lupa Password?</a>
|
<a href="{{ route('password.request') }}" class="forgot-link">Lupa Password?</a>
|
||||||
|
|
||||||
|
|
@ -639,33 +650,90 @@
|
||||||
// ================================================================
|
// ================================================================
|
||||||
// Toggle tampilan Langkah 1 (OTP section) berdasarkan role
|
// Toggle tampilan Langkah 1 (OTP section) berdasarkan role
|
||||||
// ================================================================
|
// ================================================================
|
||||||
|
// Daftar email admin
|
||||||
|
const ADMIN_EMAILS = [
|
||||||
|
'smanbondowoso1@gmail.com',
|
||||||
|
// tambah email admin lain di sini
|
||||||
|
];
|
||||||
|
|
||||||
|
function showOtpField(show) {
|
||||||
|
const otpWrap = document.getElementById('wrap-otp-field');
|
||||||
|
const otpInput = document.getElementById('otp-input');
|
||||||
|
const banner = document.getElementById('new-device-banner');
|
||||||
|
|
||||||
|
otpWrap.style.display = show ? 'block' : 'none';
|
||||||
|
banner.style.display = show ? 'block' : 'none';
|
||||||
|
otpInput.required = show;
|
||||||
|
if (!show) otpInput.value = '';
|
||||||
|
}
|
||||||
|
|
||||||
function checkAdminEmail(email) {
|
function checkAdminEmail(email) {
|
||||||
const isAdmin = ADMIN_EMAILS.includes(email.trim().toLowerCase());
|
const isAdmin = ADMIN_EMAILS.includes(email.trim().toLowerCase());
|
||||||
|
const needOtp = document.getElementById('has-otp-error').value === '1';
|
||||||
|
|
||||||
const step1 = document.getElementById('step1-section');
|
const step1 = document.getElementById('step1-section');
|
||||||
const adminDiv = document.getElementById('admin-divider');
|
const adminDiv = document.getElementById('admin-divider');
|
||||||
const otpWrap = document.getElementById('wrap-otp-field');
|
|
||||||
const otpInput = document.getElementById('otp-input');
|
|
||||||
const adminBadge = document.getElementById('admin-info-badge');
|
const adminBadge = document.getElementById('admin-info-badge');
|
||||||
|
|
||||||
if (isAdmin) {
|
if (isAdmin) {
|
||||||
// Sembunyikan Langkah 1 dan field OTP
|
|
||||||
step1.style.display = 'none';
|
step1.style.display = 'none';
|
||||||
adminDiv.style.display = 'block';
|
adminDiv.style.display = 'block';
|
||||||
otpWrap.style.display = 'none';
|
|
||||||
adminBadge.style.display = 'flex';
|
adminBadge.style.display = 'flex';
|
||||||
otpInput.required = false;
|
showOtpField(false);
|
||||||
otpInput.value = '';
|
|
||||||
} else {
|
} else {
|
||||||
// Tampilkan kembali semua untuk guru/siswa
|
|
||||||
step1.style.display = 'block';
|
step1.style.display = 'block';
|
||||||
adminDiv.style.display = 'none';
|
adminDiv.style.display = 'none';
|
||||||
otpWrap.style.display = 'block';
|
|
||||||
adminBadge.style.display = 'none';
|
adminBadge.style.display = 'none';
|
||||||
otpInput.required = true;
|
// Tampilkan OTP hanya jika server bilang device baru
|
||||||
|
showOtpField(needOtp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function selectMethod(method) {
|
||||||
|
const emailBtn = document.getElementById('btn-email-method');
|
||||||
|
const waBtn = document.getElementById('btn-wa-method');
|
||||||
|
const emailWrap = document.getElementById('wrap-email-otp');
|
||||||
|
const waWrap = document.getElementById('wrap-wa-otp');
|
||||||
|
const emailInput = document.getElementById('input-email-otp');
|
||||||
|
const waInput = document.getElementById('input-wa-otp');
|
||||||
|
const label = document.getElementById('btn-otp-label');
|
||||||
|
const methodInput = document.getElementById('otp_method');
|
||||||
|
|
||||||
|
if (method === 'email') {
|
||||||
|
emailBtn.classList.add('active');
|
||||||
|
waBtn.classList.remove('active');
|
||||||
|
emailWrap.style.display = 'block';
|
||||||
|
waWrap.style.display = 'none';
|
||||||
|
emailInput.required = true;
|
||||||
|
waInput.required = false;
|
||||||
|
waInput.value = '';
|
||||||
|
label.textContent = 'Dapatkan OTP via Email';
|
||||||
|
methodInput.value = 'email';
|
||||||
|
} else {
|
||||||
|
waBtn.classList.add('active');
|
||||||
|
emailBtn.classList.remove('active');
|
||||||
|
waWrap.style.display = 'block';
|
||||||
|
emailWrap.style.display = 'none';
|
||||||
|
waInput.required = true;
|
||||||
|
emailInput.required = false;
|
||||||
|
emailInput.value = '';
|
||||||
|
label.textContent = 'Dapatkan OTP via WhatsApp';
|
||||||
|
methodInput.value = 'wa';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
const emailEl = document.getElementById('login-email');
|
||||||
|
if (emailEl && emailEl.value.trim() !== '') {
|
||||||
|
checkAdminEmail(emailEl.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Jika server kirim need_otp, langsung tampilkan kolom OTP
|
||||||
|
const needOtp = document.getElementById('has-otp-error').value === '1';
|
||||||
|
if (needOtp) {
|
||||||
|
showOtpField(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
// ================================================================
|
// ================================================================
|
||||||
// Toggle metode OTP (email / WhatsApp)
|
// Toggle metode OTP (email / WhatsApp)
|
||||||
// ================================================================
|
// ================================================================
|
||||||
|
|
|
||||||
|
|
@ -366,7 +366,7 @@
|
||||||
|
|
||||||
<nav class="custom-topbar">
|
<nav class="custom-topbar">
|
||||||
|
|
||||||
{{-- LEFT --}}
|
<!-- {{-- LEFT --}}
|
||||||
<div style="display:flex; align-items:center; gap:12px;">
|
<div style="display:flex; align-items:center; gap:12px;">
|
||||||
{{-- Mobile sidebar toggle --}}
|
{{-- Mobile sidebar toggle --}}
|
||||||
<button class="tb-mobile-toggle" id="sidebarToggleTop">
|
<button class="tb-mobile-toggle" id="sidebarToggleTop">
|
||||||
|
|
@ -457,7 +457,7 @@
|
||||||
|
|
||||||
<a href="#" class="tb-dd-footer">Baca Semua Pesan →</a>
|
<a href="#" class="tb-dd-footer">Baca Semua Pesan →</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div> -->
|
||||||
|
|
||||||
<div class="tb-sep"></div>
|
<div class="tb-sep"></div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -123,6 +123,18 @@
|
||||||
Route::get('/', [AdminDashboard::class, 'index']);
|
Route::get('/', [AdminDashboard::class, 'index']);
|
||||||
Route::get('/dashboard', [AdminDashboard::class, 'index'])->name('dashboard');
|
Route::get('/dashboard', [AdminDashboard::class, 'index'])->name('dashboard');
|
||||||
|
|
||||||
|
Route::get('/profile', [ProfileController::class, 'index'])->name('profile');
|
||||||
|
Route::post('/profile', [ProfileController::class, 'update'])->name('profile.update');
|
||||||
|
|
||||||
|
Route::get('/guru/{id}/nonaktif', [GuruController::class, 'nonaktif'])->name('guru.nonaktif');
|
||||||
|
Route::get('/guru/{id}/aktifkan', [GuruController::class, 'aktifkan'])->name('guru.aktifkan');
|
||||||
|
|
||||||
|
// ✅ Route spesifik siswa HARUS di atas resource
|
||||||
|
Route::post('/siswa/bulk/nonaktif', [SiswaController::class, 'bulkNonaktif'])->name('siswa.bulkNonaktif');
|
||||||
|
Route::post('/siswa/nonaktif/{id}', [SiswaController::class, 'nonaktif'])->name('siswa.nonaktif');
|
||||||
|
Route::get('/siswa/aktifkan/{id}', [SiswaController::class, 'aktifkan'])->name('siswa.aktifkan');
|
||||||
|
|
||||||
|
// ✅ Resource SETELAH route spesifik
|
||||||
Route::resource('guru', GuruController::class);
|
Route::resource('guru', GuruController::class);
|
||||||
Route::resource('siswa', SiswaController::class);
|
Route::resource('siswa', SiswaController::class);
|
||||||
Route::resource('mapel', MataPelajaranController::class);
|
Route::resource('mapel', MataPelajaranController::class);
|
||||||
|
|
@ -130,16 +142,9 @@
|
||||||
Route::resource('jadwal', AdminJadwal::class);
|
Route::resource('jadwal', AdminJadwal::class);
|
||||||
|
|
||||||
Route::get('jadwal/grid', [AdminJadwal::class, 'grid'])->name('jadwal.grid');
|
Route::get('jadwal/grid', [AdminJadwal::class, 'grid'])->name('jadwal.grid');
|
||||||
|
|
||||||
Route::get('/profile', [ProfileController::class, 'index'])->name('profile');
|
|
||||||
Route::post('/profile', [ProfileController::class, 'update'])->name('profile.update');
|
|
||||||
|
|
||||||
Route::get('/guru/{id}/nonaktif', [GuruController::class, 'nonaktif'])->name('guru.nonaktif');
|
|
||||||
Route::get('/guru/{id}/aktifkan', [GuruController::class, 'aktifkan'])->name('guru.aktifkan');
|
|
||||||
|
|
||||||
Route::post('/siswa/bulk/nonaktif', [SiswaController::class, 'bulkNonaktif'])->name('siswa.bulkNonaktif');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// Siswa nonaktif/aktifkan
|
// Siswa nonaktif/aktifkan
|
||||||
Route::prefix('admin')->name('admin.')->group(function () {
|
Route::prefix('admin')->name('admin.')->group(function () {
|
||||||
Route::post('/siswa/nonaktif/{id}', [SiswaController::class, 'nonaktif'])->name('siswa.nonaktif');
|
Route::post('/siswa/nonaktif/{id}', [SiswaController::class, 'nonaktif'])->name('siswa.nonaktif');
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue