Section Students

🎯 الفكرة

في جدول الأقسام:

يكون هناك زر:

عرض الطلاب
JSX

🚀 داخل جدول الأقسام

أضف زر:

<Link
  to={`/sections/${section.documentId}/students`}
  className="btn btn-info btn-sm"
>
  الطلاب
</Link>
JSX

🎯 الراوت

داخل App.jsx

<Route
  path="/sections/:id/students"
  element={<SectionStudents />}
/>
JSX

🧠 الآن لدينا خياران

الخيار الأول (أنصح به)

جلب الطلاب الخاصين بالقسم من الـ API مباشرة.


الخيار الثاني

جلب كل الطلاب ثم عمل filter.


🎯 أيهما أفضل؟

إذا لديك:

20 طالب
JSX

فالخيار الثاني جيد.

أما إذا لاحقًا أصبح لديك:

5000 طالب
JSX

فهو سيئ جدًا.


🚀 لذلك أنصحك من الآن بالخيار الأول


إنشاء thunk

داخل studentsSlice

export const fetchStudentsBySection =
  createAsyncThunk(

    "students/fetchStudentsBySection",

    async (sectionId, thunkAPI) => {

      try {

        const response =
          await axiosInstance.get(

            `/students?filters[section][documentId][$eq]=${sectionId}&populate=section.classroom.grade`

          );

        return response.data.data;

      } catch (error) {

        return thunkAPI.rejectWithValue(
          error.response?.data || error.message
        );

      }

    }

);
JSX

addCase

.addCase(
  fetchStudentsBySection.fulfilled,

  (state, action) => {

    state.students = action.payload;

  }
)
JSX

🎯 صفحة الطلاب الخاصة بالقسم

SectionStudents.jsx


جلب id من الرابط

const { id } = useParams();
JSX

Redux

const dispatch = useDispatch();

const {
  students,
  loading
} = useSelector(
  (state) => state.students
);
JSX

جلب الطلاب

useEffect(() => {

  dispatch(
    fetchStudentsBySection(id)
  );

}, [dispatch, id]);
JSX

Loading

if (loading) {

  return (
    <h3>
      جاري تحميل الطلاب...
    </h3>
  );

}
JSX

عرض الجدول

<table className="table table-bordered">

  <thead>

    <tr>

      <th>#</th>

      <th>الاسم</th>

      <th>الهاتف</th>

      <th>الإيميل</th>

    </tr>

  </thead>

  <tbody>

    {
      students.map(
        (student, index) => (

          <tr key={student.documentId}>

            <td>{index + 1}</td>

            <td>{student.name}</td>

            <td>{student.phone}</td>

            <td>{student.email}</td>

          </tr>

        )
      )
    }

  </tbody>

</table>
JSX

🚀 تحسين احترافي إضافي

يمكن أيضًا عرض معلومات القسم أعلى الصفحة.


جلب القسم

أنشئ:

fetchSingleSection(id)
JSX

ثم اعرض:

<div className="card mb-3">

  <div className="card-body">

    <h3>

      قسم:
      {section.name}

    </h3>

    <p>

      الصف:
      {section.classroom?.name}

    </p>

    <p>

      المرحلة:
      {section.classroom?.grade?.name}

    </p>

  </div>

</div>
JSX

🎯 النتيجة النهائية

سيصبح لديك تسلسل احترافي:

المراحل

الصفوف

الأقسام

طلاب القسم

تفاصيل الطالب
JSX

وهذه فعليًا نفس البنية المستخدمة في معظم أنظمة إدارة المدارس الحديثة. 🚀

فإن إنشاء:

fetchSingleSection(id)
JSX

سيكون بنفس الفكرة تمامًا.


🎯 لماذا نحتاج fetchSingleSection ؟

لنفترض أنك فتحت الرابط:

/sections/abc123/students
JSX

وتريد عرض:

القسم: A

الصف: الأول

المرحلة: الابتدائية
JSX

فأنت تحتاج بيانات القسم نفسه وليس الطلاب فقط.


🚀 الخطوة 1: إنشاء AsyncThunk

داخل sectionsSlice.js

export const fetchSingleSection = createAsyncThunk(

  "sections/fetchSingleSection",

  async (id, thunkAPI) => {

    try {

      const response = await axiosInstance.get(

        `/sections/${id}?populate=classroom.grade`

      );

      return response.data.data;

    } catch (error) {

      return thunkAPI.rejectWithValue(
        error.response?.data || error.message
      );

    }

  }

);
JSX

🧠 ماذا يحدث هنا؟

إذا كان:

id = "abc123"
JSX

سيصبح الطلب:

GET /sections/abc123?populate=classroom.grade
JSX

وسيرجع:

{
  documentId: "abc123",

  name: "A",

  classroom: {

    name: "الصف الأول",

    grade: {

      name: "المرحلة الابتدائية"

    }

  }

}
JSX

🚀 الخطوة 2: إضافة State جديد

داخل:

const initialState = {
JSX

أضف:

const initialState = {

  sections: [],

  section: null,

  loading: false,

  error: null

};
JSX

لماذا؟

لدينا الآن:

sections
JSX

للقائمة.

و:

section
JSX

لعنصر واحد.


🚀 الخطوة 3: إضافة addCase

داخل extraReducers

.addCase(
  fetchSingleSection.pending,

  (state) => {

    state.loading = true;

  }
)

.addCase(
  fetchSingleSection.fulfilled,

  (state, action) => {

    state.loading = false;

    state.section = action.payload;

  }
)

.addCase(
  fetchSingleSection.rejected,

  (state, action) => {

    state.loading = false;

    state.error = action.payload;

  }
)
JSX

🚀 الخطوة 4: استخدامه داخل الصفحة

في:

SectionStudents.jsx
JSX

استيراد

import {
  fetchSingleSection
} from "../../features/sections/sectionsSlice";
JSX

Redux

const {
  section
} = useSelector(
  (state) => state.sections
);
JSX

جلب البيانات

useEffect(() => {

  dispatch(fetchSingleSection(id));

}, [dispatch, id]);
JSX

🚀 عرض المعلومات

<div className="card mb-3">

  <div className="card-body">

    <h4>

      القسم:
      {section?.name}

    </h4>

    <h5>

      الصف:
      {section?.classroom?.name}

    </h5>

    <h5>

      المرحلة:
      {section?.classroom?.grade?.name}

    </h5>

  </div>

</div>
JSX

🎯 نقطة احترافية مهمة

إذا كنت تستخدم:

loading
JSX

مشتركًا بين:

fetchSections
fetchSingleSection
JSX

قد يحدث تضارب لاحقًا.

الأفضل مستقبلًا أن تجعل:

const initialState = {

  sections: [],

  section: null,

  sectionsLoading: false,

  singleSectionLoading: false,

  error: null

};
JSX

لكن حاليًا في مشروعك التعليمي:

loading
JSX

واحد يكفي.


💡 اقتراح معماري احترافي

في المستقبل عندما يزداد حجم النظام أنصحك أن تجعل:

studentsSlice
sectionsSlice
gradesSlice
classroomsSlice
JSX

لكل واحد:

items
item

loading
error
JSX

مثلاً:

{
  sections: [],
  section: null
}
JSX

كما فعلنا الآن، لأن هذا النمط سيتكرر مع:

  • طالب واحد
  • قسم واحد
  • صف واحد
  • مرحلة واحدة

وهو النمط المستخدم في معظم تطبيقات React + Redux الاحترافية. 🚀