Jumat, 17 Oktober 2025

Menenal dan membuat aplikasi gengan app inventor

 


<!DOCTYPE html>

<html lang="id">

<head>

    <meta charset="UTF-8">

    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <title>LKPD Digital: App Inventor - Informatika SMA Kelas XII</title>

    <!-- Tailwind CSS CDN -->

    <script src="https://cdn.tailwindcss.com"></script>

    <!-- Lucide Icons CDN -->

    <script src="https://unpkg.com/lucide@latest"></script>

    <!-- HTML2PDF CDN for PDF Export -->

    <script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js"></script>

    <!-- Font Poppins/Nunito Sans (Menggunakan Inter sebagai default Tailwind, disesuaikan sedikit) -->

    <style>

        @import url('https://fonts.googleapis.com/css2?family=Nunito+Sans:wght@300;400;600;700;900&display=swap');

        body {

            font-family: 'Nunito Sans', sans-serif;

            background-color: #eef2ff; /* Light Lavender/Blue Background */

        }

        .app-container {

            min-height: calc(100vh - 4rem);

        }

        .isometric-shadow {

            box-shadow: 10px 10px 0 0 rgba(109, 40, 217, 0.3), 15px 15px 0 0 rgba(237, 233, 254, 0.5);

        }

        .code-block {

            background-color: #3b82f6; /* Blue-600 */

            color: white;

            padding: 8px 12px;

            margin: 4px;

            border-radius: 6px;

            font-weight: 600;

            cursor: move;

            display: inline-flex;

            align-items: center;

        }

    </style>

</head>

<body class="p-4 sm:p-8">


    <div id="app" class="app-container max-w-6xl mx-auto bg-white rounded-3xl shadow-2xl p-6 sm:p-10 transition-all duration-500">

        <!-- Content Area -->

    </div>


    <!-- Modal for Tooltips and Messages -->

    <div id="app-modal" class="hidden fixed inset-0 bg-gray-900 bg-opacity-70 flex items-center justify-center p-4 z-50">

        <div class="bg-white rounded-xl p-6 max-w-lg w-full shadow-2xl transition-all duration-300 transform scale-95" id="modal-content-area">

            <h3 id="modal-title" class="text-2xl font-bold text-purple-700 mb-4 flex items-center"></h3>

            <p id="modal-message" class="text-gray-600 mb-6"></p>

            <button onclick="closeModal()" class="w-full bg-blue-600 hover:bg-blue-700 text-white font-semibold py-2 rounded-lg transition duration-150">Tutup</button>

        </div>

    </div>


    <!-- JavaScript Logic -->

    <script>

        // --- GLOBAL CONSTANTS ---

        const API_KEY = ""; // Canvas will provide this at runtime

        const GEMINI_MODEL = "gemini-2.5-flash-preview-09-2025";

        const GEMINI_API_URL = `https://generativelanguage.googleapis.com/v1beta/models/${GEMINI_MODEL}:generateContent?key=${API_KEY}`;

        const TEACHER_USER = 'guru';

        const TEACHER_PASS = 'admin';



        // --- UTILITY FUNCTIONS ---

        

        // Fungsi pembantu untuk pemuatan JSON yang aman

        const safeJSONLoad = (key, defaultVal = {}) => {

            const stored = localStorage.getItem(key);

            if (!stored) return defaultVal;

            try {

                return JSON.parse(stored);

            } catch (e) {

                console.error(`[LocalStorage Error] Gagal memuat JSON untuk kunci: ${key}. Data rusak. Menggunakan nilai default.`, stored, e);

                return defaultVal;

            }

        };



        // --- GLOBAL STATE & DATA ---

        let appState = {

            currentPage: 1,

            maxPage: 10,

            userName: localStorage.getItem('userName') || '',

            studentId: localStorage.getItem('studentId') || crypto.randomUUID(),

            currentQuizAnswers: safeJSONLoad('currentQuizAnswers'),

            assessmentAnswersPG: safeJSONLoad('assessmentAnswersPG'),

            assessmentAnswersUraian: safeJSONLoad('assessmentAnswersUraian'),

            projectSubmission: safeJSONLoad('projectSubmission'),

            reflection: safeJSONLoad('reflection'),

            score: safeJSONLoad('score', {"pg": 0, "maxPg": 15, "assessmentPg": 0, "maxAssessmentPg": 10}),

            isLoggedInAsTeacher: false,

        };


        // Semua data kuis dan asesmen

        const QUIZ_DATA = {

            // Kuis Pengenalan App Inventor (Page 3) - 5 Soal

            quiz1: [

                { id: 1, q: "Apa fungsi utama tab Blocks dalam App Inventor?", opts: ["Mengatur tampilan visual", "Menambahkan komponen baru", "Menyusun logika program", "Melihat daftar properti"], ans: 2, points: 1 },

                { id: 2, q: "Apa kegunaan dari Component Tree (daftar Komponen)?", opts: ["Tempat kode blok disusun", "Menampilkan pratinjau aplikasi", "Melihat hierarki dan daftar komponen yang digunakan", "Mengatur koneksi ke perangkat"], ans: 2, points: 1 },

                { id: 3, q: "Di mana kita mengatur warna latar belakang atau teks pada sebuah komponen?", opts: ["Palette", "Viewer", "Components Tree", "Properties"], ans: 3, points: 1 },

                { id: 4, q: "Komponen antarmuka pengguna mana yang digunakan untuk menampilkan teks statis kepada pengguna?", opts: ["Button", "Label", "TextBox", "Image"], ans: 1, points: 1 },

                { id: 5, q: "Apa tujuan dari menu Connect → AI Companion?", opts: ["Menghubungkan ke database", "Menguji aplikasi secara real-time di ponsel", "Membuat kode blok secara otomatis", "Mengunggah aplikasi ke Play Store"], ans: 1, points: 1 }

            ],

            // Kuis Senter Sederhana (Page 5) - 3 Soal

            quiz2: [

                { id: 6, q: "Apa komponen utama yang mendeteksi sentuhan pengguna dalam Latihan 2?", opts: ["Sensor", "Screen1", "Button", "Notifier"], ans: 2, points: 1 },

                { id: 7, q: "Mengapa diperlukan event 'when Button.Click'?", opts: ["Untuk mengubah teks tombol", "Untuk mendefinisikan tampilan awal", "Untuk menjalankan perintah saat tombol ditekan", "Untuk menyimpan data"], ans: 2, points: 1 },

                { id: 8, q: "Apa yang terjadi jika komponen Notifier dihapus dari aplikasi senter ini (simulasi)?", opts: ["Aplikasi tidak bisa berjalan", "Tidak ada pesan konfirmasi yang muncul", "Tombol tidak bisa diklik", "Senter tetap menyala"], ans: 1, points: 1 }

            ],

            // Asesmen Akhir (Page 7) - 10 PG

            assessmentPG: [

                { id: 9, q: "CPU pada App Inventor diwakili oleh bagian...", opts: ["Properties", "Palette", "Blocks Editor", "Viewer"], ans: 2, points: 1 },

                { id: 10, q: "Extension .aia App Inventor adalah singkatan dari...", opts: ["App Image Assets", "App Interface Archive", "App Inventor Project", "Android Internet App"], ans: 2, points: 1 },

                { id: 11, q: "Untuk menyimpan data aplikasi agar tidak hilang saat aplikasi ditutup, Anda menggunakan komponen...", opts: ["Sound", "Notifier", "TinyDB", "Sensor"], ans: 2, points: 1 },

                { id: 12, q: "Komponen yang dapat merekam posisi geografis pengguna adalah...", opts: ["Clock", "AccelerometerSensor", "LocationSensor", "ProximitySensor"], ans: 2, points: 1 },

                { id: 13, q: "Warna blok logika 'if/then' adalah...", opts: ["Hijau", "Ungu", "Biru", "Kuning"], ans: 2, points: 1 },

                { id: 14, q: "Untuk membuat tombol menjadi tidak terlihat, properti yang diatur adalah...", opts: ["Enabled", "Visible", "TextAlignment", "Width"], ans: 1, points: 1 },

                { id: 15, q: "Apa jenis variabel yang nilainya bisa diubah selama program berjalan?", opts: ["Global Constant", "Local Procedure", "Global Variable", "Local Constant"], ans: 2, points: 1 },

                { id: 16, q: "Tujuan dari fitur Export .apk App Inventor adalah...", opts: ["Mengunggah ke cloud", "Mencadangkan proyek", "Membuat file instalasi Android", "Membuka editor Blocks"], ans: 2, points: 1 },

                { id: 17, q: "Bagian 'Palette' berisi daftar...", opts: ["Properti Komponen", "Blok Kode", "Aplikasi yang dibuat", "Komponen Antarmuka"], ans: 3, points: 1 },

                { id: 18, q: "Manakah yang merupakan tipe data pada App Inventor?", opts: ["Array", "Object", "List", "Class"], ans: 2, points: 1 }

            ],

            // Asesmen Akhir (Page 7) - 3 Uraian

            assessmentUraian: [

                { id: 1, q: "Jelaskan perbedaan mendasar antara Designer View dan Blocks Editor pada App Inventor." },

                { id: 2, q: "Mengapa penting untuk memberikan nama yang jelas pada setiap komponen aplikasi?" },

                { id: 3, q: "Sebutkan minimal 3 komponen non-visible (di luar antarmuka pengguna) dan jelaskan fungsinya." }

            ],

        };


        const QUIZ_MAP = {

            3: QUIZ_DATA.quiz1,

            5: QUIZ_DATA.quiz2,

            7: QUIZ_DATA.assessmentPG,

        };

        // Menghitung ulang skor maksimal berdasarkan data kuis/asesmen yang dimuat

        appState.score.maxPg = QUIZ_DATA.quiz1.length + QUIZ_DATA.quiz2.length;

        appState.score.maxAssessmentPg = QUIZ_DATA.assessmentPG.length;


        // --- GEMINI API FUNCTIONS ---


        const callGeminiApi = async (systemPrompt, userQuery) => {

            const payload = {

                contents: [{ parts: [{ text: userQuery }] }],

                systemInstruction: {

                    parts: [{ text: systemPrompt }]

                },

                // Tidak menggunakan Google Search karena ini adalah tugas konsep spesifik App Inventor

            };


            const maxRetries = 5;

            let currentRetry = 0;


            while (currentRetry < maxRetries) {

                try {

                    const response = await fetch(GEMINI_API_URL, {

                        method: 'POST',

                        headers: { 'Content-Type': 'application/json' },

                        body: JSON.stringify(payload)

                    });


                    if (!response.ok) {

                         // Throw error to trigger retry if non-200 status

                        throw new Error(`HTTP error! status: ${response.status}`);

                    }


                    const result = await response.json();

                    const text = result.candidates?.[0]?.content?.parts?.[0]?.text;

                    

                    if (text) {

                        return text;

                    }

                    throw new Error("No text found in API response.");


                } catch (error) {

                    currentRetry++;

                    if (currentRetry >= maxRetries) {

                        console.error(`Gemini API failed after ${maxRetries} retries:`, error);

                        throw new Error("Gagal terhubung ke Asisten AI. Coba lagi.");

                    }

                    // Exponential backoff

                    const delay = Math.pow(2, currentRetry) * 1000 + Math.random() * 1000;

                    await new Promise(resolve => setTimeout(resolve, delay));

                }

            }

        };


        window.generateProjectSuggestions = async () => {

            const btn = document.getElementById('generate-btn');

            const outputDiv = document.getElementById('suggestion-output');

            const resultBox = document.getElementById('gemini-suggestions');


            const projectName = document.getElementById('project-name').value;

            const projectTujuan = document.getElementById('project-tujuan').value;


            if (projectName.trim() === '' && projectTujuan.trim() === '') {

                openModal('Perhatian', 'Mohon isi Nama Proyek atau Tujuan Aplikasi Anda terlebih dahulu.');

                return;

            }


            btn.disabled = true;

            btn.innerHTML = `${renderIcon('loader-circle', 'w-5 h-5 inline mr-2 animate-spin')} Sedang Membuat Saran...`;

            resultBox.classList.remove('hidden');

            outputDiv.innerHTML = `<p class="text-gray-500 italic">Asisten AI sedang memproses ide proyek Anda...</p>`;


            const userQuery = `Nama Proyek: ${projectName}. Tujuan: ${projectTujuan}. Beri saya daftar komponen App Inventor yang dibutuhkan (misalnya Button, Label, TinyDB, Notifier) dan deskripsi singkat tentang logika Blok utama yang diperlukan. Buat dalam format daftar terstruktur (markdown).`;


            const systemPrompt = "Anda adalah Asisten AI yang ahli dalam MIT App Inventor. Tugas Anda adalah menganalisis ide proyek siswa dan memberikan saran yang konstruktif dan teknis (berbasis blok/komponen App Inventor). Berikan respons dalam Bahasa Indonesia yang formal dan jelas.";


            try {

                const result = await callGeminiApi(systemPrompt, userQuery);

                outputDiv.innerHTML = result;


            } catch (error) {

                outputDiv.innerHTML = `<p class="text-red-500 font-semibold">${error.message || 'Terjadi kesalahan saat menghubungi API.'}</p>`;

            } finally {

                btn.disabled = false;

                btn.innerHTML = `${renderIcon('sparkles')} ✨ Sarankan Komponen & Logika`;

                lucide.createIcons(); // Re-render icons after changing innerHTML

            }

        };



        // --- UTILITY FUNCTIONS (LANJUTAN) ---


        /** Menampilkan modal custom */

        window.openModal = (title, message) => {

            document.getElementById('modal-title').innerHTML = renderIcon('info') + ' ' + title;

            document.getElementById('modal-message').innerHTML = message;

            document.getElementById('app-modal').classList.remove('hidden');

        };


        /** Menutup modal custom */

        window.closeModal = () => {

            document.getElementById('app-modal').classList.add('hidden');

        };


        /** Render Lucide Icon */

        const renderIcon = (name, classes = 'w-5 h-5 inline mr-2') => {

            // Lucide.createIcons() dipanggil di akhir renderPage

            return `<i data-lucide="${name}" class="${classes}"></i>`;

        };


        /** Load State from localStorage */

        const loadState = () => {

            // Semua status data/jawaban dimuat dengan aman saat inisialisasi appState.

            // Fungsi ini hanya memastikan halaman diambil dari hash URL.

            const hashPage = parseInt(window.location.hash.substring(1));

            appState.currentPage = !isNaN(hashPage) && hashPage >= 1 && hashPage <= appState.maxPage ? hashPage : 1;

        };


        /** Save State to localStorage */

        const saveState = (key, value) => {

            appState[key] = value;

            if (typeof value === 'object') {

                localStorage.setItem(key, JSON.stringify(value));

            } else {

                localStorage.setItem(key, value);

            }

        };


        /** Hitung skor PG total */

        const calculateTotalScore = () => {

            let correctQuiz = 0;

            let correctAssessment = 0;


            // Kuis (Page 3 & 5)

            [...QUIZ_DATA.quiz1, ...QUIZ_DATA.quiz2].forEach(q => {

                if (appState.currentQuizAnswers[q.id] === q.ans) {

                    correctQuiz += q.points;

                }

            });


            // Asesmen PG (Page 7)

            QUIZ_DATA.assessmentPG.forEach(q => {

                if (appState.assessmentAnswersPG[q.id] === q.ans) {

                    correctAssessment += q.points;

                }

            });


            const newScore = {

                pg: correctQuiz,

                maxPg: appState.score.maxPg,

                assessmentPg: correctAssessment,

                maxAssessmentPg: appState.score.maxAssessmentPg,

                total: correctQuiz + correctAssessment,

                maxTotal: appState.score.maxPg + appState.score.maxAssessmentPg

            };

            saveState('score', newScore);

            return newScore;

        };


        /** Dapatkan Kategori Nilai */

        const getCategory = (scorePercentage) => {

            if (scorePercentage >= 90) return { text: "Sangat Baik", color: "text-green-600" };

            if (scorePercentage >= 75) return { text: "Baik", color: "text-blue-600" };

            if (scorePercentage >= 60) return { text: "Cukup", color: "text-yellow-600" };

            return { text: "Perlu Bimbingan", color: "text-red-600" };

        };



        // --- NAVIGATION & PAGE RENDERING ---


        window.navigate = (pageNumber) => {

            if (pageNumber < 1 || pageNumber > appState.maxPage) return;


            // Cek otentikasi guru

            if (pageNumber === 9 && !appState.isLoggedInAsTeacher) {

                appState.currentPage = 9;

                renderPage();

                return;

            }


            if (appState.currentPage === 1 && appState.userName.trim() === '') {

                // Jangan navigasi dari page 1 jika nama kosong

                openModal('Perhatian', 'Mohon masukkan Nama Anda sebelum memulai pembelajaran.');

                return;

            }


            appState.currentPage = pageNumber;

            window.location.hash = pageNumber;

            renderPage();

        };


        const renderNavigationButtons = (prevText, nextText) => {

            const isFirst = appState.currentPage === 1;

            const isLast = appState.currentPage === appState.maxPage;

            const isTeacher = appState.currentPage === 9 && appState.isLoggedInAsTeacher;


            return `

                <div class="flex justify-between items-center mt-10 p-4 border-t border-gray-200">

                    <button onclick="navigate(${appState.currentPage - 1})"

                        class="flex items-center text-gray-600 hover:text-purple-600 transition disabled:opacity-50"

                        ${isFirst || isTeacher ? 'disabled' : ''}>

                        ${renderIcon('chevron-left')} ${prevText}

                    </button>


                    <div class="text-sm font-semibold text-gray-500">

                        Halaman ${appState.currentPage} / ${appState.maxPage}

                    </div>


                    <button onclick="navigate(${appState.currentPage + 1})"

                        class="flex items-center text-white bg-purple-600 px-6 py-2 rounded-lg hover:bg-purple-700 transition disabled:opacity-50"

                        ${isLast ? 'disabled' : ''}>

                        ${nextText} ${renderIcon('chevron-right', 'w-5 h-5 inline ml-2')}

                    </button>

                </div>

            `;

        };


        // --- PAGE CONTENT FUNCTIONS (Sesuai Urutan LKPD) ---


        const renderPage1 = () => {

            return `

                <div class="text-center py-16 bg-gradient-to-br from-indigo-50 to-white rounded-2xl shadow-lg">

                    <h2 class="text-5xl font-extrabold text-gray-800 mb-4">

                        Mengenal dan Membuat Aplikasi dengan App Inventor

                    </h2>

                    <h3 class="text-2xl font-medium text-purple-600 mb-8">

                        Belajar Membuat Aplikasi Android dengan Pemrograman Visual Blok

                    </h3>

                    <p class="text-gray-500 mb-8">

                        Nama Penyusun: <span class="font-semibold text-gray-700">Joko Purnomo, S.Kom – SMAN 1 Karangkobar</span>

                    </p>


                    <!-- Ilustrasi Animasi: Blok kode, ikon telepon -->

                    <div class="my-12 flex justify-center items-center space-x-8">

                        <div class="w-20 h-20 bg-blue-400 rounded-xl transform rotate-12 flex items-center justify-center text-white text-3xl font-bold isometric-shadow">

                            ${renderIcon('smartphone', 'w-10 h-10')}

                        </div>

                        <div class="w-20 h-20 bg-yellow-400 rounded-xl transform -rotate-6 flex items-center justify-center text-white text-3xl font-bold isometric-shadow">

                            ${renderIcon('code', 'w-10 h-10')}

                        </div>

                        <div class="w-20 h-20 bg-red-400 rounded-xl transform rotate-6 flex items-center justify-center text-white text-3xl font-bold isometric-shadow">

                            ${renderIcon('plug', 'w-10 h-10')}

                        </div>

                    </div>

                    

                    <div class="max-w-xs mx-auto mt-12 p-4 bg-gray-100 rounded-xl shadow-inner">

                        <label for="student-name" class="block text-lg font-medium text-gray-700 mb-2">Masukkan Nama Anda:</label>

                        <input type="text" id="student-name" value="${appState.userName}" onchange="saveState('userName', this.value)" 

                                placeholder="Nama Lengkap" class="w-full px-4 py-2 border-2 border-purple-300 rounded-lg focus:ring-purple-500 focus:border-purple-500 text-lg">

                        <p class="text-xs text-gray-500 mt-1">ID Siswa Anda: <span class="font-mono text-purple-600">${appState.studentId.substring(0, 8)}...</span></p>

                    </div>


                    <button onclick="navigate(2)" class="btn-primary bg-purple-600 text-white font-bold py-3 px-10 rounded-xl text-xl mt-12 hover:bg-purple-700">

                        ${renderIcon('rocket')} Mulai Belajar

                    </button>

                </div>

            `;

        };


        const renderPage2 = () => {

            const objectives = [

                "Peserta didik mampu memahami konsep pemrograman visual blok pada App Inventor.",

                "Peserta didik mampu mengenal antarmuka (Designer dan Blocks) pada App Inventor.",

                "Peserta didik mampu membuat program aplikasi sederhana menggunakan App Inventor.",

                "Peserta didik mampu membuat simulasi aplikasi 'Klik Saya' dan 'Senter Sederhana'.",

                "Peserta didik dapat menganalisis dan merefleksikan hasil karyanya dalam proyek mini."

            ];


            const instructions = [

                { icon: 'mouse-pointer-2', title: "Navigasi", desc: "Gunakan tombol 'Lanjut' dan 'Kembali' untuk berpindah antar halaman." },

                { icon: 'pencil', title: "Isi Jawaban", desc: "Jawab semua pertanyaan kuis dan isian uraian. Jawaban tersimpan otomatis." },

                { icon: 'check-circle', title: "Asesmen", desc: "Selesaikan 10 Soal Pilihan Ganda dan 3 Uraian di bagian Evaluasi untuk mendapatkan skor akhir." },

                { icon: 'printer', title: "Cetak Hasil", desc: "Setelah selesai, Anda dapat mencetak laporan hasil kerja Anda di halaman terakhir." }

            ];


            return `

                <div class="p-4 sm:p-6">

                    <h2 class="text-3xl font-bold text-gray-800 mb-6 border-b-2 border-purple-200 pb-2">

                        ${renderIcon('book-open')} Tujuan Pembelajaran dan Petunjuk

                    </h2>


                    <div class="grid md:grid-cols-2 gap-8">

                        <div class="bg-blue-50 p-6 rounded-xl shadow-inner border border-blue-200">

                            <h3 class="text-xl font-bold text-blue-700 mb-4 flex items-center">${renderIcon('target')} Tujuan Pembelajaran</h3>

                            <ul class="space-y-3 list-none pl-0">

                                ${objectives.map(obj => `<li class="flex items-start">${renderIcon('check-circle', 'w-5 h-5 text-blue-500 mt-1 flex-shrink-0')} <span class="ml-3 text-gray-700">${obj}</span></li>`).join('')}

                            </ul>

                        </div>


                        <div class="bg-purple-50 p-6 rounded-xl shadow-inner border border-purple-200">

                            <h3 class="text-xl font-bold text-purple-700 mb-4 flex items-center">${renderIcon('megaphone')} Petunjuk Penggunaan LKPD Digital</h3>

                            <div class="space-y-4">

                                ${instructions.map(inst => `

                                    <div class="flex items-start p-3 bg-white rounded-lg shadow-sm hover:shadow-md transition duration-200">

                                        ${renderIcon(inst.icon, 'w-6 h-6 text-purple-600 flex-shrink-0 mt-1')}

                                        <div class="ml-4">

                                            <p class="font-semibold text-gray-800">${inst.title}</p>

                                            <p class="text-sm text-gray-600">${inst.desc}</p>

                                        </div>

                                    </div>

                                `).join('')}

                            </div>

                        </div>

                    </div>

                </div>

                ${renderNavigationButtons('Kembali', 'Lanjut ke Materi')}

            `;

        };


        const renderPage3 = () => {

            // Logic to handle tooltip display on hover/click for App Inventor interface

            window.showPartExplanation = (part) => {

                const parts = {

                    palette: { title: "Palette", desc: "Berisi daftar komponen (Button, Label, Layout, Sensor, dll.) yang dapat Anda seret dan letakkan ke Viewer." },

                    viewer: { title: "Viewer (Layar Pratinjau)", desc: "Area tempat Anda menyusun tampilan visual aplikasi. Pratinjau ini menyerupai layar ponsel." },

                    components: { title: "Components (Daftar Komponen)", desc: "Daftar komponen yang telah Anda tambahkan ke aplikasi. Di sini Anda bisa mengganti nama komponen." },

                    properties: { title: "Properties (Properti)", desc: "Berisi pengaturan detail untuk komponen yang sedang dipilih (misalnya: mengubah teks tombol, warna, ukuran font, visibility)." },

                    designer: { title: "Designer View", desc: "Tampilan untuk mendesain antarmuka pengguna aplikasi." },

                    blocks: { title: "Blocks Editor", desc: "Tampilan tempat Anda menyusun logika pemrograman menggunakan blok-blok berwarna." }

                };

                const data = parts[part];

                openModal(`Bagian Antarmuka: ${data.title}`, data.desc);

            };


            const quizHtml = renderQuiz(QUIZ_DATA.quiz1, appState.currentQuizAnswers, (id, ans) => {

                appState.currentQuizAnswers[id] = ans;

                saveState('currentQuizAnswers', appState.currentQuizAnswers);

                calculateTotalScore(); // Hitung ulang skor setiap jawaban

            }, 'quiz1-');


            return `

                <div class="p-4 sm:p-6">

                    <h2 class="text-3xl font-bold text-gray-800 mb-6 border-b-2 border-purple-200 pb-2">

                        ${renderIcon('monitor')} 3. Pengenalan Antarmuka App Inventor

                    </h2>

                    <p class="text-gray-600 mb-6">Pahami bagian-bagian utama App Inventor dengan mengklik setiap area berwarna di ilustrasi antarmuka di bawah.</p>

                    

                    <!-- App Inventor Interface Visualization -->

                    <div class="relative max-w-4xl mx-auto border-4 border-gray-700 rounded-lg bg-gray-100 shadow-xl overflow-hidden">

                        <img src="https://placehold.co/900x500/A0BFFF/1E3A8A?text=App+Inventor+Interface+Mockup" alt="Mockup Antarmuka App Inventor" class="w-full h-auto opacity-50">

                        <div class="absolute inset-0">

                            <!-- Clickable Overlay Areas -->

                            <div onclick="showPartExplanation('palette')" class="absolute top-[10%] left-[2%] w-[18%] h-[60%] bg-blue-500 opacity-30 hover:opacity-70 transition cursor-pointer flex items-center justify-center text-white font-bold text-lg rounded-md">Palette</div>

                            <div onclick="showPartExplanation('viewer')" class="absolute top-[10%] left-[22%] w-[30%] h-[70%] bg-green-500 opacity-30 hover:opacity-70 transition cursor-pointer flex items-center justify-center text-white font-bold text-lg rounded-md">Viewer</div>

                            <div onclick="showPartExplanation('components')" class="absolute top-[10%] left-[54%] w-[20%] h-[20%] bg-yellow-500 opacity-30 hover:opacity-70 transition cursor-pointer flex items-center justify-center text-white font-bold text-lg rounded-md">Components</div>

                            <div onclick="showPartExplanation('properties')" class="absolute top-[35%] left-[54%] w-[20%] h-[45%] bg-red-500 opacity-30 hover:opacity-70 transition cursor-pointer flex items-center justify-center text-white font-bold text-lg rounded-md">Properties</div>

                            <div onclick="showPartExplanation('designer')" class="absolute top-[2%] left-[2%] w-[72%] h-[6%] bg-purple-500 opacity-30 hover:opacity-70 transition cursor-pointer flex items-center justify-center text-white font-bold text-lg rounded-md">Designer</div>

                            <div onclick="showPartExplanation('blocks')" class="absolute top-[2%] left-[76%] w-[22%] h-[6%] bg-pink-500 opacity-30 hover:opacity-70 transition cursor-pointer flex items-center justify-center text-white font-bold text-lg rounded-md">Blocks Editor</div>

                        </div>

                    </div>


                    <h3 class="text-2xl font-bold text-gray-800 my-8 flex items-center">${renderIcon('zap')} Kuis Interaktif (Pengenalan)</h3>

                    <div id="quiz-area-3">

                        ${quizHtml}

                    </div>


                </div>

                ${renderNavigationButtons('Kembali', 'Lanjut ke Latihan 1')}

            `;

        };


        const renderPage4 = () => {

            const currentAnswer = appState.currentQuizAnswers[0] || '';

            const questionId = 'uraian1';


            window.handleUraianChange = (id, value) => {

                const newAnswers = { ...appState.assessmentAnswersUraian, [id]: value };

                saveState('assessmentAnswersUraian', newAnswers);

            };


            return `

                <div class="p-4 sm:p-6">

                    <h2 class="text-3xl font-bold text-gray-800 mb-6 border-b-2 border-purple-200 pb-2">

                        ${renderIcon('code-2')} 4. Latihan 1: Aplikasi "Klik Saya"

                    </h2>

                    <p class="text-gray-600 mb-6">Mari kita buat aplikasi pertama. Fokus pada logika pemrograman blok.</p>

                    

                    <div class="grid md:grid-cols-2 gap-8">

                        <div class="bg-gray-50 p-6 rounded-xl shadow-lg">

                            <h3 class="text-xl font-bold text-purple-700 mb-4">Simulasi & Langkah Kerja</h3>

                            <ul class="space-y-4 text-gray-700">

                                <li class="flex items-start">${renderIcon('1-square', 'w-5 h-5 text-blue-500 mt-1 flex-shrink-0')} Tambahkan Komponen: <span class="font-semibold ml-2">Button1</span> dan <span class="font-semibold">Label1</span>.</li>

                                <li class="flex items-start">${renderIcon('2-square', 'w-5 h-5 text-blue-500 mt-1 flex-shrink-0')} Atur Properti: Ubah <span class="font-semibold">Label1.Text</span> menjadi “Selamat Datang!”.</li>

                                <li class="flex items-start">${renderIcon('3-square', 'w-5 h-5 text-blue-500 mt-1 flex-shrink-0')} Buka Blocks Editor, susun logika seperti di sebelah.</li>

                            </ul>

                            

                            <div class="mt-8 text-center">

                                <div id="phone-viewer" class="w-48 h-80 mx-auto border-4 border-gray-900 rounded-3xl p-4 bg-white shadow-xl flex flex-col items-center justify-center">

                                    <span id="label1-text" class="text-lg mb-4 text-gray-700">Selamat Datang!</span>

                                    <button id="button1" onclick="document.getElementById('label1-text').textContent = 'Anda Berhasil Mengklik!'" 

                                            class="bg-green-500 text-white px-4 py-2 rounded-full hover:bg-green-600 transition">

                                        KLIK SAYA

                                    </button>

                                </div>

                                <p class="text-sm text-gray-500 mt-2">Simulasi Aplikasi (Coba Klik Tombol)</p>

                            </div>

                        </div>


                        <div class="bg-indigo-100 p-6 rounded-xl shadow-lg">

                            <h3 class="text-xl font-bold text-purple-700 mb-4">Simulasi Blok Kode</h3>

                            <div id="blocks-area" class="min-h-[250px] bg-white border-2 border-dashed border-indigo-300 rounded-lg p-4 flex flex-col items-start space-y-2">

                                <div class="code-block bg-yellow-600">when Button1.Click do</div>

                                <div class="ml-8 code-block bg-blue-600">set Label1.Text to</div>

                                <div class="ml-16 code-block bg-red-600">"Anda Berhasil Mengklik!"</div>

                                

                            </div>

                            <p class="text-sm text-gray-600 mt-4">Simulasi visual blok yang tersusun rapi.</p>


                            <h3 class="text-xl font-bold text-gray-800 mt-8 mb-4">Kuis Reflektif (Uraian)</h3>

                            <p class="mb-3 text-gray-700 font-medium">Jelaskan fungsi event 'when Button1.Click' dalam program ini. (Simpan jawaban)</p>

                            <textarea id="uraian-q1" onchange="handleUraianChange('${questionId}', this.value)" rows="4" 

                                class="w-full p-3 border border-gray-300 rounded-lg focus:ring-purple-500 focus:border-purple-500">${appState.assessmentAnswersUraian[questionId] || ''}</textarea>

                        </div>

                    </div>

                </div>

                ${renderNavigationButtons('Kembali', 'Lanjut ke Latihan 2')}

            `;

        };


        const renderPage5 = () => {

            let isLightOn = false;


            window.toggleSenter = () => {

                const screen = document.getElementById('senter-screen');

                const button = document.getElementById('senter-button');

                

                isLightOn = !isLightOn;


                if (isLightOn) {

                    screen.classList.remove('bg-gray-800');

                    screen.classList.add('bg-yellow-200', 'shadow-2xl', 'shadow-yellow-400/80');

                    button.textContent = 'Matikan Senter';

                    button.classList.remove('bg-green-500');

                    button.classList.add('bg-red-500');

                    openModal('Senter Aktif!', 'Simulasi Notifier: Senter berhasil dinyalakan! (Suara/Cahaya Aktif)');

                } else {

                    screen.classList.remove('bg-yellow-200', 'shadow-2xl', 'shadow-yellow-400/80');

                    screen.classList.add('bg-gray-800');

                    button.textContent = 'Nyalakan Senter';

                    button.classList.remove('bg-red-500');

                    button.classList.add('bg-green-500');

                    openModal('Senter Nonaktif', 'Simulasi Notifier: Senter dimatikan.');

                }

            };


            const quizHtml = renderQuiz(QUIZ_DATA.quiz2, appState.currentQuizAnswers, (id, ans) => {

                appState.currentQuizAnswers[id] = ans;

                saveState('currentQuizAnswers', appState.currentQuizAnswers);

                calculateTotalScore();

            }, 'quiz2-');


            return `

                <div class="p-4 sm:p-6">

                    <h2 class="text-3xl font-bold text-gray-800 mb-6 border-b-2 border-purple-200 pb-2">

                        ${renderIcon('flashlight')} 5. Latihan 2: Aplikasi "Senter Sederhana"

                    </h2>

                    <p class="text-gray-600 mb-6">Aplikasi ini menggunakan Button, Screen, dan komponen non-visible (Notifier/Sound) untuk efek senter.</p>

                    

                    <div class="grid md:grid-cols-2 gap-8">

                        <div class="bg-gray-50 p-6 rounded-xl shadow-lg flex flex-col items-center">

                            <h3 class="text-xl font-bold text-purple-700 mb-4">Simulasi Aplikasi Senter</h3>

                            

                            <div id="senter-screen" class="w-48 h-80 border-8 border-gray-900 rounded-3xl p-4 bg-gray-800 shadow-xl transition-all duration-300 flex items-center justify-center">

                                <button id="senter-button" onclick="toggleSenter()" 

                                        class="bg-green-500 text-white font-bold px-4 py-2 rounded-lg hover:bg-green-600 transition">

                                    Nyalakan Senter

                                </button>

                            </div>

                            <p class="text-sm text-gray-500 mt-2">Coba klik tombol di simulasi ponsel.</p>

                            

                            <div class="mt-6 text-center">

                                <h4 class="font-semibold text-gray-700 mb-2">Blok Logika Inti:</h4>

                                <div class="inline-flex flex-col items-start border border-indigo-300 bg-indigo-100 p-3 rounded-lg">

                                    <div class="code-block bg-yellow-600">when SenterButton.Click do</div>

                                    <div class="ml-8 code-block bg-blue-600">call Notifier1.ShowAlert notice: "Senter Aktif"</div>

                                    <div class="ml-8 code-block bg-blue-600">set Screen1.BackgroundColor to Yellow</div>

                                </div>

                            </div>

                        </div>


                        <div class="p-6 rounded-xl shadow-lg bg-white border border-gray-100">

                            <h3 class="text-xl font-bold text-gray-800 mb-4 flex items-center">${renderIcon('help-circle')} Kuis Singkat Latihan 2</h3>

                            <div id="quiz-area-5">

                                ${quizHtml}

                            </div>

                        </div>

                    </div>

                </div>

                ${renderNavigationButtons('Kembali', 'Lanjut ke Proyek Mini')}

            `;

        };


        const renderPage6 = () => {

            window.handleProjectInput = (key, value) => {

                const newProject = { ...appState.projectSubmission, [key]: value };

                saveState('projectSubmission', newProject);

            };

            

            const p = appState.projectSubmission;


            return `

                <div class="p-4 sm:p-6">

                    <h2 class="text-3xl font-bold text-gray-800 mb-6 border-b-2 border-purple-200 pb-2">

                        ${renderIcon('hard-hat')} 6. Proyek Mini: Kartu Nama Digital / Kalkulator Sederhana

                    </h2>

                    <p class="text-gray-600 mb-8">Pilih salah satu proyek dan buatlah di App Inventor. Dokumentasikan proses dan hasilnya di bawah ini.</p>

                    

                    <div class="max-w-3xl mx-auto bg-white p-8 rounded-xl shadow-2xl border border-indigo-200">

                        <div class="space-y-6">

                            <div>

                                <label class="block text-sm font-medium text-gray-700 mb-1">1. Nama Proyek Pilihan</label>

                                <input type="text" id="project-name" value="${p.name || ''}" onchange="handleProjectInput('name', this.value)" 

                                    placeholder="Contoh: Kartu Nama Digital Saya" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-purple-500 focus:border-purple-500">

                            </div>

                            <div>

                                <label class="block text-sm font-medium text-gray-700 mb-1">2. Tujuan Utama Aplikasi</label>

                                <textarea id="project-tujuan" onchange="handleProjectInput('tujuan', this.value)" rows="3" 

                                    placeholder="Jelaskan 1-2 kalimat tujuan aplikasi Anda." class="w-full p-4 border border-gray-300 rounded-lg focus:ring-purple-500 focus:border-purple-500">${p.tujuan || ''}</textarea>

                            </div>


                            <!-- Gemini AI Suggestion Button -->

                            <div class="pt-4 border-t border-gray-100">

                                <button onclick="generateProjectSuggestions()" id="generate-btn"

                                    class="w-full btn-primary bg-pink-600 text-white font-bold py-3 px-8 rounded-lg hover:bg-pink-700 transition duration-150 disabled:opacity-50">

                                    ${renderIcon('sparkles')} ✨ Sarankan Komponen & Logika

                                </button>

                            </div>

                            <!-- Gemini AI Suggestion Output -->

                            <div id="gemini-suggestions" class="mt-4 p-6 bg-pink-50 border border-pink-200 rounded-xl hidden">

                                <h4 class="text-xl font-bold text-pink-700 mb-3 flex items-center">${renderIcon('lightbulb')} Saran dari AI Asisten:</h4>

                                <div id="suggestion-output" class="text-gray-700 whitespace-pre-wrap"></div>

                            </div>



                            <div>

                                <label class="block text-sm font-medium text-gray-700 mb-1">3. Komponen Utama yang Digunakan</label>

                                <input type="text" value="${p.components || ''}" onchange="handleProjectInput('components', this.value)" 

                                    placeholder="Contoh: Label (x5), Button (x2), Image, TinyDB" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-purple-500 focus:border-purple-500">

                            </div>

                            <div>

                                <label class="block text-sm font-medium text-gray-700 mb-1">4. Link atau Deskripsi Hasil Tampilan</label>

                                <textarea onchange="handleProjectInput('imageResult', this.value)" rows="5" 

                                    placeholder="Deskripsikan tampilan akhir aplikasi Anda atau paste link ke screenshot (jika ada)." class="w-full p-4 border border-gray-300 rounded-lg focus:ring-purple-500 focus:border-purple-500">${p.imageResult || ''}</textarea>

                            </div>

                        </div>

                        

                        <div class="mt-8 text-center">

                            <button onclick="openModal('Tersimpan Otomatis', 'Jawaban Proyek Mini Anda telah disimpan sementara di perangkat Anda.')" 

                                class="btn-primary bg-indigo-600 text-white font-bold py-3 px-8 rounded-lg hover:bg-indigo-700">

                                ${renderIcon('save')} Kirim Jawaban (Tersimpan Lokal)

                            </button>

                            <p class="text-xs text-gray-500 mt-2">Data disimpan secara lokal (localStorage) dan dapat diakses guru melalui Halaman Guru.</p>

                        </div>

                    </div>

                </div>

                ${renderNavigationButtons('Kembali', 'Lanjut ke Evaluasi')}

            `;

        };


        const renderPage7 = () => {

            const quizPGHtml = renderQuiz(QUIZ_DATA.assessmentPG, appState.assessmentAnswersPG, (id, ans) => {

                appState.assessmentAnswersPG[id] = ans;

                saveState('assessmentAnswersPG', appState.assessmentAnswersPG);

                calculateTotalScore();

            }, 'assessmentPG-');

            

            window.handleUraianChange = (id, value) => {

                const newAnswers = { ...appState.assessmentAnswersUraian, [id]: value };

                saveState('assessmentAnswersUraian', newAnswers);

            };


            const uraianHtml = QUIZ_DATA.assessmentUraian.map(q => `

                <div class="mb-6 p-4 bg-white rounded-lg shadow-inner border border-gray-100">

                    <p class="font-medium text-gray-700 mb-3">${q.id}. ${q.q}</p>

                    <textarea id="uraian-ass-${q.id}" onchange="handleUraianChange('ass-${q.id}', this.value)" rows="4" 

                        class="w-full p-3 border border-gray-300 rounded-lg focus:ring-purple-500 focus:border-purple-500">${appState.assessmentAnswersUraian[`ass-${q.id}`] || ''}</textarea>

                </div>

            `).join('');


            const score = calculateTotalScore();

            const scorePercentage = (score.total / score.maxTotal) * 100;

            const category = getCategory(scorePercentage);


            return `

                <div class="p-4 sm:p-6">

                    <h2 class="text-3xl font-bold text-gray-800 mb-6 border-b-2 border-red-400 pb-2">

                        ${renderIcon('clipboard-check')} 7. Evaluasi (Asesmen Akhir)

                    </h2>

                    <p class="text-gray-600 mb-8">Jawab semua soal di bawah. Nilai Pilihan Ganda akan otomatis dihitung.</p>


                    <!-- Skor Saat Ini -->

                    <div class="bg-purple-100 p-5 rounded-xl mb-8 border-l-4 border-purple-600 shadow-md">

                        <p class="text-xl font-bold text-purple-800">Skor Pilihan Ganda Saat Ini:</p>

                        <p class="text-4xl font-extrabold text-purple-600">${score.assessmentPg} / ${score.maxAssessmentPg}</p>

                        <p class="text-lg font-medium text-gray-700 mt-2">Skor Total PG (Kuis + Asesmen): ${score.pg + score.assessmentPg} / ${score.maxTotal}</p>

                        <p class="text-lg font-medium ${category.color}">Kategori Performansi PG: ${category.text}</p>

                    </div>


                    <h3 class="text-2xl font-bold text-gray-800 mb-4 flex items-center">${renderIcon('list-checks')} A. Soal Pilihan Ganda (10 Soal)</h3>

                    <div id="quiz-area-7" class="mb-10">

                        ${quizPGHtml}

                    </div>


                    <h3 class="text-2xl font-bold text-gray-800 mb-4 flex items-center">${renderIcon('file-text')} B. Soal Uraian (3 Soal)</h3>

                    <p class="text-red-600 mb-4">Jawaban uraian akan dinilai oleh Guru.</p>

                    <div id="uraian-area-7">

                        ${uraianHtml}

                    </div>


                </div>

                ${renderNavigationButtons('Kembali', 'Lanjut ke Refleksi')}

            `;

        };


        const renderPage8 = () => {

            window.handleReflectionInput = (key, value) => {

                const newReflection = { ...appState.reflection, [key]: value };

                saveState('reflection', newReflection);

            };

            

            const r = appState.reflection;


            return `

                <div class="p-4 sm:p-6">

                    <h2 class="text-3xl font-bold text-gray-800 mb-6 border-b-2 border-blue-400 pb-2">

                        ${renderIcon('feather')} 8. Refleksi Siswa

                    </h2>

                    <p class="text-gray-600 mb-8">Berikan pendapat jujur Anda mengenai pengalaman belajar App Inventor ini.</p>

                    

                    <div class="max-w-3xl mx-auto bg-white p-8 rounded-xl shadow-2xl border border-blue-200">

                        <div class="space-y-6">

                            <div>

                                <label class="block text-sm font-medium text-gray-700 mb-1">1. Apa bagian paling menarik dari belajar App Inventor?</label>

                                <textarea onchange="handleReflectionInput('menarik', this.value)" rows="3" 

                                    class="w-full p-3 border border-gray-300 rounded-lg focus:ring-blue-500 focus:border-blue-500">${r.menarik || ''}</textarea>

                            </div>

                            <div>

                                <label class="block text-sm font-medium text-gray-700 mb-1">2. Apa kesulitan utama yang kamu alami saat menyusun blok kode?</label>

                                <textarea onchange="handleReflectionInput('kesulitan', this.value)" rows="3" 

                                    class="w-full p-3 border border-gray-300 rounded-lg focus:ring-blue-500 focus:border-blue-500">${r.kesulitan || ''}</textarea>

                            </div>

                            <div>

                                <label class="block text-sm font-medium text-gray-700 mb-1">3. Apa ide aplikasimu berikutnya yang ingin kamu buat dengan App Inventor?</label>

                                <textarea onchange="handleReflectionInput('ide', this.value)" rows="3" 

                                    class="w-full p-3 border border-gray-300 rounded-lg focus:ring-blue-500 focus:border-blue-500">${r.ide || ''}</textarea>

                            </div>

                        </div>

                        

                        <div class="mt-8 text-center">

                            <button onclick="openModal('Tersimpan Otomatis', 'Jawaban Refleksi Anda telah disimpan.')" 

                                class="btn-primary bg-green-600 text-white font-bold py-3 px-8 rounded-lg hover:bg-green-700">

                                ${renderIcon('save')} Simpan Refleksi

                            </button>

                        </div>

                    </div>

                </div>

                ${renderNavigationButtons('Kembali', 'Lanjut ke Halaman Guru')}

            `;

        };

        

        // --- QUIZ HELPER ---

        const renderQuiz = (questions, currentAnswers, handler, namePrefix) => {

            return questions.map(q => `

                <div class="mb-6 p-5 bg-gray-50 rounded-xl shadow-md border border-gray-100">

                    <p class="font-bold text-gray-800 mb-3">${q.id}. ${q.q}</p>

                    <div class="space-y-2">

                        ${q.opts.map((opt, i) => `

                            <label class="flex items-center cursor-pointer p-2 rounded-lg hover:bg-gray-100 transition 

                                ${currentAnswers[q.id] === i ? 'bg-purple-100 border-l-4 border-purple-500' : ''}">

                                <input type="radio" name="${namePrefix}${q.id}" value="${i}" 

                                    ${currentAnswers[q.id] === i ? 'checked' : ''} 

                                    onclick="(${handler.toString().replace(/"/g, "'")})(${q.id}, ${i})"

                                    class="w-4 h-4 text-purple-600 focus:ring-purple-500">

                                <span class="ml-3 text-gray-700">${String.fromCharCode(65 + i)}. ${opt}</span>

                            </label>

                        `).join('')}

                    </div>

                </div>

            `).join('');

        };



        // --- PAGE 9: TEACHER DASHBOARD ---


        const renderPage9 = () => {

            if (!appState.isLoggedInAsTeacher) {

                return renderTeacherLogin();

            } else {

                return renderTeacherDashboardContent();

            }

        };


        const renderTeacherLogin = () => {

            window.attemptTeacherLogin = () => {

                const user = document.getElementById('teacher-user').value;

                const pass = document.getElementById('teacher-pass').value;


                if (user === TEACHER_USER && pass === TEACHER_PASS) {

                    appState.isLoggedInAsTeacher = true;

                    saveState('isLoggedInAsTeacher', true);

                    renderPage();

                } else {

                    openModal('Akses Ditolak', 'Username atau Password Guru salah.');

                }

            };

            

            return `

                <div class="max-w-md mx-auto p-8 bg-white rounded-xl shadow-2xl mt-12 border-t-4 border-red-500">

                    <h2 class="text-3xl font-bold text-red-600 mb-6 text-center">${renderIcon('lock')} Halaman Guru / Analisis Hasil</h2>

                    <p class="text-gray-600 mb-4 text-center">Silakan masuk untuk melihat rekap data siswa.</p>

                    <div class="space-y-4">

                        <div>

                            <label for="teacher-user" class="block text-sm font-medium text-gray-700">Username</label>

                            <input type="text" id="teacher-user" value="guru" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-red-500 focus:border-red-500">

                        </div>

                        <div>

                            <label for="teacher-pass" class="block text-sm font-medium text-gray-700">Password</label>

                            <input type="password" id="teacher-pass" value="admin" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-red-500 focus:border-red-500">

                        </div>

                    </div>

                    <button onclick="attemptTeacherLogin()" class="w-full mt-6 bg-red-600 hover:bg-red-700 text-white font-semibold py-3 rounded-lg transition duration-150">

                        ${renderIcon('log-in')} Masuk

                    </button>

                    <button onclick="navigate(8)" class="w-full mt-4 text-gray-600 hover:text-red-500 transition">

                        ${renderIcon('arrow-left', 'w-4 h-4 inline mr-1')} Kembali ke Refleksi

                    </button>

                </div>

            `;

        };


        const renderTeacherDashboardContent = () => {

            // Logika Mengambil Semua Data Siswa dari LocalStorage

            const allStudentsData = [];

            for (let i = 0; i < localStorage.length; i++) {

                const key = localStorage.key(i);

                if (key.startsWith('score_')) {

                    const studentId = key.split('_')[1];

                    const studentName = localStorage.getItem(`userName_${studentId}`) || 'Siswa Anonim';

                    

                    // Menggunakan safeJSONLoad

                    const scoreObj = safeJSONLoad(`score_${studentId}`);

                    const project = safeJSONLoad(`projectSubmission_${studentId}`, {});

                    const reflection = safeJSONLoad(`reflection_${studentId}`, {});


                    // Memastikan data skor memiliki nilai default jika parsing gagal

                    const totalScore = (scoreObj.pg || 0) + (scoreObj.assessmentPg || 0);

                    const maxTotal = (scoreObj.maxTotal || 0) === 0 ? 1 : (scoreObj.maxTotal || 1); // Avoid division by zero

                    const scorePercentage = (totalScore / maxTotal) * 100;

                    const category = getCategory(scorePercentage);


                    allStudentsData.push({

                        id: studentId,

                        name: studentName,

                        score: totalScore,

                        maxScore: maxTotal,

                        pgScore: scoreObj.pg || 0,

                        assessmentPgScore: scoreObj.assessmentPg || 0,

                        percentage: scorePercentage,

                        category: category.text,

                        categoryColor: category.color,

                        project: project,

                        reflection: reflection

                    });

                }

            }


            // Tambahkan data siswa yang sedang login jika belum ada (hanya skor PG yang dihitung)

            if (!allStudentsData.some(d => d.id === appState.studentId)) {

                const score = calculateTotalScore();

                const totalScore = score.pg + score.assessmentPg;

                const maxTotal = score.maxTotal;

                const scorePercentage = (totalScore / maxTotal) * 100;

                const category = getCategory(scorePercentage);

                

                allStudentsData.push({

                    id: appState.studentId,

                    name: appState.userName || 'Anda (Siswa)',

                    score: totalScore,

                    maxScore: maxTotal,

                    pgScore: score.pg,

                    assessmentPgScore: score.assessmentPg,

                    percentage: scorePercentage,

                    category: category.text,

                    categoryColor: category.color,

                    project: appState.projectSubmission,

                    reflection: appState.reflection

                });

            }



            // --- Sorting Logic (Sort by percentage descending by default) ---

            allStudentsData.sort((a, b) => b.percentage - a.percentage);


            const tableRows = allStudentsData.map((data, index) => `

                <tr class="hover:bg-gray-50 transition duration-150">

                    <td class="px-4 py-3 text-sm font-medium text-gray-900">${index + 1}</td>

                    <td class="px-4 py-3 text-sm font-medium text-purple-700">${data.name}</td>

                    <td class="px-4 py-3 text-sm text-gray-500 font-mono">${data.id.substring(0, 4)}...</td>

                    <td class="px-4 py-3 text-center text-sm font-semibold">${data.score} / ${data.maxScore}</td>

                    <td class="px-4 py-3 text-center text-sm font-semibold">${data.percentage.toFixed(1)}%</td>

                    <td class="px-4 py-3 text-center text-sm ${data.categoryColor} font-bold">${data.category}</td>

                    <td class="px-4 py-3 text-center">

                        <button onclick="showDetail('${data.id}')" class="text-blue-600 hover:text-blue-800 text-sm font-medium">${renderIcon('eye', 'w-4 h-4 inline mr-1')} Detail</button>

                    </td>

                </tr>

            `).join('');


            window.showDetail = (studentId) => {

                const data = allStudentsData.find(d => d.id === studentId);

                if (!data) return;

                

                // Mengambil jawaban uraian spesifik siswa dengan safeJSONLoad

                const uraianAnswers = safeJSONLoad(`assessmentAnswersUraian_${studentId}`, {});


                const detailMessage = `

                    <div class="max-h-[70vh] overflow-y-auto">

                        <h4 class="text-xl font-bold text-purple-700 mb-3">${data.name} (${data.percentage.toFixed(1)}%)</h4>

                        <div class="space-y-4">

                            <!-- Skor -->

                            <div class="p-3 bg-purple-50 rounded-lg border border-purple-200">

                                <p class="font-semibold text-gray-800">Skor Total PG (Otomatis): ${data.score} / ${data.maxScore}</p>

                                <p class="text-sm text-gray-600">Kuis: ${data.pgScore}, Asesmen: ${data.assessmentPgScore}</p>

                                <p class="font-bold ${data.categoryColor} mt-1">Kategori: ${data.category}</p>

                            </div>

                            

                            <!-- Proyek -->

                            <div class="p-3 bg-indigo-50 rounded-lg border border-indigo-200">

                                <p class="font-bold text-gray-800">Proyek Mini: ${data.project.name || 'Belum Diisi'}</p>

                                <p class="text-sm mt-1">Tujuan: ${data.project.tujuan || 'N/A'}</p>

                                <p class="text-sm">Komponen: ${data.project.components || 'N/A'}</p>

                                <p class="text-sm">Hasil: ${data.project.imageResult || 'N/A'}</p>

                            </div>


                            <!-- Refleksi -->

                            <div class="p-3 bg-blue-50 rounded-lg border border-blue-200">

                                <p class="font-bold text-gray-800">Refleksi Siswa:</p>

                                <p class="text-sm mt-1"><span class="font-semibold">Menarik:</span> ${data.reflection.menarik || 'N/A'}</p>

                                <p class="text-sm"><span class="font-semibold">Kesulitan:</span> ${data.reflection.kesulitan || 'N/A'}</p>

                                <p class="text-sm"><span class="font-semibold">Ide:</span> ${data.reflection.ide || 'N/A'}</p>

                            </div>


                            <!-- Uraian -->

                            <div class="p-3 bg-red-50 rounded-lg border border-red-200">

                                <p class="font-bold text-red-800">Jawaban Uraian (Perlu Penilaian Manual):</p>

                                ${Object.keys(uraianAnswers).map(key => {

                                    const qId = key.split('-')[1];

                                    const questionText = QUIZ_DATA.assessmentUraian.find(q => `ass-${q.id}` === key)?.q || `Pertanyaan ID ${qId}`;

                                    return `<p class="text-sm mt-1"><span class="font-semibold">${qId}. ${questionText}:</span> ${uraianAnswers[key]}</p>`;

                                }).join('') || '<p class="text-sm text-gray-600">Belum ada jawaban uraian.</p>'}

                            </div>

                        </div>

                    </div>

                `;

                openModal(`Detail Hasil Siswa: ${data.name}`, detailMessage);

            };

            

            window.handleLogout = () => {

                appState.isLoggedInAsTeacher = false;

                saveState('isLoggedInAsTeacher', false);

                renderPage();

            };


            window.exportResultsToPdf = () => {

                const element = document.getElementById('results-table-container');

                const studentName = appState.userName; // Nama siswa yang login

                const options = {

                    margin: 10,

                    filename: `Laporan_Kelas_AppInventor_${new Date().toISOString().substring(0, 10)}.pdf`,

                    image: { type: 'jpeg', quality: 0.98 },

                    html2canvas: { scale: 2 },

                    jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait' }

                };


                html2pdf().set(options).from(element).save();

                openModal('Eskpor PDF', 'Laporan hasil kelas berhasil diunduh.');

            };


            return `

                <div class="p-4 sm:p-6" id="teacher-dashboard-main">

                    <h2 class="text-3xl font-bold text-gray-800 mb-6 border-b-2 border-red-400 pb-2">

                        ${renderIcon('bar-chart-2')} 9. Analisis Hasil Pembelajaran

                    </h2>

                    <p class="text-gray-600 mb-6">Anda masuk sebagai Guru. Total ${allStudentsData.length} data siswa ditemukan.</p>


                    <div class="flex justify-between items-center mb-6">

                        <button onclick="handleLogout()" class="flex items-center text-red-600 hover:text-red-700 font-semibold transition">

                            ${renderIcon('log-out', 'w-5 h-5 mr-1')} Logout

                        </button>

                        <button onclick="exportResultsToPdf()" class="btn-primary bg-yellow-600 text-white font-semibold py-2 px-4 rounded-lg hover:bg-yellow-700">

                            ${renderIcon('printer', 'w-5 h-5 mr-1')} Cetak Hasil / Ekspor PDF

                        </button>

                    </div>


                    <div id="results-table-container">

                        <h3 class="text-2xl font-bold text-gray-800 mb-4">Tabel Rekapitulasi Hasil Siswa (PG Score)</h3>

                        <div class="overflow-x-auto rounded-xl shadow-lg border border-gray-200">

                            <table class="min-w-full divide-y divide-gray-200">

                                <thead class="bg-gray-800 text-white">

                                    <tr>

                                        <th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider">No.</th>

                                        <th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider">Nama Siswa</th>

                                        <th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider">ID Siswa</th>

                                        <th class="px-4 py-3 text-center text-xs font-medium uppercase tracking-wider">Nilai PG (Tot)</th>

                                        <th class="px-4 py-3 text-center text-xs font-medium uppercase tracking-wider">Persentase</th>

                                        <th class="px-4 py-3 text-center text-xs font-medium uppercase tracking-wider">Kategori</th>

                                        <th class="px-4 py-3 text-center text-xs font-medium uppercase tracking-wider">Aksi</th>

                                    </tr>

                                </thead>

                                <tbody class="bg-white divide-y divide-gray-200">

                                    ${tableRows}

                                </tbody>

                            </table>

                        </div>

                    </div>

                    

                    <div class="mt-8">

                        <h3 class="text-2xl font-bold text-gray-800 mb-4">Grafik Batang Performa Kelas (Visualisasi Sederhana)</h3>

                        <!-- Grafik Batang Sederhana (Representasi CSS) -->

                        <div class="bg-gray-100 p-4 rounded-lg shadow-inner">

                            ${allStudentsData.map(data => `

                                <div class="mb-3">

                                    <div class="text-sm font-medium text-gray-700 mb-1 flex justify-between">

                                        <span>${data.name}</span>

                                        <span class="${data.categoryColor}">${data.percentage.toFixed(1)}%</span>

                                    </div>

                                    <div class="w-full bg-gray-300 rounded-full h-4">

                                        <div class="h-4 rounded-full ${data.percentage >= 75 ? 'bg-green-500' : data.percentage >= 60 ? 'bg-yellow-500' : 'bg-red-500'} transition-all duration-700" style="width: ${data.percentage}%;"></div>

                                    </div>

                                </div>

                            `).join('')}

                        </div>

                    </div>

                </div>

                ${renderNavigationButtons('Kembali', 'Lanjut ke Penutup')}

            `;

        };


        // --- PAGE 10: PENUTUP ---


        const renderPage10 = () => {

            const score = calculateTotalScore();

            const scorePercentage = (score.total / score.maxTotal) * 100;

            const category = getCategory(scorePercentage);


            window.generateCertificate = () => {

                const element = document.createElement('div');

                element.style.width = '297mm'; // A4 width landscape

                element.style.height = '210mm'; // A4 height landscape

                element.style.padding = '20mm';

                element.style.border = '10px solid #6d28d9'; // Purple border

                element.style.backgroundColor = '#fff';

                element.style.textAlign = 'center';

                element.style.fontFamily = 'Nunito Sans, sans-serif';


                element.innerHTML = `

                    <h1 style="color:#6d28d9; font-size: 36pt; font-weight: 900; margin-top: 30pt;">SERTIFIKAT PENGHARGAAN</h1>

                    <p style="font-size: 14pt; margin-top: 5pt; color: #4b5563;">Diberikan kepada:</p>

                    <h2 style="color:#374151; font-size: 30pt; font-weight: 700; border-bottom: 3px solid #6d28d9; display: inline-block; padding-bottom: 5px;">${appState.userName || 'Nama Siswa'}</h2>

                    <p style="font-size: 16pt; margin-top: 20pt; color: #4b5563;">Atas keberhasilannya menyelesaikan pembelajaran App Inventor dengan predikat:</p>

                    <p style="font-size: 48pt; font-weight: 900; color: ${category.color.replace('text-', '#').substring(0, 7)}; margin: 20pt 0;">${category.text.toUpperCase()}</p>

                    <p style="font-size: 12pt; color: #4b5563;">Skor Pilihan Ganda Total: ${score.total} dari ${score.maxTotal} (${scorePercentage.toFixed(1)}%)</p>

                    <div style="position: absolute; bottom: 30pt; width: 80%; left: 10%;">

                        <p style="font-size: 12pt; border-top: 1px solid #ccc; padding-top: 10pt;">Kepala Sekolah / Guru Pembimbing</p>

                        <p style="font-size: 12pt; margin-top: 5pt;">Joko Purnomo, S.Kom</p>

                    </div>

                `;


                const options = {

                    margin: 5,

                    filename: `Sertifikat_${appState.userName.replace(/\s/g, '_')}.pdf`,

                    image: { type: 'jpeg', quality: 0.98 },

                    html2canvas: { scale: 2, logging: false, letterRendering: true },

                    jsPDF: { unit: 'mm', format: 'landscape', orientation: 'landscape' }

                };


                html2pdf().set(options).from(element).save();

            };


            return `

                <div class="text-center py-16 bg-gradient-to-br from-purple-50 to-white rounded-2xl shadow-lg">

                    <h2 class="text-4xl font-extrabold text-gray-800 mb-4">

                        ${renderIcon('party-popper', 'w-10 h-10 inline text-yellow-500')} Selamat!

                    </h2>

                    <h3 class="text-2xl font-medium text-purple-600 mb-6">

                        Kamu sudah menyelesaikan seluruh rangkaian pembelajaran App Inventor!

                    </h3>


                    <div class="bg-white p-8 rounded-xl shadow-xl inline-block border-4 border-purple-500 mb-8">

                        <p class="text-xl font-bold text-gray-700">Skor Pilihan Ganda Anda:</p>

                        <p class="text-6xl font-extrabold ${category.color} my-3">${score.total} / ${score.maxTotal}</p>

                        <p class="text-lg font-bold ${category.color}">Predikat: ${category.text}</p>

                    </div>


                    <p class="text-gray-600 max-w-xl mx-auto mb-10">

                        Terus kembangkan ide aplikasimu. Dunia digital menunggumu!

                    </p>


                    <div class="space-x-4">

                        <button onclick="navigate(1)" class="btn-primary bg-gray-500 text-white font-bold py-3 px-8 rounded-lg hover:bg-gray-600">

                            ${renderIcon('home')} Kembali ke Awal

                        </button>

                        <button onclick="generateCertificate()" class="btn-primary bg-yellow-500 text-white font-bold py-3 px-8 rounded-lg hover:bg-yellow-600">

                            ${renderIcon('download')} Unduh Sertifikat

                        </button>

                    </div>

                </div>

            `;

        };


        // --- MAIN RENDER FUNCTION ---


        const pageRenderers = {

            1: renderPage1,

            2: renderPage2,

            3: renderPage3,

            4: renderPage4,

            5: renderPage5,

            6: renderPage6,

            7: renderPage7,

            8: renderPage8,

            9: renderPage9,

            10: renderPage10,

        };


        window.renderPage = () => {

            const appElement = document.getElementById('app');

            const renderer = pageRenderers[appState.currentPage];


            if (renderer) {

                appElement.innerHTML = renderer();

            } else {

                appElement.innerHTML = `<div class="text-center py-20 text-red-500">Halaman tidak ditemukan.</div>`;

            }


            // Inisialisasi Lucide Icons

            lucide.createIcons();

            // Scroll to top on new page render

            window.scrollTo(0, 0); 

        };


        // --- INITIALIZATION ---

        window.onload = () => {

            loadState();

            renderPage();

            

            // Simpan status autentikasi guru terpisah untuk menghindari reset

            const storedLogin = localStorage.getItem('isLoggedInAsTeacher');

            if (storedLogin) {

                appState.isLoggedInAsTeacher = JSON.parse(storedLogin);

            }

        };


        // Handle browser back/forward buttons (hash change)

        window.onhashchange = () => {

            const hashPage = parseInt(window.location.hash.substring(1));

            if (!isNaN(hashPage) && hashPage !== appState.currentPage) {

                appState.currentPage = hashPage;

                renderPage();

            }

        };


    </script>

</body>

</html>


Tidak ada komentar:

Posting Komentar