Panduan Lengkap Sistem OMFS

Anda nak masuk sebagai siapa?

Tiga laluan ikut tujuan anda. Anda boleh tukar bila-bila masa melalui butang 🏠 Laman utama atau menu sisi.

📋

Pengetahuan am

Ringkasan sahaja: apa itu sistem ini, untuk siapa, dan apa ciri-cirinya. Untuk staf, pengurus, atau sesiapa yang mahu gambaran besar dalam 2 minit.

Lihat ringkasan →
🐣

Belajar coding dari sifar

Tak tahu coding langsung? Mula di sini: apa itu kod, cara membaca kod, 4 bahasa (HTML/CSS/TypeScript/SQL), peta bina-dari-kosong, dan cara guna Claude Code sebagai cikgu.

Mula dari asas →
📖

Faham kod & belajar

Bukan coder, tapi mahu faham kod & seni bina, dan akhirnya belajar membina sendiri. Panduan penuh: konsep asas → lapisan sistem → modul → peta jalan belajar.

Mula belajar coding →
💻

Programmer / selenggara

Anda boleh baca kod. Rujukan teknikal penuh + resipi membuat perubahan dari mudah ke sukar, supaya anda boleh selenggara & ubah sistem.

Ke Panduan Developer →
Panduan Sistem OMFS
Buku Panduan Mesra Bukan-Coder  ·  Edisi Bahasa Melayu

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.

📖 Bahasa Melayu penuh 🏥 Contoh dari sistem sebenar 🧩 Analogi + gambar rajah 🗺️ Peta jalan belajar coding 🖨️ Boleh simpan PDF
00 Mukadimah

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.

🛠️
Anda akan menguruskan aplikasi ini?
Terus ke Panduan Penyelenggara (Bahagian 16–21)
Rujukan harian: "saya nak ubah X — pergi mana?", arahan penting, apa jangan sekali-kali rosakkan, & cara pulih bila silap. Guna juga kotak Cari di atas untuk jumpa apa-apa istilah dengan pantas.

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".

Butang Simpan PDF Nampak butang "Muat Turun PDF" di penjuru atas kanan? Tekan ia bila-bila masa untuk simpan buku ini sebagai fail PDF (pilih "Save as PDF" dalam tetingkap cetak). Anda boleh baca offline, hantar pada orang lain, atau cetak.
Analogi induk yang kita guna sepanjang buku Bayangkan sistem ini sebagai sebuah RESTORAN. Pelanggan duduk di ruang makan (skrin yang anda nampak). Mereka beri pesanan kepada pelayan, yang bawa pesanan ke dapur (otak yang buat keputusan). Dapur ambil bahan dari stor & peti sejuk berlabel (tempat simpan data). Ingat tiga tempat ni — kita akan ulang banyak kali.
01 Gambaran Besar

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.

Gambaran besar sudah diringkaskan Apa sistem ini, untuk siapa, dan apa cirinya sudah dirumus dalam satu halaman pendek: Pengetahuan Am →. Buku ini tidak mengulang ringkasan itu — mulai sini kita fokus pada bagaimana ia dibina, modul demi modul. Cukup pegang tiga kerja teras: rekod pesakit, reten / laporan PG201/PG211, dan jadual on-call yang adil.

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:

PerananDalam sistemBoleh buat apa
Kaunter pendaftaranpendaftarDaftar pesakit baru, urus pembayaran, lihat senarai.
DoktordoktorIsi diagnosis & rawatan, lihat rekod pesakit sendiri.
Pentadbir klinikpentadbirUrus laporan, lihat semua pesakit (bukan urus on-call).
Pemilik/pembangun sistemsuperadminTetapan hospital, urus pengguna, kawalan penuh.
Pengurus on-calloncallJana & kunci jadual bertugas (pintu masuk berasingan).
Analogi peranan Macam dalam restoran: pelayan ambil pesanan, chef masak, kasir kira duit, pengurus pegang kunci pejabat. Tak masuk akal kalau pelayan boleh tukar gaji semua orang — jadi setiap orang ada tag jawatan yang hadkan apa mereka boleh sentuh. Inilah cara sistem lindungi data pesakit.

Mindmap: seluruh sistem dalam satu pandangan

Sistem OMFS aplikasi hospital 👤 Pesakit.daftar & rekod 🩺 Lawatandiagnosis & rawatan 📊 Retenlaporan PG201/211 🗓️ On-Calljadual adil 💬 Mesejantara staf ⚙️ Tetapanidentiti hospital
Fakta penting Sistem ini direka untuk diedarkan ke banyak hospital. Setiap hospital dapat salinan sendiri dengan nama, logo & tetapan tersendiri — itulah sebab banyak benda boleh diubah dalam "Tetapan" dan tiada nama hospital ditanam keras (hardcoded) dalam kod.
02 Asas Coding

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".

Apa itu "kod"? Kod ialah satu senarai arahan yang sangat teliti untuk komputer — macam resipi masakan. Komputer sangat patuh tetapi sangat bodoh: ia buat tepat apa yang ditulis, ikut urutan. Kalau resipi tertulis "masuk garam" tapi tak cakap berapa, ia takkan teka — ia akan tersilap atau berhenti. Sebab itu kod kena jelas dan tepat.

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 nampakMaksud 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"Kalaujika tidak …" — buat keputusan.
for … of"Untuk setiap benda dalam senarai …" — ulangan.
return"Pulangkan hasil ini" keluar dari mesin kecil.
async / awaitKerja yang ambil sedikit masa (cth. pergi ke database). await = "tunggu siap dahulu, baru sambung".
Janji Anda tak perlu ingat jadual ini. Rujuk balik bila perlu. Tujuannya satu sahaja: bila nampak { atau => atau async, anda tak terkejut — anda tahu ia cuma corak biasa.

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).

contoh ringkas
// "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
// 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.

syarat
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."

ulangan
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.

objek
const pesakit = {
  nama: "Ahmad bin Ali",
  umur: 34,
  jantina: "Lelaki",
  telefon: "012-3456789",
};

pesakit.nama; // ambil medan 'nama' → "Ahmad bin Ali"
Itu sahaja asasnya Bekas (variable), mesin kecil (function), keputusan (if), ulangan (loop), kad maklumat (object). Hampir semua kod dalam sistem ini ialah gabungan lima benda ini, cuma disusun jadi lebih besar. Tahniah — anda baru baca kod sebenar!
Chef mesra memegang kad 'Resipi = Kod' di dapur klinik
Kod itu macam resipi: senarai langkah teliti — bahan (variable), mesin kecil (function), & simpang keputusan (if) — yang komputer ikut tepat-tepat.
03 Seni Bina

Tiga lapisan: Skrin · Otak · Ingatan

Hampir semua aplikasi moden dibina daripada tiga bahagian besar. Faham tiga ni, anda faham rangka sistem ini.

Lapisan 1

Skrin / Frontend

Apa yang anda nampak & sentuh — butang, borang, senarai, warna. Berjalan dalam pelayar (browser) anda. Dibina dengan React.

🍽️ = Ruang makan & menu restoran

Lapisan 2

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

Lapisan 3

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.

SKRIN React (browser) OTAK Worker + Hono INGATAN D1 database 1. minta (API) 2. ambil data 3. pulang data 4. papar di skrin
API dalam bahasa biasa API = "tetingkap pesanan" antara pelayan dan dapur. Pelayan tak masak sendiri; dia hulur slip pesanan ikut format yang dapur faham, dan dapur hulur balik pinggan siap. Selagi kedua-duanya setuju format slip, mereka boleh bekerja tanpa masuk campur kerja satu sama lain.

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.

worker/routes/hospital.ts
// 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.

Kenapa berlapis-lapis macam ni? Supaya setiap bahagian boleh diubah tanpa rosakkan yang lain. Tukar warna butang? Sentuh Skrin sahaja. Tukar peraturan siapa boleh sunting? Sentuh Otak sahaja. Pengasingan ini buat sistem besar kekal teratur — bukan "kuih lapis bercampur".
04 Peralatan

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.

AlatApa dia (bahasa biasa)Analogi dapurUntuk lapisan
ReactCara membina skrin yang berubah-ubah secara automatik bila data berubah.Susun atur ruang makan & menu yang kemas kini sendiri.Skrin
TypeScriptBahasa kod utama. "Type" bermaksud ia semak silap awal (cth. nombor vs teks).Resipi yang ada senarai bahan + amaran kalau silap bahan.Semua
ViteAlat yang "masak" kod mentah jadi versi laju untuk pelayar.Dapur penyediaan — kemas & pek sebelum hidang.Pembinaan
Tailwind CSSCara cepat beri gaya/warna/jarak pada skrin guna label ringkas.Set hiasan & pinggan siap pakai.Skrin
HonoRangka ringan untuk Otak melayan permintaan API.Sistem slip pesanan dapur.Otak
Cloudflare WorkerKomputer awan tempat Otak berjalan, dekat dengan pengguna seluruh dunia.Dapur yang ada di banyak lokasi.Otak
Cloudflare D1Database (jenis SQLite) tempat data disimpan kekal.Stor & peti sejuk berlabel.Ingatan
ZodPenjaga pintu — semak data masuk betul bentuk sebelum diterima.Penjaga dapur periksa slip pesanan sah.Otak
WranglerAlat untuk hantar kod naik ke Cloudflare & urus database.Lori penghantaran ke cawangan.Operasi
Git & GitHubBuku sejarah setiap perubahan kod + simpanan dalam talian.Buku resipi induk dengan rekod setiap pindaan.Operasi
Kenapa pilih awan (cloud), bukan komputer hospital sendiri? Letak sistem di Cloudflare (awan) bermakna tak perlu beli & jaga server sendiri di hospital, ia laju di mana-mana, dan ada salinan automatik. Untuk projek ini, ia juga percuma pada peringkat awal — setiap hospital boleh ada salinan sendiri tanpa kos pelayan.

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:

shared/types.ts
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".

05 Cerita Sebenar

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:

worker/services/HospitalConfigService.ts
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.

SQL itu apa? Baris SELECT config FROM hospital_config WHERE id = 1 ialah SQL — bahasa untuk bercakap dengan database. Terjemahannya: "Ambilkan ruangan 'config' dari jadual 'hospital_config', baris yang id-nya 1." Macam cakap pada pekerja stor: "Ambilkan fail nombor 1 dari kabinet hospital_config."
Kenapa berlapis pengawal? Perhatikan ada tiga lapis pengawal sebelum data disimpan: (1) tag jawatan, (2) penjaga bentuk data (Zod), (3) sahkan semula dalam pekerja. Dalam sistem hospital, data salah/rosak boleh bahaya — jadi kita "tak percaya membuta", kita semak di setiap pintu.
06 Peta Projek

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.

omfs-system/ ├─ src/ # SKRIN — semua yang pengguna nampak (React) │ ├─ pages/ # satu folder ikut peranan: doctor/, registrar/, ... │ ├─ components/ # bahagian skrin boleh guna semula (butang, jadual) │ └─ contexts/ # data dikongsi seluruh app (cth. tetapan hospital) ├─ worker/ # OTAK — pelayan API (Cloudflare Worker + Hono) │ ├─ routes/ # "pintu" API ikut topik: hospital.ts, oncall.ts ... │ ├─ services/ # "pekerja" yang uruskan database │ └─ contracts/ # penjaga bentuk data (Zod) ├─ shared/ # "type" dikongsi Skrin + Otak (cth. Patient) ├─ migrations/ # INGATAN — arahan bina/ubah jadual database ├─ public/ # fail tetap: logo, templat laporan Excel └─ docs/ # dokumen panduan (termasuk fail ini boleh diletak)

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.

Tip: tarik bulatan · hover untuk serlah jiran · roda tetikus untuk zum · seret latar untuk geser.

📊 (Di sini ada graf interaktif bahagian sistem — buka fail ini dalam pelayar untuk meneroka; ia disembunyikan dalam cetakan PDF.)

Petua mencari Nak ubah apa yang nampak? Pergi src/. Nak ubah peraturan/logik? Pergi worker/. Nak ubah struktur data? Pergi migrations/.
Analogi src/ = ruang depan kedai · worker/ = dapur · migrations/ = pelan susun atur stor · shared/ = senarai istilah yang depan & dapur kongsi.
07 Pendalaman · Ingatan

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.

idnamaumurjantinatelefon
p-001Ahmad bin Ali34Lelaki012-3456789
p-002Siti binti Omar52Perempuan019-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).

patients seorang pesakit visits satu lawatan diagnoses diagnosis managements rawatan visit_doctors doktor terlibat 1 → banyak

Bagaimana anak tahu siapa induknya? Setiap baris visits menyimpan patient_id — "rujukan" yang menunjuk balik ke baris pesakit. Inilah foreign key (kunci asing).

Analogi induk-anak Bayangkan fail kabinet: satu fail tebal bagi setiap pesakit (induk). Di dalamnya, banyak helaian lawatan (anak). Setiap helaian lawatan tertulis di sudut atas "Milik fail: p-001" — itulah foreign key, supaya helaian tak tersesat ke fail orang lain.

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.

migrations/0045_hospital_config.sql (ringkas)
-- 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
);
Bahaya "padam berantai" (cascade delete) Sebab anak menunjuk pada induk, memadam induk boleh memadam semua anaknya secara automatik. Kalau seseorang padam jadual visits tanpa berhati-hati, semua diagnosis & rawatan boleh lenyap kekal. Sebab itulah projek ini ada peraturan ketat: salin jadual anak dulu sebelum sentuh induk. Ini contoh kenapa jurutera berpengalaman bergerak perlahan dekat data.
08 Pendalaman · Skrin

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.

Halaman Senarai Pesakit Jadual Pesakit Baris (kad pesakit) Baris (kad pesakit) Lencana status Bar carian + butang "Daftar Baru"

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.

konsep komponen React
// 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.

Analogi state Macam papan skor digital di padang. Anda cuma tukar nombor di belakang (state); papan terus berubah sendiri di hadapan penonton. Anda tak perlu padam dan tulis semula angka di papan secara manual. "Tukar data → skrin ikut" — itulah maksud reactive dalam nama "React".
useState — ingatan komponen
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.

Kenapa fail dipecah kecil? Projek ini ada peraturan: komponen tak boleh terlalu besar (cth. melebihi 250–400 baris). Bila satu fail jadi gemuk, ia dipecah jadi komponen lebih kecil. Sebab? Fail kecil senang difahami, diuji, & diubah — macam menyimpan barang dalam banyak kotak berlabel, bukan satu longgokan besar.
09 Pendalaman · Sambungan

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 kerjaMaksudContoh dalam sistem
GETAmbil / baca data (tak ubah apa-apa)Ambil tetapan hospital, senarai pesakit
POSTCipta data baruDaftar pesakit baru, hantar mesej
PUTKemas kini data sedia adaSimpan tetapan hospital
DELETEPadam dataBuang seorang pengguna
Analogi kata kerja Pada slip pesanan dapur: GET = "tengok menu", POST = "buat pesanan baru", PUT = "tukar pesanan saya", DELETE = "batalkan pesanan". Niat jelas, dapur tak silap.

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.

contoh jawapan JSON
{
  "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:

KodMaksudBila berlaku
200Berjaya ✅Permintaan elok, ini datanya.
400Permintaan salahBentuk data tak betul (penjaga Zod tolak).
401Belum log masukTiada pas/tag yang sah.
403Tak dibenarkanDah log masuk, tapi jawatan tak cukup.
404Tak jumpaAlamat atau rekod tak wujud.
500Ralat dalamanOtak tersilap sendiri — perlu disiasat.
Petua menyiasat Bila sesuatu rosak, langkah pertama jurutera ialah tengok kod status. 401/403? Masalah log masuk/kebenaran. 400? Data dihantar salah bentuk. 500? Bug di Otak. Nombor ini ibarat suhu badan pesakit — petunjuk pertama ke mana hendak menyiasat.
10 Keselamatan

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.

Peraturan emas data Dalam sistem ini ada amaran besar: jangan sesekali padam jadual visits tanpa salin dulu jadual anaknya (diagnosis, rawatan) — kerana database akan "ikut padam berantai" dan data klinikal boleh lenyap kekal. Inilah jenis pengetahuan yang menjadikan seseorang jurutera yang berhati-hati, bukan sekadar penaip kod.
PDPA dalam bahasa biasa PDPA = undang-undang lindungi data peribadi. Praktiknya di sini: jangan kongsi nama/IC pesakit di tempat tak sepatutnya (cth. bila minta bantuan teknikal, terangkan skrin & langkah, bukan butiran pesakit). Sistem ini sengaja direka untuk menjadikan perkara betul itu mudah.
11 Modul · Kajian Kes

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

Input senarai doktor cuti / tak ada rekod tugas lalu cuti umum Enjin Keadilan kira "tally" setiap orang beri berat (weights) elak 2 hari berturut had tugas / kredit cuti Jadual adil siap, telus, boleh dikunci pengurus
Analogi keadilan Bayangkan tujuh adik-beradik berkongsi tugas jaga rumah malam. Cara adil: simpan papan markah berapa kali setiap orang dah jaga. Giliran seterusnya diberi kepada yang markahnya paling rendah, sambil ikut peraturan ("jangan dua malam berturut", "yang baru balik cuti dapat sikit dulu"). Itulah tally + weights + constraints dalam enjin ni.

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".

Pengajaran reka bentuk Modul ini menunjukkan corak yang sama berulang: data masuk → peraturan diproses → hasil keluar → boleh disemak manusia. Sistem tak menggantikan pertimbangan manusia (pengurus masih boleh kunci/ubah) — ia cuma membuat kerja berat & kira-kira yang membosankan, dengan adil & pantas.
12 Modul · Kajian Kes

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.

Analogi Macam mesin pengisi borang automatik: anda dah kumpul semua resit sepanjang bulan (data lawatan); mesin baca semua, jumlahkan, & isi borang cukai rasmi dengan kemas — anda cuma tekan "cetak". Yang penting: borang kekal mengikut format standard KKM, cuma baris fasiliti boleh diubah ikut hospital.
Kenapa guna templat, bukan bina borang dari kosong? Borang KKM ada format tetap yang mesti dipatuhi. Dengan menggunakan fail templat sebenar & cuma "menuang" angka ke dalamnya, laporan sentiasa kelihatan rasmi & betul — sistem tak "mereka" rupa borang sendiri.
Sel hitam & kelabu = "jangan isi" Borang PG211 sengaja menghitam/mengelabukan sel untuk kombinasi yang mustahil, & sistem tidak menulis apa-apa ke dalamnya: Ibu Mengandung untuk umur 6 tahun ke bawah; Bersekolah untuk umur luar 7–19 (pra-sekolah 5–6 tak dikira); Pesara untuk umur bawah 30; & Punca Rujukan untuk lawatan Ulangan. Lajur "Kedatangan" (D) pula sentiasa ditulis 0 bila kosong supaya semakan automatik borang (lajur AJ/AK: jumlah jantina & etnik mesti sama dengan jumlah) kekal BETUL. Kaedah salin helaian "Gabungan" juga diperbetulkan supaya warna sel tak rosak (Excel tak lagi minta "repair"). Aturan sama ini dikuatkuasakan di kaunter pendaftaran — petugas tak boleh menanda kombinasi mustahil ini (kotak dimatikan automatik mengikut umur/jantina).
13 Modul · Kajian Kes

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.

Analogi kaunter klinik Bayangkan kaunter pendaftaran di klinik: petugas tanya kad pengenalan, buka fail (atau buka yang baru kalau pesakit kali pertama), dan beri nombor giliran. Sistem ini buat perkara sama — cuma ia juga memastikan nombor giliran itu betul-betul unik, tak silap salin, & tak bertembung walaupun dua petugas mendaftar serentak.

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.

JenisContoh dimasukkanCara sistem simpan
MyKad / MyKid000907-08-0222Digit sahaja: 000907080222 (sengkang dibuang)
Pasport / UNHCRA1234567 / 901-12345Huruf+nombor, HURUF BESAR, simbol dibuang
No. pesaraKerajaan / ATMDisimpan 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. 123123/2026). Masalahnya: dua petugas boleh cuba beri nombor sama pada masa hampir sama. Sistem hadang ini dengan dua lapisan.

No. dimasuk + akhiran /2026 Lapisan 1: pra-semak No. ni dah dipakai untuk pesakit Baru tahun ni? Belum → simpan ✓ lawatan didaftar Sudah → mesej mesra "Cuba 124/2026" (cadang nombor seterusnya)

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:

worker/services/VisitService.ts (ringkas)
// 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}.`
  );
}
Kunci sistem (system lock) Pentadbir boleh "kunci" tarikh lama supaya rekod bulan yang sudah dilaporkan tak boleh diubah. Jika petugas cuba daftar lawatan untuk tarikh sebelum tarikh kunci, sistem tolak dengan mesej jelas — bukan ralat bisu. Logik: worker/system-lock.ts (getSystemLockDate).
🎨 Slot kartun — jana di Google Flow guna prompt dalam komen HTML di atas, kemudian ganti kotak ini dengan imej base64.
Kaunter pendaftaran: setiap pesakit dapat satu nombor unik — sistem cadang nombor seterusnya kalau yang dipilih sudah dipakai.
14 Modul · Kajian Kes

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.

Analogi fail perubatan Bayangkan satu fail pesakit yang tebal. Setiap kali pesakit datang, doktor selit satu helaian baru: apa penyakitnya (diagnosis), apa rawatan diberi (management), & tandakan "helaian ini sudah lengkap atau belum". Sistem ini cuma fail itu dalam bentuk digital — tetapi dua doktor tak boleh tulis pada helaian sama serentak, supaya tiada catatan bertindih.

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).

BahagianJadualApa disimpan
DiagnosisdiagnosesKategori utama + subkategori + bilangan
RawatanmanagementsKategori + sub + sub-sub + bilangan
Doktor terlibatvisit_doctorsSiapa 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:

shared/types.ts
export type RetenStatus =
  'BELUM_SELESAI' | 'SELESAI' | 'BATAL';
BELUM_SELESAI baru daftar / dalam rawatan SELESAI dikira dalam laporan ✓ BATAL lawatan dibatalkan doktor lengkapkan batalkan
StatusMaksudWarna lencana
SELESAIRekod lengkap — dikira dalam laporanHijau
BELUM_SELESAIMasih perlu dilengkapkan doktorMerah
BATAL (Batal Lawatan)Lawatan dibatalkan, kekal untuk auditKelabu
Peraturan: status lawatan mengatasi status doktor Satu lawatan boleh ada beberapa doktor, masing-masing dengan statusnya. Tetapi jika status peringkat lawatan ialah SELESAI atau BATAL, itulah yang dipaparkan — ia mengatasi status per-doktor. Peraturan ini ditulis dalam CLAUDE.md (§6.1) supaya setiap skrin paparkan benda sama: Selesai · Belum Selesai · Batal Lawatan, tanpa nama doktor dilekatkan di depan.

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.

Kenapa kunci ini penting Tanpa kunci, dua doktor boleh menyimpan perubahan berbeza pada rekod sama — satu menimpa kerja yang lain tanpa sesiapa sedar. Dalam data perubatan, kehilangan catatan senyap begini berbahaya. Kunci memaksa giliran: seorang siap dulu, baru yang lain boleh masuk.
15 Modul · Kajian Kes

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.

Analogi papan nota dapur Mesej macam nota pelekat (sticky note) di papan dapur restoran: "Meja 5 minta tanpa bawang". Bila kerja siap, nota ditanda selesai & dialih ke laci arkib — tak dibuang (boleh rujuk semula), cuma tak lagi mengganggu papan. Temu janji pula macam buku tempahan meja resepsionis: siapa, bila, dengan siapa.

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).

Hantar tulis + pilih penerima Peti masuk penerima baca Selesai done_at + done_by Arkib archived_at

Mesej vs temu janji

MesejTemu janji
Jadualmessagesappointments
TujuanNota/komunikasi antara stafTempah lawatan pesakit akan datang
Maklumat utamaIsi, penerima, kaitan pesakitTarikh, masa, doktor, pesakit
Siapa nampakPenghantar & penerimaSemua peranan klinikal (untuk koordinasi)
Cerdik: snapshot vs maklumat terkini Temu janji menyimpan salinan (snapshot) nama/IC/telefon pesakit pada masa tempahan — berguna untuk entri lama atau manual. Tetapi bila temu janji itu terikat pada pesakit sebenar (patient_id), skrin memaparkan maklumat terkini pesakit (guna COALESCE dalam SQL). Jadi kalau pesakit tukar nombor telefon, temu janji akan datang tunjuk nombor baru — tanpa kehilangan rekod lama.
16 Modul · Kajian Kes

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.

Analogi rangkaian restoran Bayangkan satu resipi rasmi yang dikongsi semua cawangan restoran. Setiap cawangan guna resipi sama (kod), tetapi ada papan tanda sendiri, alamat sendiri, & tunai sendiri yang tak bercampur dengan cawangan lain. Bila resipi dikemas kini, semua cawangan boleh terima versi baru — tapi wang & pelanggan setiap cawangan kekal terpisah. Begitulah multi-hospital di sini.

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 keselamatanAkaun Cloudflare & database sendiri
Versi yang dikeluarkanBaris fasiliti laporan, senarai surat, IT tempatan
Satu kod (repo yang sama) Hospital A akaun + DB sendiri Hospital B akaun + DB sendiri Hospital C akaun + DB sendiri

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.

shared/hospital-identity.ts (ringkas)
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.

Kenapa akaun sendiri, bukan satu pelayan pusat? Setiap hospital berjalan dalam akaun Cloudflare percuma mereka sendiri — kos sifar, & ia berskala secara linear (10 hospital = 10 akaun percuma, bukan satu bil besar). Lebih penting: data setiap hospital terasing fizikal — tiada risiko data hospital A terbocor ke hospital B. Pemilik sistem cuma pegang token berskop untuk menjalankan kemas kini, bukan menyimpan data sesiapa.
🎨 Slot kartun — jana di Google Flow guna prompt dalam komen HTML di atas, kemudian ganti kotak ini dengan imej base64.
Satu kod dikongsi semua hospital; data & akaun setiap hospital terasing & selamat.
17 Modul · Kajian Kes

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).

Analogi kad akses berwarna Bayangkan setiap kakitangan ada kad akses berlainan warna. Kad hijau (kerani) buka kaunter pendaftaran; kad biru (doktor) buka bilik rawatan; kad merah (pentadbir) buka bilik fail & tetapan. Pintu (sistem) baca kad sebelum benarkan masuk — tiada kad yang betul, pintu tak buka. Itulah peranan + token.

Lima peranan

Penting: sistem simpan nama peranan dalam Bahasa Melayu — guna nama Inggeris (registrar/doctor/admin) takkan padan.

Peranan (DB)LabelBoleh buat
pendaftarKaunterDaftar pesakit, bayaran, temu janji
doktorDoktorLihat/isi rekod klinikal pesakit
pentadbirAdminUrus pengguna, log audit, laporan, tetapan
superadminSuperadminSemua 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.

Pintu utama kata laluan sistem Pilih peranan kerani / doktor / pentadbir Token + akses 12 jam (kiosk: 30 hari) Pintu on-call (terasing)

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:

JenisTempoh sahKenapa
Peranan biasa12 jamHabis satu syif, log masuk semula
Kiosk (system)30 hariKomputer 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.

Kenapa peranan ini penting (keselamatan) Semakan peranan dibuat di pelayan (middleware requireRole), bukan cuma sembunyi butang di skrin. Sembunyi butang sahaja tak cukup — orang yang tahu boleh hantar permintaan terus. Sebab itu setiap laluan terlindung mengesahkan token + peranan dahulu, sebelum sentuh data. Hanya segelintir laluan terbuka tanpa log masuk (cth. identiti hospital untuk papar logo) & ia disenarai tegas dalam PUBLIC_PATHS.
18 Modul · Kajian Kes

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.

Analogi mesin daftar tunai Bayangkan juruwang restoran yang pandai: setiap pelanggan, dia tahu sama ada bayar tunai, guna baucar syarikat (e-GL), atau makan percuma (jemputan). Pada hujung hari dia beri dua jumlah — berapa dikutip & berapa dikecualikan. Modul bayaran buat persis itu untuk klinik.

Tiga kategori pesakit

Sistem mengelaskan setiap lawatan ke salah satu daripada tiga kategori — ini menentukan bagaimana ia muncul dalam laporan:

KategoriSiapaBayaran
e-GL eglDijamin kerajaan (surat jaminan)Ditanggung — pengecualian
Orang Awam awamRakyat biasa (warganegara)Bayar mengikut kadar
Bukan warganegara asingEtnik "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.

worker/routes/registrar/bayaran.ts (ringkas)
// 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.

Lawatan dalam tempoh setiap baris ada angka Asing ikut kategori + umur + sekolah Jumlah dikutip jumlahBayaran (RM) Jumlah dikecualikan jumlahPengecualian (RM)
Kenapa dua jumlah, bukan satu? Hospital perlu lapor bukan sahaja berapa wang masuk, tetapi juga berapa nilai rawatan yang diberi percuma/ditanggung — kerana itu membuktikan perkhidmatan kepada golongan dikecualikan. Sebab itu laporan tunjuk jumlahBayaran (dikutip) & jumlahPengecualian (dikecualikan) berasingan. Skrin: src/pages/registrar/BayaranReportPage.tsx.
19 Modul · Kajian Kes

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.

Analogi app telefon yang menyegar sendiri Macam app di telefon yang kadang-kadang papar "versi baru tersedia". Tapi di sini ada syarat tambahan penting: app tak boleh tiba-tiba muat semula (reload) di tengah kerja — bayangkan doktor sedang taip rekod, lalu skrin reload sendiri & catatan hilang. Sebab itu kemas kini dibuat pada masa selamat sahaja.

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.

vite.config.ts
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:

1. Baru buka (≤10 saat) Pasang senyap automatik — tiada kerja belum disimpan, jadi selamat reload terus. 2. Tengah sesi Beratur dulu — dipasang bila pengguna tukar halaman (masa selamat semula jadi). 3. Sandaran (5 minit) Jika tak tukar halaman, banner mesra muncul — pengguna tekan untuk segar.

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.

Jangan tukar kepada autoUpdate Ini keputusan reka bentuk yang sengaja. Menukar registerType kepada 'autoUpdate' akan memulihkan risiko reload-tengah-kerja yang boleh hilangkan rekod pesakit yang sedang ditaip. Kalau penyelenggara nampak baris ini, biarkan ia 'prompt'.
20 Modul · Kajian Kes

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.

Analogi loceng asap dapur Dapur tak perlu tukang masak berdiri menjaga dapur sepanjang masa. Loceng asap berbunyi sendiri bila ada asap — walaupun tengah malam, walaupun semua orang tidur. Modul amaran buat persis itu: ia "berbunyi" (hantar mesej) sebaik sahaja ralat berlaku, supaya orang yang betul boleh datang padamkan api sebelum ia merebak.

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:

JenisBila berlakuContoh
🔴 Ralat Worker criticalPelayan tersilap teruk semasa melayan permintaanPangkalan data tak dapat dicapai semasa simpan lawatan
🟠 Cron gagal cronTugas berjadual (auto-kemas) gagalPembersihan mesej lama tersekat
🟡 Ralat klien clientSkrin pengguna terhantuk ralat di pelayarSatu 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.

Ralat berlaku cth. simpan gagal Buang PII IC · telefon · emel Tapis ulangan elak banjir mesej Saluran hospital Discord

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:

worker/observability/alert.ts (ringkas)
// 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.

Satu hospital, satu saluran Setiap hospital ada saluran amarannya sendiri (ditetapkan melalui rahsia DISCORD_WEBHOOK_URL), & setiap mesej ditanda dengan nama hospital (HOSPITAL_LABEL). Pemilik sistem boleh memantau semua hospital dari satu tempat, tetapi tahu serta-merta yang mana satu sedang bermasalah.
Senyap secara lalai — & tak pernah merosakkan permintaan Jika rahsia DISCORD_WEBHOOK_URL tak ditetapkan, modul ini tidak buat apa-apa (no-op) — jadi pemasangan baru & mesin pembangunan tetap senyap sehingga pemilik menyambungkannya. Lebih penting: jika penghantaran amaran itu sendiri gagal, ralatnya ditelan diam-diam & tidak melambatkan atau merosakkan permintaan pesakit yang asal. Loceng asap tak boleh jadi punca kebakaran. Sumber: worker/observability/alert.ts, disambung di worker/index.ts (app.onError + tugas cron + /api/client-errors).
21 Modul · Kajian Kes

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.

Analogi buku log pengawal keselamatan Di pintu masuk bangunan, pengawal mencatat setiap orang yang masuk: nama, masa, tujuan. Tiada siapa boleh memadam catatan lama; ia hanya bertambah. Bila berlaku sesuatu, buku log itu menjawab "siapa ada di sini & bila". Log audit ialah buku log digital untuk setiap tindakan penting dalam sistem.

Apa yang dicatat

Setiap baris audit menyimpan empat perkara sahaja — ringkas, tetapi cukup untuk menjawab "siapa, buat apa, bila":

MedanMaksud
idNombor unik baris ini
user_idSiapa — diambil dari token log masuk, bukan dari borang
actionApa — cth. SETUP_PASSWORD_CHANGED
created_atBila — cap masa tindakan

Bagaimana satu catatan dibuat

Tindakan penting berlaku cth. tukar kata laluan logAction(...) siapa = token, bukan borang Baris audit_logs siapa · apa · bila
worker/audit.ts
await env.DB.prepare(
  'INSERT INTO audit_logs (id, user_id, action, created_at) VALUES (?, ?, ?, ?)'
).bind(log_id, userId, action, new Date().toISOString()).run();
Kenapa "siapa" mesti datang dari token, bukan borang Kalau sistem percaya borang untuk menentukan siapa pelakunya, sesiapa boleh menulis nama orang lain & menyalahkan mereka. Sebab itu pelaku (user_id) sentiasa diambil dari token log masuk yang disahkan (jwt.sub) — bukti yang tak boleh dipalsukan oleh penghantar permintaan. Peraturan ini ditegakkan dalam .claude/rules/api.md.
Mencatat tak boleh menggagalkan kerja sebenar Sama seperti loceng asap dalam modul sebelum (§20), penulisan audit dibuat secara "tak menyekat": jika ia gagal, ralat dicatat ke log tetapi tidak menggagalkan permintaan asal. Mencatat sejarah tak patut menghalang pesakit didaftar. Sumber: worker/audit.ts (logAction), jadual audit_logs.
22 Modul · Kajian Kes

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.

Analogi telefon baharu Bila buka kotak telefon baharu, ia tak terus boleh guna — skrin "sediakan telefon anda" muncul: pilih bahasa, tetapkan PIN, log masuk akaun. Begitu juga sistem ini pada pemasangan pertama: ia memaparkan wizard persediaan sehingga selesai. Hanya selepas itu app biasa terbuka.

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:

Pasang baharu setup_completed = 0 Penjaga halang alih ke /setup Wizard 6 langkah tukar kata laluan dulu App biasa flag = 1

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:

LangkahApaWajib?
0 · Kata laluanTukar kata laluan sementara kepada milik sendiriYa
1 · IdentitiNama hospital, alamat, logo letterheadBoleh nanti
2 · PenggunaTambah akaun kerani / doktor / pentadbirBoleh nanti
3 · Templat on-callPilih corak rotasi (OMFS / tunggal / dua peringkat)Boleh nanti
4 · Semak katalogSahkan senarai diagnosis & rawatanBoleh nanti
5 · SelesaiTandakan 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:

worker/routes/setup.ts (ringkas)
// 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 });
});
Kenapa wajib tukar kata laluan dulu Skrip provision menanam akaun superadmin dengan kata laluan rawak yang dijana sekali & ditunjuk kepada pemasang. Tetapi langkah ini memastikan ia mesti ditukar sebelum apa-apa kerja klinikal — supaya tiada hospital tertinggal pada kata laluan permulaan. Pemeriksaan dibuat di pelayan: kata laluan baharu tak boleh sama dengan yang lama, & mesti sekurang-kurangnya 8 aksara.
Sambungan ke cerita multi-hospital Suis setup_completed ditambah melalui migrasi 0046_setup_completed.sql dengan lalai 0 — jadi setiap hospital baharu bermula dengan wizard, manakala hospital sedia ada (HSR) ditandakan siap secara berasingan selepas naik taraf. Inilah kepingan terakhir yang menjadikan satu salinan kod boleh diserahkan kepada mana-mana hospital & "berdiri sendiri" tanpa penyelenggara perlu sentuh pangkalan data. Sumber: src/pages/superadmin/setup/, worker/routes/setup.ts.
23 Kualiti

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

PemeriksaanApa ia buatAnalogi
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
LintPastikan gaya kod kemas & konsisten.Periksa kebersihan dapur
Saiz failHalang fail jadi terlalu besar & sukar dijaga.Had berat bagasi
Analogi senarai semak juruterbang Sebelum kapal terbang berlepas, juruterbang tak "rasa" semuanya okay — mereka ikut senarai semak satu per satu. Begitu juga di sini: kod tak diterbitkan atas dasar "nampak macam jalan". Ia mesti lulus semua pemeriksaan dahulu. Kalau satu gagal, penerbangan (deploy) dibatalkan.

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.

Peraturan ujian projek ini Ujian di sini menggunakan database sebenar (versi tempatan), bukan database "pura-pura". Sebabnya: pernah berlaku ujian pura-pura lulus, tetapi sistem sebenar gagal. Pengajaran disimpan jadi peraturan — itulah cara pasukan yang baik "belajar sekali, tak ulang silap".
24 Penerbitan

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.

1. Tulis kod di komputer 2. git push hantar ke GitHub 3. Semakan auto uji + bina (CI) 4. Deploy naik ke Cloudflare 5. Pengguna! 🎉
Analogi penerbitan git push = hantar resipi baru ke ibu pejabat. Semakan auto (CI) = penyelia rasa dulu, pastikan tak masin/tak hangus — kalau gagal, tak dihantar ke cawangan. Deploy = resipi yang lulus disebar ke semua cawangan supaya pelanggan dapat hidangan baru. Dalam projek ini, menolak kod ke "main" itu sendiri yang mencetuskan penerbitan — automatik.

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.

Istilah yang anda akan dengar CI/CD = proses automatik uji + terbit. Wrangler = alat yang sebenarnya menolak kod ke Cloudflare. main = "salinan rasmi" kod yang akan diterbitkan. Anda tak perlu hafal — cuma kenal bila terjumpa.
25 Peta Jalan

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.

Perhentian 1 · Faham, jangan tulis

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.

Perhentian 2 · Asas web

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.

Perhentian 3 · Bahasa sebenar projek

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.

Perhentian 4 · Skrin hidup

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.

Perhentian 5 · Otak & data

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.

Perhentian 6 · Alat sebenar

Git, terminal & pelayar

Belajar simpan perubahan dengan Git, jalankan npm run dev untuk buka salinan tempatan, & guna "Developer Tools" pelayar untuk mengintip apa berlaku.

Perhentian 7 · Sumbang sesuatu

Ubahan kecil sebenar

Tukar satu label, betulkan satu ejaan, tambah satu medan kecil. Uji secara tempatan. Inilah saat anda bertukar dari "pembaca" kepada "pembina".

Rahsia sebenar belajar coding Bukan menghafal — tetapi membaca banyak kod orang & membuat perubahan kecil berulang kali. Setiap kali anda ubah sesuatu & ia berfungsi, otak anda belajar. Sistem ini memberi anda kod sebenar, bermakna, & lengkap untuk dijadikan padang latihan.
Nak belajarSumber percuma yang bagusKenapa
Asas web (HTML/CSS/JS)freeCodeCamp · The Odin ProjectBerstruktur, banyak latihan langsung.
JavaScript mendalamjavascript.infoPenerangan jelas, contoh banyak.
Reactreact.dev (Learn)Dokumen rasmi, ada tutorial.
SQL/databaseSQLBolt · sqlite.orgLatihan SQL terus dalam pelayar.
Git"Learn Git Branching" (visual)Belajar Git secara visual & main-main.
Mascot gigi berkot doktor mendaki tangga pembelajaran menuju 'istana' projek siap
Belajar coding ialah pendakian berperingkat: dari membaca, ke asas web, ke bahasa projek, akhirnya membina sendiri. Satu anak tangga pada satu masa.
26 Penyelenggara · Ubah Apa

"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.

Peraturan emas Sebelum sentuh kod, cari dahulu dalam "Tetapan" aplikasi (log masuk sebagai superadmin). Banyak benda — nama hospital, logo, pengguna, peraturan on-call — direka untuk diubah terus dalam app, tanpa pengaturcara.

Boleh ubah TANPA kod (dalam aplikasi)

Saya nak ubah…Pergi ke (dalam app)
Nama / logo / alamat / no. telefon hospitalLog masuk superadminTetapan Hospital
Baris fasiliti pada laporan PG201/PG211Tetapan Hospital → bahagian Laporan PG201/PG211
Senarai penerima surat rasmiTetapan Hospital → Senarai rujukan
Nombor IT / sokongan tempatan hospital (di "Bantuan")Tetapan Hospital → IT / Sokongan Tempatan
Tambah staf, tukar peranan, beri kuasa urus on-callLog masuk superadmin/pentadbirUrus Pengguna
Staf berhenti / bertukar — keluarkan akses merekaUrus 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 penggunaUrus Pengguna → pengguna berkenaan
Kenapa "Nyahaktif", bukan "Padam"? Bila seorang staf berhenti atau bertukar, tekan Nyahaktif — jangan padam akaun. Sebabnya: nama mereka sudah terikat pada rekod lawatan pesakit dan log audit yang lama. Padam akaun = hilang siapa yang buat apa (penting untuk audit MOH). Nyahaktif pula menutup akses log masuk dengan serta-merta tetapi mengekalkan semua sejarah. Pengguna yang dinyahaktifkan akan hilang dari senarai pilihan doktor/staf, dan kalau dia sedang log masuk, dia akan dilog keluar pada permintaan seterusnya. Nak benarkan semula? Tekan Aktifkan. Akaun yang betul-betul tiada sejarah sahaja boleh dipadam; sistem akan halang (mesej 409) jika cuba padam akaun yang ada rekod.

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, simpanMudah
Warna / saiz / jarak sesuatu elemenKelas Tailwind dalam fail .tsx yang sama (cth. text-red-600)Mudah
Mesej ralat atau ayat dalam Bahasa MelayuCari teks itu dalam src/ atau worker/Mudah
Siapa boleh buat sesuatu tindakan (kebenaran)worker/routes/*.ts — cari requireRole(...)Sederhana
Tambah medan baru pada pesakit/lawatanPerlu 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
Cara cari teks dengan pantas Nak cari di mana satu ayat dipaparkan? Buka projek dalam editor kod (cth. VS Code), tekan Ctrl+Shift+F, taip teks yang anda nampak pada skrin. Editor tunjuk fail tepat tempat ia ditulis. Inilah kemahiran #1 penyelenggara — cari, jangan hafal. (Kotak "Cari" di atas buku ini berfungsi cara yang sama untuk dokumen ni.)
27 Penyelenggara · Arahan

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.

ArahanApa ia buatBila guna
npm installMuat turun semua "bahan" (library) projek perluKali pertama buka projek di komputer baru
npm run devBuka salinan app di komputer anda (localhost)Bila buat & uji perubahan
npm run build"Masak" kod untuk versi sebenar — tangkap sebahagian ralatSebelum terbit
npx tsc --noEmitSemak silap jenis dataSebelum terbit
npm testJalankan semua ujian automatikSelepas setiap perubahan — gagal = jangan terbit
git statusTunjuk fail mana yang anda ubahSebelum simpan (commit)
git add . && git commit -m "…" && git pushSimpan kerja ke GitHub — push ini yang menerbitkan appSelepas siap satu kerja
npm run db:migrate:localPasang semua migration ke database tempatanSetup pertama / selepas tarik migration baru
wrangler d1 execute … --remoteJalankan soalan SQL pada database LANGSUNGMenyemak data sebenar (hati-hati!)
Dua amaran penting (1) Bendera --remote bermaksud "database SEBENAR (langsung)". Tanpa ia, anda kena database tempatan. Sentiasa semak dua kali sebelum jalankan pada --remote.   (2) Untuk projek ini, menolak kod ke main di GitHub itu sendiri menerbitkan app (auto-deploy). Pastikan semua ujian hijau dahulu — push = terbit.
28 Penyelenggara · Jangan Rosak

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').

29 Penyelenggara · Pemulihan

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:

pemulihan data
# 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

Butang "Rollback" satu klik Buka papan pemuka Cloudflare → Workers → Deployments. Ada butang Rollback yang memulangkan ke versi sebelumnya dengan satu klik. Selepas itu, betulkan masalah di komputer, uji, baru terbit semula.

Database tiba-tiba lambat

Biasanya kerana "index" hilang. Jalankan EXPLAIN QUERY PLAN SELECT …; jika ia kata SCAN TABLE, buat migration baru dengan CREATE INDEX.

30 Penyelenggara · AI

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?

Peraturan emas dengan AI Buat SATU perubahan kecil pada satu masa. Uji. Simpan (commit). Baru perubahan seterusnya. AI boleh buat 10 benda sekali gus — tetapi kalau 1 rosak, mencari yang mana lebih susah daripada membuat 10 perubahan kecil berasingan. Anda jadi arkitek yang tahu app patut buat apa; biar AI uruskan kebanyakan bagaimana — tetapi sahkan semuanya.
31 Penyelenggara · Minda

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.

Dalam 3–6 bulan Dengan pembelajaran konsisten, dalam 3 bulan anda beralih dari "saya tak tahu kod ini buat apa" ke "saya boleh baca kebanyakannya & bertanya soalan bernas". Dalam 6 bulan, anda membuat perubahan sebenar sendiri. AI ialah rakan pengaturcara anda — jadilah arkitek yang tahu app patut buat apa.

Fail rujukan dalam projek (untuk dibaca bila bersedia)

FailApa di dalamnya
HANDOVER.mdPanduan serahan versi Inggeris (asas bahagian penyelenggara ini)
ARCHITECTURE_PLAIN.mdPenerangan seni bina lebih dalam, bahasa biasa
LEARN_FULLSTACK.mdLaluan pembelajaran terikat pada kod ini
FINDINGS_AND_PLAN_PLAIN.mdPenemuan audit + rancangan, bahasa biasa
CLAUDE.mdArahan untuk AI bila menyunting repo ini — berbaloi diimbas
graphify-out/GRAPH_REPORT.mdPeta semua fail kod & cara ia berhubung
docs/Operasi armada, provisioning hospital baru, sokongan
32 Rujukan Developer

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.

💻
Untuk developer / coder
Buka Panduan Developer →
Seni bina, lapisan (routes→services→contracts), auth & JWT, database & migrasi, build/deploy/CI, multi-hospital & fleet, ujian — dengan graf seni bina + graf modul interaktif.
Kenapa berasingan? Audiens berbeza: panduan ini guna analogi & bahasa harian; Panduan Developer mengandaikan anda baca kod. Memisahkannya memastikan kedua-duanya kekal fokus & tidak terlalu panjang.
33 Soalan Lazim

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.

34 Kamus

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.
35 Praktik

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".

Pesanan terakhir Anda tak perlu jadi genius untuk faham sistem ini — anda cuma perlu sabar & ingin tahu. Setiap pengaturcara hebat pernah berdiri di tempat anda sekarang: pening tengok kod buat kali pertama. Bezanya, mereka teruskan membaca, mencuba, & bertanya. Sistem ini ialah guru yang sabar — ia takkan penat anda tanya. Mulakan dari mana-mana, dan teruskan.