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, iaitu 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, seringkas mungkin

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"Kalau … jika 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, iaitu 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, iaitu bahan (variable), mesin kecil (function) dan 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, sebab 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, iaitu 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, iaitu 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, iaitu 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".

📄 Surat rasmi (Word)

Selepas jadual diterbitkan, pengurus tekan "Muat turun Word (.docx)" — ada dua surat: Surat On-Call (panggilan kecemasan penuh) & Surat Tagger. Setiap satu ada kepala surat (Jata Negara), jadual giliran & footer MSQH di bawah setiap muka surat. Buka dalam Word untuk cetak / simpan PDF. (Dulu cetak terus dari pelayar — ditukar 2026-06-22 kerana footer tak duduk betul.) Pakar yang bertugas sepanjang bulan tiada kolum sendiri lagi (mulai 2026-09-09) — nama dan nombor telefon mereka ada dalam perenggan di bawah jadual, dan hari yang diganti disebut di situ juga (“kecuali pada …”). Dua kolum itu dibuang kerana setiap satu membawa satu nama sahaja untuk sebulan penuh tetapi memicit kolum lain sampai surat jadi 3 muka. Sekarang kembali 2 muka.

👋 Pegawai berhenti

Baru (Ogos 2026): bila seorang pegawai berhenti atau bertukar, tekan butang Pegawai berhenti di tab Kakitangan dan isi "Berkuat kuasa dari" — tarikh dia benar-benar berhenti. Tugas sebelum tarikh itu kekal seperti yang sudah dicetak, jadi dia boleh habiskan bulan semasa; tugas mulai tarikh itu dikosongkan dan ditanda perlu pengganti. Dia terus keluar daripada senarai bila jadual bulan berikutnya dijana. Kotak di bawah tarikh menunjukkan bulan demi bulan berapa tugas kekal dan berapa dikosongkan sebelum anda sahkan. Nama pegawai yang sudah berhenti tetap kekal pada jadual lama & surat yang sudah dicetak. Baru (Ogos 2026): giliran tetap PPP kini menyambung jadual yang telah diterbitkan. Dahulu, bila bilangan orang dalam giliran berubah, seluruh pusingan beralih dan ada yang boleh dapat giliran semula sehari selepas habis on-call 24 jam. Sistem kini mengikat giliran pada hari terakhir yang telah diterbitkan, jadi bulan berikutnya bermula dengan orang yang sepatutnya. Anda tidak perlu buat apa-apa — ia berlaku sendiri.

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).
Amaran "reten belum selesai" sebelum jana laporan Sebelum anda tekan Jana PG201/PG211, sistem mengira berapa lawatan dalam julat tarikh itu yang reten belum selesai (doktor belum habis isi). Jika ada, satu kotak kuning muncul — contohnya "3 daripada 50 lawatan dalam julat ini belum selesai reten". Ia tidak menghalang anda muat turun, tetapi ingat: angka diagnosis & rawatan mungkin kurang selagi ada reten belum siap, kerana angka itu diambil daripada apa yang doktor sudah isi. Untuk laporan bulanan rasmi, tunggu semua reten jadi Selesai dahulu, baru jana. Lawatan Batal (pulang awal sebelum rawatan) tidak dikira sebagai "belum selesai" — dan juga tidak dikira dalam kehadiran pada PG201 & PG211 (pesakit yang pulang sebelum dirawat tidak dikira hadir). Aturan sama dipakai pada Ringkasan Statistik & halaman Rekod Pegawai (Batal tidak dikira), dan panel Perbandingan Doktor lebih ketat lagi — doktor hanya dikira pada lawatan yang reten doktor itu sudah Selesai, kerana nama operator pada reten belum selesai masih boleh berubah. Pentadbir nampak kiraan seluruh klinik di halaman Penjanaan Laporan; doktor nampak kiraan sendiri dalam kotak Jana PG201 di halaman Muat Turun.

Tiga tingkat panggilan (mulai Oktober 2026)

Hospital menetapkan sendiri berapa tingkat panggilan yang dijalankan di Urus On-Call → Tetapan Rotasi → Tingkat Panggilan. HSR menjalankan tiga:

  • Panggilan Pertama — pegawai untuk kes baru, satu kolam gabungan (OMFS + Dental Paeds + Primer) Isnin hingga Ahad, dipilih secara adil oleh sistem. Tagger kekal dalam tingkat ini.
  • Panggilan Kedua — pegawai untuk pesakit dalam wad. Bagi OMFS, ia mengikut pusingan wad klinik (kitaran tetap 6 minggu, blok Isnin–Ahad) dan sistem mengiranya sendiri. Bagi Dental Paeds, tiada kitaran tetap — nama diisi dengan tangan, sama ada oleh YM pada Jadual Bulanan atau oleh pegawai sendiri di kiosk. Setiap hospital memilih caranya di Tetapan Rotasi: satu minggu penuh (seorang pegang Isnin hingga Ahad) atau ikut tarikh (pegawai ambil mana-mana hari yang dia mahu, seperti Tagger). Dental Paeds HSR menggunakan cara ikut tarikh, kerana mereka mengatur giliran mengikut cuti masing-masing.
  • Panggilan Ketiga — pakar tetap (tidak berubah).

Peraturan penting: pemegang Panggilan Kedua tidak akan dipilih untuk Panggilan Pertama sepanjang tempoh yang dia pegang. Bagi peranan satu minggu penuh, itu bermakna seminggu Isnin hingga Ahad, bukan hari yang sama sahaja. Bagi peranan ikut tarikh, ia hanya hari yang dia ambil, dan hari lain dalam minggu itu dia bebas seperti biasa. Jika pegawai itu bercuti, mereka bertukar dengan rakan seperti biasa (sistem tidak mengganti sendiri) dan sistem menolak pertukaran yang akan meletakkan seseorang dalam tempoh di mana dia sudah memegang Panggilan Pertama. Peraturan ini untuk pegawai sahaja; giliran PPP tidak berubah. Butang Terbitkan akan bertanya dahulu jika ada tarikh yang masih kosong. Surat on-call turut memaparkan dua lajur baharu "Pegawai Pergigian (Panggilan Kedua)".

Apa yang berlaku bila jadual dijana semula

"Jana Semula" bermaksud kira semula seluruh bulan dari kosong. Ia bukan betulkan satu dua hari. Dalam ujian sistem, mengisi satu tarikh panggilan kedua sudah cukup untuk mengubah 27 daripada 31 hari panggilan pertama. Hari unit KPSR dan KPTM turut berubah, kerana unit ialah calon biasa dalam senarai yang sama dengan doktor.

Sebab itu urutan kerja ini penting:

  1. Masukkan dahulu semua cuti, locum OT, tarikh panggilan kedua dan kakitangan baharu.
  2. Barulah Jana Jadual.
  3. Lepas itu barulah unit isi nama doktor, dan barulah buka pertukaran.
  4. Bila jadual sudah betul, tekan Terbitkan. Bulan yang diterbitkan tidak boleh dijana semula melainkan dibuka semula, dan itulah perlindungan terbaik.

Kotak pengesahan sebelum Jana Semula kini memberitahu berapa pertukaran akan dipadam (termasuk yang sudah dipersetujui dan menunggu kelulusan), dan selepas jana ia menyenaraikan tarikh yang namanya gugur supaya boleh diisi semula. Nama yang diisi sendiri dikekalkan hanya jika harinya masih sama.

Superadmin boleh menghidupkan Tunggu peranan ini penuh sebelum unit isi nama pada mana-mana peranan di Tetapan Rotasi. Bila dihidupkan, unit tidak boleh isi nama sehingga peranan itu lengkap. Ini menghalang seorang pegawai daripada memegang panggilan pertama dan kedua pada hari yang sama.

Tajuk pada surat, dan PPP di muka surat sendiri

Surat on-call kini dicetak dalam dua jadual. Muka surat pertama membawa pegawai dan pakar; PPP dicetak pada muka surat sendiri dengan tajuknya sendiri. Sebabnya mudah: sepuluh lajur pada kertas A4 menegak menjadikan setiap lajur terlalu sempit sehingga nama berpecah tiga baris. Dengan pemisahan ini setiap lajur mendapat ruang sebenar.

Tajuk setiap lajur boleh ditaip sendiri di Tetapan Rotasi → Tajuk Pada Surat. Sistem menyediakan tajuk lalai, dan anda hanya menaip jika hospital anda menggunakan istilah lain. Contohnya HSR tiada panggilan ketiga (panggilan ketiga bermaksud pakar perunding), jadi lajur pakar ditulis "Pakar On-call" supaya ia tidak dibaca sebagai panggilan ketiga. Hospital lain yang memang mempunyai pakar perunding boleh mengekalkan tiga tingkat seperti biasa. Biarkan medan kosong untuk kekal dengan tajuk lalai.

Pada kalendar, Panggilan Kedua pegawai dipaparkan terus di bawah Pegawai Bertugas, iaitu di atas PPP Panggilan Kedua, dengan warnanya sendiri dalam petunjuk warna di atas jadual. Kad pengisian manual (bertajuk Blok Manual atau Tarikh Manual, mengikut cara yang dipilih) hanya muncul bagi bulan yang sudah dijana dengan peranan ini, jadi ia tidak akan keluar pada bulan sebelum Oktober 2026. Menukar cara pengisian selamat pada bila-bila masa: jadual yang sudah dijana tidak terjejas dan nama yang sudah diisi kekal, cuma cara memilih tarikh yang berubah.

Pesakit Dikongsi — bila dua doktor rawat pesakit yang sama

Kadang satu lawatan dirawat oleh lebih daripada seorang doktor (kotak "Rawatan Dibuat oleh Operator Lain Juga" ditanda dalam Rekod Klinikal). Panel "Pesakit Dikongsi" menunjukkan pesakit-pesakit ini & bagaimana kerja dibahagikan: bagi setiap pesakit, ia senaraikan setiap doktor yang terlibat dengan bilangan lawatan dikongsi, diagnosis, & rawatan yang doktor itu sendiri buat.

  • Pentadbir/Superadmin nampak panel ini di halaman Penjanaan Laporan (seluruh fasiliti).
  • Doktor nampak versi sendiri di halaman Rekod Pegawai — hanya pesakit yang doktor itu kongsi dengan rakan.
Cuma untuk lihat — borang rasmi tak berubah Panel ini hanya memaparkan pecahan kerja; ia tidak menyentuh borang PG201/PG211. Di halaman Rekod Pegawai (doktor), panel ini mengikut julat tarikh di penjuru atas halaman secara automatik — sama seperti semua statistik lain di situ. Di halaman Penjanaan Laporan (pentadbir), pilih julat tarikh & tekan Muat. Pengiraan diagnosis/rawatan setiap doktor dibuat ikut operator baris itu; jika satu baris pada lawatan dikongsi tak ada operator (kosong), ia tidak dikira pada mana-mana doktor (kabur) tetapi masih kekal dalam jumlah fasiliti.
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 untuk laporan. (Dahulu keunikan nombor pendaftaran hanya disemak untuk pesakit Baru; sejak 24 Ogos 2026 ia disemak untuk semua lawatan — dua pertembungan sebenar tahun ini berlaku pada baris Ulangan.)

Nombor pendaftaran & cara cegah pertembungan

Kaunter menaip sendiri nombor pendaftaran pesakit Baru, mengikut daftar kaunter yang sedia ada. Daftar bertulis itulah rujukan sebenar bagi tahun ini. (Antara 11 Julai dan 24 Ogos 2026 sistem pernah memberi nombor secara automatik. Ia telah ditarik balik: satu nombor luar jangkaan yang dimasukkan pada 24 Julai menyebabkan kiraan automatik melompat jauh, dan kaunter terpaksa membetulkan nombor secara manual hampir setiap hari. Nombor yang terjejas telah dibetulkan.) Pesakit Ulangan kekal guna nombor tahun semasa mereka (diisi automatik selepas "Muatkan Maklumat"). Satu pesakit = satu nombor bagi satu tahun. Sejak 24 Ogos 2026 peraturan itu bukan lagi sekadar amalan — ia disimpan dalam daftar nombor di dalam database sendiri (jadual patient_registration_numbers), jadi database yang menolak kesilapan, bukan hanya kod.

Kaunter taip no. cth. 124/2026 Lapisan 1: pra-semak Ada pesakit lain pegang no. ni tahun ni? Belum → simpan ✓ lawatan didaftar Sudah → mesej mesra "Sudah digunakan oleh [nama pesakit] — semak daftar"

Cari Pesakit — cari orang, bukan tarikh (Ogos 2026)

Halaman Cari Pesakit ada pada semua peranan (Kaunter, Doktor, Pentadbir, Superadmin) di menu sebelah kiri. Ia menjawab soalan yang senarai lawatan tak boleh jawab: pesakit ini pernah datang tak, dan apa nombor dia tahun ini?

  • Tiada julat tarikh langsung. Senarai lawatan perlu tarikh; kalau pesakit terakhir datang lapan bulan lepas, anda takkan jumpa dia di situ. Halaman ini cari orang, jadi tarikh tak relevan.
  • Buka terus papar semua pesakit ikut nama A–Z — boleh belek, bukan wajib menaip.
  • Cari ikut nama, no. kad pengenalan, no. telefon, atau no. pendaftaran tahun ini. Taip lebih banyak perkataan untuk tapis lebih halus.
  • Klik satu baris — barulah sistem ambil 3 lawatan terkini pesakit itu. Sebelum diklik, sejarah tak dimuatkan langsung; itu yang buat halaman ini ringan.
  • Bulatan jantina: biru = Lelaki, merah jambu = Perempuan. Kalau jantina yang tersimpan tak sepadan dengan kad pengenalan, bulatan bertukar kuning dan sistem beritahu terus di baris itu.
  • Lencana Baru bermaksud pesakit belum pernah datang tahun ini; Ulangan bermaksud sudah pernah. Ia mengira kedatangan, bukan sama ada dia sudah ada nombor.

Analitik pesakit — pada halaman profil

Buka profil mana-mana pesakit: di atas ada jalur ringkas (jumlah lawatan · lawatan pertama · lawatan terakhir · temu janji akan datang), dan tab baharu Analitik memaparkan kategori diagnosis dan rawatan pesakit itu sepanjang masa.

Di tab Lawatan, antara dua kad lawatan kini tertulis jarak masa antaranya — contoh "Jarak 3 bulan dari lawatan sebelum". Itu yang menunjukkan pesakit yang sepatutnya datang semula dua minggu tetapi baru muncul tujuh bulan kemudian.

Kenapa tiada graf untuk lawatan Lebih separuh pesakit (54%) hanya ada satu atau dua lawatan, dan hanya 2% merentasi lebih daripada satu tahun. Graf garis bagi kebanyakan pesakit hanya akan jadi satu titik. Sebab itu lawatan dipapar sebagai garis masa, bukan graf — manakala diagnosis dan rawatan memang dapat graf, kerana 54% pesakit membawa 2–3 kategori berbeza.

Nombor pendaftaran automatik — ada, tetapi belum dihidupkan

Sejak 24 Ogos 2026 sistem ada kaunter nombor mengikut tahun. Ia berbeza sama sekali daripada percubaan Julai yang ditarik balik: nombor kini disimpan dalam barisnya sendiri, bukan dikira daripada nombor lawatan yang boleh disunting kerani. Itu yang menyebabkan masalah Julai dahulu.

Ia belum dihidupkan untuk mana-mana tahun. Tahun 2026 kekal manual — kaunter menaip nombor dari daftar bertulis, sama seperti hari ini. Superadmin boleh menghidupkannya untuk tahun baharu di Penyelenggaraan Sistem → Nombor pendaftaran automatik. Bila dihidupkan, kaunter bermula selepas nombor tertinggi yang sudah digunakan tahun itu, jadi tiada nombor dalam daftar bertulis akan diberi dua kali. Siri huruf (contoh A1/2026) tidak diurus oleh kaunter ini — ia kekal ditaip oleh kerani.

Lapisan 2 ialah jaring keselamatan di dalam database sendiri. Daftar nombor itu menyimpan satu baris sahaja bagi setiap pesakit + tahun, dan satu nombor hanya boleh dimiliki oleh seorang pesakit dalam tahun yang sama. Jadi walaupun dua kaunter menyimpan serentak (perlumbaan), database menolak yang kedua — bukan bergantung pada kod semata-mata. Semua laluan yang menulis nombor pendaftaran (pendaftaran kaunter, borang kemaskini, dan import CSV) melalui daftar yang sama.

Tiga perkara yang petugas akan perasan di kaunter:

  • Nombor sudah dipakai orang lain — sistem tolak dan menyebut nama pesakit yang memegangnya, supaya kaunter boleh semak daftar bertulis. Sistem tidak lagi mencadangkan "cuba nombor seterusnya": daftar bertulis kaunter itulah rujukan sebenar, bukan kiraan komputer.
  • Pesakit Ulangan — jika nombor yang terisi automatik tidak sama dengan nombor sebenar pesakit itu bagi tahun ini, sistem terus guna nombor sebenar dan pendaftaran diteruskan. Petugas tidak tersekat di ruangan yang memang tidak boleh ditaip.
  • Betulkan nombor — bila nombor dibetulkan melalui borang kemaskini, semua lawatan pesakit itu bagi tahun sama berubah sekali, dalam satu simpanan. Dahulu bahagian ini dilangkau apabila bulan itu sudah dikunci — itulah puncanya seorang pesakit boleh memegang dua nombor merentas tujuh lawatan.
Cerdik: nombor tahun semasa di Profil Pesakit Di halaman Profil Pesakit (tab Maklumat), sistem kini memaparkan No. Pendaftaran tahun semasa pesakit — diambil daripada lawatan Baru tahun ini, atau lawatan terawal tahun itu jika tiada Baru. Kalau pesakit belum ada lawatan untuk tahun semasa, ruangan itu tunjuk "—". Tahun diambil dari jam pelayan (waktu Malaysia), bukan jam komputer petugas.
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).

Baharu: sebaik selepas Pentadbir/Superadmin menjana laporan PG201 atau PG211, sistem terus tanya: "Kunci sistem sehingga [tarikh akhir laporan]?" — tekan Kunci untuk terus mengunci rekod yang baru sahaja dilaporkan supaya Doktor/Kaunter tak boleh ubah lagi, atau Nanti untuk tangguh. Kalau sistem sudah dikunci sehingga tarikh itu (atau lebih), tiada soalan ditunjuk. Kunci masih boleh dibuka semula oleh Pentadbir di panel "Kunci / Buka Sistem".
🎨 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.

Tanya AI terus dari rekod klinikal Tak pasti kod PG201 mana untuk kes di depan anda? Tekan butang "Tanya AI" di bahagian atas halaman Rekod Klinikal — tetingkap pembantu AI terbuka di sebelah, jadi anda boleh tanya & terus isi diagnosis/prosedur tanpa perlu pergi ke halaman Panduan PG201 & buka semula rekod. Butang Panduan PG OMFS 201 (glosari penuh) masih ada di sebelahnya.
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.

Rawatan belum dituntut — peringatan kurang tuntut

PG201 sendiri tidak mempunyai peraturan "rawatan mesti sepadan dengan diagnosis"; ini penambahbaikan jabatan untuk mengelakkan kurang tuntut. Sistem menyemak setiap diagnosis dalam tempoh reten yang masih terbuka dan memaparkan kad kuning "Rawatan belum dituntut" di papan pemuka doktor (pesakit sendiri), pentadbir dan superadmin apabila:

  • diagnosis itu belum ada rawatan berpasangan pada pesakit yang sama — rawatan pada lawatan kemudian, walaupun bulan berikutnya, dikira; bilangan rawatan boleh melebihi diagnosis (tiga laserasi = satu diagnosis, tiga rawatan), dan bilangan yang kurang hanya dicatat sebagai nota; atau
  • pesakit itu tiada sebarang pengimejan pada mana-mana lawatan — satu rekod pengimejan pada mana-mana lawatan sudah memadai.

Reten yang masih diisi diberi tempoh 7 hari sebelum diperingatkan, kecuali reten itu sudah Selesai. Setiap baris ada butang Buka reten (lawatan yang membawa diagnosis) dan Abaikan dengan sebab (dirujuk ke pusat lain, pesakit menolak, tidak hadir) — sebab itu direkod dalam log audit dan peringatan itu tidak dipaparkan lagi.

Di mana rawatan direkod: pada lawatan ia dilakukan. Hari yang sama dengan diagnosis — tambah pada reten asal. Hari lain (klinik, wad atau dewan bedah) — kaunter mendaftarkan hari itu sebagai lawatan (Ulangan) dan doktor merekod rawatan pada reten hari itu. PG201 mengira prosedur pada bulan ia dilakukan, jadi pembedahan September yang ditulis pada reten Ogos akan masuk laporan bulan yang salah.

Senarai rawatan yang dikira untuk setiap diagnosis disimpan dalam katalog, bukan dalam kod. Superadmin boleh melihat dan mengubahnya di Katalog (/superadmin/katalog) — lihat Bahagian "Cara edit Katalog" di bawah.

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.

Baru (Julai 2026): selepas bulan lama dikunci, halaman senarai lawatan (kini dipanggil Semua Lawatan, semua peranan) secara automatik memaparkan julat tarikh selepas tarikh kunci sehingga hari ini — iaitu tempoh reten yang masih terbuka. Papan pemuka pula menunjukkan kad amaran kuning "Reten belum selesai" untuk tempoh yang sama, supaya doktor dan pentadbir sedar reten tertunggak sebelum tutup reten hujung bulan. Nama doktor yang telah dinyahaktifkan juga tidak lagi muncul dalam penapis doktor di halaman laporan dan senarai.

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.

Bar "perubahan belum disimpan" — bila ia muncul, bila ia senyap

Sistem ada satu bar terapung di bawah skrin yang muncul apabila ada suntingan belum disimpan, dengan dua butang: Set Semula & Simpan Perubahan. Ia dikongsi oleh semua borang — tiada halaman membina bar sendiri.

Peraturan bila ia muncul mudah, dan ia sengaja: bar ini untuk mengubah sesuatu yang sudah tersimpan, bukan untuk mencipta sesuatu yang baharu.

KeadaanBar muncul?Sebab
Doktor isi reten kali pertamaTidakBelum ada apa-apa tersimpan untuk dibandingkan — bar akan melekat sepanjang masa, jadi ia menjadi hiasan, bukan amaran
Doktor pinda reten yang sudah disimpanYaAda versi tersimpan yang boleh hilang jika tertinggal
Kaunter daftar pesakit baharuTidakRekod baharu — sama sebab
Kaunter sunting maklumat pesakit/lawatanYaMenukar data sedia ada
Tampal ID hamparan Google Sheets kali pertamaTidakIni persediaan, bukan suntingan

Setiap borang ini tetap ada butang Simpan sendiri di dalam borang (contoh: Simpan Rekod, Daftar Pesakit). Itulah yang menyimpan rekod baharu apabila bar tidak muncul — jadi tiada apa yang tersekat.

Set Semula bertanya dahulu Tidak seperti kebanyakan aplikasi, Set Semula membuka kotak pengesahan sebelum membuang suntingan. Pada satu reten, apa yang dibuang ialah kerja klinikal yang baru ditaip — itu tidak patut hilang dengan satu klik tersasar.
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.
Sebab temu janji dari Rekod Klinikal Bila doktor tambah temu janji terus dari Rekod Klinikal, sebab lalai ialah Review. Ruangan butiran sebab hanya perlu diisi untuk MOS/XN atau Lain-lain — jadi temu janji biasa cuma tunjuk sebabnya sahaja (cth. "Review"), tanpa teks tambahan automatik.
Tarikh dijaga & amaran temu janji berganda (2026-07-23) Ruangan tarikh pelayar kadang boleh terima tahun salah (cth. taip laju & tersalah jadi 20226-06-15 bukan 2026-06-15). Sistem sekarang sekat ini tiga lapis: kekang pilih-tarikh (min/max tahun), semakan di skrin sebelum simpan, & semakan di server (tarikh tidak sah ditolak terus, tidak boleh masuk pangkalan data). Selain itu, bila petugas tambah temu janji baru untuk pesakit yang sudah ada temu janji pada tarikh sama, sistem papar amaran & senarai temu janji sedia ada dahulu sebelum benarkan simpan — supaya tidak tertambah dua kali secara tidak sengaja.
Waktu temu janji & kiraan masa menunggu (2026-08-11) Laporan HPIA mengira masa menunggu bermula dari masa daftar atau waktu temu janji — mana yang lebih lewat. Contoh: temu janji pukul 10:00 tetapi pesakit sampai 8:00 pagi & dirawat 10:30 — masa menunggunya 30 minit, bukan 2 jam 30 minit, sebab pesakit belum lagi "menunggu" sebelum waktu janjinya sampai. Kalau pesakit sampai lewat (janji 8:00, sampai 9:00), kiraan bermula dari 9:00 seperti biasa.

Sebab itu, bila kaunter tanda Pesakit Temu Janji semasa pendaftaran, ada ruangan baharu Waktu Temu Janji. Kalau temu janji itu sudah ada dalam skrin Temu Janji, waktunya diisi automatik — kaunter cuma perlu taip sendiri kalau temu janji itu dibuat secara lisan atau atas kertas. Doktor pula nampak waktu ini (baca sahaja) di sebelah Waktu mula rawatan dalam Rekod Klinikal. Lawatan tanpa temu janji tidak berubah langsung — kiraannya tetap dari masa daftar.
Carian ikut tarikh & format tarikh dd/mm/yyyy (2026-07-30) Bila anda taip nama pesakit dalam kotak carian Temu Janji, ia kini ikut tarikh yang dipilih — bukan cari semua tarikh sekali gus. Nak cari merentas semua tarikh? Tandakan kotak Cari Tanpa Tarikh Spesifik. Ruangan pilih-tarikh penapis juga sekarang sentiasa papar tarikh dalam format hh/bb/tttt (cth. 30/07/2026) pada semua peranti — dulu telefon tetapan Amerika papar 07/30/2026 (bulan/hari terbalik).
Baris PG101B tidak hilang garisan lagi bila sebulan lebih 491 pesakit (2026-07-31) Dalam Google Sheet PG101B, hiasan baris (garisan kotak, tulisan kecil, tarikh format 31/07/2026, warna merah untuk pesakit Baru) dulu disiapkan setakat baris 500 sahaja — bermakna 491 pesakit sebulan. Bulan Julai 2026 pertama kali lepas had itu, jadi pesakit ke-492 dan seterusnya keluar kosong tanpa garisan, dan tarikh terpapar sebagai 2026-07-31. Data tidak pernah hilang atau salah — cuma paparan. Sekarang sistem kira sendiri berapa jauh perlu dihias ikut bilangan pesakit bulan itu (sentiasa lebih 200 baris di hadapan), jadi tiada had — 500, 2,000 atau 12,000 pesakit sebulan pun sama.

Untuk bulan yang sudah terjejas: Superadmin → kad Google Sheets → tekan Repair sekali untuk tahun berkenaan. Ia akan hias semula semua tab bulan tahun itu.
Salinan simpanan Google Sheet diambil jam 7 malam setiap hari (2026-07-31) Buku simpanan (arkib) PG101B yang berasingan itu bukan salinan serta-merta — ia diambil sekali sehari pada 7:00 malam. Jadi kalau anda buka arkib pada tengah hari, pesakit yang didaftar hari ini memang belum ada di situ; ia masuk malam nanti. Nak salin sekarang juga? Superadmin → kad Google Sheets → Backup arkib sekarang.
Laporan Penuh Pesakit kini ikut urutan tarikh & masa lawatan (2026-07-31) Fail Excel Laporan Penuh Pesakit dulu tersusun ikut abjad nama, jadi tarikh nampak melompat-lompat (6 Mei, 12 Jun, 26 Jun, 31 Mei…). Sekarang ia tersusun ikut tarikh lawatan, kemudian ikut masa pesakit sampai — sama seperti urutan sebenar klinik menerima pesakit pada hari itu. Pesakit yang datang beberapa kali muncul sekali bagi setiap lawatan, pada tarikh masing-masing.
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.

Nombor e-GL: tak perlu taip semula

Nombor surat jaminan berbentuk 6786/GL/1994626-2026-00001. Bahagian depan — kod hospital dan nombor pengenalan pesakit — tidak pernah berubah untuk pesakit yang sama. Hanya lima digit di hujung naik, dan itu pun cuma apabila GL baharu dikeluarkan.

Sebab itu, apabila pesakit kakitangan kerajaan mendaftar berulang dan kerani menanda kotak e-GL, sistem terus mengisi nombor dari lawatan terakhir dan menunjukkan tarikh lawatan itu:

"Diisi dari lawatan 12/03/2026. Sahkan nombor berjalan 00001 — GL baharu akan menaikkannya."

Tiga perkara yang kerani perlu tahu:

  • Semak lima digit terakhir. Kalau pesakit datang dengan GL baharu, tukar bahagian itu sahaja — bahagian depan kekal.
  • Nombor hanya masuk selepas kotak e-GL ditanda. Lawatan yang bukan tuntutan GL tidak akan tersimpan dengan nombor GL.
  • Amaran kuning bukan sekatan. Kalau nombor tidak menepati format biasa, sistem beri peringatan tetapi tetap membenarkan pendaftaran diteruskan — kerani tidak akan tersekat di kaunter.

Kenapa ini penting: semakan data pada Ogos 2026 mendapati 10 daripada 25 pesakit e-GL berulang membawa sekurang-kurangnya satu nombor tersalah taip (digit tertukar, GL jadi DG, garis miring hilang). Punca ralat itu ialah menaip semula, bukan GL yang bertukar.

Bahagian tetap nombor e-GL dalam profil pesakit

Profil pesakit kini memaparkan bahagian nombor e-GL yang tidak pernah berubah — kod hospital dan nombor pengenalan pesakit — contohnya 6786/GL/1994626. Dua bahagian yang berubah dipapar dalam warna kuning sebagai -TTTT-NNNNN: TTTT ialah tahun dan NNNNN ialah nombor berjalan.

Ia sengaja dipapar begitu supaya tiada siapa tersilap menyalinnya sebagai nombor penuh. Dua bahagian kuning itu mesti diambil dari surat GL yang asal setiap kali.

Bahagian tetap ini tidak perlu ditaip atau dikemas kini sesiapa — sistem mengambilnya sendiri daripada nombor e-GL yang kerani simpan. Semasa pendaftaran ulangan, ia terus mengisi sehingga tahun, tinggal nombor berjalan sahaja untuk ditaip.

Senarai "Nombor e-GL / OKU tidak sepadan"

Bila nombor seorang pesakit berbeza antara dua lawatan, salah satunya mesti salah. Sistem mengesan sendiri keadaan ini dan memaparkannya sebagai kad pada papan pemuka — termasuk papan pemuka Kaunter. Klik kad itu untuk membuka senarai penuh.

Setiap baris menunjukkan nama pesakit, sebab percanggahan, dan setiap nombor yang direkodkan berserta tarikh lawatannya. Butang Kemaskini membawa terus ke rekod pesakit itu — tidak perlu mencari.

  • Senarai membersih sendiri. Sebaik nombor dibetulkan, pesakit itu hilang dari senarai. Tiada siapa perlu memadam apa-apa.
  • GL baharu pun disenaraikan. Sistem tidak boleh membezakan GL baharu daripada salah taip — hanya surat asal boleh. Kalau semakan mendapati nombor itu memang betul, biarkan sahaja; ia akan kekal di senarai sehingga kedua-dua lawatan menggunakan nombor yang sama.
  • Kad hilang bila senarai kosong, jadi ia hanya muncul bila ada kerja.
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.

Jadual Lampiran C di dalam skrin

Halaman Laporan Bayaran memaparkan satu jadual yang disusun sama seperti borang kertas — ‘LAMPIRAN C’ Laporan Bulanan Kutipan Hasil — supaya angka di skrin boleh dibandingkan terus dengan borang yang ditandatangan dan dihantar. Tiga kategori (A Kakitangan Kerajaan, B Orang Awam, C Warganegara Asing), setiap satu dengan tiga baris bernombor: (i) Pesakit Luar, (ii) Pemeriksaan Perubatan, (iii) Lain-lain kutipan (gigi palsu).

Baris bertanda sengkang — bukan sifar Baris (ii) Pemeriksaan Perubatan memaparkan tanda sengkang (—), bukan 0. Sebabnya: sistem tidak merekod bayaran pemeriksaan perubatan, dan menulis 0 pada borang hasil rasmi bermaksud “tiada kutipan” — satu kenyataan yang berbeza daripada “tiada rekod”. Isi baris itu secara manual pada borang jika berkenaan. Lajur pengecualian bagi gigi palsu juga bertanda sengkang atas sebab yang sama.

Peraturan kiraan: Cara A (keputusan PKPD, 14/08/2026)

Satu lawatan dikira dalam SATU lajur sahaja. Kalau ada apa-apa bayaran dikutip — pendaftaran atau rawatan — lawatan itu dikira Membayar, walaupun sebahagian caj dikecualikan. Hanya lawatan yang tiada bayaran langsung dikira di lajur Percuma / Dikecualikan. Kerana itu jumlah bilangan sentiasa padan dengan jumlah pesakit.

Kesan yang perlu diketahui Warga emas yang dikecualikan caj pendaftaran RM5 tetapi membayar RM15 caj rawatan dikira sebagai Membayar, dan RM5 pengecualian itu tidak dilaporkan dalam Amaun Pengecualian. Kalau lawatan berikutnya dia datang tanpa rawatan — hanya pendaftaran yang dikecualikan — barulah lawatan itu dikira Percuma.
Kaunter tidak berubah Kotak Pengecualian / Percuma tetap ditanda seperti biasa setiap kali ada caj dikecualikan, dan Nilai Pengecualian tetap diisi. Laporan yang menentukan cara mengira — bukan kaunter. Jadi kalau peraturan bertukar kelak, tiada data perlu dimasukkan semula.

Yang masih tersilap ialah bila kotak Percuma ditanda sedangkan Bayaran Pendaftaran ada nilai — bermakna RM5 itu sebenarnya dikutip, jadi tiada apa yang dikecualikan. Betulkan lawatan itu melalui borang edit pendaftaran.

Muat turun atau e-mel borang Lampiran C

Butang Jana Lampiran C di penjuru halaman Laporan Bayaran mengeluarkan borang itu sebagai fail Excel. Ia terbuka kepada Kaunter, Pentadbir dan Superadmin — ketiga-tiganya memang sudah ada halaman Laporan Bayaran. Kaunter juga boleh sampai ke tempat yang sama melalui kad Lampiran C di halaman Jana Laporan.

Dalam kotak yang terbuka, pilih julat tarikh dahulu, kemudian pilih fail yang dikehendaki:

  • Borang Lampiran C — borang rasmi yang ditandatangani, bagi julat tarikh yang dipilih, dalam bentuk fail PDF. Angkanya adalah angka yang sama seperti jadual di skrin, termasuk tanda sengkang pada baris yang sistem tiada rekod.
  • Lejar harian — buku kerja harian bagi julat yang sama: satu baris setiap hari, dan satu tab bagi setiap bulan yang disentuh julat itu. Ini buku kerja, bukan borang yang dihantar.
Kenapa PDF, bukan Excel? Borang ini dicetak dan ditandatangani, jadi saiznya tidak boleh berubah-ubah. Dalam Excel, saiz kertas dan skala cetakan ditentukan oleh tetapan komputer masing-masing, dan tulisan yang panjang boleh terpotong di dalam kotaknya. PDF mengunci semuanya: A4 menegak, satu muka surat, jadual memenuhi kertas, susunan dan lebar lajur sama seperti borang kertas. Buka dan tekan cetak — tiada tetapan yang perlu diubah.

Lejar harian kekal fail Excel, kerana itu buku kerja yang memang perlu diisi dan diedit.
Hantar ke e-mel pergi ke mana? Ke alamat e-mel laporan yang ditetapkan dalam Tetapan Hospital. Tiada alamat ditaip di sini — itu memang sengaja, supaya setiap hospital hanya menghantar ke alamatnya sendiri.
Baris (iii) tidak sama bagi setiap kategori Bagi A (Kakitangan Kerajaan) dan B (Orang Awam), baris (iii) ialah gigi palsu. Bagi C (Warganegara Asing), baris (iii) ialah ibu mengandung — ikut borang kertas.

Sebab itu, bagi kategori C sahaja, pesakit ibu mengandung ditolak daripada baris (i) dan dipindahkan ke baris (iii). Kalau tidak, wang yang sama dikira dua kali dan JUMLAH akan tersalah lebih. Contoh: 6 lawatan berbayar RM300, 2 daripadanya ibu mengandung (RM90) → baris (i) tunjuk 4 / RM210, baris (iii) tunjuk 2 / RM90, JUMLAH kekal RM300.
Julat tarikh ikut apa yang dipilih, bukan bulan penuh Kalau borang bulanan yang dikehendaki, pilih 1 haribulan hingga hari terakhir bulan itu — barulah baris ketiga borang tertulis BULAN OGOS TAHUN 2026 seperti borang kertas. Kalau julat bukan satu bulan penuh, baris itu akan tulis tarikh sebenar (contoh TEMPOH 05/08/2026 — 09/08/2026) supaya lima hari tidak tersalah baca sebagai sebulan penuh.
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 7 langkah tukar kata laluan dulu App biasa flag = 1

Tujuh langkah wizard

Langkah pertama (tukar kata laluan) wajib — sistem tak boleh dibuka dengan kata laluan provision yang dikongsi. Langkah Identiti (nama hospital) juga wajib — ia dicetak pada borang rasmi PG201/PG211, jadi ia tidak boleh dilangkau. Langkah lain boleh dilangkau ("Buat nanti") & disiapkan kemudian — kecuali pilihan jabatan, yang kekal selepas wizard selesai:

LangkahApaWajib?
0 · Kata laluanTukar kata laluan sementara kepada milik sendiriYa
1 · Pilih jabatanOMFS (borang PG201/PG211) atau jabatan lain (laporan ringkasan generik)Lalai OMFS · set-once
2 · IdentitiNama hospital, alamat, negeri, logo letterheadYa (nama hospital + negeri wajib)
3 · PenggunaTambah akaun kerani / doktor / pentadbirBoleh nanti
4 · Templat on-callPilih corak rotasi (OMFS / tunggal / dua peringkat)Boleh nanti
5 · Semak katalogSahkan senarai diagnosis & rawatanBoleh nanti
6 · SelesaiTandakan wizard siap → suis jadi 1—

Pasang untuk jabatan lain (bukan OMFS)

Secara lalai sistem ini ialah pemasangan OMFS — ia mengeluarkan borang kebangsaan PG201/PG211. Tetapi rangka kerja yang sama boleh dipasang untuk jabatan lain: pada Langkah 1 wizard ("Pilih Jabatan"), pilih "Jabatan lain". Ini menukar pakej laporan sahaja — pendaftaran pesakit, peranan pengguna, dan jadual on-call kekal sama.

Apa yang berubah bila jabatan = "lain":

BahagianOMFSJabatan lain (generik)
LaporanBorang PG201 & PG211 kebangsaan"Laporan Ringkasan" Excel (kategori × kumpulan umur), muat turun terus
Katalog diagnosis/rawatanSenarai OMFS terkunci (terikat borang)Boleh ubah sepenuhnya — mula dari butang "Mula dari templat" di editor katalog
Pembantu AIPanduan OMFS terperinciPanduan ringkas berpandukan katalog + teks panduan jabatan sendiri

Selepas wizard, bahagian yang boleh diubah berada di halaman superadmin "Tetapan Jabatan" (/superadmin/tetapan-jabatan) — nama paparan jabatan & teks panduan AI. Katalog disunting di /superadmin/katalog, di mana butang "Mula dari templat" muncul hanya untuk jabatan bukan OMFS.

Pilihan jabatan kekal selepas pasang Pilihan jabatan ialah set-once: sebaik wizard selesai (setup_completed = 1), jenis jabatan dibekukan & tak boleh ditukar. Ini melindungi hospital sedia ada daripada secara senyap menukar output laporannya. Jadi pilih dengan betul semasa wizard — untuk unit OMFS biasa, biarkan lalai (OMFS).

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 superadmin → Tetapan Hospital
Baris fasiliti pada laporan PG201/PG211Tetapan Hospital → bahagian Laporan PG201/PG211
E-mel penerima laporan (laporan yang dihantar melalui e-mel)Tetapan Hospital → bahagian Laporan PG201/PG211 → E-mel penerima laporan. Kosong = e-mel laporan dimatikan; muat turun Excel masih boleh. Panel "Perkhidmatan e-mel (Resend)" di bawahnya menunjukkan sama ada e-mel sudah dikonfigurasi, dengan butang "Hantar e-mel ujian" untuk menguji penghantaran (simpan dahulu jika baru tukar alamat).
Hamparan Google Sheets (LIVE / ARKIB) untuk PG101BLog masuk superadmin → Dashboard → kad Penyelenggaraan Sistem → kad Google Sheets PG101B → Konfigurasi hamparan. Tampal ID hamparan (atau URL penuh), kongsikan hamparan itu dengan e-mel akaun servis yang dipaparkan. ID hamparan bukan rahsia — jadi ia diurus di sini, bukan oleh pengaturcara.
Tukar hamparan setiap tahun baharu (elak tersilap tulis ke tab tahun lama)Awal tahun: (1) cipta hamparan Google baharu, (2) kongsi dengan e-mel akaun servis, (3) tampal ID baharu di Konfigurasi hamparan → Tukar ID hamparan LIVE dan Simpan, (4) jalankan Init Tahun untuk tahun baharu, (5) Backfill jika perlu pindah rekod. Sistem akan menghalang penulisan tahun baharu ke tab tahun lama sehingga langkah ini dibuat.
Senarai penerima surat rasmiTetapan Hospital → Senarai rujukan
Hospital rujukan pakar pada surat on-callTetapan Hospital → Hospital Rujukan Pakar. Senaraikan hospital yang pakarnya boleh dirujuk semasa pakar hospital ini tiada. Nama-nama ini dicetak dalam perenggan konsultasi pada surat on-call. Jika senarai ini kosong, perenggan itu tidak dicetak langsung dan perenggan seterusnya dinomborkan semula — jadi surat tidak akan ada nombor yang tertinggal.
Nombor IT / sokongan tempatan hospital (di "Bantuan")Tetapan Hospital → IT / Sokongan Tempatan
Tambah staf, tukar peranan, beri kuasa urus on-callLog masuk superadmin/pentadbir → Urus 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 Umum
Muat set cuti umum mengikut negeri (untuk jadual on-call — hari cuti dikira syif berat 24 jam)Pilih Negeri di Tetapan Hospital dahulu, kemudian Modul On-Call (pengurus) → tab Cuti Umum → butang Muat cuti umum <negeri>. Butang ini hanya menambah tarikh dari set negeri (2026–2027) — cuti yang anda tambah sendiri tidak akan dipadam. Wizard persediaan juga memuatkannya secara automatik selepas langkah Identiti.
Tarikh locum OT (pegawai tidak boleh on-call hari itu)Modul On-Call (pengurus) → tab Tetapan Rotasi → bahagian Sekatan On-Call Sebelum OT → butang Urus tarikh locum OT. Pilih pegawai, masukkan satu atau beberapa tarikh, Simpan. Pegawai itu tidak akan dipilih untuk apa-apa panggilan pada tarikh OT dan malam sebelumnya. Kalau jadual bulan itu sudah dijana, ia akan bertanda perlu jana semula. Jana semula supaya sekatan ini diambil kira. Kalau pegawai itu masih tersangkut pada hari OT sendiri, contohnya kerana dia pegang blok panggilan kedua minggu itu, panel Jadual Bulanan akan tunjuk amaran kuning dengan nama dan tarikhnya.
Pegawai panggilan kedua nak ambil minggu sendiriPegawai buka kiosk on-call → tab Status Saya → kad Blok Panggilan Kedua Saya → Ambil pada minggu yang dia nak, kemudian masukkan PIN. Kalau tersilap, tekan Lepaskan pada minggu itu juga. Kad ini hanya keluar untuk pegawai yang kumpulannya memang mengisi blok mingguan, dan hanya untuk bulan yang jadualnya sudah dijana. YM masih boleh isi atau tukar mana-mana minggu di Jadual Bulanan → Blok Manual, termasuk mengosongkan minggu yang sudah ada nama.
Senarai diagnosis & rawatan (katalog pilihan dalam borang rekod klinikal)Log masuk superadmin → Katalog (/superadmin/katalog)
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.
Cara edit Katalog diagnosis & rawatan — Log masuk superadmin, buka Katalog (/superadmin/katalog, atau jubin "Katalog" di papan pemuka). Halaman ini ialah senarai pilihan yang doktor nampak dalam borang rekod klinikal (diagnosis & rawatan). Anda boleh tambah kategori/subkategori baharu, ubah nama, susun semula, dan padam — kemudian tekan Simpan. Perubahan terus dipakai untuk semua pengguna tanpa keluar kod.
  • Entri rasmi PG201 dikunci. Sesetengah entri ada lambang kunci dan kotak namanya tak boleh diubah/dipadam. Sebabnya: nama entri itu sepadan tepat dengan lajur borang rasmi PG201 kebangsaan — menukar namanya akan rosakkan laporan rasmi. Anda masih boleh tambah entri baharu di sekelilingnya.
  • Tambah = selamat; tukar nama = TIDAK tulis semula sejarah. Lawatan lama menyimpan pilihan sebagai teks. Kalau anda tukar nama satu entri, rekod lama masih simpan nama lama — sistem tidak mengarang semula sejarah. Jadi tambah entri baharu bila perlu; tukar nama hanya untuk membetulkan ejaan/susunan.
  • Laporan rasmi PG201/PG211 tidak berubah. Penjana laporan tidak membaca katalog ini — ia guna lajur kebangsaan yang tetap. Mengedit katalog hanya mengubah pilihan dalam borang, bukan bentuk borang.
  • Rawatan berpasangan untuk setiap diagnosis. Dalam tab Diagnosis, setiap baris ada satu butang kecil yang memberitahu rawatan apa yang sistem cari untuk diagnosis itu. Butang ini boleh ditekan walaupun nama entri terkunci, sebab yang terkunci ialah namanya, bukan senarai rawatannya. Ada tiga pilihan: Guna tetapan asal (senarai yang sistem sudah sediakan), Pilih rawatan sendiri, atau Tidak perlu rawatan untuk diagnosis seperti perundingan. Kalau anda tanda satu kumpulan rawatan, semua rawatan dalam kumpulan itu dikira, termasuk yang ditambah kemudian. Senarai inilah yang digunakan oleh peringatan Rawatan belum dituntut.

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.

Apa itu bar hitam "Belum disimpan" di bawah skrin?

Ia muncul sebaik sahaja anda ubah apa-apa dalam borang yang sudah pernah disimpan — kemaskini lawatan, butiran pesakit, rekod klinikal, Tetapan Hospital, katalog, tetapan rotasi on-call. Ia mengingatkan anda yang perubahan itu masih dalam pelayar sahaja, belum masuk ke sistem.

Dua butang: Simpan Perubahan menyimpan (kalau lebih satu bahagian diubah, semuanya disimpan sekali), dan Set Semula membuang perubahan dan kembali kepada nilai terakhir yang tersimpan — ia akan minta pengesahan dahulu. Bar itu hilang sendiri selepas anda simpan atau tinggalkan halaman. Borang pendaftaran pesakit baharu tiada bar ini kerana tiada rekod lama untuk dibandingkan.

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.