دمج التعديل مع الاضافة

سنبني:

1. StudentForm.jsx
2. AddStudent.jsx
3. EditStudent.jsx
4. Routes
Dart

وسأشرح:

  • لماذا هذا الملف موجود
  • ما وظيفته
  • كيف يعمل
  • كيف تتفاعل الملفات مع بعضها

🔥

🎯 أولًا: لماذا الدمج أصلًا؟

قبل الدمج كان عندك:

AddStudent.jsx
EditStudent.jsx
Dart

وكل واحد يحتوي:

  • نفس الحقول
  • نفس validation
  • نفس JSX
  • نفس select
  • نفس logic

🎯 الحل الاحترافي

إنشاء:

Reusable Form Component
Dart

اسمه:

StudentForm.jsx
Dart
🧠 المعمارية النهائية
pages/
 └── students/
      ├── AddStudent.jsx
      └── EditStudent.jsx

components/
 └── forms/
      └── StudentForm.jsx
Dart
🚀 الملف الأول
📄 StudentForm.jsx
🎯 وظيفة هذا الملف

هذا هو:

الفورم الحقيقي الوحيد
Dart

الذي:

  • ينشئ الطالب
  • يعدل الطالب
📄 components/forms/StudentForm.jsx
import toast from "react-hot-toast";

import { useNavigate } from "react-router-dom";

import { useEffect, useMemo } from "react";

import { useForm } from "react-hook-form";

import { useDispatch, useSelector } from "react-redux";

import {
  addStudent,
  updateStudent
} from "../../features/students/studentsSlice";

import { fetchGrades }
from "../../features/grades/gradesSlice";

import { fetchClassRooms }
from "../../features/classrooms/ClassroomsSlice";

import { fetchSections }
from "../../features/sections/SectionsSlice";

const StudentForm = ({ student }) => {

  // =====================================
  // Hooks
  // =====================================

  const dispatch = useDispatch();

  const navigate = useNavigate();

  // =====================================
  // Redux State
  // =====================================

  const { grades } = useSelector(
    (state) => state.grades
  );

  const { classrooms } = useSelector(
    (state) => state.classrooms
  );

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

  // =====================================
  // React Hook Form
  // =====================================

  const {
    register,
    handleSubmit,
    watch,
    reset,
    formState: { errors }

  } = useForm({

    mode: "onChange",

    defaultValues: {

      st_code: student?.st_code || "",

      name: student?.name || "",

      age: student?.age || "",

      phone: student?.phone || "",

      email: student?.email || "",

      address: student?.address || "",

      blood: student?.blood || "",

      father: student?.father || "",

      mother: student?.mother || "",

      grade:
        student?.section?.classroom?.grade?.documentId || "",

      classroom:
        student?.section?.classroom?.documentId || "",

      section:
        student?.section?.documentId || ""

    }

  });

  // =====================================
  // Fetch Data
  // =====================================

  useEffect(() => {

    dispatch(fetchGrades());

    dispatch(fetchClassRooms());

    dispatch(fetchSections());

  }, [dispatch]);

  // =====================================
  // تعبئة البيانات بالتعديل
  // =====================================

  useEffect(() => {

    if (student) {

      reset({

        st_code: student.st_code,

        name: student.name,

        age: student.age,

        phone: student.phone,

        email: student.email,

        address: student.address,

        blood: student.blood,

        father: student.father,

        mother: student.mother,

        grade:
          student.section?.classroom?.grade?.documentId || "",

        classroom:
          student.section?.classroom?.documentId || "",

        section:
          student.section?.documentId || ""

      });

    }

  }, [student, reset]);

  // =====================================
  // مراقبة الحقول
  // =====================================

  const selectedGrade = watch("grade");

  const selectedClassroom = watch("classroom");

  // =====================================
  // فلترة الصفوف
  // =====================================

  const filteredClassRooms = useMemo(() => {

    if (!selectedGrade) return [];

    return classrooms.filter(

      (classroom) =>

        classroom.grade?.documentId === selectedGrade

    );

  }, [selectedGrade, classrooms]);

  // =====================================
  // فلترة الأقسام
  // =====================================

  const filteredSections = useMemo(() => {

    if (!selectedClassroom) return [];

    return sections.filter(

      (section) =>

        section.classroom?.documentId === selectedClassroom

    );

  }, [selectedClassroom, sections]);

  // =====================================
  // Submit Form
  // =====================================

  const onSubmit = (data) => {

    const studentData = {

      st_code: data.st_code,

      name: data.name,

      age: data.age,

      phone: data.phone,

      email: data.email,

      address: data.address,

      blood: data.blood,

      father: data.father,

      mother: data.mother,

      // 🔥 نخزن القسم فقط
      section: data.section

    };

    // =================================
    // UPDATE
    // =================================

    if (student) {

      dispatch(

        updateStudent({

          id: student.documentId,

          data: studentData

        })

      )

        .unwrap()

        .then(() => {

          toast.success("تم تعديل الطالب");

          navigate("/students");

        });

    }

    // =================================
    // CREATE
    // =================================

    else {

      dispatch(addStudent(studentData))

        .unwrap()

        .then(() => {

          toast.success("تم إضافة الطالب");

          reset();

          navigate("/students");

        });

    }

  };

  return (

    <div className="card">

      {/* Header */}

      <div className="card-header">

        <h3 className="card-title">

          {student
            ? "تعديل الطالب"
            : "إضافة طالب جديد"}

        </h3>

      </div>

      {/* Body */}

      <div className="card-body">

        <form onSubmit={handleSubmit(onSubmit)}>

          {/* =========================== */}
          {/* Student Name */}
          {/* =========================== */}

          <div className="mb-3">

            <label className="form-label">
              اسم الطالب
            </label>

            <input
              type="text"
              className="form-control"

              {...register("name", {

                required: "اسم الطالب مطلوب",

                minLength: {

                  value: 3,

                  message:
                    "الاسم يجب أن يكون 3 أحرف على الأقل"

                }

              })}
            />

            {errors.name && (

              <small className="text-danger">

                {errors.name.message}

              </small>

            )}

          </div>

          {/* =========================== */}
          {/* Grade */}
          {/* =========================== */}

          <div className="mb-3">

            <label className="form-label">
              المرحلة
            </label>

            <select
              className="form-select"

              {...register("grade", {

                required: "المرحلة مطلوبة"

              })}
            >

              <option value="">
                اختر المرحلة
              </option>

              {grades?.map((grade) => (

                <option
                  key={grade.documentId}
                  value={grade.documentId}
                >
                  {grade.name}
                </option>

              ))}

            </select>

          </div>

          {/* =========================== */}
          {/* Classroom */}
          {/* =========================== */}

          <div className="mb-3">

            <label className="form-label">
              الصف
            </label>

            <select
              className="form-select"

              {...register("classroom", {

                required: "الصف مطلوب"

              })}
            >

              <option value="">
                اختر الصف
              </option>

              {filteredClassRooms.map((classroom) => (

                <option
                  key={classroom.documentId}
                  value={classroom.documentId}
                >
                  {classroom.name}
                </option>

              ))}

            </select>

          </div>

          {/* =========================== */}
          {/* Section */}
          {/* =========================== */}

          <div className="mb-3">

            <label className="form-label">
              القسم
            </label>

            <select
              className="form-select"

              {...register("section", {

                required: "القسم مطلوب"

              })}
            >

              <option value="">
                اختر القسم
              </option>

              {filteredSections.map((section) => (

                <option
                  key={section.documentId}
                  value={section.documentId}
                >
                  {section.name}
                </option>

              ))}

            </select>

          </div>

          {/* =========================== */}
          {/* Submit */}
          {/* =========================== */}

          <button
            type="submit"
            className="btn btn-primary"
          >

            {student
              ? "حفظ التعديلات"
              : "إضافة الطالب"}

          </button>

        </form>

      </div>

    </div>

  );

};

export default StudentForm;
JSX

🧠 ماذا تعلمنا هنا؟

الشيءالسبب
student propتحديد add/edit
resetتعبئة بيانات التعديل
watchمراقبة المرحلة والصف
useMemoفلترة محسنة
if(student)تحديد نوع العملية
🚀 الملف الثاني
📄 AddStudent.jsx
🎯 وظيفته

هذه ليست صفحة فورم حقيقية.

بل فقط:

Wrapper Page
Dart
📄 pages/students/AddStudent.jsx
import StudentForm
from "../../components/forms/StudentForm";

const AddStudent = () => {

  return <StudentForm />;

};

export default AddStudent;
Dart
🧠 ماذا يحدث هنا؟

لم نرسل:

student

إذًا:

student = undefined

👉 الفورم يصبح:

  • Create Mode 🔥
🚀 الملف الثالث
📄 EditStudent.jsx
🎯 وظيفته

هذه الصفحة:

  • تجلب الطالب من Redux
  • ترسله للفورم
📄 pages/students/EditStudent.jsx
import { useParams }
from "react-router-dom";

import { useSelector }
from "react-redux";

import StudentForm
from "../../components/forms/StudentForm";

const EditStudent = () => {

  // =================================
  // Route Param
  // =================================

  const { id } = useParams();

  // =================================
  // Redux State
  // =================================

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

  // =================================
  // Find Student
  // =================================

  const student = students.find(

    (student) =>

      student.documentId === id

  );

  return (

    <StudentForm student={student} />

  );

};

export default EditStudent;
Dart
🧠 ماذا يحدث هنا؟
useParams

تجلب:

/students/abc123/edit
Dart
id

تصبح:

abc123
Dart
ثم:
find()
Dart

يجلب الطالب المطلوب.

ثم:
<StudentForm student={student} />
Dart

🔥

🚀 الملف الرابع
📄 Routes
داخل App.jsx
<Route
  path="/students/create"
  element={<AddStudent />}
/>

<Route
  path="/students/:id/edit"
  element={<EditStudent />}
/>
Dart
🧠 ماذا يحدث؟
create

يفتح:

Add Mode

edit

يفتح:

Edit Mode

🎯 أهم فكرة بالدمج كله

💡 الفرق الحقيقي بين add/edit

هو فقط:

وجود student أو عدمه
Dart

🔥


🚀 ماذا ربحنا؟
قبلبعد
كود مكررreusable
صيانة صعبةسهلة
تعديل مزدوجمرة واحدة
validation مكررموحد

💡 قاعدة احترافية جدًا
إذا كررت JSX أكثر من مرة → اصنع reusable component