Salin keseluruhan kod ini ke Google Apps Script editor, kemudian deploy sebagai Web App.
// ============================================================
// SISTEM HEM BERSEPADU v3.1 â Google Apps Script
// Deploy: Execute as Me | Access: Anyone
// Selepas deploy, jalankan autoSetup() sekali sahaja
// ============================================================
const SS = SpreadsheetApp.getActiveSpreadsheet();
const HEADERS = {
KEHADIRAN: ['Kelas','Keseluruhan','Jan','Feb','Mac','Apr','Mei','Jun','Jul','Ogs','Sep','Okt','Nov','Dis'],
MURID_TEGAR: ['nama','kelas','tingkatan','th_bulan','dari_lalu','jumlah','status','trend'],
MURID_PROFIL: ['id','name','ic','class','tingkatan'],
SURAT_AMARAN: ['id','studentId','studentName','type','referenceNo','issueDate','absentDates']
};
// ââ CORS HEADERS âââââââââââââââââââââââââââââââââââââââââââââ
function setCorsHeaders(output) {
return output
.setHeader('Access-Control-Allow-Origin', '*')
.setHeader('Access-Control-Allow-Methods', 'GET, POST')
.setHeader('Access-Control-Allow-Headers', 'Content-Type');
}
function out(d) {
const output = ContentService
.createTextOutput(JSON.stringify(d))
.setMimeType(ContentService.MimeType.JSON);
return setCorsHeaders(output);
}
// ââ ROUTING doGet âââââââââââââââââââââââââââââââââââââââââââââ
function doGet(e) {
const a = (e.parameter && e.parameter.action) || '';
const b = (e.parameter && e.parameter.bulan) || '';
if (a==='getKehadiran') return out(getKehadiran());
if (a==='getTegar') return out(getTegar());
if (a==='getMurid') return out(getMurid());
if (a==='getSurat') return out(getSurat());
if (a==='getTegarBulanList') return out(getTegarBulanList());
if (a==='getTegarByBulan') return out(getTegarByBulan(b));
return out({error:'unknown action', received: a});
}
// ââ ROUTING doPost ââââââââââââââââââââââââââââââââââââââââââââ
function doPost(e) {
try {
const b = JSON.parse(e.postData.contents);
if (b.action==='addMurid') return out(addMurid(b.data));
if (b.action==='addSurat') return out(addSurat(b.data));
if (b.action==='delMurid') return out(delRow('MURID_PROFIL',b.id));
if (b.action==='delSurat') return out(delRow('SURAT_AMARAN',b.id));
if (b.action==='uploadTegarBulan') return out(uploadTegarBulan(b.bulan,b.data));
if (b.action==='uploadKehadiran') return out(uploadKehadiran(b.data));
return out({error:'unknown action'});
} catch(err) {
return out({error: 'Parse error: ' + err.message});
}
}
// ââ AUTO SETUP ââââââââââââââââââââââââââââââââââââââââââââââââ
function autoSetup() {
Object.keys(HEADERS).forEach(name => ensureSheet(name, HEADERS[name]));
return {success: true, message: 'Semua tab berjaya dicipta'};
}
function ensureSheet(name, headers) {
let ws = SS.getSheetByName(name);
if (!ws) {
ws = SS.insertSheet(name);
ws.appendRow(headers);
ws.getRange(1, 1, 1, headers.length)
.setFontWeight('bold')
.setBackground('#1e3a5f')
.setFontColor('#ffffff');
ws.setFrozenRows(1);
}
return ws;
}
// ââ UPLOAD KEHADIRAN ââââââââââââââââââââââââââââââââââââââââââ
function uploadKehadiran(data) {
if (!Array.isArray(data) || !data.length)
return {error: 'Data kosong'};
const ws = ensureSheet('KEHADIRAN', HEADERS.KEHADIRAN);
const lastRow = ws.getLastRow();
if (lastRow > 1) ws.deleteRows(2, lastRow - 1);
const rows = data.map(d => [
d.Kelas||'', d.Keseluruhan||0,
d.Jan||0, d.Feb||0, d.Mac||0, d.Apr||0,
d.Mei||0, d.Jun||0, d.Jul||0, d.Ogs||0,
d.Sep||0, d.Okt||0, d.Nov||0, d.Dis||0
]);
ws.getRange(2, 1, rows.length, HEADERS.KEHADIRAN.length).setValues(rows);
return {success: true, count: data.length};
}
// ââ KEHADIRAN READ ââââââââââââââââââââââââââââââââââââââââââââ
function getKehadiran() {
const ws = ensureSheet('KEHADIRAN', HEADERS.KEHADIRAN);
const rows = ws.getDataRange().getValues();
const data = [];
for (let i=1; i{const n=parseFloat(String(v).replace('%',''));return isNaN(n)?0:+n.toFixed(2);};
data.push({
Kelas:String(r[0]).trim(), Keseluruhan:p(r[1]),
Jan:p(r[2]), Feb:p(r[3]), Mac:p(r[4]), Apr:p(r[5]),
Mei:p(r[6]), Jun:p(r[7]), Jul:p(r[8]), Ogs:p(r[9]),
Sep:p(r[10]), Okt:p(r[11]), Nov:p(r[12]), Dis:p(r[13])
});
}
return {success:true, data};
}
// ââ MURID TEGAR âââââââââââââââââââââââââââââââââââââââââââââââ
function getTegar() {
const ws = ensureSheet('MURID_TEGAR', HEADERS.MURID_TEGAR);
const rows = ws.getDataRange().getValues();
const data = [];
for (let i=1; i s.getName());
const bulanList = sheets
.filter(n => /^TEGAR_\d{4}_\d{2}$/.test(n))
.map(n => n.replace('TEGAR_',''))
.sort();
return {success:true, data:bulanList};
}
function getTegarByBulan(bulan) {
if (!bulan) return {error:'bulan diperlukan'};
const ws = SS.getSheetByName('TEGAR_' + bulan);
if (!ws) return {success:true, data:[], nota:'Tab tidak wujud'};
const rows = ws.getDataRange().getValues();
const data = [];
for (let i=1; i 0) {
const rows = data.map(d => [
d.nama||'', d.kelas||'', d.tingkatan||'',
d.th_bulan||0, d.dari_lalu||0, d.jumlah||0,
d.status||'', d.trend||'Statik'
]);
ws.getRange(2,1,rows.length,headers.length).setValues(rows);
}
// Kemaskini tab MURID_TEGAR (data terkini)
const mainWs = ensureSheet('MURID_TEGAR', HEADERS.MURID_TEGAR);
const lastRow = mainWs.getLastRow();
if (lastRow > 1) mainWs.deleteRows(2, lastRow - 1);
if (data.length > 0) {
const rows = data.map(d => [
d.nama||'', d.kelas||'', d.tingkatan||'',
d.th_bulan||0, d.dari_lalu||0, d.jumlah||0,
d.status||'', d.trend||'Statik'
]);
mainWs.getRange(2,1,rows.length,headers.length).setValues(rows);
}
return {success:true, bulan:bulan, count:data.length};
}
// ââ MURID PROFIL ââââââââââââââââââââââââââââââââââââââââââââââ
function getMurid() {
const ws = ensureSheet('MURID_PROFIL', HEADERS.MURID_PROFIL);
const rows = ws.getDataRange().getValues();
const data = [];
for (let i=1; i