Selasa, 06 Januari 2026

APPS Pengaduan Layanan Sekolah

 



38691924

0.55 38
4.35 69
7.28 19
11.06 24



Code.gs

// ===================================================================
// 1. KONFIGURASI APLIKASI
// ===================================================================

// Ganti dengan ID Spreadsheet Anda
const SHEET_ID = 'UBAH DENGAN SPREADSHEET ID'; 
const SHEET_NAME = 'Pengaduan';
const SHEET_CONFIG = 'Kategori'; // Sheet untuk simpan Kategori & Password Admin

// Ganti dengan ID Folder Google Drive (Pastikan folder ini "Anyone with link" -> Viewer)
const FOLDER_ID = 'UBAH DENGAN FOLDER ID';

// Ganti dengan Email Admin Sekolah (Untuk menerima notifikasi laporan masuk)
const ADMIN_EMAIL = 'UBAH DENGAN EMAIL';

// ===================================================================
// 2. FUNGSI UTAMA (DO GET)
// ===================================================================

function doGet() {
  return HtmlService.createHtmlOutputFromFile('index')
    .setTitle('Sistem Pengaduan Masyarakat Sekolah')
    .setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL)
    .addMetaTag("viewport", "width=device-width, initial-scale=1.0");
}

// ===================================================================
// 3. FUNGSI DATABASE & HELPER
// ===================================================================

function setupInitialData() {
  const ss = SpreadsheetApp.openById(SHEET_ID);
  let sheet = ss.getSheetByName(SHEET_NAME);
  
  if (!sheet) {
    sheet = ss.insertSheet(SHEET_NAME);
    // Header Kolom (Pastikan urutan ini jangan diubah)
    sheet.appendRow([
      'ID', 'Tanggal', 'Nama', 'Nomor HP', 'Kategori', 
      'Isi Pengaduan', 'Anonim', 'Status', 'Foto Bukti', 
      'Tanggapan Admin', 'Email Pelapor' 
    ]);
    sheet.getRange(1, 1, 1, 11).setFontWeight('bold');
    sheet.setFrozenRows(1);
  }
  return sheet;
}

function getConfigSheet() {
  const ss = SpreadsheetApp.openById(SHEET_ID);
  let sheet = ss.getSheetByName(SHEET_CONFIG);
  
  if (!sheet) {
    sheet = ss.insertSheet(SHEET_CONFIG);
    // Setup Default Config
    sheet.getRange('A1').setValue('Daftar Kategori').setFontWeight('bold');
    sheet.getRange('B1').setValue('Password Admin').setFontWeight('bold');
    // Default Data
    const defaultKategori = [['Sarana Prasarana'], ['Akademik'], ['Kesiswaan'], ['Layanan Publik'], ['Bullying/Kekerasan']];
    sheet.getRange(2, 1, defaultKategori.length, 1).setValues(defaultKategori);
    sheet.getRange('B2').setValue('admin123'); // Password Default
  }
  return sheet;
}

function generateId() {
  // Format: ADU-TIMESTAMP-ACAK (Contoh: ADU-1736-XYZ)
  const randomStr = Math.random().toString(36).substring(2, 5).toUpperCase();
  const timestamp = Date.now().toString().substring(6, 10); 
  return 'ADU-' + timestamp + randomStr;
}

function formatTanggal(dateObj) {
  if (!dateObj) return '-';
  // Format: DD/MM/YYYY HH:mm
  return Utilities.formatDate(new Date(dateObj), "Asia/Jakarta", "dd/MM/yyyy HH:mm");
}

function kirimEmail(to, subject, htmlBody) {
  try {
    MailApp.sendEmail({
      to: to,
      subject: subject,
      htmlBody: htmlBody,
      name: "Sistem Pengaduan Sekolah"
    });
  } catch (e) {
    console.log("Gagal kirim email: " + e.toString());
  }
}

// ===================================================================
// 4. FUNGSI PUBLIK (FORM & LIST)
// ===================================================================

function getDaftarKategori() {
  const sheet = getConfigSheet();
  const lastRow = sheet.getLastRow();
  if (lastRow < 2) return [];
  const data = sheet.getRange(2, 1, lastRow - 1, 1).getValues().flat();
  return data.filter(String);
}

function simpanPengaduan(data) {
  try {
    // REVISI: Menggunakan setupInitialData() menggantikan getSheet()
    const sheet = setupInitialData();
    const id = generateId(); // Membuat ID Unik
    const tanggal = new Date();
    let fotoUrl = '-';

    // ==========================================
    // 1. PROSES UPLOAD FOTO KE GOOGLE DRIVE
    // ==========================================
    if (data.fileData && data.fileName && data.mimeType) {
      try {
        // Decode data gambar dari Base64
        const decoded = Utilities.base64Decode(data.fileData);
        const blob = Utilities.newBlob(decoded, data.mimeType, "Foto_" + id);
        
        // Simpan ke Folder Drive
        const folder = DriveApp.getFolderById(FOLDER_ID);
        const file = folder.createFile(blob);
        
        // Atur izin file agar bisa dilihat publik (Anyone with link -> Viewer)
        file.setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.VIEW);

        // Menggunakan domain lh3.googleusercontent.com agar gambar bisa tampil di tag <img>
        fotoUrl = "https://lh3.googleusercontent.com/d/" + file.getId();

      } catch (e) {
        return { success: false, message: 'Gagal upload gambar (Cek ID Folder): ' + e.toString() };
      }
    }
    
    // ==========================================
    // 2. SIMPAN DATA KE GOOGLE SHEET
    // ==========================================
    sheet.appendRow([
      id,                                           // Kolom A: ID
      tanggal,                                      // Kolom B: Tanggal
      data.anonim === 'true' ? 'Anonim' : data.nama,// Kolom C: Nama
      data.anonim === 'true' ? '-' : data.nomorHp,  // Kolom D: No HP
      data.kategori,                                // Kolom E: Kategori
      data.isiPengaduan,                            // Kolom F: Isi
      data.anonim,                                  // Kolom G: Status Anonim
      'Belum Diproses',                             // Kolom H: Status Awal
      fotoUrl,                                      // Kolom I: Link Foto
      '',                                           // Kolom J: Tanggapan (Kosong dulu)
      data.email || '-'                             // Kolom K: Email Pelapor
    ]);

    // ==========================================
    // 3. KIRIM NOTIFIKASI EMAIL
    // ==========================================
    try {
      // A. Kirim Notifikasi ke Admin Sekolah
      const adminSubject = `[Laporan Baru] ${data.kategori} - ${id}`;
      const adminBody = `
        <h3>Laporan Baru Masuk</h3>
        <p><b>ID Tiket:</b> ${id}</p>
        <p><b>Kategori:</b> ${data.kategori}</p>
        <p><b>Pelapor:</b> ${data.anonim === 'true' ? 'Anonim' : data.nama}</p>
        <p><b>Isi Laporan:</b><br/>${data.isiPengaduan}</p>
        <p><a href="${fotoUrl}" target="_blank">Lihat Bukti Foto</a></p>
        <hr/>
        <p><i>Silakan login ke dashboard admin untuk menindaklanjuti.</i></p>
      `;
      kirimEmail(ADMIN_EMAIL, adminSubject, adminBody);

      // B. Kirim Auto-Reply ke Pelapor (Jika email diisi valid)
      if (data.email && data.email.includes('@')) {
         const pelaporSubject = `[Tanda Terima] Laporan Pengaduan ID: ${id}`;
         const pelaporBody = `
           <div style="font-family: Arial, sans-serif; padding: 20px; border: 1px solid #e5e7eb; border-radius: 8px; max-width: 600px;">
             <h2 style="color: #1e40af; margin-top: 0;">Laporan Diterima</h2>
             <p>Halo, laporan pengaduan Anda telah berhasil masuk ke sistem kami.</p>
             
             <div style="background-color: #f3f4f6; padding: 15px; border-radius: 6px; margin: 20px 0; border-left: 4px solid #3b82f6;">
               <p style="margin: 0; font-size: 0.9em; color: #4b5563;">ID Tiket Anda:</p>
               <h1 style="margin: 5px 0; color: #111827; letter-spacing: 1px;">${id}</h1>
             </div>

             <table style="width: 100%; border-collapse: collapse; font-size: 0.9em; margin-bottom: 20px;">
               <tr><td style="padding: 5px 0; color: #6b7280; width: 100px;">Tanggal</td><td>: ${formatTanggal(tanggal)}</td></tr>
               <tr><td style="padding: 5px 0; color: #6b7280;">Kategori</td><td>: ${data.kategori}</td></tr>
             </table>

             <p style="font-size: 0.9em; color: #b45309; background-color: #fffbeb; padding: 10px; border-radius: 4px;">
               <strong>PENTING:</strong> Simpan ID Tiket di atas. Anda dapat menggunakannya untuk melacak status/respon laporan melalui menu <b>Tracking</b> di website.
             </p>
             
             <hr style="border: 0; border-top: 1px solid #eee; margin: 20px 0;">
             <p style="font-size: 0.8em; color: #9ca3af; text-align: center;">Email ini dikirim otomatis oleh Sistem Pengaduan Sekolah.</p>
           </div>
         `;
         kirimEmail(data.email, pelaporSubject, pelaporBody);
      }
    } catch (emailError) {
      console.log("Gagal kirim email: " + emailError.toString());
    }

    // ==========================================
    // 4. KEMBALIKAN RESPONS SUKSES + ID
    // ==========================================
    return { 
      success: true, 
      message: 'Pengaduan berhasil disimpan!', 
      id: id 
    };

  } catch (error) {
    return { success: false, message: 'Error Server: ' + error.toString() };
  }
}

function getPengaduanPublik() {
  try {
    // REVISI: Menggunakan setupInitialData() menggantikan getSheet()
    const sheet = setupInitialData();
    if (sheet.getLastRow() <= 1) return [];
    
    const data = sheet.getDataRange().getValues();
    const result = [];
    
    // Loop dari baris 2 (Data)
    for (let i = 1; i < data.length; i++) {
      const row = data[i];
      // Ambil hanya data yang valid
      if(row[0] && row[5]) {
         result.push({
            id: row[0], // Untuk key
            tanggal: formatTanggal(row[1]),
            kategori: row[4],
            ringkasan: row[5].length > 100 ? row[5].substring(0, 100) + '...' : row[5],
            status: row[7],
            fotoUrl: row[8],
            tanggapan: row[9]
         });
      }
    }
    return result.reverse(); // Tampilkan yang terbaru di atas
  } catch (e) {
    return [];
  }
}

// ===================================================================
// 5. FUNGSI TRACKING (Cek Status)
// ===================================================================

function cariStatusPengaduan(idTiket) {
  try {
    // REVISI: Menggunakan setupInitialData() menggantikan getSheet()
    const sheet = setupInitialData();
    const data = sheet.getDataRange().getValues();
    const searchId = idTiket.toString().trim().toUpperCase();
    
    for (let i = 1; i < data.length; i++) {
      // Kolom 0 adalah ID
      if (data[i][0].toString().trim().toUpperCase() === searchId) {
        const row = data[i];
        return {
          success: true,
          data: {
            id: row[0],
            tanggal: formatTanggal(row[1]),
            nama: row[6] === 'true' ? 'Anonim' : row[2],
            kategori: row[4],
            isi: row[5],
            status: row[7],
            foto: row[8],
            tanggapan: row[9] || 'Belum ada tanggapan.'
          }
        };
      }
    }
    return { success: false, message: 'ID Tiket tidak ditemukan.' };
  } catch (error) {
    return { success: false, message: 'Error: ' + error.toString() };
  }
}

// ===================================================================
// 6. FUNGSI ADMIN
// ===================================================================

function loginAdmin(password) {
  const sheet = getConfigSheet();
  const storedPass = sheet.getRange('B2').getValue().toString();
  if (password === storedPass) {
    return { success: true };
  } else {
    return { success: false, message: 'Password salah!' };
  }
}

function getPengaduanAdmin() {
  // REVISI: Menggunakan setupInitialData() menggantikan getSheet()
  const sheet = setupInitialData();
  if (sheet.getLastRow() <= 1) return [];
  
  const data = sheet.getDataRange().getValues();
  const result = [];
  
  for (let i = 1; i < data.length; i++) {
    const row = data[i];
    result.push({
      id: row[0],
      tanggal: formatTanggal(row[1]),
      nama: row[2],
      nomorHp: row[3],
      kategori: row[4],
      isiPengaduan: row[5],
      anonim: row[6],
      status: row[7],
      fotoUrl: row[8],
      tanggapan: row[9],
      email: row[10]
    });
  }
  return result.reverse();
}

function updateStatus(id) {
  // REVISI: Menggunakan setupInitialData() menggantikan getSheet()
  const sheet = setupInitialData();
  const data = sheet.getDataRange().getValues();
  for (let i = 1; i < data.length; i++) {
    if (data[i][0] === id) {
      sheet.getRange(i + 1, 8).setValue('Sudah Diproses');
      // Kirim Notifikasi Selesai ke Email Pelapor
      const emailPelapor = data[i][10];
      if (emailPelapor && emailPelapor.includes('@')) {
         const body = `
           <h3>Laporan Selesai</h3>
           <p>Laporan Anda dengan ID <b>${id}</b> telah ditandai <b>SELESAI</b>.</p>
           <p>Terima kasih telah membantu sekolah menjadi lebih baik.</p>
         `;
         kirimEmail(emailPelapor, `[Status Update] Tiket ${id} Selesai`, body);
      }
      return { success: true };
    }
  }
  return { success: false, message: 'Data tidak ditemukan' };
}

function hapusPengaduan(id) {
  // REVISI: Menggunakan setupInitialData() menggantikan getSheet()
  const sheet = setupInitialData();
  const data = sheet.getDataRange().getValues();
  for (let i = 1; i < data.length; i++) {
    if (data[i][0] === id) {
      sheet.deleteRow(i + 1);
      return { success: true };
    }
  }
  return { success: false, message: 'Data tidak ditemukan' };
}

function simpanTanggapanAdmin(id, isiTanggapan) {
  try {
    // REVISI: Menggunakan setupInitialData() menggantikan getSheet()
    const sheet = setupInitialData();
    const data = sheet.getDataRange().getValues();
    for (let i = 1; i < data.length; i++) {
      if (data[i][0] === id) {
        // Simpan Tanggapan di Kolom J (Index 9)
        sheet.getRange(i + 1, 10).setValue(isiTanggapan);
        // Otomatis ubah status jadi Sudah Diproses
        sheet.getRange(i + 1, 8).setValue('Sudah Diproses');
        // Kirim Email Balasan ke Pelapor
        const emailPelapor = data[i][10];
        if (emailPelapor && emailPelapor.includes('@')) {
           const body = `
             <h3>Tanggapan Admin Sekolah</h3>
             <p>Laporan ID: <b>${id}</b></p>
             <div style="background-color: #f0fdf4; padding: 15px; border-left: 5px solid #16a34a;">
               <b>Respon:</b><br/>
               "${isiTanggapan}"
             </div>
           `;
           kirimEmail(emailPelapor, `[Tanggapan] Tiket ${id}`, body);
        }
        
        return { success: true };
      }
    }
    return { success: false, message: 'Data tidak ditemukan' };
  } catch (error) {
    return { success: false, message: error.toString() };
  }
}

// ===================================================================
// FITUR EKSPOR EXCEL
// ===================================================================

function generateExcelAdmin() {
  // Kita set default type 'laporan_pengaduan'
  return generateExcel('laporan_pengaduan', {});
}

function generateExcel(type, filters) {
  try {
    // 1. Membuat Spreadsheet baru sementara
    var timestamp = Utilities.formatDate(new Date(), 'Asia/Jakarta', 'yyyyMMdd_HHmmss');
    var fileName = "Export_" + type + "_" + timestamp;
    var ss = SpreadsheetApp.create(fileName);
    var sheet = ss.getActiveSheet();

    // 2. Mengambil data
    var data = getExportData(type, filters);
    var headers = [];

    // 3. Definisi Header Laporan Pengaduan
    if (type === 'laporan_pengaduan') {
      headers = [
        "No", 
        "ID Tiket", 
        "Tanggal Masuk", 
        "Nama Pelapor", 
        "Nomor HP", 
        "Email", 
        "Kategori", 
        "Isi Pengaduan", 
        "Status", 
        "Tanggapan Sekolah"
      ];
    }

    // 4. Menulis Header
    sheet.appendRow(headers);
    // Styling Header (Kuning, Bold, Border)
    var headerRange = sheet.getRange(1, 1, 1, headers.length);
    headerRange.setFontWeight('bold')
               .setBackground('#FFFF00')
               .setBorder(true, true, true, true, true, true)
               .setHorizontalAlignment('center')
               .setVerticalAlignment('middle');

    // 5. Menulis Data
    if (data && data.length > 0) {
      // Mulai tulis dari baris ke-2
      sheet.getRange(2, 1, data.length, data[0].length).setValues(data);
      // Auto Resize Kolom
      sheet.autoResizeColumns(1, headers.length);
    }
    
    // 6. Atur Izin File (Agar bisa didownload script)
    var fileId = ss.getId();
    var file = DriveApp.getFileById(fileId);
    file.setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.VIEW);

    // 7. Generate Link Download .xlsx
    var downloadUrl = "https://docs.google.com/spreadsheets/d/" + fileId + "/export?format=xlsx";

    return { 
      success: true, 
      url: downloadUrl,
      filename: fileName + ".xlsx"
    };

  } catch (e) {
    return { 
      success: false, 
      message: 'Gagal ekspor: ' + e.toString() 
    };
  }
}

function getExportData(type, filters) {
  var result = [];
  
  if (type === 'laporan_pengaduan') {
    // REVISI: Menggunakan setupInitialData() menggantikan getSheet()
    var sheet = setupInitialData();
    var lastRow = sheet.getLastRow();
    
    if (lastRow > 1) {
      var data = sheet.getRange(2, 1, lastRow - 1, 11).getValues();
      
      // Loop data untuk diformat
      for (var i = 0; i < data.length; i++) {
        var row = data[i];
        
        var no = i + 1;
        var id = row[0];
        var tanggal = formatTanggal(row[1]); 
        var nama = row[6] === 'true' ? 'Anonim' : row[2]; 
        var hp = row[6] === 'true' ? '-' : row[3];
        var email = row[10] || '-';
        var kategori = row[4];
        var isi = row[5];
        var status = row[7];
        var tanggapan = row[9] || '-';

        // Push ke array hasil (Urutan harus sama dengan headers di generateExcel)
        result.push([
          no,
          id,
          tanggal,
          nama,
          "'" + hp, // Tambah kutip satu agar HP tidak jadi angka ilmiah di Excel
          email,
          kategori,
          isi,
          status,
          tanggapan
        ]);
      }
    }
  }
  return result;
}

// ===================================================================
// FUNGSI KHUSUS: MEMPERBAIKI LINK FOTO YANG RUSAK (EXPIRED)
// ===================================================================
function perbaikiLinkLama() {
  // REVISI: Menggunakan setupInitialData() menggantikan getSheet()
  const sheet = setupInitialData();
  const data = sheet.getDataRange().getValues();
  
  // Loop dari baris ke-2 (Data)
  for (let i = 1; i < data.length; i++) {
    const urlLama = data[i][8]; // Kolom I (Index 8) adalah Foto Bukti
    
    // Cek apakah url berisi format lama "uc?export=view"
    if (urlLama && urlLama.includes("drive.google.com/uc?export=view")) {
      try {
        const idFile = urlLama.split('id=')[1];
        if (idFile) {
          const urlBaru = "https://lh3.googleusercontent.com/d/" + idFile;
          sheet.getRange(i + 1, 9).setValue(urlBaru);
          console.log(`Baris ${i + 1} diperbarui: ${urlBaru}`);
        }
      } catch (e) {
        console.log(`Gagal update baris ${i + 1}: ${e.toString()}`);
      }
    }
  }
}

Index.html

<!DOCTYPE html>
<html lang="id">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Sistem Pengaduan Masyarakat Sekolah</title>
  <script src="https://cdn.tailwindcss.com"></script>
  <script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
  <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
  
<style>
    /* Menggunakan style yang sama dengan sebelumnya */
    * { font-family: 'Inter', sans-serif; box-sizing: border-box; }
    body { background-color: #f3f4f6; color: #1f2937; }
    
    #landingPage {
      background: linear-gradient(rgba(255, 255, 255, 0.85), rgba(255, 255, 255, 0.85)),
                  url('https://alazharasysyarifsumut.sch.id/wp-content/uploads/2022/09/Gedung-Kelas-Ikhwan-scaled.jpg');
      background-size: cover; background-position: center; background-attachment: fixed; min-height: 100vh;
    }

    .pro-card { background-color: #ffffff; border: 1px solid #cbd5e1; box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1); }
    .input-wrapper { position: relative; width: 100%; }
    .input-icon { position: absolute; left: 14px; top: 50%; transform: translateY(-50%); width: 20px; height: 20px; color: #9ca3af; pointer-events: none; }
    .input-wrapper.textarea-wrapper .input-icon { top: 20px; transform: none; }
    .input-with-icon { padding-left: 46px !important; }
    .input-wrapper:focus-within .input-icon { color: #2563eb; }
    
    /* Scrollbar */
    ::-webkit-scrollbar { width: 8px; height: 8px; }
    ::-webkit-scrollbar-track { background: #f1f1f1; }
    ::-webkit-scrollbar-thumb { background: #c1c1c1; border-radius: 4px; }
    ::-webkit-scrollbar-thumb:hover { background: #a8a8a8; }
    
    /* Animations */
    .transition-all { transition: all 0.3s ease; }
    @keyframes slideInRight { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
    .notification { animation: slideInRight 0.3s ease; }
    
    /* Loading */
    .loading { border: 3px solid #e5e7eb; border-top: 3px solid #2563eb; border-radius: 50%; width: 24px; height: 24px; animation: spin 1s linear infinite; }
    @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
    
    /* Badges & Buttons */
    .badge-pending { background-color: #fef3c7; color: #92400e; border: 1px solid #fcd34d; }
    .badge-done { background-color: #d1fae5; color: #065f46; border: 1px solid #6ee7b7; }
    .btn-primary { background-color: #2563eb; color: white; }
    .btn-primary:hover { background-color: #1d4ed8; }
    .btn-success { background-color: #059669; color: white; }
    .btn-success:hover { background-color: #047857; }
    .btn-danger { background-color: #dc2626; color: white; }
    .btn-danger:hover { background-color: #b91c1c; }
    .sidebar-scroll::-webkit-scrollbar { display: none; }
    .sidebar-scroll { -ms-overflow-style: none; scrollbar-width: none; }
  </style>
</head>

<body class="bg-gray-50 text-gray-800 font-sans antialiased selection:bg-blue-100 selection:text-blue-900">
  
  <div id="landingPage">
    
    <nav class="shadow-md fixed top-0 left-0 right-0 z-50 transition-all" style="background-color: rgb(26, 25, 103);">
      <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
        <div class="flex items-center justify-between h-16">
          <div class="flex items-center">
            <div class="flex-shrink-0 flex items-center gap-3">
              <img src="https://upload.wikimedia.org/wikipedia/commons/thumb/9/9c/Logo_of_Ministry_of_Education_and_Culture_of_Republic_of_Indonesia.svg/2048px-Logo_of_Ministry_of_Education_and_Culture_of_Republic_of_Indonesia.svg.png" 
                   alt="Logo Sekolah" 
                   class="h-10 w-10 object-contain bg-white rounded-full p-1 shadow-sm">
              <div class="flex flex-col">
                <h1 class="text-white text-lg md:text-xl font-bold tracking-wide leading-tight">SMPN 1001 Bengkulu</h1>
                <span class="text-blue-200 text-xs font-medium tracking-wide">Sistem Pengaduan Masyarakat</span>
              </div>
            </div>
          </div>
          <div>
            <button onclick="showLoginModal()" class="text-white hover:bg-white hover:bg-opacity-20 px-4 py-2 rounded-lg transition-all font-medium border border-transparent hover:border-white/30 text-sm md:text-base">
              🔐 Login Admin
            </button>
          </div>
        </div>
      </div>
    </nav>

    <div class="pt-24 pb-16 px-4">
      <div class="max-w-7xl mx-auto">
        
        <div class="text-center mb-10">
          <h2 class="text-4xl md:text-5xl font-bold text-gray-900 mb-4">Sistem Pengaduan Masyarakat</h2>
          <p class="text-gray-600 text-lg max-w-2xl mx-auto font-medium">
             Sampaikan aspirasi dan keluhan Anda untuk kemajuan bersama.
          </p>

          <div class="mt-8 max-w-xl mx-auto px-4">
            <div class="bg-white p-2 rounded-full shadow-lg border border-blue-100 flex items-center hover:shadow-xl transition-shadow">
              <div class="pl-4 text-gray-400">
                <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg>
              </div>
              <input type="text" id="inputTracking" 
                     class="w-full px-4 py-3 bg-transparent border-none focus:outline-none focus:ring-0 text-gray-700 placeholder-gray-400" 
                     placeholder="Masukkan ID Pengaduan (Contoh: ADU-1736...)"
                     onkeypress="handleEnterTracking(event)">
              <button onclick="cekStatus()" id="btnTracking" 
                      class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-3 rounded-full font-bold transition-all shadow-md flex items-center gap-2 flex-shrink-0">
                <span>Lacak</span>
                <div id="loadingTracking" class="hidden w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin"></div>
              </button>
            </div>
            <p class="text-xs text-gray-500 mt-2">Simpan ID Pengaduan Anda setelah mengirim laporan untuk mengecek status.</p>
          </div>
          </div>

        <div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
          
          <div class="pro-card rounded-xl p-6 md:p-8 bg-white/95 backdrop-blur-sm border-t-4 shadow-xl" style="border-top-color: rgb(26, 25, 103);">
            <h3 class="text-2xl font-bold text-gray-800 mb-6 border-b pb-4">📝 Form Pengaduan</h3>
            <form id="formPengaduan" onsubmit="submitPengaduan(event)">
              
              <div class="mb-4">
                <label class="block text-gray-700 text-sm font-semibold mb-2">Nama Lengkap</label>
                <div class="input-wrapper group">
                  <svg class="input-icon group-hover:text-blue-500 transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"></path></svg>
                  <input type="text" id="nama" required class="input-with-icon w-full px-4 py-3 rounded-lg bg-white text-gray-900 border border-gray-300 focus:ring-2 focus:ring-blue-500 transition-all" placeholder="Masukkan nama lengkap">
                </div>
              </div>
              
              <div class="mb-4">
                <label class="block text-gray-700 text-sm font-semibold mb-2">Nomor HP (WhatsApp)</label>
                <div class="input-wrapper group">
                  <svg class="input-icon group-hover:text-blue-500 transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z"></path></svg>
                  <input type="tel" id="nomorHp" required class="input-with-icon w-full px-4 py-3 rounded-lg bg-white text-gray-900 border border-gray-300 focus:ring-2 focus:ring-blue-500 transition-all" placeholder="081234567890">
                </div>
              </div>

              <div class="mb-4">
                <label class="block text-gray-700 text-sm font-semibold mb-2">Email (Opsional)</label>
                <div class="input-wrapper group">
                  <svg class="input-icon group-hover:text-blue-500 transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"></path></svg>
                  <input type="email" id="emailPelapor" class="input-with-icon w-full px-4 py-3 rounded-lg bg-white text-gray-900 border border-gray-300 focus:ring-2 focus:ring-blue-500 transition-all" placeholder="email@contoh.com (Untuk notifikasi balasan)">
                </div>
                <p class="text-xs text-gray-500 mt-1 ml-1 flex items-center gap-1">
                  <svg class="w-3 h-3 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
                  Isi email agar mendapat salinan ID Tiket & notifikasi balasan.
                </p>
              </div>

              <div class="mb-4">
                <label class="block text-gray-700 text-sm font-semibold mb-2">Kategori</label>
                <div class="input-wrapper group">
                  <svg class="input-icon group-hover:text-blue-500 transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"></path></svg>
                  <select id="kategori" required class="input-with-icon w-full px-4 py-3 rounded-lg bg-white text-gray-900 border border-gray-300 focus:ring-2 focus:ring-blue-500 transition-all">
                    <option value="" class="text-gray-500">Pilih Kategori</option>
                  </select>
                </div>
              </div>

              <div class="mb-4">
                <label class="block text-gray-700 text-sm font-semibold mb-2">Isi Pengaduan</label>
                <div class="input-wrapper textarea-wrapper group">
                  <svg class="input-icon group-hover:text-blue-500 transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z"></path></svg>
                  <textarea id="isiPengaduan" required rows="5" class="input-with-icon w-full px-4 py-3 rounded-lg bg-white text-gray-900 border border-gray-300 focus:ring-2 focus:ring-blue-500 transition-all" placeholder="Jelaskan detail pengaduan Anda..."></textarea>
                </div>
              </div>

              <div class="mb-4">
                 <label class="block text-gray-700 text-sm font-semibold mb-2">Foto Bukti (Opsional)</label>
                 <div class="input-wrapper group">
                    <svg class="input-icon group-hover:text-blue-500 transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"></path></svg>
                    <input type="file" id="fotoBukti" accept="image/*" class="input-with-icon w-full px-4 py-3 rounded-lg bg-white border border-gray-300 text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded-full file:border-0 file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100 transition-all">
                 </div>
              </div>

              <div class="mb-6">
                <label class="flex items-center cursor-pointer select-none">
                  <input type="checkbox" id="anonim" class="w-5 h-5 rounded text-blue-600 border-gray-300 focus:ring-blue-500">
                  <span class="ml-3 text-gray-600 text-sm">Kirim sebagai Anonim (Identitas dirahasiakan di publik)</span>
                </label>
              </div>

              <button type="submit" class="w-full btn-primary font-bold py-3.5 px-6 rounded-lg shadow-lg hover:shadow-xl transform hover:-translate-y-0.5 transition-all flex justify-center items-center">
                <span id="btnSubmitText">Kirim Pengaduan</span>
                <span id="btnSubmitLoading" class="hidden"><div class="loading inline-block" style="border-top-color: white; border-right-color: rgba(255,255,255,0.2);"></div></span>
              </button>
            </form>
          </div>

          <div class="pro-card rounded-xl p-6 md:p-8 flex flex-col h-full bg-white/95 backdrop-blur-sm border-t-4 shadow-xl" style="border-top-color: rgb(26, 25, 103);">
            <div class="flex flex-wrap items-center justify-between mb-6 border-b pb-4 gap-2">
              <h3 class="text-2xl font-bold text-gray-800">📊 Pengaduan Terkini</h3>
              <div class="flex items-center gap-2">
                <select id="publicLimit" onchange="renderPublicList()" class="bg-gray-50 border border-gray-300 text-gray-700 text-xs rounded-lg focus:ring-blue-500 focus:border-blue-500 block p-2">
                  <option value="10">10</option>
                  <option value="25">25</option>
                  <option value="50">50</option>
                  <option value="100">100</option>
                  <option value="all">Semua</option>
                </select>
                <button onclick="loadPengaduanPublik()" class="text-blue-600 hover:bg-blue-50 p-2 rounded-lg transition-all" title="Refresh Data">
                  <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path></svg>
                </button>
              </div>
            </div>
            <div id="listPengaduanPublik" class="space-y-4 max-h-[700px] overflow-y-auto sidebar-scroll pr-2">
              </div>
          </div>
        </div>
      </div>
    </div>
  </div>

  <div id="loginModal" class="hidden fixed inset-0 bg-gray-900 bg-opacity-60 z-50 flex items-center justify-center p-4 backdrop-blur-sm transition-opacity">
    <div class="bg-white rounded-xl shadow-2xl p-8 max-w-md w-full border border-gray-200 transform scale-100 transition-transform">
      <h3 class="text-2xl font-bold text-gray-900 mb-6 text-center">🔐 Login Admin</h3>
      <form onsubmit="loginAdmin(event)">
        <div class="mb-6">
          <label class="block text-gray-700 text-sm font-semibold mb-2">Password</label>
          <div class="input-wrapper group">
            <svg class="input-icon group-hover:text-blue-500 transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"></path></svg>
            <input type="password" id="adminPassword" required class="input-with-icon w-full px-4 py-3 rounded-lg bg-gray-50 text-gray-900 border border-gray-300 focus:ring-2 focus:ring-blue-500 transition-all" placeholder="Masukkan password admin">
          </div>
        </div>
        <div class="flex gap-3">
          <button type="submit" id="btnLoginAdmin" class="flex-1 btn-primary font-bold py-3 px-6 rounded-lg flex justify-center items-center gap-2 transition-all disabled:opacity-70 disabled:cursor-not-allowed">
             <span id="btnLoginText">Login</span>
             <span id="btnLoginLoading" class="hidden">
                <div class="loading inline-block" style="width: 20px; height: 20px; border-width: 2px; border-top-color: white; border-right-color: rgba(255,255,255,0.2);"></div>
             </span>
          </button>
          <button type="button" onclick="hideLoginModal()" class="flex-1 bg-gray-100 text-gray-700 font-bold py-3 px-6 rounded-lg hover:bg-gray-200 transition-colors">Batal</button>
        </div>
      </form>
    </div>
  </div>

  <div id="adminPage" class="hidden bg-gray-100 min-h-screen font-sans">
    
    <div id="sidebar" class="bg-[rgb(26,25,103)] text-gray-300 fixed top-0 bottom-0 left-0 w-64 z-50 transition-all duration-300 flex flex-col font-inter shadow-2xl">
      <div class="h-16 flex items-center px-6 bg-black/20 border-b border-white/10 shrink-0">
         <img src="https://upload.wikimedia.org/wikipedia/commons/thumb/9/9c/Logo_of_Ministry_of_Education_and_Culture_of_Republic_of_Indonesia.svg/2048px-Logo_of_Ministry_of_Education_and_Culture_of_Republic_of_Indonesia.svg.png" 
              alt="Logo Sekolah" 
              class="h-8 w-8 mr-3 object-contain bg-white rounded-full p-0.5">
         <span class="text-white font-bold text-lg tracking-wider">SEKOLAH</span>
      </div>

      <div class="p-6 border-b border-white/10 shrink-0">
        <div class="flex items-center gap-4">
          <div class="relative">
            <img src="https://ui-avatars.com/api/?name=Admin&background=3b82f6&color=fff&size=128" 
                 alt="Admin" 
                 class="w-12 h-12 rounded-full border-2 border-gray-400 p-0.5">
            <div class="absolute bottom-0 right-0 w-3 h-3 bg-green-500 border-2 border-[rgb(26,25,103)] rounded-full"></div>
          </div>
          <div>
            <h4 class="text-white font-bold text-sm">Administrator</h4>
            <div class="flex items-center mt-1">
              <div class="w-2 h-2 bg-green-500 rounded-full mr-1.5 animate-pulse"></div>
              <span class="text-xs text-blue-200 font-medium">Online</span>
            </div>
          </div>
        </div>
      </div>

      <div class="flex-1 overflow-y-auto py-6 px-3 space-y-1 sidebar-scroll">
        <p class="px-3 text-xs font-bold text-blue-300/70 uppercase tracking-wider mb-2">Menu Utama</p>
        
        <button onclick="showDashboard()" id="menuDashboard" class="w-full flex items-center gap-3 px-3 py-3 rounded-lg text-gray-300 hover:bg-white/10 hover:text-white transition-all group">
          <span class="p-1.5 bg-white/10 rounded-md group-hover:bg-blue-500 transition-colors">
             <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z"></path></svg>
          </span>
          <span class="font-medium text-sm">Dashboard</span>
        </button>

        <button onclick="showDataPengaduan()" id="menuPengaduan" class="w-full flex items-center gap-3 px-3 py-3 rounded-lg text-gray-300 hover:bg-white/10 hover:text-white transition-all group">
          <span class="p-1.5 bg-white/10 rounded-md group-hover:bg-blue-500 transition-colors">
            <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01"></path></svg>
          </span>
          <span class="font-medium text-sm">Data Pengaduan</span>
        </button>
      </div>

      <div class="p-4 bg-black/20 border-t border-white/10 shrink-0">
        <button onclick="logout()" class="w-full flex items-center justify-center gap-2 bg-red-600 hover:bg-red-700 text-white py-2.5 rounded-lg transition-all shadow-lg shadow-red-900/20 group">
          <svg class="w-5 h-5 transition-transform group-hover:-translate-x-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"></path></svg>
          <span class="font-bold text-sm">Logout System</span>
        </button>
      </div>
    </div>

    <div id="adminContent" class="ml-64 transition-all duration-300 min-h-screen flex flex-col">
      
      <nav class="bg-white h-16 shadow-sm border-b border-gray-200 flex items-center justify-between px-6 sticky top-0 z-40">
        <div class="flex items-center gap-4">
           <button onclick="toggleSidebar()" class="text-gray-500 hover:text-blue-600 hover:bg-gray-100 p-2 rounded-lg transition-all focus:outline-none">
            <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h7"/>
            </svg>
          </button>
          <h1 class="text-gray-800 text-lg font-bold tracking-wide">ADMIN PANEL</h1>
        </div>
        
        <div class="hidden md:flex items-center gap-3">
          <span class="text-sm text-gray-500 font-medium">Sistem Pengaduan v2.0</span>
          <div class="w-8 h-8 bg-gray-100 rounded-full flex items-center justify-center text-gray-500 ring-2 ring-gray-100">
             🔔
          </div>
        </div>
      </nav>

      <div class="p-6">
          
          <div id="dashboardSection">
            <div class="w-full">
                <div class="mb-8 flex items-center justify-between">
                  <div>
                    <h2 class="text-2xl font-bold text-gray-800">Dashboard Ringkasan</h2>
                    <p class="text-gray-500 text-sm mt-1">Pantau statistik pengaduan masuk secara realtime.</p>
                  </div>
                </div>
                
                <div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
                  <div class="relative overflow-hidden rounded-2xl bg-gradient-to-br from-blue-600 via-blue-700 to-indigo-800 p-6 text-white shadow-lg transition-all duration-300 hover:-translate-y-1 hover:shadow-2xl group">
                    <div class="absolute -top-10 -right-10 h-32 w-32 rounded-full bg-white opacity-10 blur-2xl transition-all group-hover:opacity-20"></div>
                    <div class="relative z-10 flex items-start justify-between">
                      <div>
                         <p class="mb-1 text-xs font-bold uppercase tracking-widest text-blue-200">Total Pengaduan</p>
                         <h3 class="text-5xl font-extrabold tracking-tight text-white" id="totalPengaduan">0</h3>
                         <div class="mt-4 flex items-center gap-2">
                            <span class="rounded-full bg-white/20 px-2 py-0.5 text-[10px] font-medium text-white backdrop-blur-sm">Laporan Masuk</span>
                         </div>
                      </div>
                      <div class="rounded-xl bg-white/10 p-3 text-white backdrop-blur-sm shadow-inner ring-1 ring-white/20">
                        <span class="text-3xl">📝</span>
                      </div>
                    </div>
                  </div>

                  <div class="relative overflow-hidden rounded-2xl bg-gradient-to-br from-orange-400 via-orange-500 to-red-500 p-6 text-white shadow-lg transition-all duration-300 hover:-translate-y-1 hover:shadow-2xl group">
                    <div class="absolute -top-10 -right-10 h-32 w-32 rounded-full bg-white opacity-10 blur-2xl transition-all group-hover:opacity-20"></div>
                    <div class="relative z-10 flex items-start justify-between">
                      <div>
                        <p class="mb-1 text-xs font-bold uppercase tracking-widest text-orange-100">Belum Diproses</p>
                        <h3 class="text-5xl font-extrabold tracking-tight text-white" id="belumDiproses">0</h3>
                        <div class="mt-4 flex items-center gap-2">
                           <span class="rounded-full bg-white/20 px-2 py-0.5 text-[10px] font-medium text-white backdrop-blur-sm">Perlu Tindakan</span>
                        </div>
                      </div>
                      <div class="rounded-xl bg-white/10 p-3 text-white backdrop-blur-sm shadow-inner ring-1 ring-white/20">
                         <span class="text-3xl">⏳</span>
                      </div>
                    </div>
                  </div>

                  <div class="relative overflow-hidden rounded-2xl bg-gradient-to-br from-emerald-400 via-emerald-600 to-teal-700 p-6 text-white shadow-lg transition-all duration-300 hover:-translate-y-1 hover:shadow-2xl group">
                    <div class="absolute -top-10 -right-10 h-32 w-32 rounded-full bg-white opacity-10 blur-2xl transition-all group-hover:opacity-20"></div>
                    <div class="relative z-10 flex items-start justify-between">
                      <div>
                        <p class="mb-1 text-xs font-bold uppercase tracking-widest text-emerald-100">Sudah Selesai</p>
                        <h3 class="text-5xl font-extrabold tracking-tight text-white" id="sudahDiproses">0</h3>
                         <div class="mt-4 flex items-center gap-2">
                           <span class="rounded-full bg-white/20 px-2 py-0.5 text-[10px] font-medium text-white backdrop-blur-sm">Terselesaikan</span>
                        </div>
                      </div>
                      <div class="rounded-xl bg-white/10 p-3 text-white backdrop-blur-sm shadow-inner ring-1 ring-white/20">
                        <span class="text-3xl">✅</span>
                      </div>
                    </div>
                  </div>
                </div>

                <div class="bg-white rounded-xl p-6 shadow-sm border border-gray-100 mb-8">
                    <div class="flex items-center justify-between mb-6">
                        <div>
                            <h3 class="text-lg font-bold text-gray-800">Statistik Berdasarkan Kategori</h3>
                            <p class="text-sm text-gray-500">Jumlah pengaduan yang masuk per kategori.</p>
                        </div>
                    </div>
                    <div class="relative h-80">
                        <canvas id="categoryChart"></canvas>
                    </div>
                </div>
            </div>
          </div>

          <div id="dataPengaduanSection" class="hidden">
             <div class="w-full"> 
                <div class="bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden">
                  <div class="p-6 border-b border-gray-200">
                    
                    <div class="flex flex-col md:flex-row md:items-center justify-between gap-4 mb-6">
                      <div>
                          <h2 class="text-xl font-bold text-gray-800">Data Pengaduan Masuk</h2>
                         <p class="text-gray-500 text-xs">Kelola dan pantau semua laporan dari sini.</p>
                      </div>
                      
                      <div class="flex gap-2">
                        <button onclick="downloadExcel()" id="btnExportExcel" class="bg-green-600 text-white hover:bg-green-700 px-4 py-2 rounded-lg transition-all text-sm font-bold flex items-center gap-2 shadow-sm">
                            <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path></svg>
                            <span>Ekspor Excel</span>
                            <div id="loadingExcel" class="hidden w-3 h-3 border-2 border-white border-t-transparent rounded-full animate-spin"></div>
                        </button>

                        <button onclick="loadPengaduanAdmin()" class="bg-blue-50 text-blue-600 hover:bg-blue-100 px-4 py-2 rounded-lg transition-all text-sm font-bold flex items-center gap-2">
                            <span>🔄</span> Refresh Data
                        </button>
                      </div>
                    </div>

                    <div class="flex flex-col md:flex-row justify-between items-center gap-4">
                      
                      <div class="flex items-center gap-2 text-sm text-gray-600 w-full md:w-auto">
                        <span>Tampilkan</span>
                        <select id="itemsPerPage" onchange="changeItemsPerPage()" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block p-2">
                          <option value="10">10</option>
                          <option value="25">25</option>
                          <option value="50">50</option>
                          <option value="100">100</option>
                          <option value="all">Semua</option>
                        </select>
                        <span>data</span>
                      </div>

                      <div class="flex flex-col md:flex-row gap-3 w-full md:w-auto items-center">
                        
                         <select id="filterStatus" onchange="handleSearch()" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full md:w-48 p-2.5">
                          <option value="">Semua Status</option>
                          <option value="Belum Diproses">⏳ Belum Diproses</option>
                          <option value="Sudah Diproses">✅ Sudah Diproses</option>
                        </select>

                        <div class="relative w-full md:w-64">
                          <div class="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
                            <svg class="w-4 h-4 text-gray-500" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 20"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m19 19-4-4m0-7A7 7 0 1 1 1 8a7 7 0 0 1 14 0Z"/></svg>
                          </div>
                          <input type="text" id="searchInput" onkeyup="handleSearch()" class="block w-full p-2.5 pl-10 text-sm text-gray-900 border border-gray-300 rounded-lg bg-gray-50 focus:ring-blue-500 focus:border-blue-500" placeholder="Cari data...">
                        </div>
                        
                      </div>
                    </div>
                  </div>

                  <div class="overflow-x-auto">
                    <table class="w-full" id="tablePengaduan">
                      <thead class="bg-gray-50 border-b border-gray-200">
                        <tr>
                          <th class="px-6 py-4 text-left text-xs font-bold text-gray-500 uppercase tracking-wider">Tanggal</th>
                          <th class="px-6 py-4 text-left text-xs font-bold text-gray-500 uppercase tracking-wider">Nama</th>
                          <th class="px-6 py-4 text-left text-xs font-bold text-gray-500 uppercase tracking-wider">Nomor HP</th>
                          <th class="px-6 py-4 text-left text-xs font-bold text-gray-500 uppercase tracking-wider">Kategori</th>
                          <th class="px-6 py-4 text-left text-xs font-bold text-gray-500 uppercase tracking-wider">Isi Pengaduan</th>
                          <th class="px-6 py-4 text-left text-xs font-bold text-gray-500 uppercase tracking-wider">Foto</th>
                          <th class="px-6 py-4 text-left text-xs font-bold text-gray-500 uppercase tracking-wider">Tanggapan</th>
                          <th class="px-6 py-4 text-left text-xs font-bold text-gray-500 uppercase tracking-wider">Status</th>
                          <th class="px-6 py-4 text-left text-xs font-bold text-gray-500 uppercase tracking-wider">Aksi</th>
                        </tr>
                      </thead>
                      <tbody id="tbodyPengaduan" class="text-gray-700 divide-y divide-gray-200 bg-white"></tbody>
                    </table>
                  </div>

                  <div class="bg-gray-50 px-6 py-4 border-t border-gray-200 flex flex-col md:flex-row items-center justify-between gap-4">
                    <span class="text-sm text-gray-700" id="paginationInfo">
                      Menampilkan <span class="font-semibold text-gray-900" id="startRange">0</span> - <span class="font-semibold text-gray-900" id="endRange">0</span> dari <span class="font-semibold text-gray-900" id="totalData">0</span> data
                    </span>
                    <div class="inline-flex mt-2 xs:mt-0">
                        <button onclick="prevPage()" id="btnPrev" class="flex items-center justify-center px-4 h-10 text-base font-medium text-white bg-blue-600 rounded-l hover:bg-blue-700 disabled:bg-gray-300 disabled:cursor-not-allowed">Prev</button>
                        <button onclick="nextPage()" id="btnNext" class="flex items-center justify-center px-4 h-10 text-base font-medium text-white bg-blue-600 border-0 border-l border-blue-700 rounded-r hover:bg-blue-700 disabled:bg-gray-300 disabled:cursor-not-allowed">Next</button>
                    </div>
                  </div>
                </div>
             </div>
          </div>

      </div>
    </div>
  </div>

  <div id="imageModal" class="hidden fixed inset-0 z-[60] bg-black bg-opacity-90 flex items-center justify-center p-4 backdrop-blur-sm transition-opacity duration-300" onclick="closeImageModal()">
    <button class="absolute top-4 right-4 text-white hover:text-gray-300 transition-colors bg-black bg-opacity-50 rounded-full p-2">
         <svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path></svg>
    </button>
    <div class="relative w-full max-w-5xl h-full flex items-center justify-center pointer-events-none">
        <img id="modalImageFull" src="" alt="Preview" class="max-w-full max-h-[90vh] object-contain rounded-lg shadow-2xl pointer-events-auto transition-transform duration-300 transform scale-95 opacity-0">
    </div>
  </div>

  <div id="modalTracking" class="hidden fixed inset-0 bg-gray-900 bg-opacity-60 z-50 flex items-center justify-center p-4 backdrop-blur-sm transition-opacity">
    <div class="bg-white rounded-2xl shadow-2xl max-w-lg w-full overflow-hidden border border-gray-200 transform scale-100 transition-transform">
      <div class="bg-[rgb(26,25,103)] px-6 py-4 flex items-center justify-between">
        <h3 class="text-white font-bold text-lg flex items-center gap-2">
          <span>🔍</span> Detail Pengaduan
        </h3>
        <button onclick="closeTrackingModal()" class="text-white/70 hover:text-white transition-colors">
          <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path></svg>
        </button>
      </div>

      <div class="p-6 overflow-y-auto max-h-[70vh]">
        
        <div class="flex justify-center mb-6">
          <span id="trackStatusBadge" class="px-6 py-2 rounded-full text-sm font-extrabold uppercase tracking-widest shadow-sm border">
            STATUS
          </span>
        </div>

        <div class="space-y-4">
          <div class="flex justify-between border-b border-gray-100 pb-3">
             <div>
               <p class="text-xs text-gray-500 uppercase font-bold">ID Tiket</p>
               <p class="text-gray-800 font-mono font-bold" id="trackId">-</p>
             </div>
             <div class="text-right">
               <p class="text-xs text-gray-500 uppercase font-bold">Tanggal</p>
               <p class="text-gray-800 font-medium text-sm" id="trackTanggal">-</p>
             </div>
          </div>

          <div>
             <p class="text-xs text-gray-500 uppercase font-bold mb-1">Pengirim</p>
             <p class="text-gray-800 font-medium" id="trackNama">-</p>
          </div>
          
          <div>
             <p class="text-xs text-gray-500 uppercase font-bold mb-1">Isi Laporan</p>
             <div class="bg-gray-50 p-3 rounded-lg border border-gray-100 text-gray-700 text-sm leading-relaxed" id="trackIsi">
               -
             </div>
          </div>

          <div class="bg-blue-50 rounded-xl p-4 border border-blue-100">
             <div class="flex items-center gap-2 mb-2">
                <span class="text-lg">💬</span>
                <p class="text-blue-900 font-bold text-sm">Tanggapan Sekolah</p>
             </div>
             <p class="text-gray-700 text-sm italic" id="trackTanggapan">
               Belum ada tanggapan.
             </p>
          </div>

          <div id="trackFotoContainer" class="hidden pt-2">
             <button onclick="lihatFotoTracking()" class="w-full py-2 bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-lg text-sm font-bold transition-colors flex items-center justify-center gap-2">
               <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"></path></svg>
               Lihat Bukti Foto
             </button>
             <input type="hidden" id="trackFotoUrl">
          </div>

        </div>
      </div>
      
      <div class="bg-gray-50 px-6 py-4 text-center">
        <button onclick="closeTrackingModal()" class="text-blue-600 font-bold text-sm hover:underline">Tutup Jendela</button>
      </div>
    </div>
  </div>

  <div id="notificationContainer" class="fixed top-24 right-4 z-50 space-y-2"></div>




<script>
    // ==========================================
    // 1. STATE & KONFIGURASI GLOBAL
    // ==========================================
    let sidebarOpen = true;
    let isLoggedIn = false;
    
    // Variabel untuk Admin Table (Filter & Pagination)
    let allPengaduanData = []; // Master data dari server
    let filteredData = [];     // Data setelah difilter
    let currentPage = 1;       // Halaman aktif
    let itemsPerPage = 10;     // Baris per halaman
    
    // Variabel untuk Publik & Chart
    let allPublicData = [];
    let categoryChartInstance = null;

    // ==========================================
    // 2. INISIALISASI SAAT HALAMAN DIMUAT
    // ==========================================
    document.addEventListener('DOMContentLoaded', function() {
      // 1. Load Opsi Kategori untuk Form
      loadKategoriOptions();

      // 2. Cek Sesi Login di LocalStorage
      const session = localStorage.getItem('adminSession');
      
      if (session === 'active') {
        // Jika sesi aktif, masuk mode Admin
        isLoggedIn = true;
        document.getElementById('landingPage').classList.add('hidden');
        document.getElementById('adminPage').classList.remove('hidden');
        showDashboard(); 
      } else {
        // Jika tidak, tampilkan halaman Publik
        loadPengaduanPublik();
      }

      // 3. Listener Resize Layar
      handleResize();
    });

    // ==========================================
    // 3. LOGIKA FORMULIR PUBLIK (KIRIM LAPORAN)
    // ==========================================
    
    function loadKategoriOptions() {
       const select = document.getElementById('kategori');
       const loadingOpt = document.createElement('option');
       loadingOpt.text = "Memuat kategori...";
       select.add(loadingOpt);

       google.script.run
         .withSuccessHandler(function(data) {
            select.innerHTML = '<option value="" class="text-gray-500">Pilih Kategori</option>';
            if (!data || data.length === 0) {
               const opt = document.createElement('option');
               opt.text = "Umum"; opt.value = "Umum";
               select.appendChild(opt);
            } else {
               data.forEach(item => {
                  const opt = document.createElement('option');
                  opt.value = item; opt.textContent = item;
                  select.appendChild(opt);
               });
            }
          })
         .withFailureHandler(function(error) {
            console.error("Gagal kategori:", error);
            select.innerHTML = '<option value="">Gagal memuat data</option>';
         })
         .getDaftarKategori();
    }

    function submitPengaduan(event) {
      event.preventDefault();
      
      // UI Loading
      document.getElementById('btnSubmitText').classList.add('hidden');
      document.getElementById('btnSubmitLoading').classList.remove('hidden');

      const fileInput = document.getElementById('fotoBukti');
      const file = fileInput.files[0];

      // Ambil Data Form (Termasuk Email)
      const data = {
        nama: document.getElementById('nama').value,
        nomorHp: "'" + document.getElementById('nomorHp').value,
        email: document.getElementById('emailPelapor').value, // [PENTING] Ambil Email
        kategori: document.getElementById('kategori').value,
        isiPengaduan: document.getElementById('isiPengaduan').value,
        anonim: document.getElementById('anonim').checked.toString()
      };

      if (file) {
        // Validasi File (Maks 5MB)
        if (file.size > 5 * 1024 * 1024) { 
           showNotification('Ukuran file terlalu besar (Maks 5MB)', 'error');
           resetBtnLoading();
           return;
        }
        
        const reader = new FileReader();
        reader.onload = function(e) {
          data.fileData = e.target.result.split(',')[1];
          data.mimeType = file.type;
          data.fileName = file.name;
          kirimKeServer(data);
        };
        reader.readAsDataURL(file);
      } else {
        kirimKeServer(data);
      }
    }

    function kirimKeServer(data) {
      google.script.run
        .withSuccessHandler(function(result) {
          resetBtnLoading();
          
          if (result.success) {
            // [PENTING] Tampilkan ID Tiket ke User
            Swal.fire({
                title: 'Laporan Diterima!',
                html: `
                  <p>Terima kasih, laporan Anda telah kami terima.</p>
                  <div style="margin: 20px 0; padding: 15px; background: #f3f4f6; border: 2px dashed #3b82f6; border-radius: 10px;">
                    <p style="font-size: 0.9em; color: #6b7280; margin-bottom: 5px;">ID Tiket Anda:</p>
                    <h2 style="font-size: 1.8em; font-weight: 800; color: #1e40af; letter-spacing: 2px; margin: 0;">${result.id}</h2>
                  </div>
                  <p style="font-size: 0.9em; color: #ef4444;">*Harap catat ID ini untuk mengecek status laporan.</p>
                  <p style="font-size: 0.8em; color: #9ca3af; margin-top: 10px;">(Salinan bukti juga dikirim ke email jika Anda mengisinya)</p>
                `,
                icon: 'success',
                confirmButtonText: 'Siap, Sudah Dicatat!',
                confirmButtonColor: '#2563eb'
            });
            
            // Reset Form
            document.getElementById('formPengaduan').reset();
            
            // Refresh Data Publik
            if(!isLoggedIn) loadPengaduanPublik(); 
          } else {
            showNotification(result.message, 'error');
          }
        })
        .withFailureHandler(function(error) {
          resetBtnLoading();
          showNotification('Gagal mengirim: ' + error.message, 'error');
        })
        .simpanPengaduan(data);
    }

    function resetBtnLoading() {
      document.getElementById('btnSubmitText').classList.remove('hidden');
      document.getElementById('btnSubmitLoading').classList.add('hidden');
    }

    // ==========================================
    // 4. LOGIKA TRACKING (USER CEK STATUS)
    // ==========================================
    
    function handleEnterTracking(event) {
      if (event.key === "Enter") {
        cekStatus();
      }
    }

    function cekStatus() {
      const input = document.getElementById('inputTracking');
      const idTiket = input.value.trim();
      const btn = document.getElementById('btnTracking');
      const loading = document.getElementById('loadingTracking');

      if (!idTiket) {
        Swal.fire('Input Kosong', 'Mohon masukkan ID Pengaduan Anda.', 'warning');
        return;
      }

      // UI Loading State
      btn.disabled = true;
      btn.querySelector('span').textContent = 'Mencari...';
      loading.classList.remove('hidden');

      google.script.run
        .withSuccessHandler(function(result) {
          // Reset UI
          btn.disabled = false;
          btn.querySelector('span').textContent = 'Lacak';
          loading.classList.add('hidden');

          if (result.success) {
            tampilkanModalTracking(result.data);
          } else {
            Swal.fire('Tidak Ditemukan', result.message, 'error');
          }
        })
        .withFailureHandler(function(error) {
          btn.disabled = false;
          btn.querySelector('span').textContent = 'Lacak';
          loading.classList.add('hidden');
          Swal.fire('Error', 'Gagal koneksi ke server: ' + error.message, 'error');
        })
        .cariStatusPengaduan(idTiket);
    }

    function tampilkanModalTracking(data) {
      // Isi Data ke Modal
      document.getElementById('trackId').textContent = data.id;
      document.getElementById('trackTanggal').textContent = data.tanggal;
      document.getElementById('trackNama').textContent = data.nama;
      document.getElementById('trackIsi').textContent = data.isi;
      document.getElementById('trackTanggapan').textContent = data.tanggapan;
      
      // Atur Badge Status
      const badge = document.getElementById('trackStatusBadge');
      if (data.status === 'Sudah Diproses') {
        badge.className = 'px-6 py-2 rounded-full text-sm font-extrabold uppercase tracking-widest shadow-sm border bg-green-100 text-green-700 border-green-200';
        badge.textContent = '✅ SELESAI';
      } else {
        badge.className = 'px-6 py-2 rounded-full text-sm font-extrabold uppercase tracking-widest shadow-sm border bg-yellow-100 text-yellow-700 border-yellow-200';
        badge.textContent = '⏳ DIPROSES';
      }

      // Atur Foto
      const btnFoto = document.getElementById('trackFotoContainer');
      const urlFoto = document.getElementById('trackFotoUrl');
      
      if (data.foto && data.foto !== '-' && data.foto !== '') {
         btnFoto.classList.remove('hidden');
         urlFoto.value = data.foto;
      } else {
         btnFoto.classList.add('hidden');
         urlFoto.value = '';
      }

      document.getElementById('modalTracking').classList.remove('hidden');
    }

    function closeTrackingModal() {
      document.getElementById('modalTracking').classList.add('hidden');
    }

    function lihatFotoTracking() {
       const url = document.getElementById('trackFotoUrl').value;
       if(url) showImageModal(url); 
    }

    // ==========================================
    // 5. LIST PENGADUAN PUBLIK
    // ==========================================

    function loadPengaduanPublik() {
      const container = document.getElementById('listPengaduanPublik');
      container.innerHTML = '<div class="text-center py-8"><div class="loading mx-auto" style="border-top-color: #2563eb;"></div></div>';
      
      google.script.run
        .withSuccessHandler(function(data) {
          allPublicData = data;
          renderPublicList();
        })
        .withFailureHandler(function(error) {
          container.innerHTML = `<div class="text-center text-red-500 py-8">Gagal memuat data: ${error.message}</div>`;
        })
        .getPengaduanPublik();
    }

    function renderPublicList() {
      const container = document.getElementById('listPengaduanPublik');
      const limitElement = document.getElementById('publicLimit');
      const limitVal = limitElement ? limitElement.value : '10';
      
      if (!allPublicData || allPublicData.length === 0) {
        container.innerHTML = `
          <div class="text-center py-8 bg-gray-50 rounded-lg border border-dashed border-gray-300">
            <p class="text-gray-500">Belum ada pengaduan publik.</p>
          </div>`;
        return;
      }

      let displayData = [];
      if (limitVal === 'all') {
        displayData = allPublicData;
      } else {
        displayData = allPublicData.slice(0, parseInt(limitVal));
      }

      let html = '';
      displayData.forEach(item => {
        const statusClass = item.status === 'Sudah Diproses' ? 'badge-done' : 'badge-pending';
        
        let imgHtml = '';
        if (item.fotoUrl && item.fotoUrl !== '-') {
           imgHtml = `
             <a href="javascript:void(0)" onclick="showImageModal('${item.fotoUrl}')" class="ml-4 flex-shrink-0 relative group block cursor-pointer">
              <img src="${item.fotoUrl}" alt="Bukti" class="w-24 h-24 object-cover rounded-lg border border-gray-200 shadow-sm transition-transform group-hover:scale-105">
              <div class="absolute inset-0 bg-black bg-opacity-0 group-hover:bg-opacity-10 transition-all rounded-lg flex items-center justify-center">
                 <span class="text-white opacity-0 group-hover:opacity-100 text-xs font-bold">🔍</span>
              </div>
             </a>`;
        }

        let tanggapanHtml = '';
        if (item.tanggapan && item.tanggapan !== '') {
           const safeTanggapan = item.tanggapan.replace(/"/g, '&quot;');
           tanggapanHtml = `
             <div class="mt-3 bg-blue-50/80 border-l-4 border-blue-600 p-3 rounded-r-lg animate-fade-in">
                <div class="flex items-center gap-2 mb-1">
                   <img src="https://upload.wikimedia.org/wikipedia/commons/thumb/9/9c/Logo_of_Ministry_of_Education_and_Culture_of_Republic_of_Indonesia.svg/2048px-Logo_of_Ministry_of_Education_and_Culture_of_Republic_of_Indonesia.svg.png" class="w-4 h-4 object-contain">
                   <span class="text-xs font-bold text-blue-800">Tanggapan Sekolah:</span>
                </div>
                <p class="text-xs text-gray-700 italic leading-relaxed">"${safeTanggapan}"</p>
             </div>
           `;
        }

        html += `
          <div class="bg-white rounded-lg p-4 border border-gray-200 shadow-sm hover:shadow-md transition-all mb-4">
            <div class="flex items-start justify-between">
              <div class="flex-1 min-w-0 pr-2">
                <div class="flex items-center gap-2 mb-2">
                  <span class="text-xs text-gray-500 font-medium">${item.tanggal}</span>
                  <span class="${statusClass} text-[10px] px-2 py-0.5 rounded-full font-bold uppercase tracking-wide">
                     ${item.status}
                  </span>
                </div>
                <h4 class="text-gray-900 font-bold text-sm mb-1 truncate">${item.kategori}</h4>
                <p class="text-gray-600 text-sm leading-relaxed line-clamp-3 mb-2">${item.ringkasan}</p>
                ${tanggapanHtml}
              </div>
              ${imgHtml}
            </div>
          </div>`;
      });
      container.innerHTML = html;
    }

    // ==========================================
    // 6. LOGIN & ADMIN SYSTEM
    // ==========================================
    
    function showLoginModal() {
      document.getElementById('loginModal').classList.remove('hidden');
    }

    function hideLoginModal() {
      document.getElementById('loginModal').classList.add('hidden');
      document.getElementById('adminPassword').value = '';
    }

    function loginAdmin(event) {
      event.preventDefault();
      const btn = document.getElementById('btnLoginAdmin');
      const textSpan = document.getElementById('btnLoginText');
      const loadingSpan = document.getElementById('btnLoginLoading');
      const passwordInput = document.getElementById('adminPassword');

      textSpan.classList.add('hidden');
      loadingSpan.classList.remove('hidden');
      btn.disabled = true;
      
      const password = passwordInput.value;
      
      google.script.run
        .withSuccessHandler(function(result) {
          textSpan.classList.remove('hidden');
          loadingSpan.classList.add('hidden');
          btn.disabled = false;

          if (result.success) {
            localStorage.setItem('adminSession', 'active');
            isLoggedIn = true;
            hideLoginModal();
            document.getElementById('landingPage').classList.add('hidden');
            document.getElementById('adminPage').classList.remove('hidden');
            showNotification('Login berhasil!', 'success');
            showDashboard();
          } else {
            showNotification(result.message, 'error');
            passwordInput.value = '';
            passwordInput.focus();
          }
        })
        .withFailureHandler(function(error) {
          textSpan.classList.remove('hidden');
          loadingSpan.classList.add('hidden');
          btn.disabled = false;
          showNotification('Gagal login: ' + error.message, 'error');
        })
        .loginAdmin(password);
    }

    function logout() {
      Swal.fire({
        title: 'Konfirmasi Logout',
        text: "Apakah Anda yakin ingin keluar dari sesi Admin?",
        icon: 'warning',
        showCancelButton: true,
        confirmButtonColor: '#d33',
        cancelButtonColor: '#3085d6',
        confirmButtonText: 'Ya, Keluar!',
        cancelButtonText: 'Batal',
        reverseButtons: true
      }).then((result) => {
        if (result.isConfirmed) {
          localStorage.removeItem('adminSession');
          isLoggedIn = false;
          document.getElementById('adminPage').classList.add('hidden');
          document.getElementById('landingPage').classList.remove('hidden');
          
          Swal.fire({
            title: 'Berhasil!',
            text: 'Anda telah keluar dari sistem.',
            icon: 'success',
            timer: 1500,
            showConfirmButton: false
          });
          loadPengaduanPublik();
        }
      });
    }

    function toggleSidebar() {
      sidebarOpen = !sidebarOpen;
      const sidebar = document.getElementById('sidebar');
      const content = document.getElementById('adminContent');
      
      if (sidebarOpen) {
        sidebar.style.marginLeft = '0';
        content.style.marginLeft = '256px';
      } else {
        sidebar.style.marginLeft = '-256px';
        content.style.marginLeft = '0';
      }
    }

    function showDashboard() {
      document.getElementById('dashboardSection').classList.remove('hidden');
      document.getElementById('dataPengaduanSection').classList.add('hidden');
      
      document.getElementById('menuDashboard').classList.add('bg-[#2a2a3c]', 'text-white');
      document.getElementById('menuPengaduan').classList.remove('bg-[#2a2a3c]', 'text-white');
      
      loadPengaduanAdmin();
    }

    function showDataPengaduan() {
      document.getElementById('dashboardSection').classList.add('hidden');
      document.getElementById('dataPengaduanSection').classList.remove('hidden');

      document.getElementById('menuDashboard').classList.remove('bg-[#2a2a3c]', 'text-white');
      document.getElementById('menuPengaduan').classList.add('bg-[#2a2a3c]', 'text-white');

      loadPengaduanAdmin();
    }

    // ==========================================
    // 7. MANAJEMEN DATA ADMIN (TABLE, CHART)
    // ==========================================
    
    function loadPengaduanAdmin() {
      const tbody = document.getElementById('tbodyPengaduan');
      if(tbody) tbody.innerHTML = `<tr><td colspan="9" class="text-center py-8"><div class="loading mx-auto" style="border-top-color: #2563eb;"></div></td></tr>`;
      
      google.script.run
        .withSuccessHandler(function(data) {
          allPengaduanData = data;
          filteredData = data;

          // Update Statistik Ringkasan
          const total = data.length;
          const belum = data.filter(item => item.status === 'Belum Diproses').length;
          const sudah = data.filter(item => item.status === 'Sudah Diproses').length;
          
          if(document.getElementById('totalPengaduan')) {
              document.getElementById('totalPengaduan').textContent = total;
              document.getElementById('belumDiproses').textContent = belum;
              document.getElementById('sudahDiproses').textContent = sudah;
          }
           
          // Render Chart (Jika di Dashboard)
          if(!document.getElementById('dashboardSection').classList.contains('hidden')) {
             renderChart();
          }

          // Render Table (Jika di Halaman Data)
          if(!document.getElementById('dataPengaduanSection').classList.contains('hidden')) {
             handleSearch(); 
          }
        })
        .withFailureHandler(function(error) {
          showNotification('Gagal memuat data: ' + error.message, 'error');
        })
        .getPengaduanAdmin();
    }

    function renderChart() {
        const counts = {};
        allPengaduanData.forEach(item => {
            const categoryName = item.kategori ? item.kategori.trim() : "Tanpa Kategori";
            counts[categoryName] = (counts[categoryName] || 0) + 1;
        });

        const chartLabels = Object.keys(counts);
        const chartDataPoints = Object.values(counts);
        
        if (categoryChartInstance) {
            categoryChartInstance.destroy();
        }

        const ctx = document.getElementById('categoryChart').getContext('2d');
        const gradientBg = ctx.createLinearGradient(0, 0, 0, 400);
        gradientBg.addColorStop(0, 'rgba(59, 130, 246, 0.9)');
        gradientBg.addColorStop(1, 'rgba(59, 130, 246, 0.1)');
        
        categoryChartInstance = new Chart(ctx, {
            type: 'bar',
            data: {
                labels: chartLabels,
                datasets: [{
                    label: 'Jumlah Pengaduan',
                    data: chartDataPoints,
                    backgroundColor: gradientBg,
                    borderColor: 'rgba(59, 130, 246, 1)',
                    borderWidth: 1,
                    borderRadius: 8,
                    barThickness: 'flex',
                    maxBarThickness: 40,
                }]
            },
            options: {
                responsive: true,
                maintainAspectRatio: false,
                plugins: {
                    legend: { display: false },
                    tooltip: {
                        backgroundColor: '#1e1e2d',
                        padding: 12,
                        cornerRadius: 8,
                    }
                },
                scales: {
                    y: {
                        beginAtZero: true,
                        grid: { borderDash: [5, 5] },
                        ticks: { stepSize: 1 }
                    },
                    x: {
                        grid: { display: false }
                    }
                }
            }
        });
    }

    function renderTable() {
      const tbody = document.getElementById('tbodyPengaduan');
      tbody.innerHTML = '';

      const totalItems = filteredData.length;
      const totalPages = itemsPerPage === 'all' ? 1 : Math.ceil(totalItems / itemsPerPage);
      
      if (currentPage > totalPages) currentPage = totalPages || 1;
      if (currentPage < 1) currentPage = 1;

      let displayData = [];
      let startRange = 0;
      let endRange = 0;

      if (totalItems > 0) {
          if (itemsPerPage === 'all') {
              displayData = filteredData;
              startRange = 1;
              endRange = totalItems;
          } else {
              const startIndex = (currentPage - 1) * itemsPerPage;
              const endIndex = startIndex + parseInt(itemsPerPage);
              displayData = filteredData.slice(startIndex, endIndex);
              startRange = startIndex + 1;
              endRange = Math.min(endIndex, totalItems);
          }
      }

      document.getElementById('startRange').textContent = startRange;
      document.getElementById('endRange').textContent = endRange;
      document.getElementById('totalData').textContent = totalItems;
      document.getElementById('btnPrev').disabled = currentPage === 1;
      document.getElementById('btnNext').disabled = currentPage === totalPages || totalPages === 0;

      if (displayData.length === 0) {
        tbody.innerHTML = `<tr><td colspan="9" class="px-6 py-8 text-center text-gray-500 bg-gray-50">Tidak ada data ditemukan.</td></tr>`;
        return;
      }

      let html = '';
      displayData.forEach(item => {
        const isDone = item.status === 'Sudah Diproses';
        const disableBtn = isDone ? 'opacity-50 cursor-not-allowed' : '';
        
        let fotoLink = '<span class="text-xs text-gray-400 italic">Tidak ada</span>';
        if (item.fotoUrl && item.fotoUrl !== '-') {
            fotoLink = `<a href="javascript:void(0)" onclick="showImageModal('${item.fotoUrl}')" class="text-blue-600 hover:text-blue-800 underline text-xs font-medium flex items-center gap-1"><svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"></path></svg> Lihat</a>`;
        }
        
        const rawTanggapan = item.tanggapan || '';
        const safeTanggapan = rawTanggapan.replace(/'/g, "\\'").replace(/"/g, '&quot;');
        const displayTanggapan = rawTanggapan 
            ? `<span class="text-gray-700 italic">"${rawTanggapan}"</span>` 
            : `<span class="text-gray-400 text-xs">Belum ditanggapi</span>`;

        html += `
          <tr class="hover:bg-blue-50/50 transition-colors border-b border-gray-100 last:border-0 align-top">
            <td class="px-6 py-4 text-sm text-gray-500 whitespace-nowrap">${item.tanggal}</td>
            <td class="px-6 py-4 text-sm font-bold text-gray-800">${item.nama}</td>
            <td class="px-6 py-4 text-sm text-gray-600 whitespace-nowrap">
               <span class="font-mono bg-gray-100 px-2 py-1 rounded text-xs tracking-wide">${item.nomorHp || '-'}</span>
            </td>
            <td class="px-6 py-4 text-sm text-gray-600">
               <span class="bg-gray-100 text-gray-600 px-2 py-1 rounded text-xs font-medium border border-gray-200">${item.kategori}</span>
            </td>
            <td class="px-6 py-4 text-sm text-gray-600 max-w-xs">
              <div class="line-clamp-2" title="${item.isiPengaduan}">${item.isiPengaduan}</div>
            </td>
            <td class="px-6 py-4 text-sm whitespace-nowrap">${fotoLink}</td>
            <td class="px-6 py-4 text-sm max-w-xs">
               <div class="line-clamp-2 text-xs border-l-2 border-blue-300 pl-2">
                 ${displayTanggapan}
               </div>
            </td>
            <td class="px-6 py-4 whitespace-nowrap">
              <span class="${isDone ? 'badge-done' : 'badge-pending'} text-xs px-3 py-1 rounded-full font-bold uppercase tracking-wide flex w-fit items-center gap-1">
                 ${isDone ? '✓' : '⏳'} ${item.status}
              </span>
            </td>
            <td class="px-6 py-4 whitespace-nowrap">
              <div class="flex gap-2">
                <button onclick="bukaModalTanggapan('${item.id}', '${safeTanggapan}')" 
                        class="bg-blue-600 hover:bg-blue-700 text-white text-xs px-3 py-1.5 rounded shadow-sm hover:shadow-md transition-all flex items-center gap-1" 
                        title="Beri Tanggapan">
                     💬 Balas
                </button>
                <button onclick="updateStatusPengaduan('${item.id}')" 
                        class="btn-success text-white text-xs px-3 py-1.5 rounded shadow-sm hover:shadow-md transition-all ${disableBtn}" 
                        title="Tandai Selesai" ${isDone ? 'disabled' : ''}>
                    ✓
                </button>
                <button onclick="hapusPengaduanConfirm('${item.id}')" 
                        class="bg-white border border-red-200 text-red-600 hover:bg-red-50 text-xs px-3 py-1.5 rounded shadow-sm hover:shadow-md transition-all"
                        title="Hapus Data">
                    🗑️
                </button>
              </div>
            </td>
          </tr>
        `;
      });
      tbody.innerHTML = html;
    }

    function handleSearch() {
        const keyword = document.getElementById('searchInput').value.toLowerCase();
        const statusFilter = document.getElementById('filterStatus').value;

        filteredData = allPengaduanData.filter(item => {
            const matchesKeyword = (
                item.nama.toLowerCase().includes(keyword) ||
                item.kategori.toLowerCase().includes(keyword) ||
                item.isiPengaduan.toLowerCase().includes(keyword)
            );
            const matchesStatus = statusFilter === "" || item.status === statusFilter;
            return matchesKeyword && matchesStatus;
        });

        currentPage = 1;
        renderTable();
    }

    function changeItemsPerPage() {
        const value = document.getElementById('itemsPerPage').value;
        itemsPerPage = value === 'all' ? 'all' : parseInt(value);
        currentPage = 1;
        renderTable();
    }

    function prevPage() {
        if (currentPage > 1) {
            currentPage--;
            renderTable();
        }
    }

    function nextPage() {
        const totalItems = filteredData.length;
        const totalPages = itemsPerPage === 'all' ? 1 : Math.ceil(totalItems / itemsPerPage);
        if (currentPage < totalPages) {
            currentPage++;
            renderTable();
        }
    }

    function updateStatusPengaduan(id) {
      Swal.fire({
        title: 'Konfirmasi Proses',
        text: "Apakah Anda yakin ingin menandai pengaduan ini sebagai Selesai?",
        icon: 'question',
        showCancelButton: true,
        confirmButtonColor: '#10b981', 
        cancelButtonColor: '#6b7280', 
        confirmButtonText: 'Ya, Selesaikan!',
        cancelButtonText: 'Batal',
        reverseButtons: true
      }).then((result) => {
        if (result.isConfirmed) {
          Swal.fire({
            title: 'Memproses...', text: 'Sedang memperbarui status data.',
            allowOutsideClick: false, showConfirmButton: false,
            didOpen: () => { Swal.showLoading(); }
          });

          google.script.run
            .withSuccessHandler(function(result) {
              if (result.success) {
                Swal.fire({ title: 'Berhasil!', text: 'Status diperbarui menjadi Selesai.', icon: 'success', timer: 1500, showConfirmButton: false });
                loadPengaduanAdmin(); 
              } else {
                Swal.fire('Gagal!', result.message, 'error');
              }
            })
            .withFailureHandler(function(error) {
              Swal.fire('Error!', 'Gagal update status: ' + error.message, 'error');
            })
            .updateStatus(id);
        }
      });
    }

    function hapusPengaduanConfirm(id) {
      Swal.fire({
        title: 'Hapus Data?',
        text: "Apakah Anda yakin ingin menghapus data ini secara PERMANEN?",
        icon: 'warning',
        showCancelButton: true,
        confirmButtonColor: '#d33', 
        cancelButtonColor: '#3085d6', 
        confirmButtonText: 'Ya, Hapus!',
        cancelButtonText: 'Batal',
        reverseButtons: true
      }).then((result) => {
        if (result.isConfirmed) {
          Swal.fire({
            title: 'Memproses...', text: 'Sedang menghapus data.',
            allowOutsideClick: false, didOpen: () => { Swal.showLoading(); }
          });

          google.script.run
            .withSuccessHandler(function(result) {
              if (result.success) {
                Swal.fire({ title: 'Terhapus!', text: 'Data pengaduan berhasil dihapus.', icon: 'success', timer: 1500, showConfirmButton: false });
                loadPengaduanAdmin(); 
              } else {
                Swal.fire('Gagal!', result.message, 'error');
              }
            })
            .withFailureHandler(function(error) {
              Swal.fire('Error!', 'Gagal menghapus: ' + error.message, 'error');
            })
           .hapusPengaduan(id);
        }
      });
    }

    function bukaModalTanggapan(id, currentText) {
      const textVal = currentText === 'undefined' ? '' : currentText;

      Swal.fire({
        title: 'Tanggapan Sekolah',
        input: 'textarea',
        inputLabel: 'Tulis tanggapan atau tindak lanjut untuk laporan ini:',
        inputValue: textVal,
        inputPlaceholder: 'Contoh: Laporan diterima, tim sarpras akan segera memperbaiki...',
        showCancelButton: true,
        confirmButtonText: 'Kirim Tanggapan',
        cancelButtonText: 'Batal',
        confirmButtonColor: '#2563eb',
        showLoaderOnConfirm: true,
        preConfirm: (text) => {
          if (!text) {
            Swal.showValidationMessage('Tanggapan tidak boleh kosong!');
          } else {
             return new Promise((resolve) => {
                google.script.run
                  .withSuccessHandler((result) => {
                      if(result.success) {
                        resolve();
                        loadPengaduanAdmin();
                        Swal.fire({ title: 'Terkirim!', text: 'Tanggapan berhasil disimpan.', icon: 'success', timer: 1500, showConfirmButton: false });
                      } else {
                        Swal.showValidationMessage(result.message);
                      }
                  })
                  .withFailureHandler((error) => {
                      Swal.showValidationMessage(`Request failed: ${error}`);
                  })
                  .simpanTanggapanAdmin(id, text);
             });
          }
        }
      });
    }

    // ==========================================
    // 8. UTILITIES (NOTIFIKASI & GAMBAR)
    // ==========================================
    

// ==========================================
    // EKSPOR EXCEL FUNCTION
    // ==========================================
    function downloadExcel() {
      const btn = document.getElementById('btnExportExcel');
      const loading = document.getElementById('loadingExcel');
      const text = btn.querySelector('span'); // Span teks "Ekspor Excel"

      // UI Loading State
      btn.disabled = true;
      btn.classList.add('opacity-75', 'cursor-not-allowed');
      loading.classList.remove('hidden');
      text.textContent = 'Memproses...';

      // Panggil Server
      google.script.run
        .withSuccessHandler(function(result) {
          // Reset UI
          btn.disabled = false;
          btn.classList.remove('opacity-75', 'cursor-not-allowed');
          loading.classList.add('hidden');
          text.textContent = 'Ekspor Excel';

          if (result.success) {
            // Tampilkan Notif Sukses & Buka Link
            Swal.fire({
              title: 'Siap Mengunduh!',
              text: 'File Excel berhasil dibuat. Unduhan akan segera dimulai.',
              icon: 'success',
              timer: 2000,
              showConfirmButton: false
            });
            
            // Buka link download di tab baru
            window.open(result.url, '_blank');
          } else {
            Swal.fire('Gagal!', result.message, 'error');
          }
        })
        .withFailureHandler(function(error) {
          // Reset UI Error
          btn.disabled = false;
          btn.classList.remove('opacity-75', 'cursor-not-allowed');
          loading.classList.add('hidden');
          text.textContent = 'Ekspor Excel';
          
          Swal.fire('Error Server', error.message, 'error');
        })
        .generateExcelAdmin(); // Memanggil fungsi wrapper di server
    }

    function showImageModal(url) {
      const modal = document.getElementById('imageModal');
      const img = document.getElementById('modalImageFull');
      img.src = url;
      modal.classList.remove('hidden');
      setTimeout(() => {
        img.classList.remove('scale-95', 'opacity-0');
        img.classList.add('scale-100', 'opacity-100');
      }, 50);
    }

    function closeImageModal() {
      const modal = document.getElementById('imageModal');
      const img = document.getElementById('modalImageFull');
      img.classList.remove('scale-100', 'opacity-100');
      img.classList.add('scale-95', 'opacity-0');
      setTimeout(() => {
        modal.classList.add('hidden');
        img.src = ''; 
      }, 300);
    }

    function showNotification(message, type = 'success') {
      const container = document.getElementById('notificationContainer');
      const bgClass = type === 'success' ? 'bg-green-600' : 'bg-red-600';
      
      const notification = document.createElement('div');
      notification.className = `notification ${bgClass} text-white px-6 py-4 rounded-lg shadow-lg max-w-md flex items-center justify-between gap-4`;
      notification.innerHTML = `
        <span class="font-medium text-sm">${message}</span>
        <button onclick="this.parentElement.remove()" class="text-white hover:text-gray-200 font-bold">✕</button>
      `;
      container.appendChild(notification);
      setTimeout(() => { 
        if(notification.parentElement) notification.remove(); 
      }, 5000);
    }

    function handleResize() {
      const width = window.innerWidth;
      const sidebar = document.getElementById('sidebar');
      const content = document.getElementById('adminContent');
      
      if (!document.getElementById('adminPage').classList.contains('hidden')) {
          if (width < 768) {
            sidebar.style.marginLeft = '-256px';
            content.style.marginLeft = '0';
            sidebarOpen = false;
          } else {
            sidebar.style.marginLeft = '0';
            content.style.marginLeft = '256px';
            sidebarOpen = true;
          }
      }
    }
    
    window.addEventListener('resize', handleResize);
</script>
</body>
</html>







Tidak ada komentar:

Posting Komentar

Desain UI Template

  https://www.hyperui.dev/ https://htmlrev.com/ https://github.com/tailwindtoolbox/Landing-Page https://www.w3schools.com/css/css_rwd_templa...