🎯 الفكرة
في جدول الأقسام:
يكون هناك زر:
عرض الطلاب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
);
}
}
);JSXaddCase
.addCase(
fetchStudentsBySection.fulfilled,
(state, action) => {
state.students = action.payload;
}
)JSX🎯 صفحة الطلاب الخاصة بالقسم
SectionStudents.jsx
جلب id من الرابط
const { id } = useParams();JSXRedux
const dispatch = useDispatch();
const {
students,
loading
} = useSelector(
(state) => state.students
);JSXجلب الطلاب
useEffect(() => {
dispatch(
fetchStudentsBySection(id)
);
}, [dispatch, id]);JSXLoading
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/studentsJSXوتريد عرض:
القسم: 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.gradeJSXوسيرجع:
{
documentId: "abc123",
name: "A",
classroom: {
name: "الصف الأول",
grade: {
name: "المرحلة الابتدائية"
}
}
}JSX🚀 الخطوة 2: إضافة State جديد
داخل:
const initialState = {JSXأضف:
const initialState = {
sections: [],
section: null,
loading: false,
error: null
};JSXلماذا؟
لدينا الآن:
sectionsJSXللقائمة.
و:
sectionJSXلعنصر واحد.
🚀 الخطوة 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.jsxJSXاستيراد
import {
fetchSingleSection
} from "../../features/sections/sectionsSlice";JSXRedux
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🎯 نقطة احترافية مهمة
إذا كنت تستخدم:
loadingJSXمشتركًا بين:
fetchSections
fetchSingleSectionJSXقد يحدث تضارب لاحقًا.
الأفضل مستقبلًا أن تجعل:
const initialState = {
sections: [],
section: null,
sectionsLoading: false,
singleSectionLoading: false,
error: null
};JSXلكن حاليًا في مشروعك التعليمي:
loadingJSXواحد يكفي.
💡 اقتراح معماري احترافي
في المستقبل عندما يزداد حجم النظام أنصحك أن تجعل:
studentsSlice
sectionsSlice
gradesSlice
classroomsSliceJSXلكل واحد:
items
item
loading
errorJSXمثلاً:
{
sections: [],
section: null
}JSXكما فعلنا الآن، لأن هذا النمط سيتكرر مع:
- طالب واحد
- قسم واحد
- صف واحد
- مرحلة واحدة
وهو النمط المستخدم في معظم تطبيقات React + Redux الاحترافية. 🚀