Fahami Sistem OMFS
& Belajar Coding dari A hingga Z
Satu sistem hospital sebenar, dibedah perlahan-lahan dalam bahasa harian — tanpa jargon yang memeningkan. Walaupun anda tak pernah menulis satu baris kod, selepas baca panduan ini anda akan faham bagaimana sistem ini hidup dan boleh mula belajar membina perisian sendiri.
📑 Kandungan — Perjalanan Anda
- Mukadimah: cara guna buku ini
- Apa itu Sistem OMFS?
- Konsep asas coding (sangat ringkas)
- Tiga lapisan: Skrin · Otak · Ingatan
- Peralatan (stack) — apa & kenapa
- Ikut satu data dari klik ke simpan
- Peta fail projek
- Database secara mendalam (Ingatan)
- React secara mendalam (Skrin hidup)
- API & HTTP secara mendalam
- Keselamatan & data pesakit
- Modul On-Call: keadilan automatik
- Laporan PG201/PG211 automatik
- Pendaftaran pesakit & nombor lawatan
- Rekod klinikal doktor
- Mesej & temu janji antara staf
- Cerita multi-hospital: satu app, banyak hospital
- Log masuk, peranan & siapa boleh buat apa
- Bayaran & reten kewangan
- PWA: pasang di telefon & kemas kini sendiri
- Pemantauan & amaran ralat (loceng asap)
- Log audit: jejak siapa buat apa
- Wizard pasang kali pertama (first-run)
- Pengujian & kualiti (kenapa selamat)
- Bagaimana ia "hidup" di internet
- Peta jalan belajar coding
- 🛠️ Nak ubah sesuatu — pergi mana?
- 🛠️ Arahan penting (commands)
- 🛠️ 5 perkara jangan dirosakkan
- 🛠️ Bila rosak — cara pulih
- 🛠️ Minta AI ubah dengan selamat
- 🛠️ Minda & bacaan seterusnya
- 💻 Panduan Developer (halaman berasingan) ↗
- Soalan lazim (FAQ)
- Kamus istilah
- Latihan praktikal & penutup
Selamat datang — baca ini dulu
Buku ini ditulis untuk seorang yang tak tahu apa-apa pasal coding tetapi mahu faham sistem ini dan, lama-kelamaan, mahu belajar membina sendiri.
Buku ini akan buat apa?
Ia ibarat lawatan berpandu ke dalam sebuah kilang. Kita masuk satu-satu bilik, tengok apa kerja mesin di situ, kenapa ia penting, dan macam mana semua bilik bekerjasama. Setiap kali ada perkataan teknikal, kita bandingkan dengan benda harian dulu (dapur, restoran, fail hospital), baru kita tunjuk kod sebenar.
Macam mana nak baca?
Baca ikut turutan dari atas ke bawah kali pertama — setiap bahagian dibina atas bahagian sebelumnya. Tak payah hafal. Kalau jumpa kotak kod dan rasa pening, langkau dulu, baca penerangan di bawahnya. Faham "kenapa" lebih penting daripada hafal "macam mana".
Apa itu Sistem OMFS?
OMFS bermaksud Oral & Maxillofacial Surgery — Pembedahan Mulut & Maksilofasial (rawatan gigi, rahang, mulut & muka). Sistem ini ialah buku rekod digital untuk jabatan tersebut di hospital: menggantikan kertas yang senang hilang & susah dikira dengan rekod yang boleh dibuka dari mana-mana, dengan kiraan automatik dan data yang selamat.
Siapa yang guna? (peranan)
Sistem ini tahu siapa anda dan hanya benarkan anda buat kerja anda. Ini dipanggil peranan (role). Nama peranan disimpan dalam Bahasa Melayu:
| Peranan | Dalam sistem | Boleh buat apa |
|---|---|---|
| Kaunter pendaftaran | pendaftar | Daftar pesakit baru, urus pembayaran, lihat senarai. |
| Doktor | doktor | Isi diagnosis & rawatan, lihat rekod pesakit sendiri. |
| Pentadbir klinik | pentadbir | Urus laporan, lihat semua pesakit (bukan urus on-call). |
| Pemilik/pembangun sistem | superadmin | Tetapan hospital, urus pengguna, kawalan penuh. |
| Pengurus on-call | oncall | Jana & kunci jadual bertugas (pintu masuk berasingan). |
Mindmap: seluruh sistem dalam satu pandangan
Konsep asas coding — sangat ringkas
Sebelum kita masuk sistem, mari kenal lima idea asas. Kalau anda faham lima ni, 80% kod akan "nampak masuk akal".
Sebelum mula — bahasa & simbol yang anda akan nampak
Kod sistem ini ditulis terutamanya dalam bahasa bernama TypeScript (sepupu rapat JavaScript — bahasa yang menggerakkan hampir semua laman web). Untuk bahagian lain ada juga HTML & CSS (rupa skrin) dan SQL (bercakap dengan database). Anda tak perlu hafal apa-apa di bawah — cuma kenali corak berulang ini, dan setiap kotak kod selepas ini akan terbaca macam ayat biasa.
| Anda akan nampak | Maksud dalam bahasa harian |
|---|---|
| // ... teks ... | Komen — nota untuk manusia; komputer abaikan. Dalam buku ini, baca komen dahulu — ia menerangkan baris itu. |
| { } | Kurungan besar = "mula … tamat": satu kumpulan langkah yang pergi bersama (satu blok). |
| ( ) | Kurungan biasa = bahan yang diberi kepada sesuatu (input). |
| ; | Koma bertitik = noktah, penghujung satu arahan. |
| => | Anak panah = "… maka buat ini" (cara ringkas menulis fungsi kecil). |
| const | "Buat satu bekas (nilai tetap)". let = bekas yang boleh tukar nilai. |
| function | "Ini satu mesin kecil (fungsi)" — masuk bahan, keluar hasil. |
| if … else | "Kalau … jika tidak …" — buat keputusan. |
| for … of | "Untuk setiap benda dalam senarai …" — ulangan. |
| return | "Pulangkan hasil ini" keluar dari mesin kecil. |
| async / await | Kerja yang ambil sedikit masa (cth. pergi ke database). await = "tunggu siap dahulu, baru sambung". |
1 · Pemboleh ubah (variable) — bekas berlabel
Pemboleh ubah ialah bekas yang ada nama, untuk simpan satu maklumat. Macam bekas dapur bertulis "Gula". Dalam kod kita guna const (nilai tetap) atau let (nilai boleh tukar).
// "bekas" bernama namaHospital, isinya satu ayat teks
const namaHospital = "Hospital Slim River";
// "bekas" bernama umurPesakit, isinya nombor
let umurPesakit = 34;
umurPesakit = 35; // boleh tukar sebab guna 'let'
2 · Fungsi (function) — mesin kecil yang buat satu kerja
Fungsi ialah satu set langkah yang dibungkus dan diberi nama, supaya boleh diguna berulang kali. Masuk sesuatu (bahan), keluar sesuatu (hasil) — macam pengisar: masuk buah, tekan butang, keluar jus.
// fungsi bernama 'kira umur', terima tahun lahir, pulangkan umur
function kiraUmur(tahunLahir) {
const tahunSekarang = 2026;
return tahunSekarang - tahunLahir; // hasil yang dipulangkan
}
kiraUmur(1990); // hasilnya 36
3 · Syarat (if / else) — buat keputusan
Komputer boleh pilih jalan: "kalau ... maka ... jika tidak ...". Macam staf kaunter: kalau pesakit warga emas, beri keutamaan; jika tidak, ikut giliran biasa.
if (umurPesakit >= 60) {
beriKeutamaan(); // kalau 60 ke atas
} else {
ikutGiliranBiasa(); // jika tidak
}
4 · Ulangan (loop) — buat kerja sama banyak kali
Daripada tulis arahan yang sama 100 kali, kita suruh komputer ulang. Macam: "untuk setiap pesakit dalam senarai, cetak namanya."
for (const pesakit of senaraiPesakit) {
cetak(pesakit.nama); // ulang untuk setiap seorang
}
Baca begini: "untuk setiap" (itulah for … of) pesakit di dalam senarai senaraiPesakit, buat apa yang ada dalam kurungan besar { } — iaitu cetak namanya. Senarai 5 orang? Ia ulang 5 kali. Senarai 500? 500 kali. Anda tulis arahan itu sekali sahaja.
5 · Objek (object) — satu rekod dengan banyak medan
Objek ialah satu kad maklumat lengkap — satu benda yang ada banyak butiran berlabel. Macam kad pesakit: ada nama, umur, jantina, alamat, semua dalam satu kad.
const pesakit = {
nama: "Ahmad bin Ali",
umur: 34,
jantina: "Lelaki",
telefon: "012-3456789",
};
pesakit.nama; // ambil medan 'nama' → "Ahmad bin Ali"
Tiga lapisan: Skrin · Otak · Ingatan
Hampir semua aplikasi moden dibina daripada tiga bahagian besar. Faham tiga ni, anda faham rangka sistem ini.
Skrin / Frontend
Apa yang anda nampak & sentuh — butang, borang, senarai, warna. Berjalan dalam pelayar (browser) anda. Dibina dengan React.
🍽️ = Ruang makan & menu restoran
Otak / Backend
Bahagian yang buat keputusan & jaga peraturan — "siapa boleh buat apa", "data ni sah ke tak". Anda tak nampak ia. Dibina dengan Cloudflare Worker + Hono.
👨🍳 = Dapur & chef
Ingatan / Database
Tempat simpan semua data secara kekal — rekod pesakit, lawatan, pengguna. Tersusun dalam jadual. Dibina dengan Cloudflare D1 (SQLite).
🧊 = Stor & peti sejuk berlabel
Bagaimana ketiga-tiganya bercakap
Skrin tak boleh terus sentuh Ingatan — itu bahaya (bayangkan pelanggan masuk dapur ambil sendiri). Skrin minta kepada Otak melalui satu "tetingkap pesanan" yang dipanggil API. Otak periksa permintaan, ambil/ubah data di Ingatan, lepas itu hantar balik jawapan ke Skrin.
Kod sebenar dari sistem ini
Mari tengok satu contoh betul-betul dari sistem OMFS: bahagian Otak yang melayan permintaan "beri saya tetapan hospital". Jangan risau setiap simbol — baca komen (teks selepas //) dan penerangan di bawah.
// Bila skrin minta GET /api/hospital/config ...
app.get('/api/hospital/config', async (c) => {
const svc = new HospitalConfigService(c.env, 'system');
const config = await svc.get(); // pergi ambil dari Ingatan
return ok(c, config); // pulang balik ke Skrin
});
Baca macam ayat biasa: "Bila ada permintaan ambil tetapan hospital, panggil pekerja khas (service), suruh dia get() data, lepas itu pulangkan." Itu sahaja. Otak tak simpan data sendiri — ia minta "pekerja" (service) uruskan urusan database. Kita jumpa pekerja itu sebentar lagi.
Perkataan baru di sini: app = "Otak" kita (pelayan yang menerima permintaan). app.get('/api/...') = "bila ada permintaan jenis ambil (GET) ke alamat ini, buat langkah berikut". async + await = kerja ini pergi ke Ingatan (ambil masa sedikit), jadi "tunggu siap dahulu". new HospitalConfigService(...) = "panggil seorang pekerja khas". Corak lain — const, (c) =>, { }, return — semua ada dalam jadual simbol tadi.
Peralatan (stack) — apa & kenapa
"Stack" bermaksud set alat & bahan yang dipilih untuk bina sistem ini — macam senarai peralatan & mesin dapur sebuah restoran. Berikut yang penting, dijelaskan secara biasa.
| Alat | Apa dia (bahasa biasa) | Analogi dapur | Untuk lapisan |
|---|---|---|---|
| React | Cara membina skrin yang berubah-ubah secara automatik bila data berubah. | Susun atur ruang makan & menu yang kemas kini sendiri. | Skrin |
| TypeScript | Bahasa kod utama. "Type" bermaksud ia semak silap awal (cth. nombor vs teks). | Resipi yang ada senarai bahan + amaran kalau silap bahan. | Semua |
| Vite | Alat yang "masak" kod mentah jadi versi laju untuk pelayar. | Dapur penyediaan — kemas & pek sebelum hidang. | Pembinaan |
| Tailwind CSS | Cara cepat beri gaya/warna/jarak pada skrin guna label ringkas. | Set hiasan & pinggan siap pakai. | Skrin |
| Hono | Rangka ringan untuk Otak melayan permintaan API. | Sistem slip pesanan dapur. | Otak |
| Cloudflare Worker | Komputer awan tempat Otak berjalan, dekat dengan pengguna seluruh dunia. | Dapur yang ada di banyak lokasi. | Otak |
| Cloudflare D1 | Database (jenis SQLite) tempat data disimpan kekal. | Stor & peti sejuk berlabel. | Ingatan |
| Zod | Penjaga pintu — semak data masuk betul bentuk sebelum diterima. | Penjaga dapur periksa slip pesanan sah. | Otak |
| Wrangler | Alat untuk hantar kod naik ke Cloudflare & urus database. | Lori penghantaran ke cawangan. | Operasi |
| Git & GitHub | Buku sejarah setiap perubahan kod + simpanan dalam talian. | Buku resipi induk dengan rekod setiap pindaan. | Operasi |
Jenis "Type" — kenapa TypeScript penting
Ingat objek kad pesakit tadi? Dalam sistem ini, bentuk kad itu ditakrifkan sekali, supaya Skrin dan Otak sama-sama setuju apa itu "Pesakit". Ini kod sebenar:
interface Patient {
id: string;
nama: string;
umur: number;
jantina: 'Lelaki' | 'Perempuan'; // hanya dua nilai ini sah
alamat: string;
telefon: string;
emel?: string; // tanda ? = pilihan, boleh tiada
}
Sekarang kalau ada kod cuba letak jantina: "Kucing", TypeScript akan menjerit awal-awal sebelum sistem dilancarkan — silap ditangkap di meja, bukan di hadapan pesakit. Itulah nilai "type".
Ikut satu data: dari "klik" ke "tersimpan"
Mari ikut satu perjalanan sebenar: seorang superadmin tukar nama & logo hospital dalam halaman Tetapan, lepas itu tekan "Simpan". Apa berlaku di belakang tabir?
Superadmin tekan "Simpan" di Skrin
Halaman Tetapan (React) kumpul semua yang ditaip — nama, alamat, logo — jadi satu objek, dan hantar permintaan PUT /api/hospital/config ke Otak.
Otak semak "tag jawatan"
Sebelum apa-apa, Otak periksa: betul ke ini superadmin? Kalau bukan, terus tolak. (Ini requireRole('superadmin').)
Penjaga pintu (Zod) semak bentuk data
Data yang masuk diperiksa: nama betul teks? logo tak terlalu besar? Kalau bentuk salah, Otak pulang mesej ralat — data buruk tak sampai ke Ingatan.
Pekerja (service) gabung & simpan
Pekerja HospitalConfigService ambil tetapan lama, gabung dengan perubahan baru, sahkan sekali lagi, kemudian tulis ke database.
Catat dalam log audit
Sistem catat "siapa ubah apa & bila" — penting untuk hospital (jejak tanggungjawab).
Pulang hasil → Skrin kemas kini
Otak pulangkan tetapan terkini. Skrin terus papar nama/logo baru — tanpa perlu muat semula halaman.
Tengok "pekerja" itu dalam kod
Inilah HospitalConfigService sebenar — pekerja yang uruskan database supaya Otak tak perlu kotorkan tangan dengan SQL. Fokus pada komen:
class HospitalConfigService {
// terima 'env' (alat sistem) + 'actorId' (siapa buat kerja)
constructor(private env, private actorId) {}
// baca tetapan dari database
async get() {
const row = await this.env.DB
.prepare('SELECT config FROM hospital_config WHERE id = 1')
.first();
if (!row) return DEFAULT_HOSPITAL_IDENTITY; // belum disetel? guna lalai
return JSON.parse(row.config);
}
async update(patch) {
const current = await this.get(); // ambil yang lama
const merged = { ...current, ...patch }; // gabung perubahan
// ... sahkan, tulis ke DB, dan catat audit ...
await logAction(this.env, this.actorId, 'UPDATE_HOSPITAL_CONFIG');
}
}
Perkataan baru: class = "acuan untuk membina pekerja" (kumpul fungsi berkaitan dalam satu tempat). this = "pekerja ini sendiri". SELECT … FROM … ialah SQL — bahasa khas untuk bercakap dengan database ("ambil lajur config dari jadual hospital_config"). { ...current, ...patch } = "salin semua yang lama, kemudian timpa dengan yang baru" (gabung). Selebihnya — async, await, const, if, return — anda sudah kenal.
Peta fail — di mana semua benda tinggal
Projek ini ada ratusan fail. Tapi ia tersusun rapi macam rak buku berlabel. Kalau anda tahu rak mana, anda jumpa apa-apa dalam beberapa saat.
Peta hidup — main & sentuh sendiri
Senarai di atas statik. Ini peta yang sama tetapi hidup: setiap bulatan ialah satu bahagian sistem, dan garisan menyambung bahagian yang bekerja bersama. Tarik bulatan untuk gerakkannya, hover (atau sentuh) untuk serlahkan jirannya, dan guna roda tetikus untuk zum.
📊 (Di sini ada graf interaktif bahagian sistem — buka fail ini dalam pelayar untuk meneroka; ia disembunyikan dalam cetakan PDF.)
Database secara mendalam
Kita masuk lebih dalam ke lapisan "Ingatan". Database ialah tempat data hidup selamanya — faham strukturnya, anda faham 70% sistem.
Jadual, baris, lajur
Database jenis ini (SQLite) menyimpan data dalam jadual — sama persis macam helaian spreadsheet. Setiap baris ialah satu rekod (satu pesakit), setiap lajur ialah satu butiran (nama, umur). Setiap baris ada id unik — macam nombor pendaftaran yang tak berulang.
| id | nama | umur | jantina | telefon |
|---|---|---|---|---|
| p-001 | Ahmad bin Ali | 34 | Lelaki | 012-3456789 |
| p-002 | Siti binti Omar | 52 | Perempuan | 019-8765432 |
Inilah jadual patients. Mudah, bukan? Sebuah database cuma banyak jadual macam ini yang saling berhubung.
Hubungan: induk & anak (yang paling penting)
Seorang pesakit boleh datang banyak kali. Setiap lawatan ialah satu baris dalam jadual visits, dan setiap lawatan boleh ada banyak diagnosis & rawatan. Ini hubungan satu-ke-banyak (induk → anak).
Bagaimana anak tahu siapa induknya? Setiap baris visits menyimpan patient_id — "rujukan" yang menunjuk balik ke baris pesakit. Inilah foreign key (kunci asing).
Migration — pelan pengubahsuaian database
Kita tak ubah struktur database secara rawak. Setiap perubahan ditulis dalam fail migration bernombor (cth. 0045_hospital_config.sql). Sistem ingat migration mana telah dijalankan, jadi ia takkan ulang. Macam pelan pengubahsuaian bangunan bernombor: setiap pindaan direkod ikut turutan, boleh dijejak.
-- bina jadual untuk simpan identiti hospital
CREATE TABLE hospital_config (
id INTEGER PRIMARY KEY, -- nombor unik baris
config TEXT, -- data tetapan (nama, logo, ...)
updated_at TEXT,
updated_by TEXT
);
React secara mendalam — kenapa skrin "hidup"
Lapisan Skrin dibina dengan React. Tiga idea — komponen, props, state — menjelaskan kenapa skrin boleh berubah sendiri tanpa muat semula.
Komponen — blok Lego skrin
Daripada membina satu halaman gergasi, React memecahkannya kepada komponen kecil yang boleh diguna semula: satu butang, satu kad pesakit, satu baris jadual. Halaman besar ialah komponen kecil yang disusun.
Props — "tetapan" yang dihantar ke komponen
Komponen yang sama boleh dipakai berkali-kali dengan data berbeza. Kita "hantar" data masuk melalui props — macam argumen pada fungsi. Satu komponen KadPesakit, dipakai untuk semua pesakit, cuma props-nya berlainan.
// komponen terima 'props': satu objek pesakit
function KadPesakit({ pesakit }) {
return (
<div className="kad">
<h3>{pesakit.nama}</h3>
<p>Umur: {pesakit.umur}</p>
</div>
);
}
// dipakai dua kali, props berbeza
<KadPesakit pesakit={ahmad} />
<KadPesakit pesakit={siti} />
Bahagian yang nampak macam HTML di dalam kod itu dipanggil JSX — cara React membenarkan kita tulis rupa skrin terus di dalam kod.
State — ingatan sementara yang membuat skrin "hidup"
Inilah keajaiban React. State ialah data sementara komponen. Apabila state berubah, React melukis semula bahagian skrin itu secara automatik — tanpa anda menyentuh skrin secara manual.
const [carian, setCarian] = useState('');
// 'carian' = nilai semasa, 'setCarian' = cara ubahnya
// bila pengguna taip, kita ubah state → senarai auto-ditapis
<input onChange={(e) => setCarian(e.target.value)} />
Perkataan baru: useState('') = "minta React simpan satu ingatan kecil, mula dengan kosong". Ia pulangkan dua benda sekali — [carian, setCarian] = nilai semasa + cara mengubahnya. onChange = "bila kandungan kotak taip berubah, buat ini". e.target.value = "apa yang pengguna taip sekarang". Jadi: taip → setCarian ubah ingatan → React lukis semula senarai yang ditapis. Itulah "papan skor" tadi.
API & HTTP secara mendalam
Bagaimana sebenarnya Skrin "bercakap" dengan Otak? Melalui HTTP — bahasa standard internet. Mari pecahkan slip pesanan itu.
Kata kerja HTTP — niat permintaan
Setiap permintaan ke Otak membawa satu kata kerja yang menyatakan niatnya:
| Kata kerja | Maksud | Contoh dalam sistem |
|---|---|---|
| GET | Ambil / baca data (tak ubah apa-apa) | Ambil tetapan hospital, senarai pesakit |
| POST | Cipta data baru | Daftar pesakit baru, hantar mesej |
| PUT | Kemas kini data sedia ada | Simpan tetapan hospital |
| DELETE | Padam data | Buang seorang pengguna |
Alamat (endpoint) & format data (JSON)
Setiap pintu ada alamat: /api/hospital/config. Data yang dihantar pergi-balik ditulis dalam format JSON — senarai "label: nilai" yang kemas & mudah dibaca manusia mahupun mesin.
{
"success": true,
"data": {
"hospitalName": "Hospital Slim River",
"address": { "negeri": "Perak" }
}
}
Kod status — jawapan ringkas Otak
Setiap jawapan datang dengan satu nombor status yang memberitahu apa berlaku:
| Kod | Maksud | Bila berlaku |
|---|---|---|
| 200 | Berjaya ✅ | Permintaan elok, ini datanya. |
| 400 | Permintaan salah | Bentuk data tak betul (penjaga Zod tolak). |
| 401 | Belum log masuk | Tiada pas/tag yang sah. |
| 403 | Tak dibenarkan | Dah log masuk, tapi jawatan tak cukup. |
| 404 | Tak jumpa | Alamat atau rekod tak wujud. |
| 500 | Ralat dalaman | Otak tersilap sendiri — perlu disiasat. |
Keselamatan & data pesakit
Ini sistem hospital — data pesakit itu sulit dan dilindungi undang-undang (PDPA). Keselamatan bukan tambahan; ia dibina ke dalam setiap pintu.
🎫 Tag masuk digital (JWT)
Bila anda log masuk, sistem beri satu "pas" digital bertanda siapa anda & jawatan anda. Setiap permintaan ke Otak kena tunjuk pas ini. Otak tengok pas, baru benarkan kerja. Pas ada tarikh luput — hilang/curi pun tak kekal selamanya.
🔒 Kata laluan tak disimpan terus
Kata laluan anda tak pernah disimpan sebagai teks asal. Ia ditukar jadi kod kusut sehala (bcrypt) — walaupun seseorang curi database, mereka tak nampak kata laluan sebenar. Macam simpan cap jari, bukan kunci.
👮 Tag jawatan di setiap pintu
requireRole('superadmin') dipasang pada pintu-pintu sensitif. Doktor tak boleh masuk pintu tetapan hospital; kaunter tak boleh padam pengguna. Setiap pintu jaga sendiri.
📝 Log audit
Setiap perubahan penting dicatat: siapa, buat apa, bila. Kalau ada pertikaian, ada jejak. Inilah logAction(...) yang kita nampak dalam pekerja tadi.
Modul On-Call: keadilan automatik
Ini contoh terbaik bagaimana kod menyelesaikan masalah manusia yang sukar — membahagikan giliran bertugas (on-call) doktor secara adil, sesuatu yang sentiasa jadi punca pertikaian bila dibuat dengan tangan.
Masalah sebenar
Setiap hari ada doktor yang perlu bersedia dipanggil (on-call), termasuk hujung minggu & cuti umum yang "berat". Buat secara manual, mudah jadi berat sebelah — ada yang kena banyak, ada yang terlepas. Orang rasa tak adil. Sistem ini membina jadual secara automatik dengan keadilan boleh dikira.
Bagaimana "keadilan" dikira
Ciri-ciri penting modul ini
🔄 Tukar giliran (swap)
Doktor boleh mohon tukar giliran; pengurus luluskan. Sistem hantar pemberitahuan ke peti mesej pengurus.
📄 Bulan manual
Bulan lama yang diimport dari kertas ditanda "Manual" & dikecualikan daripada kiraan keadilan supaya tak mengganggu masa depan.
🚪 Pintu masuk berasingan
On-call ada "pintu" log masuk sendiri yang berasingan daripada sistem klinikal — pengasingan keselamatan.
🗓️ Kalendar awam
Staf boleh lihat jadual, muat turun ke kalendar telefon, & semak "kehadiran saya".
Laporan PG201/PG211 automatik
PG201 & PG211 ialah borang laporan rasmi KKM. Mengisinya dengan tangan setiap bulan memenatkan & mudah silap. Sistem ini menjananya secara automatik daripada data yang sudah dimasukkan.
Kumpul data lawatan
Sistem mengambil semua lawatan dalam tempoh dipilih daripada database (Ingatan).
Kira ringkasan
Bilangan pesakit baru vs ulangan, jenis rawatan, jumlah — semua dikira secara automatik (tiada kira-kira manual).
Tuang ke templat Excel
Angka ditulis ke dalam templat rasmi (public/templates/PG201.xlsx) pada sel yang betul, termasuk baris identiti fasiliti hospital.
Muat turun
Fail Excel siap dihantar ke pelayar untuk dimuat turun — sedia untuk dihantar/dicetak.
Pendaftaran pesakit & nombor lawatan
Ini pintu masuk pertama setiap pesakit — kaunter pendaftaran. Di sinilah identiti pesakit direkod & satu nombor lawatan dikeluarkan. Kelihatan mudah, tetapi di sebaliknya sistem bekerja keras supaya tiada dua pesakit berkongsi nombor yang sama.
Tiga jenis pengenalan
Bukan semua pesakit ada MyKad. Sistem terima tiga jenis pengenalan utama, & ia membersihkan (canonicalize) nombor itu sebelum simpan supaya carian kemudian sentiasa jumpa pesakit yang betul.
| Jenis | Contoh dimasukkan | Cara sistem simpan |
|---|---|---|
| MyKad / MyKid | 000907-08-0222 | Digit sahaja: 000907080222 (sengkang dibuang) |
| Pasport / UNHCR | A1234567 / 901-12345 | Huruf+nombor, HURUF BESAR, simbol dibuang |
| No. pesara | Kerajaan / ATM | Disimpan hanya jika status Kerajaan/ATM; jika tidak, dikosongkan |
Logik ini hidup dalam worker/patient-identification.ts (fungsi canonicalIdentificationForStorage) & worker/nombor-pesara.ts. Sebab disimpan "bersih": supaya bila petugas taip 000907-08-0222 atau 000907080222, kedua-duanya jumpa pesakit yang sama.
Walk-in, temu janji, atau on-call?
Setiap lawatan ditanda jenis kedatangannya. Ini cuma tiga "suis" pada rekod lawatan (visits) — penting untuk laporan & aliran kerja.
🚶 Walk-in
Pesakit datang terus tanpa temu janji. Suis walk_in.
📅 Temu janji
Datang ikut tarikh yang ditempah. Suis temu_janji.
🌙 On-call
Kes kecemasan luar waktu. Suis oncall.
Sistem juga membezakan pesakit Baru vs Ulangan (jenis_kehadiran) — penting kerana nombor pendaftaran unik hanya dikuatkuasakan untuk pesakit Baru.
Nombor pendaftaran & cara cegah pertembungan
Setiap pesakit Baru dapat nombor pendaftaran, & sistem tambah akhiran tahun secara automatik (cth. 123 → 123/2026). Masalahnya: dua petugas boleh cuba beri nombor sama pada masa hampir sama. Sistem hadang ini dengan dua lapisan.
Lapisan 2 ialah jaring keselamatan: walaupun dua daftar lolos serentak (perlumbaan), database sendiri ada peraturan unik (migration 0029, partial UNIQUE INDEX) yang menolak pendua. Pra-semak menjadikan kes biasa mesra (cadang nombor seterusnya); peraturan database menangkap kes jarang. Inilah kod sebenar yang menghasilkan mesej itu:
// Nombor ni dah dipakai untuk pesakit Baru?
if (dup) {
// cari nombor terbesar tahun ni, cadang +1
const next = (maxRow?.m ?? 0) + 1;
throw new VisitValidationError(
`Nombor pendaftaran ${noPendaftaran} sudah digunakan... Cuba ${next}/${year}.`
);
}
Rekod klinikal doktor
Selepas pesakit didaftar, doktor membuka lawatan itu & mengisi rekod perubatan — diagnosis, rawatan, & status. Bahagian ini ialah "buku catatan klinikal" digital, dengan dua perlindungan penting: kunci serentak & status reten.
Apa ada dalam satu lawatan
Satu lawatan (visits) boleh mengandungi banyak diagnosis & banyak rawatan — hubungan induk→anak yang kita belajar di Bahagian 07. Setiap diagnosis/rawatan juga tahu doktor mana yang catat (operator_id).
| Bahagian | Jadual | Apa disimpan |
|---|---|---|
| Diagnosis | diagnoses | Kategori utama + subkategori + bilangan |
| Rawatan | managements | Kategori + sub + sub-sub + bilangan |
| Doktor terlibat | visit_doctors | Siapa rawat + status reten setiap doktor |
Status reten: kitaran hidup satu lawatan
Setiap lawatan ada satu status reten — ringkasnya, "rekod ini sudah siap untuk dikira dalam laporan atau belum". Hanya tiga keadaan yang sah:
export type RetenStatus =
'BELUM_SELESAI' | 'SELESAI' | 'BATAL';
| Status | Maksud | Warna lencana |
|---|---|---|
| SELESAI | Rekod lengkap — dikira dalam laporan | Hijau |
| BELUM_SELESAI | Masih perlu dilengkapkan doktor | Merah |
| BATAL (Batal Lawatan) | Lawatan dibatalkan, kekal untuk audit | Kelabu |
Kunci rekod — elak dua doktor tulis serentak
Bila seorang doktor membuka satu rekod untuk diubah, sistem mengunci rekod itu (locked_by, locked_at). Doktor lain nampak ia sedang dikunci & oleh siapa. Selain itu ada kunci sistem (system_locked) — bulan lama yang sudah dilaporkan tak boleh diubah lagi.
Mesej & temu janji antara staf
Klinik perlu komunikasi dalaman: petugas tinggal nota untuk doktor, doktor balas, & semua orang nampak jadual temu janji pesakit. Modul ini dua bahagian — peti mesej & buku temu janji — kedua-duanya ringkas tetapi penting untuk koordinasi.
Kitaran hidup satu mesej
Setiap mesej (messages) ada penghantar, penerima, & isi. Ia boleh dikaitkan dengan seorang pesakit (patient_id) supaya konteks jelas. Satu mesej boleh dihantar ke beberapa penerima sekaligus (to_user_ids).
Mesej vs temu janji
| Mesej | Temu janji | |
|---|---|---|
| Jadual | messages | appointments |
| Tujuan | Nota/komunikasi antara staf | Tempah lawatan pesakit akan datang |
| Maklumat utama | Isi, penerima, kaitan pesakit | Tarikh, masa, doktor, pesakit |
| Siapa nampak | Penghantar & penerima | Semua peranan klinikal (untuk koordinasi) |
Cerita multi-hospital: satu app, banyak hospital
Sistem ini bermula untuk satu hospital, tetapi direka supaya boleh diserahkan kepada hospital lain. Rahsianya: kod yang sama dijalankan untuk semua, tetapi identiti & data setiap hospital terpisah sepenuhnya. Bahagian ini menerangkan bagaimana satu app menjadi banyak hospital tanpa bercampur.
Apa yang dikongsi vs apa yang berbeza
| Dikongsi (sama untuk semua) | Berbeza (per-hospital) |
|---|---|
| Kod aplikasi (ciri, logik, skrin) | Nama, logo, alamat, kontak hospital |
| Struktur database (migration) | Data sebenar (pesakit, lawatan) — terasing |
| Peraturan keselamatan | Akaun Cloudflare & database sendiri |
| Versi yang dikeluarkan | Baris fasiliti laporan, senarai surat, IT tempatan |
Identiti hospital — satu suis tetapan
Setiap hospital simpan identitinya dalam satu baris tunggal (hospital_config, migration 0045). Tetapan ini dibaca oleh laluan awam GET /api/hospital/config — sebab itu logo & nama hospital boleh dipaparkan sebelum sesiapa log masuk.
interface HospitalIdentity {
appName: string; // "Sistem OMFS"
hospitalName: string; // nama penuh hospital
address: { ... }; // alamat
contact: { tel, fax, email, web };
letterhead: { ... }; // kepala surat + penandatangan
logos: { ... }; // logo (base64)
itSupport?: { ... }; // IT tempatan (pilihan)
}
Pasang baharu vs kemas kini semua
Ada dua kerja berbeza, dua skrip berbeza:
🏗️ Provisioning (pasang baharu)
Skrip scripts/provision-hospital.mjs menubuhkan satu hospital baharu dalam akaun Cloudflare percuma mereka sendiri — buat database, salin tetapan, tukar kata laluan benih kepada rawak.
🚀 Fleet release (kemas kini)
Skrip scripts/release-all.mjs mengemas kini SEMUA hospital ke versi sama dengan satu arahan — bukan automatik dari CI, supaya satu komit buruk tak terus melanda semua.
Log masuk, peranan & siapa boleh buat apa
Dalam hospital, bukan semua orang patut nampak semua benda — kerani kaunter, doktor, & pentadbir ada tugas berbeza. Bahagian ini menerangkan bagaimana sistem tahu siapa anda (log masuk) & apa anda dibenarkan buat (peranan).
Lima peranan
Penting: sistem simpan nama peranan dalam Bahasa Melayu — guna nama Inggeris (registrar/doctor/admin) takkan padan.
| Peranan (DB) | Label | Boleh buat |
|---|---|---|
| pendaftar | Kaunter | Daftar pesakit, bayaran, temu janji |
| doktor | Doktor | Lihat/isi rekod klinikal pesakit |
| pentadbir | Admin | Urus pengguna, log audit, laporan, tetapan |
| superadmin | Superadmin | Semua admin + tetapan sistem + alat pembaikan |
| oncall | (pintu on-call) | Hanya laluan jadual on-call — terasing |
Konsep "dua pintu"
Ada satu peranan sementara, system (mod kiosk), yang macam pintu utama berkunci kata laluan sistem. Selepas masuk pintu utama, barulah pengguna pilih peranan sebenar mereka (kerani/doktor/pentadbir). On-call pula ada pintu berasingan sepenuhnya — supaya orang yang urus jadual on-call tak perlu (dan tak boleh) masuk sistem klinikal.
Token: tanda pengenalan sementara
Selepas log masuk, sistem beri pengguna satu token (JWT) — macam pas pelawat bercap masa. Setiap permintaan ke pelayan bawa token ini; pelayan semak ia sah & belum luput. Tempoh sah dipilih ikut peranan:
| Jenis | Tempoh sah | Kenapa |
|---|---|---|
| Peranan biasa | 12 jam | Habis satu syif, log masuk semula |
| Kiosk (system) | 30 hari | Komputer kongsi — elak taip kata laluan setiap kali tukar peranan |
Sumber: worker/core-utils.ts (signJwt). Kata laluan disimpan ditatah (bcrypt, 10 pusingan) — tak pernah disimpan teks biasa. Kata laluan lama (format SHA-256) ditatah semula automatik kepada bcrypt bila pengguna log masuk.
Bayaran & reten kewangan
Setiap lawatan ada cerita wangnya: ada yang bayar penuh, ada yang dijamin kerajaan (e-GL), ada yang dikecualikan (kanak-kanak, warga emas). Sistem mengira semua ini secara automatik & menghasilkan ringkasan kewangan — supaya kaunter tak perlu kira dengan kalkulator.
Tiga kategori pesakit
Sistem mengelaskan setiap lawatan ke salah satu daripada tiga kategori — ini menentukan bagaimana ia muncul dalam laporan:
| Kategori | Siapa | Bayaran |
|---|---|---|
| e-GL egl | Dijamin kerajaan (surat jaminan) | Ditanggung — pengecualian |
| Orang Awam awam | Rakyat biasa (warganegara) | Bayar mengikut kadar |
| Bukan warganegara asing | Etnik "BUKAN WARGANEGARA" | Kadar warga asing |
Ada satu peraturan halus: jika lawatan ber-e-GL tetapi ditanda tanggungan kakitangan kerajaan, ia dikira balik sebagai Orang Awam — bukan e-GL. Logik ini hidup dalam worker/routes/registrar/bayaran.ts.
// Tentukan kategori setiap lawatan
const kat = r.egl
? (r.tanggungan_kakitangan_kerajaan ? 'awam' : 'egl')
: (r.etnik === 'BUKAN WARGANEGARA' ? 'asing' : 'awam');
Apa yang dikira
💵 Bayaran
Yuran pendaftaran + yuran rawatan (bayaran + bayaran_rawatan).
🦷 Gigi palsu
Dikira berasingan (kuantiti + harga).
🆓 Percuma
Dikecualikan ikut umur (warga emas, kanak-kanak) & peringkat sekolah.
PWA: pasang di telefon & kemas kini sendiri
Walaupun ia laman web, sistem ini boleh "dipasang" di telefon atau komputer macam app biasa, & boleh menerima versi baru secara automatik. Teknologi ini dipanggil PWA (Progressive Web App). Bahagian ini menerangkan bagaimana ia kemas kini sendiri — dengan berhati-hati.
Kenapa "prompt", bukan "auto"
Alat PWA (workbox) ada dua mod: autoUpdate (reload sendiri) & prompt (tunggu izin). Sistem ini sengaja guna prompt — kerana autoUpdate boleh reload di tengah kerja klinikal & hilangkan data yang belum disimpan.
VitePWA({
// 'prompt' (bukan 'autoUpdate'): workbox TAK reload sendiri.
// autoUpdate salah di sini sebab ia BOLEH hilangkan data pesakit.
registerType: 'prompt',
})
Polisi kemas kini hibrid
Bila versi baru tersedia, masa ia dipasang bergantung pada keadaan pengguna — tiga senario:
Kawalan ini hidup dalam src/components/PwaUpdatePrompt.tsx. Bila tiba masanya, app hantar isyarat SKIP_WAITING kepada service worker, kemudian baru muat semula — versi baru dipasang tanpa kejutan.
Pemantauan & amaran ralat — bila sistem rosak, siapa tahu dulu?
Bayangkan satu hospital jauh guna sistem ini, dan tiba-tiba sesuatu rosak pada pukul 3 pagi. Tiada siapa sedang tengok skrin. Bagaimana penyelenggara tahu? Modul ini ialah loceng asap sistem — bila ralat berlaku, ia menghantar amaran terus kepada manusia, tanpa sesiapa perlu mengawasi papan pemuka.
Tiga jenis amaran
Sistem mengelaskan ralat kepada tiga peringkat — setiap satu warna berbeza dalam saluran amaran, supaya penyelenggara nampak sekali pandang mana yang paling kritikal:
| Jenis | Bila berlaku | Contoh |
|---|---|---|
| 🔴 Ralat Worker critical | Pelayan tersilap teruk semasa melayan permintaan | Pangkalan data tak dapat dicapai semasa simpan lawatan |
| 🟠 Cron gagal cron | Tugas berjadual (auto-kemas) gagal | Pembersihan mesej lama tersekat |
| 🟡 Ralat klien client | Skrin pengguna terhantuk ralat di pelayar | Satu halaman gagal papar di telefon kerani |
Perjalanan satu amaran
Sebelum mesej amaran keluar dari sistem, ia melalui dua "pintu pelindung" penting: buang maklumat pesakit, kemudian tapis ulangan. Barulah ia sampai ke saluran hospital.
Pintu pelindung 1: buang maklumat pesakit
Ini sistem kesihatan — satu mesej ralat tak boleh sekali-kali membawa nombor IC, telefon, atau emel pesakit keluar dari pelayan. Sebelum apa-apa dihantar, sistem menyapu bersih corak ini dahulu & gantikan dengan label kosong. Lebih baik tersilap buang terlalu banyak daripada terlepas satu:
// IC Malaysia 991231-14-5678 → [IC]
.replace(/\b\d{6}-\d{2}-\d{4}\b/g, '[IC]')
// Emel → [EMAIL], telefon → [PHONE]
.replace(/\b[\w.+-]+@[\w-]+\.[\w.-]+\b/g, '[EMAIL]')
Pintu pelindung 2: tapis ulangan
Kalau satu kerosakan menyebabkan ralat pada setiap permintaan, sistem boleh hantar beratus mesej dalam seminit — membanjiri saluran sehingga tak berguna. Sebab itu amaran yang sama hanya dihantar sekali setiap seminit. Tujuannya kawalan banjir, bukan kira tepat.
Log audit — jejak "siapa buat apa"
Dalam sistem hospital, bukan cukup sekadar data betul — kita juga perlu tahu siapa mengubahnya & bila. Kalau satu rekod pesakit dipadam atau kata laluan ditukar, mesti ada jejak yang tak boleh dinafikan. Itulah tugas log audit.
Apa yang dicatat
Setiap baris audit menyimpan empat perkara sahaja — ringkas, tetapi cukup untuk menjawab "siapa, buat apa, bila":
| Medan | Maksud |
|---|---|
| id | Nombor unik baris ini |
| user_id | Siapa — diambil dari token log masuk, bukan dari borang |
| action | Apa — cth. SETUP_PASSWORD_CHANGED |
| created_at | Bila — cap masa tindakan |
Bagaimana satu catatan dibuat
await env.DB.prepare(
'INSERT INTO audit_logs (id, user_id, action, created_at) VALUES (?, ?, ?, ?)'
).bind(log_id, userId, action, new Date().toISOString()).run();
Wizard pasang kali pertama (first-run)
Ini melengkapkan cerita serahan multi-hospital (§16): bila hospital baru memasang sistem buat kali pertama, ia tak sepatutnya terus terbuka dengan kata laluan lalai & tetapan kosong. Wizard memandu pentadbir hospital langkah demi langkah — & memaksa tukar kata laluan dahulu — sebelum sistem boleh digunakan.
Penjaga pintu: satu suis
Satu suis tunggal dalam pangkalan data — settings.setup_completed — menentukan sama ada wizard perlu dijalankan. Ia bermula pada 0 (belum siap) untuk setiap pemasangan baharu. Selagi ia 0, "penjaga pintu" (SetupGate) mengalihkan pengguna ke /setup:
Enam langkah wizard
Langkah pertama (tukar kata laluan) wajib — sistem tak boleh dibuka dengan kata laluan provision yang dikongsi. Langkah 1–4 boleh dilangkau ("Buat nanti") & disiapkan kemudian:
| Langkah | Apa | Wajib? |
|---|---|---|
| 0 · Kata laluan | Tukar kata laluan sementara kepada milik sendiri | Ya |
| 1 · Identiti | Nama hospital, alamat, logo letterhead | Boleh nanti |
| 2 · Pengguna | Tambah akaun kerani / doktor / pentadbir | Boleh nanti |
| 3 · Templat on-call | Pilih corak rotasi (OMFS / tunggal / dua peringkat) | Boleh nanti |
| 4 · Semak katalog | Sahkan senarai diagnosis & rawatan | Boleh nanti |
| 5 · Selesai | Tandakan wizard siap → suis jadi 1 | — |
Status persediaan: laluan terbuka
Skrin perlu tahu sama ada wizard sudah siap sebelum sesiapa log masuk — jadi pemeriksaan status ialah laluan awam (tiada token diperlukan), tetapi menyiapkan wizard tetap memerlukan superadmin:
// PUBLIC — frontend boleh semak sebelum log masuk
app.get('/api/setup/status', async (c) => {
const row = await c.env.DB.prepare(
'SELECT setup_completed FROM settings WHERE id = ?'
).bind('default').first();
return ok(c, { setupCompleted: (row?.setup_completed ?? 0) === 1 });
});
Pengujian & kualiti — kenapa ia selamat
Dalam perisian hospital, satu bug boleh memudaratkan. Sebab itu sebelum apa-apa kod diterbitkan, ia mesti melepasi beberapa "pintu pemeriksaan" automatik.
Pintu pemeriksaan sebelum terbit
| Pemeriksaan | Apa ia buat | Analogi |
|---|---|---|
| Semak jenis (tsc) | Pastikan tiada salah jenis data (nombor vs teks). | Eja-semak tatabahasa |
| Bina (build) | Cuba "masak" seluruh kod — gagal jika ada ralat. | Cuba masak resipi penuh |
| Ujian (test) | Ratusan ujian automatik sahkan logik masih betul. | Rasa setiap hidangan |
| Lint | Pastikan gaya kod kemas & konsisten. | Periksa kebersihan dapur |
| Saiz fail | Halang fail jadi terlalu besar & sukar dijaga. | Had berat bagasi |
Ujian ditulis dahulu (TDD)
Untuk logik penting, jurutera kadang-kadang menulis ujian dahulu, sebelum kod sebenar — menyatakan "hasil yang betul sepatutnya begini", barulah menulis kod sehingga ujian lulus. Macam menetapkan jawapan peperiksaan dahulu, kemudian memastikan kerja anda menepatinya.
Bagaimana ia "hidup" di internet
Menulis kod di komputer tak bermakna ia terus boleh digunakan orang. Ia perlu "diterbitkan" (deploy) ke awan. Inilah perjalanan dari komputer pembangun ke skrin pengguna.
Cerita istimewa: satu sistem, banyak hospital
Sistem ini direka untuk diberi kepada banyak hospital. Setiap hospital mendapat salinan dalam akaun awan percuma mereka sendiri, dengan nama, logo, jadual on-call, & tetapan tersendiri. Pemilik menyimpan satu set "skrip" untuk memasang hospital baru dan mengemas kini semua hospital ke versi sama dengan satu arahan. Inilah sebab seni bina berlapis & "tiada nama ditanam keras" itu penting — ia membolehkan sistem berkembang tanpa menulis semula.
Peta jalan belajar coding (guna sistem ini sebagai buku teks)
Anda tak perlu kursus mahal. Sistem sebenar ini ialah buku teks terbaik. Ikut peta ini perlahan-lahan — anggap setiap perhentian seminggu-dua, ikut kelapangan.
Baca & kenali
Habiskan buku ini. Buka aplikasi sebenar, klik setiap halaman. Cuba padankan apa yang anda nampak dengan tiga lapisan (Skrin/Otak/Ingatan). Matlamat: rasa "biasa", bukan mahir.
HTML, CSS, sikit JavaScript
HTML = rangka halaman, CSS = gaya/warna, JavaScript = gerak/logik. Belajar di freeCodeCamp atau The Odin Project (percuma). Cuba bina satu halaman "Kad Pesakit" ringkas sendiri.
JavaScript → TypeScript
Dalami lima asas (variable, function, if, loop, object) sehingga selesa. Kemudian belajar "type" — kenapa ia tangkap silap awal. Sekarang baca semula contoh kod dalam buku ini; ia akan jadi lebih jelas.
React + Tailwind
Belajar bagaimana React buat skrin berubah ikut data. Buka folder src/components/, pilih satu komponen kecil, cuba faham & ubah teks/warna pada salinan tempatan.
API, Hono & SQL asas
Faham bagaimana satu "pintu" API dibina (lihat worker/routes/hospital.ts). Belajar SQL asas: SELECT, INSERT, UPDATE. Inilah bahasa bercakap dengan Ingatan.
Git, terminal & pelayar
Belajar simpan perubahan dengan Git, jalankan npm run dev untuk buka salinan tempatan, & guna "Developer Tools" pelayar untuk mengintip apa berlaku.
Ubahan kecil sebenar
Tukar satu label, betulkan satu ejaan, tambah satu medan kecil. Uji secara tempatan. Inilah saat anda bertukar dari "pembaca" kepada "pembina".
| Nak belajar | Sumber percuma yang bagus | Kenapa |
|---|---|---|
| Asas web (HTML/CSS/JS) | freeCodeCamp · The Odin Project | Berstruktur, banyak latihan langsung. |
| JavaScript mendalam | javascript.info | Penerangan jelas, contoh banyak. |
| React | react.dev (Learn) | Dokumen rasmi, ada tutorial. |
| SQL/database | SQLBolt · sqlite.org | Latihan SQL terus dalam pelayar. |
| Git | "Learn Git Branching" (visual) | Belajar Git secara visual & main-main. |
"Saya nak ubah sesuatu — pergi mana?"
Ini halaman paling penting untuk anda sebagai penyelenggara baru. Kebanyakan perubahan harian tak perlu sentuh kod langsung — boleh terus dalam aplikasi.
Boleh ubah TANPA kod (dalam aplikasi)
| Saya nak ubah… | Pergi ke (dalam app) |
|---|---|
| Nama / logo / alamat / no. telefon hospital | Log masuk superadmin → Tetapan Hospital |
| Baris fasiliti pada laporan PG201/PG211 | Tetapan Hospital → bahagian Laporan PG201/PG211 |
| Senarai penerima surat rasmi | Tetapan Hospital → Senarai rujukan |
| Nombor IT / sokongan tempatan hospital (di "Bantuan") | Tetapan Hospital → IT / Sokongan Tempatan |
| Tambah staf, tukar peranan, beri kuasa urus on-call | Log masuk superadmin/pentadbir → Urus Pengguna |
| Staf berhenti / bertukar — keluarkan akses mereka | Urus Pengguna → butang Nyahaktif di baris pengguna (bukan "padam") |
| Peraturan jadual on-call (kumpulan, slot, keadilan, cuti umum) | Modul On-Call (pengurus) → tab Tetapan Rotasi / Cuti Awam |
| Set semula kata laluan pengguna | Urus Pengguna → pengguna berkenaan |
Perlu sentuh kod (minta bantuan AI + uji dahulu)
Untuk yang ini, ingat peta fail di Bahagian 06: src/ = Skrin, worker/ = Otak, migrations/ = struktur Ingatan.
| Saya nak ubah… | Pergi ke (dalam kod) | Susah? |
|---|---|---|
| Teks/perkataan pada skrin (tajuk, label butang) | Fail .tsx dalam src/pages/ atau src/components/ — cari teks itu, ubah, simpan | Mudah |
| Warna / saiz / jarak sesuatu elemen | Kelas Tailwind dalam fail .tsx yang sama (cth. text-red-600) | Mudah |
| Mesej ralat atau ayat dalam Bahasa Melayu | Cari teks itu dalam src/ atau worker/ | Mudah |
| Siapa boleh buat sesuatu tindakan (kebenaran) | worker/routes/*.ts — cari requireRole(...) | Sederhana |
| Tambah medan baru pada pesakit/lawatan | Perlu 3 tempat serentak: shared/types.ts + migration baru + borang di src/ + route di worker/ | Besar |
| Tukar struktur database (jadual/lajur) | Fail migration BARU dalam migrations/ (jangan edit yang lama!) | Besar |
Arahan penting (commands)
"Command" ialah arahan yang anda taip dalam terminal (tetingkap hitam untuk bercakap dengan komputer). Anda tak perlu hafal — simpan jadual ini sebagai rujukan.
| Arahan | Apa ia buat | Bila guna |
|---|---|---|
| npm install | Muat turun semua "bahan" (library) projek perlu | Kali pertama buka projek di komputer baru |
| npm run dev | Buka salinan app di komputer anda (localhost) | Bila buat & uji perubahan |
| npm run build | "Masak" kod untuk versi sebenar — tangkap sebahagian ralat | Sebelum terbit |
| npx tsc --noEmit | Semak silap jenis data | Sebelum terbit |
| npm test | Jalankan semua ujian automatik | Selepas setiap perubahan — gagal = jangan terbit |
| git status | Tunjuk fail mana yang anda ubah | Sebelum simpan (commit) |
| git add . && git commit -m "…" && git push | Simpan kerja ke GitHub — push ini yang menerbitkan app | Selepas siap satu kerja |
| npm run db:migrate:local | Pasang semua migration ke database tempatan | Setup pertama / selepas tarik migration baru |
| wrangler d1 execute … --remote | Jalankan soalan SQL pada database LANGSUNG | Menyemak data sebenar (hati-hati!) |
5 perkara jangan sekali-kali rosakkan
Hampir semua silap boleh dibetulkan. Lima ini berbeza — ia boleh menyebabkan kehilangan data kekal atau kebocoran keselamatan. Hafal lima ini sahaja.
Jangan padam baris pesakit terus dari database
Memadam pesakit akan memadam semua lawatan, diagnosis & rawatannya secara automatik (cascade) — tiada "undo". Sebab itu app sengaja tiada butang "padam pesakit"; pesakit tersilap ditanda batal, bukan dipadam.
Jangan jalankan DROP TABLE visits tanpa backup
Sama bahaya cascade — boleh memadam semua data klinikal. Jika terpaksa bina semula jadual, ikut corak selamat dalam migration 0026 tepat-tepat (salin jadual anak dulu).
Jangan edit fail migration lama
Database ingat migration mana sudah dijalankan. Mengedit yang lama mencipta percanggahan & merosakkan deploy. Sentiasa buat fail baru dengan nombor seterusnya.
Jangan commit kunci rahsia ke git
Fail seperti .dev.vars mengandungi kata laluan/kunci. Ia disenaraikan dalam .gitignore supaya git abaikan. Jangan paksa masuk — sekali tersilap commit, ia kekal dalam sejarah git selamanya.
Jangan guna nama peranan Inggeris dalam kod
Database simpan peranan dalam BM: pendaftar, doktor, pentadbir, superadmin. Menulis if (role === 'admin') akan gagal senyap (nilainya 'pentadbir', bukan 'admin').
Bila rosak — cara pulih
Jangan panik. Sistem ini ada beberapa "jaring keselamatan". Berikut langkah ikut jenis masalah.
App tunjuk ralat kepada pengguna
Cuba ulang sendiri
Buka app, klik ikut langkah yang sama untuk lihat ralat itu sendiri.
Tengok "Console"
Tekan F12 dalam pelayar → tab Console. Cari mesej merah — itu petunjuk pertama.
Cari mesej itu dalam kod
Minta AI: "cari mesej ralat ini dalam kod: …". Itu bawa anda ke tempat masalah.
Tersilap padam data
Cloudflare D1 ada "mesin masa" 7 hari (PITR). Langkah selamat:
# 1. Cari titik masa SEBELUM kerosakan
npx wrangler d1 time-travel info omfs-database --timestamp "2026-06-15T00:30:00Z"
# 2. Backup keadaan SEKARANG dulu (selamat)
npx wrangler d1 export omfs-database --remote --output=./backup.sql
# 3. Pulihkan ke titik tadi
npx wrangler d1 time-travel restore omfs-database --bookmark "<id-dari-langkah-1>"
Deploy rosak — app jadi tak berfungsi
Database tiba-tiba lambat
Biasanya kerana "index" hilang. Jalankan EXPLAIN QUERY PLAN SELECT …; jika ia kata SCAN TABLE, buat migration baru dengan CREATE INDEX.
Minta AI ubah dengan selamat
Anda akan banyak guna AI (seperti Claude Code) untuk menulis perubahan. AI sangat membantu tetapi boleh "terpesong" — menulis semula benda yang berfungsi atau melanggar peraturan tersembunyi. Gunakan senarai semak ini.
Sebelum minta AI
• Beritahu APA masalah, bukan BAGAIMANA selesaikan.
• Beritahu DI MANA agaknya (cth. fail tertentu).
• Beritahu APA JANGAN DISENTUH ("jangan ubah skema database").
Selepas AI siap — semak
• npx tsc --noEmit lulus?
• npm run build lulus?
• npm test semua hijau?
• AI ubah lebih banyak fail dari dijangka? Tanya kenapa.
• Ada any / @ts-ignore / .catch(()=>{})? Itu tanda bahaya.
• Sentuh migrations/? Fail BARU ke edit lama?
• Route baru — ada semak requireRole?
Minda penyelenggara & bacaan seterusnya
Anda tak perlu faham setiap baris kod untuk menyelenggara app ini. Anda cuma perlu enam perkara.
1 · Tahu setiap folder untuk apa
Bahagian 06 — peta fail.
2 · Tahu arahan asas
Bahagian 17.
3 · Tahu apa jangan rosak
Bahagian 18.
4 · Tahu minta AI selamat
Bahagian 20.
5 · Tahu cara pulih bila rosak
Bahagian 19.
6 · Belajar 1 konsep seminggu
Peta jalan, Bahagian 15.
Fail rujukan dalam projek (untuk dibaca bila bersedia)
| Fail | Apa di dalamnya |
|---|---|
| HANDOVER.md | Panduan serahan versi Inggeris (asas bahagian penyelenggara ini) |
| ARCHITECTURE_PLAIN.md | Penerangan seni bina lebih dalam, bahasa biasa |
| LEARN_FULLSTACK.md | Laluan pembelajaran terikat pada kod ini |
| FINDINGS_AND_PLAN_PLAIN.md | Penemuan audit + rancangan, bahasa biasa |
| CLAUDE.md | Arahan untuk AI bila menyunting repo ini — berbaloi diimbas |
| graphify-out/GRAPH_REPORT.md | Peta semua fail kod & cara ia berhubung |
| docs/ | Operasi armada, provisioning hospital baru, sokongan |
Panduan Developer — halaman berasingan
Rujukan teknikal penuh untuk developer kini ialah dokumen tersendiri — sama terperinci seperti panduan ini, dengan menu sisi, carian, dan graf modul interaktif sendiri.
Soalan lazim (FAQ)
Soalan yang sering ditanya orang bukan-coder yang baru hendak mula. Jawapan ringkas & jujur.
Adakah saya akan rosakkan sistem kalau tersilap?
Tidak, selagi anda berlatih pada salinan tempatan (di komputer sendiri), bukan sistem hospital sebenar. Setiap perubahan juga direkod oleh Git — boleh "undo". Membuat silap ialah cara belajar coding; persekitaran ini direka supaya silap itu selamat.
Berapa lama untuk jadi boleh?
Untuk faham & baca kod: beberapa minggu konsisten. Untuk membuat perubahan kecil sendiri: beberapa bulan. Untuk selesa membina ciri: setahun-dua dengan latihan berterusan. Yang penting bukan kelajuan, tetapi konsistensi — sikit setiap hari mengalahkan banyak sekali-sekala.
Perlukah komputer mahal atau berkuasa tinggi?
Tidak. Mana-mana komputer riba biasa yang boleh layari internet sudah memadai untuk projek ini. Alat-alatnya (editor kod, pelayar, terminal) semuanya percuma & ringan.
Perlukah saya pandai matematik?
Tidak banyak. Kebanyakan coding ialah logik & menyusun langkah, bukan matematik tinggi. Kalau anda boleh ikut resipi masakan & merancang perjalanan, anda sudah ada fikiran yang diperlukan.
Bahasa mana patut saya belajar dahulu?
Untuk projek ini: HTML & CSS dahulu (rangka & gaya), kemudian JavaScript, kemudian TypeScript & React. Belajar yang projek ini gunakan supaya setiap perkara yang anda pelajari boleh terus dilihat dalam kod sebenar.
Saya bukan dari bidang IT — boleh ke?
Boleh. Ramai pengaturcara baik datang dari bidang lain (perubatan, perniagaan, pendidikan). Malah, memahami masalah sebenar (seperti aliran kerja klinik) kadangkala lebih bernilai daripada mengetahui kod — dan itu kelebihan anda.
Bagaimana saya minta bantuan tanpa melanggar PDPA?
Terangkan skrin yang anda lihat & langkah yang anda buat, jangan sekali-kali kongsi nama/IC/butiran pesakit. Cth: "Bila saya tekan Simpan di halaman Tetapan, muncul ralat merah" — bukan menyertakan rekod pesakit. Ini melindungi data & tetap membolehkan orang membantu anda.
Kamus istilah — rujuk bila perlu
Setiap istilah dengan maksud biasa & analoginya. Tak perlu hafal — kembali ke sini bila terjumpa perkataan asing.
- Frontend
- Bahagian yang dilihat & disentuh pengguna. (Ruang makan)
- Backend
- Bahagian logik & keputusan, tak dilihat. (Dapur)
- Database
- Tempat simpan data kekal, tersusun jadual. (Stor)
- API
- Tetingkap permintaan antara Skrin & Otak. (Slip pesanan)
- React
- Alat bina skrin yang kemas kini sendiri ikut data.
- TypeScript
- Bahasa kod yang semak silap jenis data awal.
- Variable
- Bekas berlabel untuk simpan satu nilai.
- Function
- Mesin kecil: masuk sesuatu, keluar hasil.
- Object
- Satu kad maklumat dengan banyak medan berlabel.
- SQL
- Bahasa untuk bercakap dengan database.
- Zod
- Penjaga yang semak bentuk data masuk betul.
- JWT
- Pas/tag masuk digital selepas log masuk.
- Git
- Buku sejarah setiap perubahan kod.
- Deploy
- Menerbitkan kod supaya orang boleh guna.
- Migration
- Arahan membina/mengubah struktur database.
- Service
- "Pekerja" yang uruskan database untuk Otak.
Latihan praktikal & penutup
Membaca sahaja tak cukup. Cuba latihan ini — tak perlu sempurna, cuma cuba. Buat pada salinan tempatan, bukan sistem sebenar hospital.
Latihan 1 — Jadi detektif lapisan
Buka aplikasi. Pilih satu skrin (cth. senarai pesakit). Teka: bahagian mana datang dari Skrin, dari mana datang datanya (Otak → Ingatan)? Tulis dalam ayat sendiri.
Latihan 2 — Baca satu fail kod
Buka worker/routes/hospital.ts. Tanpa risau setiap simbol, cuba terangkan dalam BM apa yang dua "pintu" (GET & PUT) itu buat. Bandingkan dengan penerangan di Bahagian 3 & 5.
Latihan 3 — Cari "type"
Buka shared/types.ts. Cari interface Patient. Senaraikan semua medan yang wajib dan yang pilihan (petua: cari tanda ?).
Latihan 4 — Ubahan pertama anda
Jalankan npm run dev. Cari satu teks pada skrin, ubah ejaannya dalam kod, simpan, & tengok ia berubah di pelayar. Tahniah — anda baru sahaja "coding".