طَبِّقْهاتعلّم المبدأ، وطبّقه بيدكمقرر 302 · من الفكرة إلى المنتج (٢): نظام حجز مواعيد
الدرس 6 · الوحدة 3: التأمين والواجهة التفاعلية

المحطّة ٦: واجهة React تعرض المواعيد وتحجز

تكامل الطبقتين: واجهةٌ تعرض المواعيد وتحجزها من خادمك. ⏱ نحو 34 دقيقة.

بعد هذا الدرس ستستطيع أن:
  • تجلب بيانات الخادم وتعرضها في React.
  • ترسل طلب POST بجسم JSON من معالج حدث.
  • تحدّث الواجهة لتعكس حالة الخادم بعد التغيير.
  • تربط طبقتَي المشروع في تطبيقٍ واحدٍ يعمل.

١الطبقة الأخيرة: واجهةٌ يلمسها المستخدم

الخادم جاهزٌ من المحطّات السابقة. نبني الآن واجهة React تستهلك الـ API: تجلب المواعيد المتاحة وتعرضها، وتحجز بضغطة زرّ. هذا ربطٌ أتقنته في مقرّر 242 (fetch داخل useEffect، والإرسال في معالج الحدث)؛ هنا نطبّقه على مشروعنا.

قاعدتان من 242 نكرّرهما:

٢التطبيق الكامل يعمل

هذه الواجهة متّصلةٌ بالخادم الحقيقيّ في الصفحة. جرّب: اضغط «احجز» على موعدٍ فيختفي من المتاحة وتظهر رسالة تأكيد:

import { useState, useEffect } from 'react'; const API = 'http://localhost:3000';const PATIENT = 'سعود';   // المريض المسجَّل دخوله (من محطّة المصادقة) export default function App() {  const [slots, setSlots] = useState([]);  const [msg, setMsg] = useState('');   const load = () =>    fetch(`${API}/api/appointments?status=free`)      .then(r => r.json())      .then(d => setSlots(d.data));   useEffect(() => { load(); }, []);   async function book(id) {    const res = await fetch(`${API}/api/appointments/${id}/book`, {      method: 'POST',      headers: { 'Content-Type': 'application/json' },      body: JSON.stringify({ patient: PATIENT }),    });    const data = await res.json();    if (!res.ok) return setMsg(data.error.message);    setMsg('تمّ الحجز');    load();  }   return (    <div>      <h2>المواعيد المتاحة</h2>      {msg && <p className="msg">{msg}</p>}      <ul className="slots">        {slots.map(s => (          <li key={s.id}>            {s.doctor} {s.time}            <button onClick={() => book(s.id)}>احجز</button>          </li>        ))}      </ul>    </div>  );}

لاحظ كيف أنّ «مصدر الحقيقة» هو الخادم: بعد الحجز نعيد الجلب فتختفي البطاقة لأنّ bookedBy لم يعد null. الواجهة تعرض حالة الخادم، لا حالةً خاصّةً بها.

توقّع وتحقّق

أين نجلب البيانات عند ظهور المكوّن؟

لماذا نعيد الجلب بعد الحجز؟

fetch عند ردّ 409 ماذا يفعل؟

ما الذي يرسل جسم JSON في POST؟

الخلاصة

تحدٍّ برمجي: واجهة الحجز

برمجي

الخادم جاهزٌ في server/server.js. اكتب واجهة React تعرض المواعيد المتاحة وتحجز.

المطلوب:
  • عند الظهور: اجلب GET /api/appointments?status=free واعرض كل موعدٍ في <li> داخل <ul className="slots">، مع زرّ «احجز».
  • عند الضغط: POST /api/appointments/:id/book بالجسم {"patient": PATIENT}، ثم أعِد الجلب فيختفي الموعد المحجوز.
  • عند النجاح اعرض <p className="msg">تمّ الحجز</p>.
👀 اعرض الناتج المطلوب (وجرّبه)
✓ التحقق الآلي من حلّك:
  • الكود يعمل بلا أخطاء في Console
  • يعرض الموعدين المتاحين من الخادم
  • الحجز يرسل للخادم فيختفي الموعد وتظهر رسالة التأكيد
  • يستخدم fetch مع POST و JSON.stringify

💡 تلميح 1
الجلب في useEffect(() => { load(); }, []) مع دالّة load تجلب المتاح.
💡 تلميح 2
الحجز: async function book(id) ترسل POST ثم تستدعي load() وتضبط الرسالة.

حاول بنفسك أولًا؛ للمسألة أكثر من حلّ صحيح.

import { useState, useEffect } from 'react'; const API = 'http://localhost:3000';const PATIENT = 'سعود';   // المريض المسجَّل دخوله (من محطّة المصادقة) export default function App() {  const [slots, setSlots] = useState([]);  const [msg, setMsg] = useState('');   const load = () =>    fetch(`${API}/api/appointments?status=free`)      .then(r => r.json())      .then(d => setSlots(d.data));   useEffect(() => { load(); }, []);   async function book(id) {    const res = await fetch(`${API}/api/appointments/${id}/book`, {      method: 'POST',      headers: { 'Content-Type': 'application/json' },      body: JSON.stringify({ patient: PATIENT }),    });    const data = await res.json();    if (!res.ok) return setMsg(data.error.message);    setMsg('تمّ الحجز');    load();  }   return (    <div>      <h2>المواعيد المتاحة</h2>      {msg && <p className="msg">{msg}</p>}      <ul className="slots">        {slots.map(s => (          <li key={s.id}>            {s.doctor} {s.time}            <button onClick={() => book(s.id)}>احجز</button>          </li>        ))}      </ul>    </div>  );}

🐞 لاحظت خطأً في هذا الدرس؟ أبلغني بالبريد

📝 معلم؟ أرسل ملاحظة للمؤلف عن هذا الدرس