Guest User

Certificate Automatic via Google Apps Script

a guest
Nov 4th, 2026
13
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 10.15 KB | None | 0 0
  1. /**
  2. * SERTIFIKAT SEMINAR ONLINE (FINAL - TEMA BIRU KUNING)
  3. * Google Form -> Google Sheet -> Apps Script Web App
  4. * Kolom Sheet: A=Timestamp, B=Nama, C=Email, D=Instansi, ... F=Status
  5. */
  6.  
  7. // ===== KONFIGURASI =====
  8. var CONFIG = {
  9. judul: 'Peran Sistem Informasi dalam Pengambilan Keputusan',
  10. judulSorot: 'Berbasis Big Data',
  11. tanggal: 'Sabtu, 15 November 2026',
  12. tempat: 'Zoom / Aula Fakultas',
  13. penyelenggara: 'Kelompok 3 Big Data',
  14. penandatangan: 'Ketua Panitia',
  15. prefix: 'SEM-2026-',
  16. sheetIndex: 0
  17. };
  18.  
  19. // ===== ROUTER =====
  20. function doGet(e) {
  21. var p = e.parameter || {};
  22. if (p.no) return halamanVerifikasi_(p.no);
  23. if (p.email || p.nama) return halamanSertifikat_(p.email, p.nama);
  24. return halamanCari_();
  25. }
  26.  
  27. // ===== DATA & HELPER =====
  28. function ambilData_() {
  29. var sh = SpreadsheetApp.getActiveSpreadsheet().getSheets()[CONFIG.sheetIndex];
  30. return sh.getDataRange().getValues();
  31. }
  32.  
  33. function buatNomor_(baris) {
  34. return CONFIG.prefix + ('000' + (baris - 1)).slice(-3);
  35. }
  36.  
  37. function norm_(s) {
  38. return String(s || '').toLowerCase().replace(/\s+/g, ' ').trim();
  39. }
  40.  
  41. function esc_(s) {
  42. return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;')
  43. .replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#39;');
  44. }
  45.  
  46. // ===== TEMPLATE HALAMAN =====
  47. function bungkus_(isi) {
  48. var css = [
  49. '*{box-sizing:border-box}',
  50. 'body{margin:0;padding:16px;background:#e9eef7;font-family:Poppins,Arial,sans-serif;',
  51. '-webkit-print-color-adjust:exact;print-color-adjust:exact}',
  52.  
  53. /* kotak form & verifikasi */
  54. '.box{max-width:430px;margin:50px auto;background:#fff;padding:32px;text-align:center;',
  55. 'border-radius:16px;border-top:8px solid #fbb724;box-shadow:0 8px 30px rgba(10,36,99,.15)}',
  56. '.box h2{color:#0a2463;margin:0 0 8px}',
  57. '.box p{color:#445}',
  58. '.box input{padding:12px;width:100%;margin:10px 0;font-size:16px;border:2px solid #c9d8f5;border-radius:10px}',
  59. '.btn{padding:12px 26px;font-size:16px;font-weight:700;cursor:pointer;border:0;border-radius:999px;',
  60. 'background:#0a2463;color:#fff}',
  61. '.btn.y{background:#fbb724;color:#0a2463}',
  62.  
  63. /* sertifikat */
  64. '.wrap{width:100%;overflow:hidden}',
  65. '.stage{width:1000px;height:707px;position:relative;transform-origin:top left}',
  66. '.cert{position:absolute;inset:0;overflow:hidden;color:#0a2463;text-align:center;',
  67. 'background:radial-gradient(circle at 1px 1px,rgba(47,111,219,.13) 1.5px,transparent 1.6px) 0 0/22px 22px,',
  68. 'linear-gradient(135deg,#ffffff 0%,#eaf2ff 55%,#dbe8ff 100%)}',
  69. '.frame{position:absolute;inset:20px;border:2px solid rgba(10,36,99,.18);border-radius:14px}',
  70.  
  71. /* sudut dekoratif */
  72. '.sd{position:absolute;width:330px;height:200px;background:#fbb724;clip-path:polygon(0 0,100% 0,0 100%)}',
  73. '.sn{position:absolute;width:290px;height:172px;background:#0a2463;clip-path:polygon(0 0,100% 0,0 100%)}',
  74. '.sb{position:absolute;width:210px;height:125px;background:#2f6fdb;clip-path:polygon(0 0,100% 0,0 100%)}',
  75. '.tl{top:0;left:0}',
  76. '.br{bottom:0;right:0;transform:rotate(180deg)}',
  77.  
  78. /* isi */
  79. '.isi{position:absolute;left:110px;right:110px;top:46px}',
  80. '.pill{display:inline-block;background:#0a2463;color:#fff;font-weight:700;letter-spacing:6px;',
  81. 'font-size:15px;padding:8px 28px;border-radius:999px}',
  82. '.judul{font-size:62px;font-weight:800;letter-spacing:8px;margin:12px 0 0;line-height:1;color:#0a2463}',
  83. '.nomor{font-size:13px;color:#2f6fdb;font-weight:600;margin-top:8px;letter-spacing:1px}',
  84. '.kecil{font-size:16px;color:#34456b;margin:20px 0 4px}',
  85. '.nama{display:inline-block;font-size:44px;font-weight:800;color:#0a2463;padding:0 26px 4px;',
  86. 'border-bottom:6px solid #fbb724;line-height:1.15;max-width:100%}',
  87. '.inst{font-size:17px;font-weight:600;color:#2f6fdb;margin-top:10px}',
  88. '.ket{font-size:16px;color:#34456b;margin:16px 0 8px;line-height:1.5}',
  89. '.ket b.role{background:#0a2463;color:#fff;padding:2px 14px;border-radius:999px;font-size:14px;letter-spacing:1px}',
  90. '.tema{font-size:25px;font-weight:800;line-height:1.25;color:#0a2463}',
  91. '.sorot{display:inline-block;background:#fbb724;color:#0a2463;font-weight:800;font-size:25px;',
  92. 'padding:3px 22px;border-radius:999px;margin-top:8px}',
  93. '.tgl{font-size:14px;color:#34456b;margin-top:12px}',
  94.  
  95. /* footer */
  96. '.qr{position:absolute;left:50%;transform:translateX(-50%);bottom:26px;width:300px;background:#fff;',
  97. 'padding:10px 10px 8px;border-radius:14px;border:2px solid #fbb724;text-align:center;',
  98. 'box-shadow:0 4px 14px rgba(10,36,99,.2);color:#0a2463}',
  99. '.qr img{display:block;width:86px;height:86px;margin:0 auto}',
  100. '.qr .cap{font-size:11px;font-weight:600;margin-top:6px;line-height:1.35}',
  101. '.qr .nm{font-size:13px;font-weight:800;margin-top:2px}',
  102.  
  103. '.noprint{text-align:center;margin:18px}',
  104.  
  105. /* cetak */
  106. '@page{size:A4 landscape;margin:0}',
  107. '@media print{body{padding:0;background:#fff}.noprint{display:none}',
  108. '.wrap{height:794px!important;overflow:hidden}',
  109. '.stage{transform:scale(1.1225)!important}}'
  110. ].join('');
  111.  
  112. var js =
  113. '<script>function fit(){var w=document.getElementById("wrap");if(!w)return;' +
  114. 'var s=document.getElementById("stage");var k=Math.min(1,(window.innerWidth-32)/1000);' +
  115. 's.style.transform="scale("+k+")";w.style.height=(707*k)+"px";}' +
  116. 'window.addEventListener("resize",fit);window.addEventListener("load",fit);' +
  117. 'window.addEventListener("beforeprint",function(){var w=document.getElementById("wrap");' +
  118. 'var s=document.getElementById("stage");if(!w)return;' +
  119. 'w.style.height="794px";s.style.transform="scale(1.1225)";});' +
  120. 'window.addEventListener("afterprint",fit);</script>';
  121.  
  122. return HtmlService.createHtmlOutput(
  123. '<!DOCTYPE html><html><head><meta charset="utf-8">' +
  124. '<meta name="viewport" content="width=device-width,initial-scale=1">' +
  125. '<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;600;700;800&display=swap" rel="stylesheet">' +
  126. '<title>Sertifikat Seminar</title><style>' + css + '</style></head><body>' +
  127. isi + js + '</body></html>'
  128. ).setTitle('Sertifikat Seminar')
  129. .addMetaTag('viewport', 'width=device-width, initial-scale=1');
  130. }
  131.  
  132. // ===== HALAMAN =====
  133. function halamanCari_(pesan) {
  134. return bungkus_(
  135. '<div class="box"><h2>Unduh Sertifikat Seminar</h2>' +
  136. '<p>' + esc_(CONFIG.judul) + ' ' + esc_(CONFIG.judulSorot) + '</p>' +
  137. (pesan ? '<p style="color:#d32f2f;font-weight:700">' + esc_(pesan) + '</p>' : '') +
  138. '<p>Isi data berikut sesuai saat pendaftaran untuk membuka sertifikat.</p>' +
  139. '<form method="get" action="' + ScriptApp.getService().getUrl() + '" target="_top" style="text-align:left">' +
  140. '<label style="font-weight:700;color:#0a2463">Nama Lengkap</label>' +
  141. '<input type="text" name="nama" placeholder="Nama sesuai pendaftaran" required>' +
  142. '<label style="font-weight:700;color:#0a2463">Email</label>' +
  143. '<input type="email" name="email" placeholder="[email protected]" required>' +
  144. '<div style="text-align:center;margin-top:8px">' +
  145. '<button class="btn" type="submit">Tampilkan Sertifikat</button></div></form></div>'
  146. );
  147. }
  148.  
  149. function halamanSertifikat_(email, nama) {
  150. email = norm_(email);
  151. nama = norm_(nama);
  152. if (!email || !nama) return halamanCari_('Nama lengkap dan email wajib diisi.');
  153. var rows = ambilData_();
  154. for (var i = 1; i < rows.length; i++) {
  155. if (norm_(rows[i][2]) === email && norm_(rows[i][1]) === nama) {
  156. var nomor = buatNomor_(i + 1);
  157. var urlVerif = ScriptApp.getService().getUrl() + '?no=' + nomor;
  158. var qr = 'https://api.qrserver.com/v1/create-qr-code/?size=200x200&margin=0&data=' +
  159. encodeURIComponent(urlVerif);
  160. var status = rows[i][5] ? ' &bull; ' + esc_(String(rows[i][5]).toUpperCase()) : '';
  161. var namaSize = String(rows[i][1]).length > 28 ? 'style="font-size:34px"' : '';
  162.  
  163. var cert =
  164. '<div class="wrap" id="wrap"><div class="stage" id="stage"><div class="cert">' +
  165. '<div class="frame"></div>' +
  166. '<div class="sd tl"></div><div class="sn tl"></div><div class="sb tl"></div>' +
  167. '<div class="sd br"></div><div class="sn br"></div><div class="sb br"></div>' +
  168. '<div class="isi">' +
  169. '<div class="pill">SEMINAR NASIONAL</div>' +
  170. '<div class="judul">SERTIFIKAT</div>' +
  171. '<div class="nomor">No. ' + esc_(nomor) + '</div>' +
  172. '<div class="kecil">Diberikan kepada</div>' +
  173. '<div class="nama" ' + namaSize + '>' + esc_(rows[i][1]) + '</div>' +
  174. '<div class="inst">' + esc_(rows[i][3]) + '</div>' +
  175. '<div class="ket">atas partisipasinya sebagai <b class="role">PESERTA' + status + '</b> dalam seminar</div>' +
  176. '<div class="tema">' + esc_(CONFIG.judul) + '</div>' +
  177. '<div class="sorot">' + esc_(CONFIG.judulSorot) + '</div>' +
  178. '<div class="tgl">' + esc_(CONFIG.tanggal) + ' &bull; ' + esc_(CONFIG.tempat) + '</div>' +
  179. '</div>' +
  180. '<div class="qr"><img src="' + qr + '" alt="QR">' +
  181. '<div class="nm">' + esc_(CONFIG.penandatangan) + ' &bull; ' + esc_(CONFIG.penyelenggara) + '</div>' +
  182. '<div class="cap">Tanda tangan digital &bull; scan untuk verifikasi</div></div>' +
  183. '</div></div></div>' +
  184. '<div class="noprint"><button class="btn y" onclick="window.print()">Cetak / Simpan PDF</button></div>';
  185. return bungkus_(cert);
  186. }
  187. }
  188. return halamanCari_('Data tidak cocok. Pastikan nama lengkap dan email sama persis dengan saat mendaftar.');
  189. }
  190.  
  191. function halamanVerifikasi_(no) {
  192. no = String(no).trim();
  193. var rows = ambilData_();
  194. for (var i = 1; i < rows.length; i++) {
  195. if (buatNomor_(i + 1) === no) {
  196. return bungkus_('<div class="box"><h2 style="color:#12a150">&#10004; SERTIFIKAT VALID</h2>' +
  197. '<p>Nomor: <b>' + esc_(no) + '</b></p>' +
  198. '<p>Atas nama: <b>' + esc_(rows[i][1]) + '</b></p>' +
  199. '<p>' + esc_(CONFIG.judul) + ' ' + esc_(CONFIG.judulSorot) + '</p></div>');
  200. }
  201. }
  202. return bungkus_('<div class="box"><h2 style="color:#d32f2f">&#10006; TIDAK VALID</h2>' +
  203. '<p>Nomor sertifikat tidak ditemukan.</p></div>');
  204. }
Advertisement
Add Comment
Please, Sign In to add comment