// === Google Apps Script â Rekap Bulanan v2.3a (STRICT, tidak ubah format status) ===
// Perbaikan utama: header tanggal dibaca sebagai display string (mis. '11/8/2025')
// dan status hanya dihitung jika persis salah satu dari: Hadir / Izin / Sakit / Alpha
// (case-insensitive, tapi TIDAK menerima singkatan lain).
// GET: ?action=rekap&kelas=XI-3&bulan=8&tahun=2025
// Output: {success:true, data:[{no,nisn,nama,hadir,izin,sakit,alpha,persentase}]}
const SPREADSHEET_ID = ''; // opsional; isi jika script standalone
function _ss(){ return SPREADSHEET_ID ? SpreadsheetApp.openById(SPREADSHEET_ID) : SpreadsheetApp.getActiveSpreadsheet(); }
function doPost(e) {
try {
const data = JSON.parse(e.postData.contents);
const spreadsheet = _ss();
let sheet = spreadsheet.getSheetByName(data.kelas);
// Buat sheet baru jika belum ada
if (!sheet) {
sheet = spreadsheet.insertSheet(data.kelas);
// Buat header otomatis
sheet.getRange(1, 1).setValue('No');
sheet.getRange(1, 2).setValue('NISN');
sheet.getRange(1, 3).setValue('Nama Siswa');
// Format header
const headerRange = sheet.getRange(1, 1, 1, 3);
headerRange.setBackground('#4285f4');
headerRange.setFontColor('white');
headerRange.setFontWeight('bold');
headerRange.setHorizontalAlignment('center');
// Set column widths
sheet.setColumnWidth(1, 50); // No
sheet.setColumnWidth(2, 120); // NISN
sheet.setColumnWidth(3, 200); // Nama
}
// Format tanggal (d/m/y)
const dateObj = new Date(data.tanggal);
const tanggal = dateObj.getDate() + '/' + (dateObj.getMonth() + 1) + '/' + dateObj.getFullYear();
// Cari atau buat kolom tanggal
let headerRow = sheet.getRange(1, 1, 1, sheet.getLastColumn() || 3).getValues()[0];
let tanggalCol = headerRow.indexOf(tanggal) + 1;
if (tanggalCol === 0) {
tanggalCol = sheet.getLastColumn() + 1;
sheet.getRange(1, tanggalCol).setValue(tanggal);
// Format header tanggal
sheet.getRange(1, tanggalCol).setBackground('#4285f4');
sheet.getRange(1, tanggalCol).setFontColor('white');
sheet.getRange(1, tanggalCol).setFontWeight('bold');
sheet.getRange(1, tanggalCol).setHorizontalAlignment('center');
sheet.setColumnWidth(tanggalCol, 80);
}
// Update data absensi
data.absensi.forEach((siswa, index) => {
const row = index + 2; // Mulai dari baris 2
// Update data siswa (no, nisn, nama)
sheet.getRange(row, 1).setValue(siswa.no || (index + 1)); // No
sheet.getRange(row, 2).setValue(siswa.nisn); // NISN
sheet.getRange(row, 3).setValue(siswa.nama); // Nama
// Format baris data
const dataRange = sheet.getRange(row, 1, 1, 3);
dataRange.setHorizontalAlignment('left');
if (index % 2 === 0) {
dataRange.setBackground('#f8f9fa');
}
// Update status kehadiran
const statusCell = sheet.getRange(row, tanggalCol);
statusCell.setValue(siswa.status);
// Format warna berdasarkan status
switch(siswa.status) {
case 'Hadir':
statusCell.setBackground('#d4edda');
statusCell.setFontColor('#155724');
break;
case 'Izin':
statusCell.setBackground('#fff3cd');
statusCell.setFontColor('#856404');
break;
case 'Sakit':
statusCell.setBackground('#cce7ff');
statusCell.setFontColor('#004085');
break;
case 'Alpha':
statusCell.setBackground('#f8d7da');
statusCell.setFontColor('#721c24');
break;
}
statusCell.setHorizontalAlignment('center');
statusCell.setFontWeight('bold');
});
// Auto resize columns
sheet.autoResizeColumns(1, sheet.getLastColumn());
return ContentService
.createTextOutput(JSON.stringify({success: true, message: 'Data berhasil disimpan'}))
.setMimeType(ContentService.MimeType.JSON);
} catch (error) {
return ContentService
.createTextOutput(JSON.stringify({success: false, error: error.toString()}))
.setMimeType(ContentService.MimeType.JSON);
}
}
function doGet(e){
try{
var action = String(e.parameter.action||'').toLowerCase();
if (action === 'rekap'){
var kelas = String(e.parameter.kelas||'').trim();
var bulan = parseInt(e.parameter.bulan,10);
var tahun = parseInt(e.parameter.tahun,10);
if (!kelas || !bulan || !tahun) return _json({success:true, data:[]});
var sh = _ss().getSheetByName(kelas);
if (!sh) return _json({success:true, data:[]});
var lc = Math.max(3, sh.getLastColumn());
var lr = sh.getLastRow();
if (lr <= 1) return _json({success:true, data:[]});
// Baca header sebagai TAMPILAN string, bukan Date object
var headers = sh.getRange(1,1,1,lc).getDisplayValues()[0];
var tanggalCols = [];
for (var c=4; c<=lc; c++){
var h = String(headers[c-1]||'').replace(/-/g,'/').trim();
var p = h.split('/'); // diasumsikan D/M/YYYY
if (p.length===3){
var m = parseInt(p[1],10), y = parseInt(p[2],10);
if (m===bulan && y===tahun) tanggalCols.push(c);
}
}
if (!tanggalCols.length) return _json({success:true, data:[]});
// Ambil isi sel sebagai display values agar status berupa string yang terlihat
var vals = sh.getRange(2,1,lr-1,lc).getDisplayValues();
var out = [];
for (var i=0;i0 ? Math.round((siswa.hadir/total)*100) : 0;
out.push(siswa);
}
return _json({success:true, data:out});
}
return _json({success:true, ok:true});
}catch(err){
return _json({success:false, error:String(err)});
}
}
// ===== Helpers =====
function _json(o){ return ContentService.createTextOutput(JSON.stringify(o)).setMimeType(ContentService.MimeType.JSON); }
function _toNum(v){ var n = parseInt(String(v||'').replace(/[^\d]/g,''),10); return isNaN(n)?'':n; }
// Hanya terima kata persis (case-insensitive) tanpa singkatan lain.
function _bucketStrict(v){
var s = String(v||'').trim().toLowerCase();
if (!s) return null;
if (s === 'hadir') return 'hadir';
if (s === 'izin') return 'izin';
if (s === 'sakit') return 'sakit';
if (s === 'alpha') return 'alpha';
return null;
}