Detak jantung istirahat normal: 60โ100 bpm. Di bawah 60 = bradikardia, di atas 100 = takikardia.
Kategori ini panduan umum, bukan diagnosis. Jika muncul gejala (pusing berat, nyeri dada, sesak napas, pandangan kabur) atau angka masuk kategori Krisis/Sangat Rendah, segera konsultasi ke tenaga medis.
Belum ada data pada periode ini. Pilih rentang yang lebih panjang atau tambah catatan dulu.
Tren Tekanan Darah
Area hijau muda = rentang normal (sistol 90โ120, diastol 60โ80).
Tren Detak Jantung
Area hijau muda = detak istirahat normal (60โ100 bpm).
Google Sheets โ Cadangan Otomatis
Data utama tersimpan di penyimpanan aplikasi ini. Setiap kali Anda menyimpan, mengubah, atau menghapus catatan, aplikasi juga otomatis mengirim perubahan itu ke Google Sheet Anda. Sebaliknya, Anda juga bisa menarik data dari Sheets ke aplikasi โ berguna saat ganti perangkat atau memulihkan riwayat.
Menambahkan catatan dari Sheets yang belum ada di aplikasi. Catatan yang sudah ada di aplikasi tidak akan ditimpa. Saat aplikasi dibuka dalam keadaan kosong (mis. di perangkat baru), penarikan ini berjalan otomatis.
Gunakan ini kalau ada catatan yang gagal terkirim (mis. saat tidak ada internet ketika menyimpan). Dengan kode Apps Script terbaru, sinkronisasi ulang tidak akan membuat baris duplikat.
๐ Grafik Otomatis di Sheets
Kode Apps Script otomatis membuat sheet bernama "Grafik" berisi 2 grafik garis (Tekanan Darah & Detak Jantung) yang auto-update setiap ada catatan baru โ tanpa perlu buka aplikasi ini.
Penting: agar fitur hapus, ubah, dan anti-duplikat bekerja di Google Sheets, salin kode di bawah ini ke editor Apps Script Anda (Ekstensi โ Apps Script), ganti seluruh kode lama, lalu Deploy โ Manage deployments โ Edit โ New version โ Deploy.
// Multi-profil: setiap orang punya tab sendiri.
// Profil 'Saya' memakai tab lama 'Data'; profil lain memakai 'Data - Nama'.
function sheetNameFor(profil) {
var name = String(profil || '').trim();
if (!name || name === 'Saya') return 'Data';
name = name.replace(/[\\\/\?\*\[\]:]/g, ' ').slice(0, 60);
return 'Data - ' + name;
}
function getOrCreateDataSheet(profil) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var name = sheetNameFor(profil);
var sh = ss.getSheetByName(name);
if (!sh) {
sh = ss.insertSheet(name);
sh.appendRow(['Tanggal','Sistol','Diastol','Detak Jantung','Kategori','Catatan','ISODate']);
}
return sh;
}
// Sheets kadang mengubah string ISO menjadi objek Date.
// Fungsi ini menormalkan keduanya supaya pencocokan selalu berhasil.
function isoOf(v) {
if (Object.prototype.toString.call(v) === '[object Date]') return v.toISOString();
return String(v);
}
function findRowByIso(sh, iso) {
var rows = sh.getDataRange().getValues();
for (var i = rows.length - 1; i >= 1; i--) {
if (isoOf(rows[i][6]) === iso) return i + 1;
}
return -1;
}
function appendEntry(sh, d) {
sh.appendRow([new Date(d.isoDate), Number(d.sistol), Number(d.diastol), Number(d.hr), d.kategori, d.note, d.isoDate]);
var r = sh.getLastRow();
sh.getRange(r, 1).setNumberFormat('dd mmm yyyy, HH:mm');
sh.getRange(r, 7).setNumberFormat('@'); // simpan ISODate sebagai teks
}
function doPost(e) {
var d = e.parameter || {};
if (e.postData && e.postData.contents) {
try { d = JSON.parse(e.postData.contents); } catch (err) { /* pakai e.parameter */ }
}
var sh = getOrCreateDataSheet(d.profil);
if (d.action === 'delete') {
var row = findRowByIso(sh, d.isoDate);
if (row > 0) sh.deleteRow(row);
return ContentService.createTextOutput('deleted');
}
var existing = findRowByIso(sh, d.isoDate);
if (d.action === 'update') {
if (existing > 0) {
sh.getRange(existing, 2, 1, 5).setValues([[Number(d.sistol), Number(d.diastol), Number(d.hr), d.kategori, d.note]]);
} else {
appendEntry(sh, d);
}
return ContentService.createTextOutput('updated');
}
// Anti-duplikat: kalau isoDate sudah ada, jangan tambah baris baru
if (existing > 0) return ContentService.createTextOutput('duplicate');
appendEntry(sh, d);
ensureCharts(sh, d.profil);
return ContentService.createTextOutput('ok');
}
// Sinkronisasi dua arah: aplikasi bisa MENARIK semua data dari Sheet ini.
// action=profiles: kembalikan daftar profil berdasarkan tab 'Data' / 'Data - Nama'.
function doGet(e) {
var p = (e && e.parameter) ? e.parameter : {};
if (p.action === 'profiles') {
var sheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();
var profs = [];
for (var i = 0; i < sheets.length; i++) {
var n = sheets[i].getName();
if (n === 'Data') profs.push('Saya');
else if (n.indexOf('Data - ') === 0) profs.push(n.substring(7));
}
return ContentService.createTextOutput(JSON.stringify({ ok: true, profiles: profs }))
.setMimeType(ContentService.MimeType.JSON);
}
var sh = getOrCreateDataSheet(p.profil);
var rows = sh.getDataRange().getValues();
var out = [];
for (var i = 1; i < rows.length; i++) {
if (!rows[i][6]) continue;
out.push({
isoDate: isoOf(rows[i][6]),
sistol: Number(rows[i][1]),
diastol: Number(rows[i][2]),
hr: Number(rows[i][3]),
kategori: String(rows[i][4] || ''),
note: String(rows[i][5] || '')
});
}
return ContentService.createTextOutput(JSON.stringify({ ok: true, rows: out }))
.setMimeType(ContentService.MimeType.JSON);
}
function ensureCharts(dataSh, profil) {
var ss = dataSh.getParent();
var base = sheetNameFor(profil);
var chartName = base === 'Data' ? 'Grafik' : base.replace('Data - ', 'Grafik - ');
var chartSh = ss.getSheetByName(chartName);
if (chartSh && chartSh.getCharts().length > 0) return;
if (!chartSh) chartSh = ss.insertSheet(chartName);
var bpChart = dataSh.newChart()
.asLineChart()
.addRange(dataSh.getRange('A:A'))
.addRange(dataSh.getRange('B:B'))
.addRange(dataSh.getRange('C:C'))
.setPosition(2, 1, 0, 0)
.setOption('title', 'Tren Tekanan Darah โ ' + (profil || 'Saya'))
.setOption('legend', { position: 'bottom' })
.setOption('hAxis', { title: 'Tanggal' })
.setOption('vAxis', { title: 'mmHg' })
.setOption('width', 700)
.setOption('height', 380)
.build();
chartSh.insertChart(bpChart);
var hrChart = dataSh.newChart()
.asLineChart()
.addRange(dataSh.getRange('A:A'))
.addRange(dataSh.getRange('D:D'))
.setPosition(24, 1, 0, 0)
.setOption('title', 'Tren Detak Jantung โ ' + (profil || 'Saya'))
.setOption('legend', { position: 'none' })
.setOption('hAxis', { title: 'Tanggal' })
.setOption('vAxis', { title: 'bpm' })
.setOption('width', 700)
.setOption('height', 380)
.setOption('colors', ['#B8790A'])
.build();
chartSh.insertChart(hrChart);
}
Ingin ganti ke Spreadsheet lain? Karena URL tertanam di dalam file aplikasi ini, beri tahu Claude URL Web App baru Anda dan aplikasinya akan diperbarui.