badge fixed

This commit is contained in:
RetasyaSalsabila 2026-04-06 19:26:11 +07:00
parent f84402aff7
commit 3b21f82135
8 changed files with 600 additions and 11 deletions

View File

@ -3,9 +3,11 @@
namespace App\Http\Controllers\Siswa;
use App\Http\Controllers\Controller;
use App\Models\Badge;
use App\Models\Challenge;
use App\Models\PesertaChallenge;
use App\Models\Leaderboard;
use App\Models\SiswaBadge;
use App\Services\BadgeService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
@ -108,6 +110,11 @@ public function submit(Request $request, $id_challenge)
? $now->year . '/' . ($now->year + 1)
: ($now->year - 1) . '/' . $now->year;
// Snapshot badge sebelum submit — untuk deteksi badge baru
$badgeSebelum = SiswaBadge::where('id_siswa', $siswa->id_siswa)
->pluck('id_badge')
->toArray();
DB::transaction(function () use ($siswa, $challenge, $jawabanJson, $totalExp, $semester, $tahunAjaran) {
PesertaChallenge::create([
'id_challenge' => $challenge->id_challenge,
@ -138,9 +145,22 @@ public function submit(Request $request, $id_challenge)
->each(fn($row, $i) => $row->update(['ranking' => $i + 1]));
});
// --- Cek & berikan badge challenge (di luar transaksi agar tidak block) ---
// Cek & berikan badge challenge
app(BadgeService::class)->checkChallengeBadges($siswa->id_siswa);
// Deteksi badge yang baru didapat (selisih sebelum & sesudah)
$badgeSesudah = SiswaBadge::where('id_siswa', $siswa->id_siswa)
->pluck('id_badge')
->toArray();
$idBadgeBaru = array_diff($badgeSesudah, $badgeSebelum);
// Simpan ke session agar bisa ditampilkan di halaman hasil
if (!empty($idBadgeBaru)) {
$badgeBaru = Badge::whereIn('id_badge', $idBadgeBaru)->get();
session()->flash('badge_baru', $badgeBaru);
}
return redirect()->route('siswa.challenge.hasil', $id_challenge);
}
@ -165,9 +185,13 @@ public function hasil($id_challenge)
$totalSoal = $challenge->soal->count();
$persentase = $totalSoal > 0 ? round(($benar / $totalSoal) * 100) : 0;
// Ambil badge baru dari session (hanya ada jika baru saja submit)
$badgeBaru = session('badge_baru', collect());
return view('siswa.challenge.hasil', compact(
'challenge', 'peserta', 'jawabanSiswa',
'benar', 'salah', 'totalSoal', 'persentase'
'benar', 'salah', 'totalSoal', 'persentase',
'badgeBaru'
));
}
}

View File

@ -4,6 +4,7 @@
use App\Http\Controllers\Controller;
use App\Models\Leaderboard;
use App\Models\SiswaBadge;
use App\Services\BadgeService;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Storage;
@ -48,28 +49,42 @@ private function getData()
public function index()
{
$data = $this->getData();
// Keluarkan 'siswa' dari data yang dikirim ke view (tidak dibutuhkan di view)
unset($data['siswa']);
return view('siswa.leaderboard.index', $data);
}
/**
* Endpoint JSON untuk polling real-time.
* Setiap kali dipanggil, badge leaderboard siswa yang sedang login
* dievaluasi ulang (diberikan atau dicabut sesuai ranking saat ini).
* Mengevaluasi badge leaderboard siswa, lalu return badge yang dimiliki
* saat ini agar JS bisa mendeteksi badge baru via localStorage.
*/
public function json()
{
$data = $this->getData();
$siswa = $data['siswa'];
// Evaluasi badge leaderboard secara real-time untuk siswa yang sedang login
// Evaluasi badge leaderboard (grant/revoke)
app(BadgeService::class)->checkLeaderboardBadges(
$siswa->id_siswa,
$siswa->id_kelas
);
// Ambil semua badge leaderboard yang dimiliki siswa saat ini
// beserta detail badge (icon, nama, deskripsi) untuk ditampilkan di pop-up
$badgeSiswa = SiswaBadge::with('badge')
->where('id_siswa', $siswa->id_siswa)
->whereHas('badge', fn($q) => $q->whereIn('syarat', ['leaderboard_top5', 'leaderboard_top1']))
->get()
->map(fn($sb) => [
'id_badge' => $sb->id_badge,
'nama_badge' => $sb->badge->nama_badge,
'deskripsi' => $sb->badge->deskripsi,
'icon_url' => asset($sb->badge->icon_badge),
]);
unset($data['siswa']);
$data['badgeSiswa'] = $badgeSiswa;
return response()->json($data);
}
}

View File

@ -3,6 +3,8 @@
namespace App\Http\Controllers\Siswa;
use App\Http\Controllers\Controller;
use App\Models\Badge;
use App\Models\SiswaBadge;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
@ -10,6 +12,27 @@
class ProfileController extends Controller
{
public function edit()
{
$siswa = Auth::guard('siswa')->user();
// Semua badge yang ada di sistem
$semuaBadge = Badge::all();
// ID badge yang dimiliki siswa
$idBadgeDimiliki = SiswaBadge::where('id_siswa', $siswa->id_siswa)
->pluck('id_badge')
->toArray();
// Gabungkan: tiap badge + flag apakah siswa memilikinya
$badges = $semuaBadge->map(function ($badge) use ($idBadgeDimiliki) {
$badge->dimiliki = in_array($badge->id_badge, $idBadgeDimiliki);
return $badge;
});
return view('siswa.profile.edit', compact('badges'));
}
public function updateAjax(Request $request)
{
$siswa = Auth::guard('siswa')->user();

View File

@ -3,13 +3,14 @@
namespace App\Http\Controllers\Siswa;
use App\Http\Controllers\Controller;
use App\Models\Badge;
use App\Models\SiswaBadge;
use App\Services\BadgeService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Carbon\Carbon;
use App\Models\Tugas;
use App\Models\PengumpulanTugas;
use App\Models\Mengajar;
use App\Services\BadgeService;
class TugasController extends Controller
{
@ -137,9 +138,26 @@ public function submit(Request $request, $id_tugas)
'status' => $status,
]);
// --- Cek & berikan badge tugas hanya jika tepat waktu ---
// Cek & berikan badge tugas hanya jika tepat waktu
if ($status === 'dikumpulkan') {
// Snapshot badge sebelum pengecekan
$badgeSebelum = SiswaBadge::where('id_siswa', $siswa->id_siswa)
->pluck('id_badge')
->toArray();
app(BadgeService::class)->checkTugasBadges($siswa->id_siswa);
// Deteksi badge yang baru didapat
$badgeSesudah = SiswaBadge::where('id_siswa', $siswa->id_siswa)
->pluck('id_badge')
->toArray();
$idBadgeBaru = array_diff($badgeSesudah, $badgeSebelum);
if (!empty($idBadgeBaru)) {
$badgeBaru = Badge::whereIn('id_badge', $idBadgeBaru)->get();
session()->flash('badge_baru', $badgeBaru);
}
}
$pesan = $status === 'terlambat'

View File

@ -450,8 +450,9 @@ .other-portals a {
color: #5b3fc0;
font-weight: 700;
text-decoration: none;
padding: 3px 8px;
padding: 5px 12px;
border-radius: 6px;
font-size: 15px;
transition: background 0.2s;
}

View File

@ -124,6 +124,98 @@
margin-top: 20px;
}
.btn-back:hover { opacity: 0.9; color: white; }
/* ── Modal Badge ─────────────────────────────────────────── */
.badge-modal-overlay {
display: none;
position: fixed;
inset: 0;
background: rgba(0,0,0,0.55);
z-index: 9999;
align-items: center;
justify-content: center;
backdrop-filter: blur(3px);
}
.badge-modal-overlay.active {
display: flex;
}
.badge-modal {
background: white;
border-radius: 24px;
padding: 36px 28px 28px;
max-width: 380px;
width: 90%;
text-align: center;
animation: popIn 0.35s cubic-bezier(0.34,1.56,0.64,1);
position: relative;
}
@keyframes popIn {
from { transform: scale(0.7); opacity: 0; }
to { transform: scale(1); opacity: 1; }
}
.badge-modal-label {
font-size: 12px;
font-weight: 700;
color: #7c3aed;
text-transform: uppercase;
letter-spacing: 1px;
margin-bottom: 20px;
}
.badge-modal-list {
display: flex;
flex-direction: column;
gap: 20px;
margin-bottom: 24px;
}
.badge-modal-item {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
}
.badge-modal-icon {
width: 100%;
max-width: 220px;
height: auto;
object-fit: contain;
margin-bottom: 14px;
}
.badge-modal-info-nama {
font-size: 16px;
font-weight: 800;
color: #1e293b;
margin-bottom: 4px;
}
.badge-modal-info-desc {
font-size: 13px;
color: #64748b;
}
.badge-modal-btn {
background: linear-gradient(135deg, #667eea, #764ba2);
color: white;
border: none;
border-radius: 12px;
padding: 12px 32px;
font-size: 14px;
font-weight: 700;
font-family: 'Poppins', sans-serif;
cursor: pointer;
width: 100%;
transition: opacity 0.2s;
}
.badge-modal-btn:hover { opacity: 0.9; }
</style>
@endpush
@ -146,6 +238,33 @@
: ($persentase >= 60 ? 'Bagus! Terus tingkatkan kemampuanmu!' : 'Jangan menyerah! Terus semangat belajar!');
@endphp
{{-- ── Pop-up Modal Badge Baru ── --}}
@if(isset($badgeBaru) && $badgeBaru->isNotEmpty())
<div class="badge-modal-overlay active" id="badgeModal">
<div class="badge-modal">
<div class="badge-modal-label">Badge Baru Diraih!</div>
<div class="badge-modal-list">
@foreach($badgeBaru as $b)
<div class="badge-modal-item">
<img
src="{{ asset($b->icon_badge) }}"
alt="{{ $b->nama_badge }}"
class="badge-modal-icon"
>
<div class="badge-modal-info-nama">{{ $b->nama_badge }}</div>
<div class="badge-modal-info-desc">{{ $b->deskripsi }}</div>
</div>
@endforeach
</div>
<button class="badge-modal-btn" onclick="document.getElementById('badgeModal').classList.remove('active')">
Sip, lanjut! 🚀
</button>
</div>
</div>
@endif
<div class="hasil-wrapper">
<div class="hasil-hero">

View File

@ -28,7 +28,6 @@
.sidebar-link.active { background: #e6f0ff; color: #1d4ed8; }
.sidebar-icon { width: 20px; height: 20px; flex-shrink: 0; object-fit: contain; }
/* Logout — disamain dengan layout guru */
.sidebar-logout {
margin-top: auto;
padding-top: 16px;
@ -131,6 +130,153 @@
.modal-toast { border-radius: 10px; padding: 10px 14px; font-size: 13px; font-weight: 500; margin-bottom: 13px; display: none; }
.modal-toast.success { background: #f0fdf4; border: 1.5px solid #86efac; color: #166534; display: block; }
.modal-toast.error { background: #fef2f2; border: 1.5px solid #fca5a5; color: #991b1b; display: block; }
/* ── Badge Koleksi di Modal ── */
.modal-badge-section { margin-top: 20px; }
.modal-badge-title {
font-size: 13px;
font-weight: 700;
color: #475569;
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 12px;
display: flex;
align-items: center;
gap: 6px;
}
.modal-badge-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 10px;
}
.modal-badge-item {
background: #f8fafc;
border: 2px solid #e2e8f0;
border-radius: 14px;
padding: 12px 8px;
text-align: center;
position: relative;
transition: all 0.2s;
}
.modal-badge-item.dimiliki {
background: #faf5ff;
border-color: #c4b5fd;
}
.modal-badge-item.belum {
opacity: 0.45;
filter: grayscale(100%);
}
.modal-badge-icon {
width: 40px;
height: 40px;
object-fit: contain;
display: block;
margin: 0 auto 7px;
}
.modal-badge-nama {
font-size: 11px;
font-weight: 700;
color: #1e293b;
line-height: 1.3;
margin-bottom: 3px;
}
.modal-badge-desc {
font-size: 10px;
color: #94a3b8;
line-height: 1.3;
}
.modal-badge-lock {
position: absolute;
top: 7px;
right: 7px;
width: 13px;
height: 13px;
color: #94a3b8;
}
.modal-badge-tag {
display: inline-block;
background: #ede9fe;
color: #7c3aed;
font-size: 9px;
font-weight: 700;
padding: 2px 6px;
border-radius: 99px;
margin-top: 4px;
}
/* ── Modal Badge Global (leaderboard) ── */
.lb-badge-modal-overlay {
display: none;
position: fixed;
inset: 0;
background: rgba(0,0,0,0.55);
z-index: 99999;
align-items: center;
justify-content: center;
backdrop-filter: blur(3px);
}
.lb-badge-modal-overlay.active { display: flex; }
.lb-badge-modal {
background: white;
border-radius: 24px;
padding: 32px 28px 24px;
max-width: 360px;
width: 90%;
text-align: center;
animation: lbPopIn 0.35s cubic-bezier(0.34,1.56,0.64,1);
}
@keyframes lbPopIn {
from { transform: scale(0.7); opacity: 0; }
to { transform: scale(1); opacity: 1; }
}
.lb-badge-modal-label {
font-size: 12px;
font-weight: 700;
color: #7c3aed;
text-transform: uppercase;
letter-spacing: 1px;
margin-bottom: 20px;
}
.lb-badge-modal-list {
display: flex;
flex-direction: column;
gap: 20px;
margin-bottom: 24px;
}
.lb-badge-modal-item {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
}
.lb-badge-modal-icon {
width: 100%;
max-width: 220px;
height: auto;
object-fit: contain;
margin-bottom: 14px;
}
.lb-badge-modal-nama {
font-size: 16px;
font-weight: 800;
color: #1e293b;
margin-bottom: 4px;
}
.lb-badge-modal-desc { font-size: 13px; color: #64748b; }
.lb-badge-modal-btn {
background: linear-gradient(135deg, #667eea, #764ba2);
color: white;
border: none;
border-radius: 12px;
padding: 12px 32px;
font-size: 14px;
font-weight: 700;
font-family: 'Poppins', sans-serif;
cursor: pointer;
width: 100%;
transition: opacity 0.2s;
}
.lb-badge-modal-btn:hover { opacity: 0.9; }
</style>
@stack('styles')
</head>
@ -222,6 +368,13 @@
</div>
{{-- PROFILE MODAL --}}
@php
use App\Models\Badge;
use App\Models\SiswaBadge;
$semuaBadgeLayout = Badge::all();
$idBadgeDimiliki = SiswaBadge::where('id_siswa', $siswa->id_siswa)->pluck('id_badge')->toArray();
@endphp
<div class="profile-modal-overlay" id="profileModalOverlay" onclick="closeOnOverlay(event)">
<div class="profile-modal">
<button class="modal-close" onclick="closeProfileModal()">
@ -266,6 +419,44 @@
</div>
<button type="submit" class="btn-save-modal" id="btn-save">Simpan Perubahan</button>
</form>
{{-- ── Badge Koleksi ── --}}
@if($semuaBadgeLayout->isNotEmpty())
<div class="modal-badge-section">
<hr class="modal-divider">
<div class="modal-badge-title">🏅 Badge Koleksiku</div>
<div class="modal-badge-grid">
@foreach($semuaBadgeLayout as $b)
@php $dimiliki = in_array($b->id_badge, $idBadgeDimiliki); @endphp
<div class="modal-badge-item {{ $dimiliki ? 'dimiliki' : 'belum' }}">
@if(!$dimiliki)
<svg class="modal-badge-lock" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/>
</svg>
@endif
<img src="{{ asset($b->icon_badge) }}" alt="{{ $b->nama_badge }}" class="modal-badge-icon">
<div class="modal-badge-nama">{{ $b->nama_badge }}</div>
<div class="modal-badge-desc">{{ $b->deskripsi }}</div>
@if($dimiliki)
<span class="modal-badge-tag"> Diraih</span>
@endif
</div>
@endforeach
</div>
</div>
@endif
</div>
</div>
{{-- Modal Badge Leaderboard Global --}}
<div class="lb-badge-modal-overlay" id="lbBadgeModal">
<div class="lb-badge-modal">
<div class="lb-badge-modal-label">Badge Baru Diraih!</div>
<div class="lb-badge-modal-list" id="lbBadgeList"></div>
<button class="lb-badge-modal-btn" onclick="dismissLbBadgeModal()">
Sip, lanjut! 🚀
</button>
</div>
</div>
@ -281,6 +472,9 @@ function updateTogglePosition(c){toggleBtn.style.left=c?'0px':SIDEBAR_W+'px';c?t
toggleBtn.addEventListener('click',function(){const c=sidebar.classList.contains('collapsed');sidebar.classList.toggle('collapsed');updateTogglePosition(!c);});
const NOTIF_URL='{{ route("siswa.notifikasi") }}';
const LB_JSON_URL='{{ route("siswa.leaderboard.json") }}';
const LB_BADGE_STORAGE_KEY='lb_badge_ids_{{ Auth::guard("siswa")->id() }}';
const notifIcons = {
materi: '<img src="{{ $iconMateri }}" alt="Materi baru" class="icon-md">',
tugas: '<img src="{{ $iconTugas }}" alt="Tugas baru" class="icon-md">'
@ -313,6 +507,83 @@ function toggleNotif(e){e.stopPropagation();document.getElementById('notifDropdo
document.addEventListener('click',e=>{if(!document.getElementById('notifWrap').contains(e.target))document.getElementById('notifDropdown').classList.remove('show');});
fetchNotif();setInterval(fetchNotif,30000);
/* ── Polling badge leaderboard ── */
const LB_BADGE_PENDING_KEY = LB_BADGE_STORAGE_KEY + '_pending';
function getSavedBadgeIds() {
try { return JSON.parse(localStorage.getItem(LB_BADGE_STORAGE_KEY) || '[]'); } catch(e) { return []; }
}
function saveBadgeIds(ids) {
try { localStorage.setItem(LB_BADGE_STORAGE_KEY, JSON.stringify(ids)); } catch(e) {}
}
function getPendingBadges() {
try { return JSON.parse(localStorage.getItem(LB_BADGE_PENDING_KEY) || '[]'); } catch(e) { return []; }
}
function savePendingBadges(badges) {
try { localStorage.setItem(LB_BADGE_PENDING_KEY, JSON.stringify(badges)); } catch(e) {}
}
function clearPendingBadges() {
try { localStorage.removeItem(LB_BADGE_PENDING_KEY); } catch(e) {}
}
function showLbBadgeModal(badges) {
const list = document.getElementById('lbBadgeList');
list.innerHTML = badges.map(b => `
<div class="lb-badge-modal-item">
<img src="${b.icon_url}" alt="${b.nama_badge}" class="lb-badge-modal-icon">
<div class="lb-badge-modal-nama">${b.nama_badge}</div>
<div class="lb-badge-modal-desc">${b.deskripsi}</div>
</div>`).join('');
document.getElementById('lbBadgeModal').classList.add('active');
}
function dismissLbBadgeModal() {
document.getElementById('lbBadgeModal').classList.remove('active');
// Baru update storage setelah user dismiss — ini yang fix masalah
const pending = getPendingBadges();
if (pending.length > 0) {
saveBadgeIds(pending.map(b => b._confirmedIds).flat());
clearPendingBadges();
}
}
async function pollLbBadge() {
try {
const res = await fetch(LB_JSON_URL);
const data = await res.json();
const currentBadges = data.badgeSiswa || [];
const currentIds = currentBadges.map(b => b.id_badge);
const savedIds = getSavedBadgeIds();
// Badge baru = ada di current tapi belum di saved
const newBadges = currentBadges.filter(b => !savedIds.includes(b.id_badge));
if (newBadges.length > 0) {
// Simpan sebagai pending — storage belum diupdate sampai user dismiss
savePendingBadges([{ _confirmedIds: currentIds }]);
showLbBadgeModal(newBadges);
} else {
// Tidak ada badge baru, update storage langsung (termasuk handle badge dicabut)
saveBadgeIds(currentIds);
}
} catch(e) {}
}
// Jalankan polling badge leaderboard setiap 10 detik
// Cek dulu apakah ada pending badge yang belum di-dismiss (dari halaman sebelumnya)
(function checkPendingOnLoad() {
const pending = getPendingBadges();
if (pending.length > 0) {
// Ada badge yang belum di-dismiss, tampilkan ulang
// Ambil dari saved + current ids untuk reconstruct badge list
// Polling akan handle ini, tapi kita perlu tampilkan modal dulu
// Gunakan data dari pending storage untuk reconstruct
pollLbBadge(); // polling akan detect karena storage belum diupdate
return;
}
pollLbBadge();
})();
setInterval(pollLbBadge, 10000);
function openProfileModal(){document.getElementById('profileModalOverlay').classList.add('show');document.body.style.overflow='hidden';}
function closeProfileModal(){document.getElementById('profileModalOverlay').classList.remove('show');document.body.style.overflow='';const t=document.getElementById('modal-toast');t.className='modal-toast';t.textContent='';}
function closeOnOverlay(e){if(e.target===document.getElementById('profileModalOverlay'))closeProfileModal();}

View File

@ -207,11 +207,129 @@
align-items: center;
gap: 8px;
}
/* ── Modal Badge ─────────────────────────────────────────── */
.badge-modal-overlay {
display: none;
position: fixed;
inset: 0;
background: rgba(0,0,0,0.55);
z-index: 9999;
align-items: center;
justify-content: center;
backdrop-filter: blur(3px);
}
.badge-modal-overlay.active {
display: flex;
}
.badge-modal {
background: white;
border-radius: 24px;
padding: 36px 28px 28px;
max-width: 380px;
width: 90%;
text-align: center;
animation: popIn 0.35s cubic-bezier(0.34,1.56,0.64,1);
position: relative;
}
@keyframes popIn {
from { transform: scale(0.7); opacity: 0; }
to { transform: scale(1); opacity: 1; }
}
.badge-modal-label {
font-size: 12px;
font-weight: 700;
color: #7c3aed;
text-transform: uppercase;
letter-spacing: 1px;
margin-bottom: 20px;
}
.badge-modal-list {
display: flex;
flex-direction: column;
gap: 20px;
margin-bottom: 24px;
}
.badge-modal-item {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
}
.badge-modal-icon {
width: 100%;
max-width: 220px;
height: auto;
object-fit: contain;
margin-bottom: 14px;
}
.badge-modal-info-nama {
font-size: 16px;
font-weight: 800;
color: #1e293b;
margin-bottom: 4px;
}
.badge-modal-info-desc {
font-size: 13px;
color: #64748b;
}
.badge-modal-btn {
background: linear-gradient(135deg, #667eea, #764ba2);
color: white;
border: none;
border-radius: 12px;
padding: 12px 32px;
font-size: 14px;
font-weight: 700;
font-family: 'Poppins', sans-serif;
cursor: pointer;
width: 100%;
transition: opacity 0.2s;
}
.badge-modal-btn:hover { opacity: 0.9; }
</style>
@endpush
@section('content')
{{-- ── Pop-up Modal Badge Baru (tugas) ── --}}
@if(session('badge_baru') && session('badge_baru')->isNotEmpty())
<div class="badge-modal-overlay active" id="badgeModal">
<div class="badge-modal">
<div class="badge-modal-label">Badge Baru Diraih!</div>
<div class="badge-modal-list">
@foreach(session('badge_baru') as $b)
<div class="badge-modal-item">
<img
src="{{ asset($b->icon_badge) }}"
alt="{{ $b->nama_badge }}"
class="badge-modal-icon"
>
<div class="badge-modal-info-nama">{{ $b->nama_badge }}</div>
<div class="badge-modal-info-desc">{{ $b->deskripsi }}</div>
</div>
@endforeach
</div>
<button class="badge-modal-btn" onclick="document.getElementById('badgeModal').classList.remove('active')">
Sip, lanjut! 🚀
</button>
</div>
</div>
@endif
<h1 class="page-title">
<img src="{{ asset('images/icon/siswat/buku1.png') }}" alt="Ikon tugas" class="icon-md">
Tugas