Panduan Kod 02 · Contoh
← Kod 01
Panduan Kod · Bahagian 02  ·  Sistem OMFS

Contoh Fail Sebenar

Sekarang kita buka fail betul-betul dalam sistem ini dan baca baris demi baris. Tetapi bukan semua 440 fail — cukup satu contoh setiap jenis. Faham 10 contoh ini, anda faham corak seluruh repo.

📝 Kontrak 🚪 Route 👨‍🍳 Service 🔒 Middleware 🗃 Migration ⚛️ React
00 Mula

Cara guna panduan ini

Panduan Kod 01 mengajar asas. Di sini kita guna asas itu untuk membaca fail sebenar. Kalau anda belum baca Kod 01, baca dulu — setiap anotasi di sini menganggap anda kenal simbol asas.

Rahsia: kod ini ada ~10 bentuk fail, bukan 440 benda berbeza

Sistem ini ada 440 fail. Bunyinya menakutkan. Tetapi rahsianya: fail-fail itu jatuh dalam segelintir bentuk yang berulang. Ada ~40 fail route — tetapi semuanya ikut corak yang sama. Faham satu route betul-betul, anda faham 40. Sebab itu kita pilih satu contoh terbaik setiap bentuk dan bedah sehabis-habisnya.

Analogi Macam belajar memandu. Anda tak perlu belajar setiap model kereta di dunia. Belajar satu kereta automatik, anda boleh pandu hampir semua kereta automatik. Bentuk fail pun begitu — corak yang sama, butiran sedikit berbeza.

Cara baca anotasi (ulang kaji ringkas)

Setiap blok kod di bawah ada anak panah : sebelah kiri kod sebenar, sebelah kanan maksudnya dalam bahasa biasa. Kami juga memendekkan sesetengah baris (ganti butiran panjang dengan ) supaya fokus pada idea, bukan setiap aksara. Untuk versi penuh tanpa potong, buka fail itu sendiri atau tanya cikgu anda:

Tanya Claude Code Saya baru belajar coding. Buka fail worker/routes/hospital.ts dan terangkan setiap baris penuh, dalam bahasa mudah. Jangan potong apa-apa.
Cara terbaik Baca setiap contoh, kemudian buka fail sebenar di sebelahnya dan bandingkan. Bila satu baris buntu — salin, tampal kepada Claude Code, tanya. Itu cara perbendaharaan kata anda membesar.
01 Backend · Bentuk 1

Kontrak (Zod) — penjaga bentuk data

Sebelum apa-apa data masuk ke sistem, ia mesti lulus pemeriksaan bentuk. Fail kontrak ini ialah senarai "data ni mesti nampak macam mana".

Apa fail ini: worker/contracts/hospital.contracts.ts mentakrif bentuk sah untuk "identiti hospital" (nama, alamat, telefon, logo…). Ia guna pustaka bernama Zod. Anggap ia borang dengan peraturan: "telefon mesti teks", "e-mel mesti betul-betul nampak macam e-mel".

Analogi Kontrak ialah pegawai di kaunter yang semak borang anda sebelum benarkan masuk. Tulis e-mel tanpa "@"? Ditolak di pintu — tak sempat rosakkan apa-apa di dalam.
worker/contracts/hospital.contracts.ts
import { z } from 'zod';                       ← bawa masuk Zod — alat penyemak bentuk data

const ContactSchema = z.object({         ← takrif bentuk "contact": satu objek dengan…
  tel: z.string(),                       ← …medan 'tel' mesti teks (string)
  email: z.string().email().or(z.literal('')), ← e-mel: format SAH, ATAU teks kosong
});

export const HospitalIdentitySchema = z.object({ ← bentuk PENUH satu hospital…
  appName: z.string().min(1),             ← nama app: teks, sekurang-kurangnya 1 huruf
  hospitalName: z.string().min(1),        ← nama hospital: wajib ada isi
  contact: ContactSchema,                  ← guna semula bentuk 'contact' di atas
  // …medan lain (address, letterhead, logos…)
});

export type HospitalIdentityInput =       ← jana 'jenis' TypeScript automatik…
  z.infer<typeof HospitalIdentitySchema>;     ← …DARI bentuk di atas (kontrak & jenis sentiasa sepadan)

Kenapa ia penting: kontrak ditulis sekali, lalu diguna di dua tempat — backend pakai ia untuk menolak data buruk (lihat e2), dan TypeScript guna baris z.infer untuk menjana jenis automatik. Jadi kalau anda tambah medan baru, kedua-dua tempat dapat tahu serta-merta. Satu sumber kebenaran.

Perkataan baru Zod — pustaka penyemak bentuk data (data validation). Schema — takrifan bentuk yang sah. import — bawa masuk kod dari fail/pustaka lain. const — nama yang nilainya tak berubah. export — benarkan fail lain guna benda ini. Type (jenis) — label TypeScript yang kata "data ni rupa macam apa".
Pergi lebih dalam (corak yang sama) Terangkan worker/contracts/oncall-config.contracts.ts baris demi baris. Ia ikut corak yang sama dengan hospital.contracts.ts — apa beza dan kenapa?
02 Backend · Bentuk 2

Route — pintu yang backend dengar

Sebuah route ialah satu alamat (cth /api/hospital/config) yang backend dengar. Bila frontend "ketuk" alamat itu, fungsi route berjalan.

Apa fail ini: worker/routes/hospital.ts daftar dua pintu untuk tetapan hospital — satu untuk baca (GET), satu untuk kemas kini (PUT). Perhatikan: route sendiri tipis. Ia tidak sentuh database terus; ia upah seorang service (e3) untuk kerja sebenar.

Analogi Route ialah pelayan restoran. Ia terima pesanan anda di meja, semak ia masuk akal, dan hantar ke dapur. Pelayan tak masak sendiri — dapur (service) yang masak.
worker/routes/hospital.ts
export function registerHospitalRoutes(app) {  ← fungsi yang mendaftar semua pintu hospital

  app.get('/api/hospital/config', async (c) => { ← PINTU 1 (GET = baca). c = konteks (req+res)
    const svc = new HospitalConfigService(c.env, 'system'); ← upah 'pekerja' yang sentuh DB
    const config = await svc.get();    ← suruh ia ambil tetapan; tunggu siap (await)
    return ok(c, config);             ← balas { success:true, data: config }
  });

  app.put('/api/hospital/config',        ← PINTU 2 (PUT = kemas kini)…
    requireRole('superadmin'), async (c) => { ← …tapi KUNCI dulu: superadmin sahaja (e4)
      const parsed = UpdateHospitalConfigRequest.safeParse(body); ← semak data ikut kontrak (e1)
      if (!parsed.success) return bad(c, '…'); ← bentuk salah → tolak dgn mesej ralat
      const updated = await svc.update(parsed.data); ← data sah → suruh pekerja simpan
      return ok(c, updated);
    });
}

Kenapa ia penting: ini corak setiap route dalam sistem: (1) kunci dengan requireRole kalau perlu, (2) semak data masuk dengan kontrak Zod, (3) hantar kerja kepada service, (4) balas dengan ok() atau bad(). Route kekal pendek dan mudah dibaca.

Peraturan rumah Dalam projek ini, route tak dibenarkan sentuh database terus — mesti melalui service. Itu yang buat 40 route nampak serupa & mudah difahami. (Lihat .claude/rules/api.md.)
Perkataan baru Route — satu alamat backend (cth /api/…). GET — minta baca data. PUT — minta kemas kini data. async / await — "tunggu kerja yang lambat (DB, rangkaian) siap dulu". Handler — fungsi yang berjalan bila pintu diketuk.
Pergi lebih dalam (corak yang sama) Terangkan worker/routes/oncall.ts baris demi baris — ia ikut corak route yang sama dengan hospital.ts di atas, cuma lebih banyak pintu.
03 Backend · Bentuk 3

Service — pekerja yang sentuh database

Kalau route ialah pelayan, service ialah tukang masak di dapur. Di sinilah kerja sebenar berlaku: baca & tulis database, gabung data, tulis jejak audit.

Apa fail ini: worker/services/HospitalConfigService.ts tahu cara baca dan simpan tetapan hospital di dalam database. Ia satu class — satu "bekas" yang membungkus data + tindakan yang berkaitan.

worker/services/HospitalConfigService.ts
export class HospitalConfigService {        ← 'pekerja' yang tahu baca/tulis tetapan hospital
  constructor(private env, private actorId) {} ← bila diupah, ingat: env (akses DB) + siapa buat

  async get() {                            ← tindakan: ambil tetapan
    const row = await this.env.DB.prepare( ← sedia satu arahan SQL untuk DB…
      'SELECT config FROM hospital_config WHERE id = 1', ← …ambil baris tunggal (id=1)
    ).first();                          ← jalankan, ambil baris pertama
    if (!row) return DEFAULT_HOSPITAL_IDENTITY; ← tiada baris? pulang nilai lalai (fallback)
    // …sahkan ikut kontrak (e1) sebelum pulang
  }

  async update(patch) {                     ← tindakan: kemas kini sebahagian tetapan
    const current = await this.get();   ← ambil yang sedia ada dulu
    const merged = { ...current, ...patch }; ← gabung: mula dari lama, timpa dengan baru
    const validated = HospitalIdentitySchema.parse(merged); ← sahkan hasil gabungan (kontrak e1)
    await this.env.DB.prepare(`INSERT … ON CONFLICT … DO UPDATE`) ← simpan (cipta-atau-ganti)
      .bind(JSON.stringify(validated), this.actorId).run();
    await logAction(this.env, this.actorId, 'UPDATE_HOSPITAL_CONFIG'); ← jejak audit: siapa buat apa
    return validated;
  }
}

Kenapa ia penting: service memusatkan logik. Tindakan update di sini buat tiga benda yang mesti berlaku bersama: gabung & sahkan, simpan ke DB, dan tulis jejak audit. Kalau logik ini bertabur dalam banyak route, satu hari satu route lupa tulis audit — pepijat senyap. Dengan service, ia ditulis sekali, betul untuk semua.

Perkataan baru Service — kelas yang memegang logik perniagaan + akses DB. Class — "acuan" yang membungkus data + tindakan. constructor — apa yang berlaku bila kelas "diupah" (dicipta). this — "diri sendiri" (objek ini). bind — isi nilai ke dalam ruang ? SQL dengan selamat (elak suntikan SQL). Audit log — rekod "siapa buat apa, bila".
Pergi lebih dalam (corak yang sama) Terangkan worker/services/OncallConfigService.ts. Bandingkan strukturnya dengan HospitalConfigService — constructor, get, update, dan kenapa ia tulis audit.
04 Backend · Bentuk 4

Middleware — kunci di depan pintu

Middleware ialah kod yang berjalan di tengah — selepas permintaan tiba, tetapi sebelum handler pintu sebenar. Di sini, tugasnya: semak pengguna dibenarkan.

Apa fail ini: worker/middleware/auth.ts ialah penjaga keselamatan. requireRole('superadmin') yang anda nampak di e2 datang dari sini.

Analogi Middleware ialah pengawal keselamatan di lobi. Sebelum anda sampai ke bilik (handler), dia semak pas anda. Tiada pas → dihalau. Pas salah jenis → dihalau. Pas betul → dia kata "silakan" dan anda teruskan.
worker/middleware/auth.ts
export const requireRole = (...roles) => ← fungsi yang PULANGKAN satu penjaga…
  async (c, next) => {                ← …penjaga = fungsi yang lari SEBELUM handler
    const jwt = c.get('jwtPayload');    ← ambil 'pas masuk' pengguna (token disahkan)
    if (!jwt) return unauthorized(c); ← tiada pas → 401 (belum log masuk)
    if (!roles.includes(jwt.role)) {  ← pas ada, tapi peranan tak dibenarkan…
      return forbidden(c, '…');     ← …→ 403 (dilarang)
    }
    return next();                  ← lulus semua → teruskan ke handler pintu
  };

Kenapa ia penting: tanpa middleware, setiap route perlu menyalin semula semakan log-masuk — mudah terlupa pada route baru, dan satu route terlupa = lubang keselamatan. Dengan requireRole, kawalan akses jadi satu baris di atas pintu. 401 bermaksud "siapa awak?" (belum log masuk); 403 bermaksud "saya kenal awak, tapi awak tak dibenarkan".

Perkataan baru Middleware — kod yang lari di tengah, antara permintaan dan handler. JWT / token — "pas masuk" digital yang membuktikan siapa pengguna. next() — "teruskan ke langkah berikut". 401 — belum log masuk. 403 — log masuk, tapi tiada kebenaran. ...roles (rest) — "terima berapa-berapa peranan jadi satu senarai".
Pergi lebih dalam Dalam worker/middleware/auth.ts, apa beza requireAuth dan requireRole? Dan di mana jwtPayload mula-mula diletak sebelum middleware ini baca?
05 Backend · Gabung

Jejak satu permintaan (01→04 bersama)

Empat bentuk tadi tidak hidup sendiri — mereka bekerja sebagai satu rantaian. Mari jejak satu permintaan "simpan tetapan hospital" dari mula sampai habis.

Pengguna tekan "Simpan" di halaman tetapan

Frontend hantar permintaan PUT /api/hospital/config dengan data borang.

Kunci: middleware (e4)

requireRole('superadmin') semak pas. Bukan superadmin → 403, berhenti di sini.

Pintu: route (e2)

Handler PUT terima data, hantar ke kontrak untuk disemak.

Penjaga bentuk: kontrak Zod (e1)

safeParse semak setiap medan. Bentuk salah → bad(), berhenti dengan mesej ralat.

Kerja sebenar: service (e3)

svc.update() gabung + sahkan + simpan ke DB + tulis audit.

Balas

Route pulang ok(c, updated). Frontend tunjuk "Berjaya disimpan".

Inilah corak teras seluruh backend Kunci → semak bentuk → service buat kerja → balas. Hampir setiap operasi tulis dalam sistem ini ikut empat langkah yang sama. Kenal corak ini, dan mana-mana route baru jadi mudah difahami.
Lihat sendiri Bila pengguna simpan tetapan hospital, senaraikan ikut turutan SETIAP fail yang terlibat, dari frontend sampai data masuk database. Saya nak nampak rantaian penuh.
06 Database · Bentuk 5

Migration mudah — tambah lajur

Struktur database tidak diubah secara rawak. Setiap perubahan ditulis sebagai satu migration — fail SQL bernombor, dijalankan sekali, ikut turutan.

Apa fail ini: migrations/0002_add_tarikh_lahir.sql ialah migration paling mudah yang wujud — ia tambah satu lajur baru ke table sedia ada.

migrations/0002_add_tarikh_lahir.sql
-- Add tarikh_lahir column to patients table   ← komen: apa migration ini buat (untuk manusia)
ALTER TABLE patients ADD COLUMN tarikh_lahir TEXT; ← tambah satu lajur baru 'tarikh_lahir' (jenis teks)

Kenapa ia penting: ini jenis perubahan database yang selamat dan paling biasa — ia cuma menambah, tak menyentuh data sedia ada. Nombor 0002 di hadapan nama penting: ia memastikan migration berjalan ikut turutan, sekali sahaja setiap satu. Sistem ini mengutamakan ALTER TABLE … ADD COLUMN berbanding membina semula table — sebab membina semula table itu bahaya (e7).

Perkataan baru Migration — satu langkah perubahan struktur database, dalam fail bernombor. ALTER TABLE — arahan SQL untuk mengubah table sedia ada. ADD COLUMN — tambah satu lajur. TEXT — jenis data "teks" dalam SQL. -- komen — nota untuk manusia; SQL abaikan.
07 Database · Bentuk 5b

Migration bahaya — cascade & backup

Kadang anda terpaksa bina semula sesebuah table, bukan sekadar tambah lajur. Di sinilah jebakan paling berbahaya dalam seluruh sistem bersembunyi.

Apa fail ini: migrations/0026_reten_batal_pulang_awal.sql perlu memperluas satu peraturan pada table visits. Untuk itu, ia terpaksa buang & bina semula table. Masalahnya: visits ada "anak-anak" table yang berpaut padanya dengan CASCADE.

Jebakan paling besar dalam sistem ini ON DELETE CASCADE bermaksud: "bila baris ibu dipadam, anak-anaknya ikut terpadam automatik." Dan SQLite menjalankan DELETE tersembunyi sebelum DROP TABLE. Jadi membuang visits tanpa berhati-hati akan memadam semua rekod klinikal anak (doktor lawatan, diagnosis, rawatan) secara kekal. Penyelesaiannya: salin anak dulu, bina semula, pulangkan anak.
migrations/0026_….sql (dipendekkan)
-- DROP TABLE visits cetus DELETE tersembunyi → CASCADE padam anak.
-- Maka: salin anak DULU sebelum buang ibu.

CREATE TABLE _mig0026_backup_visit_doctors AS SELECT * FROM visit_doctors; ← salin anak (selamat)
CREATE TABLE _mig0026_backup_diagnoses    AS SELECT * FROM diagnoses;
CREATE TABLE _mig0026_backup_managements  AS SELECT * FROM managements;

CREATE TABLE visits__mig0026 ( /* …CHECK diperluas: tambah 'BATAL' */ ); ← bina visits baru
INSERT INTO visits__mig0026 SELECT * FROM visits;  ← pindah data lama ke yang baru
DROP TABLE visits;                          ← buang yang lama (CASCADE padam anak — sebab itu kita salin!)
ALTER TABLE visits__mig0026 RENAME TO visits; ← namakan yang baru jadi 'visits'

INSERT OR IGNORE INTO visit_doctors SELECT * FROM _mig0026_backup_visit_doctors; ← pulang anak dari salinan
-- …sama untuk diagnoses & managements…
DROP TABLE _mig0026_backup_visit_doctors;     ← kemas: buang salinan sementara

Kenapa ia penting: ini bukan kod cantik — ini kod yang menyelamatkan data pesakit. Tarian "salin → bina semula → pulang" itu wajib setiap kali table visits dibina semula. Inilah sebab peraturan asas projek: elak bina semula table; utamakan ADD COLUMN (e6). Bila terpaksa juga, ikut corak 0026 ini tepat-tepat.

Perkataan baru CASCADE — "padam ibu, anak ikut terpadam". DROP TABLE — buang seluruh table (bahaya). Foreign key — pautan satu table ke table lain. CREATE TABLE … AS SELECT — cara cepat buat salinan table. INSERT OR IGNORE — masukkan data, langkau yang sudah ada. CHECK — peraturan SQL yang hadkan nilai dibenarkan dalam satu lajur.
Faham bahaya ini betul-betul Terangkan ON DELETE CASCADE macam saya budak 12 tahun, guna contoh table visits dan visit_doctors dalam sistem ini. Kenapa DROP TABLE visits boleh padam data anak?
08 Frontend · Bentuk 6

Router — peta alamat → halaman

Sekarang kita pindah ke frontend (bahagian yang pengguna nampak). Fail pertama: router — peta yang menentukan "alamat URL ini tunjuk halaman mana".

Apa fail ini: src/main.tsx ialah titik mula frontend. Bahagian terpentingnya ialah router — senarai pasangan "alamat → halaman". Ini fail React pertama anda lihat, jadi ia bercampur HTML (dipanggil JSX) dengan TypeScript.

src/main.tsx (dipendekkan)
const router = createBrowserRouter([     ← bina 'peta': alamat URL → halaman mana
  { path: "/", element: <HomePage /> },     ← alamat '/' → tunjuk HomePage
  { path: "/oncall/jadual",            ← alamat laman on-call awam…
    element: <OncallPublicPage /> },     ← …tunjuk halaman jadual on-call
  {
    element: <AppLayout />,             ← 'bekas' kongsi (header+menu) untuk anak di bawah
    children: [                          ← halaman-halaman di dalam bekas ini…
      {
        path: "/kaunter",
        element: <PrivateRoute allowedRoles={['pendaftar']}> ← KUNCI depan: pendaftar sahaja (e10)
                   <KaunterLayout /></PrivateRoute>,
        children: [                      ← sub-halaman di bawah '/kaunter'…
          { index: true, element: <RegistrarDashboardPage /> }, ← '/kaunter' → dashboard
          { path: "daftar", element: <PatientRegistrationPage /> }, ← '/kaunter/daftar' → borang daftar
        ],
      },
    ],
  },
]);

Kenapa ia penting: router ialah peta keseluruhan app. Mahu tahu halaman apa wujud dan siapa boleh masuk? Baca fail ini. Perhatikan corak bersarang: alamat di dalam children mewarisi bekas (AppLayout) dan kunci (PrivateRoute) di atasnya. <HomePage /> itu JSX — ia nampak macam HTML tapi sebenarnya cara React kata "letak komponen HomePage di sini".

Nota: "lazy" Dalam fail sebenar, kebanyakan halaman dimuat guna lazy(…) — bermaksud "jangan muat turun kod halaman ini sehingga pengguna betul-betul pergi ke sana". Ini buat app mula dengan pantas. Anda boleh abaikan butirannya buat masa ini.
Perkataan baru Router — peta alamat URL → halaman. JSX / .tsx — HTML yang ditulis dalam TypeScript (React). Component — satu kepingan UI yang boleh diguna semula (cth HomePage). path — corak alamat. children — halaman bersarang di dalam satu bekas. lazy — muat kod hanya bila perlu.
Pergi lebih dalam Buka src/main.tsx dan senaraikan semua alamat (path) untuk peranan 'doktor', dan halaman mana setiap satu tunjuk. Terangkan corak children bersarang.
09 Frontend · Bentuk 7

Context provider + custom hook

Bagaimana satu maklumat (cth nama hospital) boleh dibaca oleh mana-mana halaman tanpa dihantar tangan-ke-tangan? Jawapannya: context — satu "siaran" global.

Apa fail ini: src/contexts/HospitalConfigProvider.tsx mengambil tetapan hospital dari backend (pintu e2) sekali, lalu "menyiarkannya" ke seluruh app. Ia memperkenalkan tiga idea teras React: useState (ingatan), useEffect (kesan sampingan), dan custom hook.

src/contexts/HospitalConfigProvider.tsx (dipendekkan)
const HospitalConfigContext = createContext(DEFAULT_HOSPITAL_IDENTITY); ← cipta 'saluran siaran' global

export function HospitalConfigProvider({ children }) {  ← komponen yang menyiarkan tetapan ke anak
  const [config, setConfig] = useState(readCache() ?? DEFAULT_HOSPITAL_IDENTITY); ← ingatan: tetapan semasa
                                          ← useState beri [nilai, caraUbahNilai]

  useEffect(() => {                       ← lepas komponen muncul, jalankan sekali…
    fetch('/api/hospital/config')         ← …ambil tetapan terkini dari backend (pintu e2)
      .then(r => r.json())
      .then(({ data }) => { if (data) update(data); }) ← dapat → kemas kini paparan
      .catch(swallow('…'));            ← gagal rangkaian → biar; guna cache/lalai
  }, [update]);                            ← senarai kebergantungan (bila perlu jalan semula)

  return (
    <HospitalConfigContext.Provider value={config}> ← siarkan 'config' ke semua anak di dalam
      {children}
    </HospitalConfigContext.Provider>
  );
}

export function useHospitalConfig() {        ← 'hook' custom: cara mudah anak baca siaran
  return useContext(HospitalConfigContext); ← mana-mana komponen panggil ini → dapat tetapan
}

Kenapa ia penting: tanpa context, "nama hospital" perlu dihantar sebagai prop melalui setiap lapisan komponen — menyusahkan. Context buat ia macam siaran radio: provider menyiar, dan mana-mana komponen "tala" guna hook useHospitalConfig(). useState ialah ingatan komponen; useEffect ialah "buat sesuatu selepas muncul" (di sini: ambil data). .catch(swallow(…)) sengaja membiarkan kegagalan rangkaian senyap — sebab kita masih ada nilai cache/lalai untuk dipapar.

Perkataan baru Context — "siaran" data global supaya mana-mana komponen boleh baca. Provider — komponen yang menyiar. useState — ingatan dalam komponen; bagi [nilai, setNilai]. useEffect — jalankan kod selepas komponen muncul / berubah. Hook — fungsi React bermula dengan use…. Custom hook — hook anda sendiri (cth useHospitalConfig). Props — data dihantar masuk ke komponen (cth children).
Pergi lebih dalam Apa itu useState dan useEffect dalam React? Guna HospitalConfigProvider.tsx sebagai contoh. Kenapa useEffect ada senarai [update] di hujung?
10 Frontend · Bentuk 8

Komponen daun — pengawal laluan

Komponen "daun" ialah kepingan UI kecil dengan satu tugas. Contoh ini, PrivateRoute, ialah pasangan frontend untuk middleware backend (e4): ia jaga siapa boleh lihat satu halaman.

Apa fail ini: src/components/PrivateRoute.tsx ialah pembalut. Anda nampak ia membalut halaman di router (e8). Tugasnya: kalau pengguna belum log masuk atau peranan salah, halau ke laman lain; jika tidak, tunjuk kandungan.

src/components/PrivateRoute.tsx
export function PrivateRoute({ children, allowedRoles }) { ← komponen pembalut: jaga siapa boleh masuk
  const isAuthenticated = useAuthStore(s => s.isAuthenticated); ← baca status log masuk dari stor auth
  const role = useAuthStore(s => s.role);    ← baca peranan pengguna

  if (!isAuthenticated || !role || !allowedRoles.includes(role)) { ← belum log masuk ATAU peranan salah…
    return <Navigate to={systemToken ? '/pilih-fungsi' : '/'} replace />; ← …halau ke laman yang sesuai
  }
  return children;                       ← lulus → tunjuk kandungan yang dijaga
}

Kenapa ia penting: ini menunjukkan dua perkara. Pertama, keselamatan ada dua lapis: backend (e4) ialah lapisan sebenar yang melindungi data; PrivateRoute cuma pengalaman pengguna (elak tunjuk skrin yang mereka tak patut nampak). Kedua, ini komponen kecil yang bersih — satu tugas, mudah dibaca. Kebanyakan "daun" dalam src/components/ ikut bentuk ini.

Penting: jangan keliru dua lapisan Kawalan frontend seperti ini tidak boleh dipercayai untuk keselamatan — sesiapa boleh memintas skrin. Kebenaran sebenar mesti dikuatkuasakan di backend (middleware e4 + semakan dalam route). Frontend cuma menjadikan pengalaman kemas.
Perkataan baru Komponen daun — komponen kecil, satu tugas. Propschildren (kandungan dibalut) & allowedRoles dihantar masuk. Store — stor data dikongsi (di sini: status auth). Navigate / replace — alihkan pengguna ke alamat lain. Guard — pengawal yang membenar/menghalang.
Pergi lebih dalam Dalam PrivateRoute.tsx, kenapa ia halau ke '/pilih-fungsi' kalau ada systemToken, tapi ke '/' kalau tiada? Terangkan model auth dua-peringkat sistem ini.
11 Frontend · Bentuk 9

Hook custom — ambil data

Bentuk terakhir: custom hook yang berdiri sendiri. Ia membungkus logik "ambil data dari backend" supaya banyak komponen boleh guna semula tanpa salin-tampal.

Apa fail ini: src/hooks/useServerTime.ts ambil masa semasa dari server. Ia contoh bersih corak yang berulang di mana-mana frontend: "muat data, simpan status sedang-muat, bersih bila komponen tutup".

src/hooks/useServerTime.ts
export function useServerTime() {            ← hook custom: dapatkan masa dari server
  const [data, setData] = useState(null);  ← simpan hasil (mula: belum ada)
  const [loading, setLoading] = useState(true); ← simpan status 'sedang muat'

  useEffect(() => {                       ← lepas guna kali pertama, ambil masa…
    let cancelled = false;              ← bendera: elak set data kalau komponen dah tutup
    (async () => {
      try {
        const res = await api('/api/time'); ← panggil pintu '/api/time'
        if (!cancelled) setData(res.data); ← simpan hasil (kalau masih relevan)
      } catch {
        if (!cancelled) setData(null); ← gagal → biar kosong
      } finally {
        if (!cancelled) setLoading(false); ← apa pun jadi, 'muat' tamat
      }
    })();
    return () => { cancelled = true; };  ← bila komponen tutup, tanda 'batal'
  }, []);                                  ← [] = jalankan SEKALI sahaja

  return { data, loading };               ← bagi balik kepada komponen yang guna
}

Kenapa ia penting: mana-mana komponen kini boleh tulis const { data, loading } = useServerTime() dan dapat masa server + status muat — logik ditulis sekali. Perhatikan bendera cancelled: kalau pengguna tutup halaman sebelum data tiba, kita tidak cuba kemas kini komponen yang dah hilang. Itu corak penting yang elak ralat React biasa.

Perkataan baru Custom hook — fungsi use… anda sendiri yang membungkus logik boleh-guna-semula. loading state — status "data belum sampai". try / catch / finally — cuba; kalau gagal tangkap; akhirnya buat tak kira apa. cleanup — kod yang lari bila komponen tutup (fungsi yang useEffect pulangkan). cancelled flag — bendera elak kemas kini selepas tutup.
Pergi lebih dalam Terangkan kenapa useServerTime guna bendera 'cancelled'. Apa ralat React yang berlaku kalau kita buang bendera itu? Beri contoh mudah.
12 Seterusnya

10 bentuk → 440 fail

Anda baru sahaja membaca satu contoh setiap bentuk utama dalam sistem ini. Inilah kuncinya: hampir setiap fail lain ialah variasi salah satu bentuk ini.

Bila anda buka…Ia ikut bentuk…
worker/contracts/*.contracts.tsKontrak Zod (e1)
worker/routes/*.tsRoute (e2) — ~40 fail, corak sama
worker/services/*Service.tsService (e3)
worker/middleware/*.tsMiddleware (e4)
migrations/NNNN_*.sqlMigration mudah (e6) atau bina-semula (e7)
src/contexts/*Provider.tsxContext provider + hook (e9)
src/components/*.tsxKomponen daun (e10)
src/hooks/use*.tsCustom hook (e11)
src/pages/[role]/*.tsxHalaman = komponen besar (gabungan e9–e11)
Cara belajar dari sini Pilih satu ciri yang anda nak faham (cth on-call). Cari fail-failnya, dan untuk setiap satu, tanya diri: "ini bentuk mana?" Kemudian guna kotak Tanya Claude Code di bawah untuk minta penjelasan baris demi baris — sambil tahu anda sudah kenal coraknya.
Cikgu peribadi anda menunggu Saya dah faham 10 bentuk fail dalam Sistem OMFS. Sekarang saya nak faham ciri on-call. Senaraikan fail-fail utamanya, kelaskan setiap satu ikut bentuk (route/service/contract/komponen/hook), dan cadang turutan untuk saya baca.
Ingat Anda tidak perlu menghafal 440 fail. Anda perlu kenal ~10 bentuk — yang anda baru sahaja baca — dan tahu cara bertanya bila tersangkut. Setiap fail yang anda bedah selepas ini menjadi lebih mudah daripada yang sebelumnya. Itu sahaja yang perlu.