حالا دیگه **آخرین روز سال** شده بود و **فرانتیوم** میخواست یک پروژه مناسب طراحی بکند، میخواست تواناییهای خود را بسنجد. توی خونهی مامانبزرگش ریاکتیوم در حالی که داشت به مامانبزرگ توی خونه تکونی کمک میکرد، چشمش خورد به جدول کلماتی که بابابزرگش حل میکرد.
حالا یک ایدهی خوب برای پیادهسازی به ذهنش رسیده بود، جدول اطلاعات ...

# **پروژه اولیه**
برای دانلود **پروژهی اولیه** روی [این لینک](/contest/assignments/84122/download_problem_initial_project/307257/) کلیک کنید.
<details class="red">
<summary>**ساختار فایلها**</summary>
```plaintext
data_table/
├─ src/
│ ├─ api/
│ │ ├─ createToken.ts
│ │ ├─ logout.ts
│ │ └─ productApi.ts
│ ├─ components/
│ │ ├─ components/
│ │ │ ├─ productTable/
│ │ │ │ ├─ components/
│ │ │ │ │ ├─ ProductTableBody/
│ │ │ │ │ │ ├─ modals/
│ │ │ │ │ │ │ ├─ DeleteProductDialog.tsx
│ │ │ │ │ │ │ └─ EditProductModal.tsx
│ │ │ │ │ │ └─ ProductTableBody.tsx
│ │ │ │ │ ├─ ProductTableFooter/
│ │ │ │ │ │ ├─ components/
│ │ │ │ │ │ │ ├─ PageSizeSelector.tsx
│ │ │ │ │ │ │ ├─ PaginationControls.tsx
│ │ │ │ │ │ │ └─ TotalCountDisplay.tsx
│ │ │ │ │ │ └─ ProductTableFooter.tsx
│ │ │ │ │ └─ ProductTableHeader/
│ │ │ │ │ ├─ components/
│ │ │ │ │ │ └─ ProductTableControls.tsx
│ │ │ │ │ └─ ProductTableHeader.tsx
│ │ │ │ ├─ hooks/
│ │ │ │ │ └─ useProducts.ts
│ │ │ │ └─ ProductTable.tsx
│ │ │ ├─ AddProductModal.tsx
│ │ │ ├─ ProductForm.tsx
│ │ │ └─ ProductPageHeader.tsx
│ │ └─ ProductPage.tsx
│ ├─ pages/
│ │ ├─ LoginPage.tsx
│ │ └─ useLogin.ts
│ ├─ routes/
│ │ └─ AppRoutes.tsx
│ ├─ stores/
│ │ └─ AuthContext.tsx
│ ├─ App.tsx
│ ├─ index.css
│ ├─ main.tsx
│ └─ vite-env.d.ts
├─ .eslintrc.cjs
├─ index.html
├─ package-lock.json
├─ package.json
├─ README.md
├─ tsconfig.json
├─ tsconfig.node.json
└─ vite.config.ts
```
</details>
<details class="yellow">
<summary>**راه اندازی**</summary>
+ پس از دانلود کردن **فایل پروژه اولیه،** آن را از حالت فشرده خارج کنید.
+ سپس در ترمینال خود **دستور** `npm install` را **اجرا** کنید.
+ در نهایت پروژه را با استفاده از **دستور** `npm run dev` **اجرا** کنید.
در صورت نیاز از **دستور** `npm install --force` برای **نصب وابستگیها** استفاده کنید.
</details>
# جزئیات پیادهسازی
<details class="purple">
<summary>**دایرکتوری api** </summary>
<details class="yellow">
<summary>**فایل productApi.ts** </summary>
در این فایل، **نوع دادهای (type)** با نام `Product` تعریف شده است که مشخص میکند هر **محصول** باید دارای چه **ویژگیهایی (properties)** باشد.
در ادامه، مجموعهای از توابع شبیهسازیشده برای `endpoint`های مربوط به عملیات **CRUD** (افزودن، خواندن، ویرایش و حذف دادهها) پیادهسازی شده است تا بدون نیاز به یک **بکاند واقعی**، بتوان رفتار API را در محیط فرانتاند آزمایش کرد.
**نکته:** منظور از _شبیهسازی_ این است که در این پروژه، دادهها بهصورت محلی و در حافظه ذخیره میشوند و ارتباطی با سرور واقعی وجود ندارد.
همانطور که مشاهده میکنید، در `productAPI` توابع زیر وجود دارند:
1. getProducts
2. addProduct
3. editProduct
4. deleteProduct
که بهترتیب برای موارد زیر بهکار میروند:
1. دریافت محصولات
2. افزودن محصول جدید
3. ویرایش اطلاعات محصول
4. حذف محصول
همچنین در این پیادهسازی، قابلیتهای زیر نیز در نظر گرفته شدهاند:
1. **صفحهبندی (Pagination)** برای نمایش تدریجی دادهها
2. **جستوجو (Search)** بر اساس نام محصول
3. **مرتبسازی بر اساس قیمت (Sorting)** به دو حالت صعودی (_asc_) و نزولی (_desc_)
</details>
<details class="yellow">
<summary>**فایل logout.ts**</summary>
در این فایل اندپوینتی نوشته شده است که با استفاده از آن عملیات خروج انجام میشود.
</details>
<details class="yellow">
<summary>**فایل createToken.ts**</summary>
در این فایل، تابعی با نام **`createToken`** پیادهسازی شده است که وظیفه دارد پس از ورود موفق کاربر، دو مقدار **`access token`** و **`refresh token`** را بازگرداند.
### هدف تابع
این تابع با بررسی اطلاعات کاربری (نام کاربری و رمز عبور) که در آرایهی **`fakeUsers`** از پیش تعریف شدهاند، احراز هویت را شبیهسازی میکند.
در صورت معتبر بودن اطلاعات ورودی، توکنها تولید و بازگردانده میشوند؛ در غیر این صورت، خطایی با پیام _«نام کاربری یا رمز عبور اشتباه است»_ برگردانده میشود.
اطلاعات ورود برای این دو کاربر از پیش تعریف شده است:
```plaintext
{
username: "admin",
password : "admin123",
role : "admin as const"
}
{
username: "user",
password: "user123",
role: "user" as const
}
```
هر کاربر پس از ورود، با توجه به نقش (role) خود به مسیر (route) متفاوتی هدایت میشود.
<details class="purple">
<summary>**مسیرها (Routes) و دسترسیها**</summary>
#### مسیر `adminTableRoute`
+ در **ProductPageHeader**، دکمهی _افزودن محصول_ نمایش داده میشود و ادمین میتواند محصولات جدید اضافه کند.
+ در **ProductTableRow**، دو آیکون _ویرایش_ و _حذف_ وجود دارد که امکان ویرایش یا حذف محصول را فراهم میکنند.
#### مسیر `userTableRoute`
+ در این مسیر، امکانات مدیریتی برای کاربر عادی نمایش داده نمیشوند.
+ کاربر عادی تنها مجاز است محصولات را **مشاهده (Read)** کند.
</details>
### نکات ورود (Login)
+ تا زمانی که عملیات ورود انجام نشده باشد، کاربر اجازهی مشاهدهی صفحات داخلی را ندارد.
+ در صورت تلاش برای دسترسی مستقیم به آدرسهای دیگر، کاربر به صفحهی **Login** هدایت (redirect) میشود.
+ پس از ورود موفق، بسته به نقش کاربر:
+ کاربر **ادمین** → هدایت به `adminTableRoute`
+ کاربر **عادی** → هدایت به `userTableRoute`
</details>
</details>
<details class="purple">
<summary>**فرایند ورود کاربر**</summary>
تمامی اطلاعات مربوط به ورود کاربر، شامل **`access token`**، **`refresh token`** و **`role`**، باید با استفاده از **Context API** مدیریت شده و در **Local Storage** ذخیره شوند.
دلیل این کار آن است که تا زمانی که کاربر از سیستم خارج نشده (**Log out**) یا **رفرش توکن (refresh token)** او منقضی نشده باشد، نباید صفحهی **Login** مجدداً نمایش داده شود.
نمایش دوبارهی فرم ورود در این شرایط، از نظر **تجربهی کاربری (UX)** مناسب نیست و باعث ناهماهنگی در جریان کاربر میشود.
### فایلهایی که باید تکمیل شوند
<details class="yellow">
<summary>**فایل AppRoutes.ts**</summary>
در این فایل قرار است مسیرهای اصلی برنامه (**Routes**) بر اساس وضعیت ورود و نقش کاربر تنظیم شوند.
هدف این است که تنها کاربران مجاز بتوانند به مسیرهای مربوط به خود دسترسی داشته باشند.
+ از **React Router** برای تعریف مسیرها استفاده میشود.
+ با استفاده از **`useAuth()`** از `AuthContext`، وضعیت ورود (`isLoggedIn`) و نقش (`role`) کاربر دریافت میشود.
### منطق پیادهسازی:
1. اگر کاربر **وارد نشده باشد**:
+ تنها مسیر `/login` باید در دسترس باشد.
+ سایر مسیرها باید با استفاده از `<Navigate>` به `/login` هدایت شوند.
2. اگر کاربر **وارد شده باشد**:
+ اگر نقش او `admin` باشد → به مسیر `/adminTableRoute` هدایت شده و تنها مسیرهای مخصوص ادمین نمایش داده میشوند.
+ اگر نقش او `user` باشد → به مسیر `/userTableRoute` هدایت شده و تنها مسیرهای مخصوص کاربر معمولی نمایش داده میشوند.
</details>
<details class="yellow">
<summary>**فایل AuthContext.tsx**</summary>
در این فایل قرار است سیستم **احراز هویت (Authentication)** کاربران پیادهسازی شود.
هدف این است که با استفاده از **Context API**، وضعیت ورود کاربر، نقش او و توکنها در سطح کل اپلیکیشن مدیریت شود.
+ `AuthContext` نقش یک **Store مرکزی** را دارد که اطلاعات مربوط به ورود کاربر را نگهداری کرده و آن را برای سایر بخشهای برنامه (مانند صفحات و کامپوننتها) در دسترس قرار میدهد.
این context شامل اطلاعات زیر خواهد بود:
+ وضعیت ورود (`isLoggedIn`)
+ نقش کاربر (`role`)
+ توکنها (`accessToken` و `refreshToken`)
+ توابع ورود و خروج (`login` و `logout`)
1. تعریف تابع **`login`** برای ذخیرهی اطلاعات ورود در Local Storage و بهروزرسانی stateها.
2. تعریف تابع **`logout`** که با صدا زدن `logoutApi()` کاربر را از سیستم خارج میکند و اطلاعات را از Local Storage حذف میکند.
</details>
<details class="yellow">
<summary>**فایل LoginPage.tsx**</summary>
1. با استفاده از **useState**، مقادیر ورودی فرم را مدیریت کنید.
2. از تابع **handleLogin** برای ارسال فرم استفاده کنید و توجه داشته باشید که هنگام سابمیت نباید **صفحه رفرش شود**.
</details>
<details class="yellow">
<summary>**فایل useLogin.ts**</summary>
در این فایل قرار است یک **هوک سفارشی (Custom Hook)** برای مدیریت فرآیند ورود کاربر پیادهسازی شود.
هدف این است که عملیات **Login** بهصورت جداگانه از رابط کاربری انجام شود و منطق آن در یک هوک مستقل قرار گیرد.
### توضیحات منطق عملکرد:
1. از `useAuth()` برای دسترسی به تابع `login` که در `AuthContext` تعریف شده، استفاده میشود.
با استفاده از این تابع، پس از تأیید اطلاعات کاربر، توکنها و نقش او در **Local Storage** ذخیره میشوند.
2. از `createToken()` برای ارسال اطلاعات ورود (`username` و `password`) و دریافت **Access Token**، **Refresh Token** و **Role** استفاده میشود.
3. پس از موفقیت در ورود:
+ اگر نقش کاربر `admin` بود → کاربر باید به مسیر `/adminTableRoute` هدایت شود.
+ اگر نقش کاربر `user` بود → کاربر باید به مسیر `/userTableRoute` هدایت شود.
4. در صورت خطا (مثلاً واردکردن نام کاربری یا رمز عبور اشتباه)، پیام مناسب باید در state مربوط به `error` ذخیره و نمایش داده شود.
</details>
+ **توجه**: ساختار فایل **App.tsx** را تغییر ندهید!
</details>
<details class="yellow">
<summary>**پیادهسازی هوک useProducts**</summary>
هوک `useProducts` مسئول **مدیریت وضعیت محصولات** است. این هوک دادهها را از سرور دریافت میکند و قابلیتهای زیر را در اختیار کامپوننتهای دیگر قرار میدهد:
+ دریافت لیست محصولات از API
+ پشتیبانی از صفحهبندی (pagination)
+ جستوجو بر اساس نام محصول
+ مرتبسازی بر اساس قیمت
+ کنترل وضعیت بارگذاری (`loading`)
### متغیرهای داخلی
| متغیر | نوع | توضیح |
| ------------- | ----------- | ------------------------------------- |
| `products` | `Product[]` | لیست محصولات دریافتی از API |
| `totalCount` | `number` | تعداد کل محصولات موجود در سرور |
| `page` | `number` | شماره صفحه فعلی |
| `pageSize` | `number` | تعداد محصولات در هر صفحه (پیشفرض: ۵) |
| `search` | `string` | عبارت جستوجو برای فیلتر محصولات |
| `sortByPrice` | `"asc" | "desc" |
| `loading` | `boolean` | نشاندهنده وضعیت بارگذاری دادهها |
### توابع اصلی
#### 1. `fetchProducts`
+ وظیفه: فراخوانی `productApi.getProducts()` و تنظیم دادهها.
+ پارامترهای مورد استفاده:
`page`, `pageSize`, `search`, `sortByPrice`
+ مقادیر زیر را بهروزرسانی میکند:
+ `products`
+ `totalCount`
+ `loading`
#### 2. `useEffect`
+ وظیفه: نظارت بر تغییر در `page`, `pageSize`, `search`, `sortByPrice`
+ هر زمان یکی از مقادیر بالا تغییر کند، دوباره `fetchProducts()` را اجرا میکند.
+ در نهایت متغیرها و توابع را **return** کنید و در جاهایی که نیاز هست از آن استفاده کنید.
</details>
<details class="purple">
<summary>**نمایش اطاعات در جدول**</summary>
+ جدول ما 3 قسمت با این اسامی دارد :
<details class="yellow">
<summary>**کامپوننت ProductTableHeader.tsx**</summary>
در این بخش، ستونهای جدول محصولات بهترتیب زیر نمایش داده میشوند:
1. **نام محصول** (`productName`)
2. **دستهبندی** (`productCategory`)
3. **قیمت** (`productPrice`)
4. **موجودی** (`stock`)
5. **عملیات** (`operation`)
همچنین کامپوننت **ProductTableControls** در این قسمت قرار دارد که شامل فیلد جستوجو و دکمهی مرتبسازی است.
### **قابلیت جستوجو**
+ باید قابلیت **جستوجو بر اساس نام محصول** پیادهسازی شود.
+ محتوای بخش بدنهی جدول (**tbody**) با تغییر مقدار جستوجو بهروزرسانی خواهد شد.
### **قابلیت مرتبسازی**
+ دکمهی مرتبسازی با کلیک بین سه حالت جابهجا میشود:
1. **مرتبسازی صعودی** (ارزانترین ← گرانترین)
2. **مرتبسازی نزولی** (گرانترین ← ارزانترین)
3. **حالت پیشفرض** (بدون مرتبسازی)
در کد مربوطه، متن دکمه بسته به وضعیت فعلی بهشکل زیر تغییر میکند:
```ts
{sortByPrice === "asc"
? "مرتبسازی: ارزانترین ← گرانترین"
: sortByPrice === "desc"
? "مرتبسازی: گرانترین ← ارزانترین"
: "مرتبسازی بر اساس قیمت"}
```
</details>
<details class="yellow">
<summary>**کامپوننت ProductTableBody.tsx**</summary>
در این بخش، لیست محصولات دریافتشده باید بهصورت ردیفهای جداگانه در جدول نمایش داده شود.
این دادهها در کامپوننت **ProductTableBody** نمایش داده میشوند.
### شرح عملکرد
1. هر محصول شامل اطلاعاتی مانند نام، دستهبندی، قیمت و موجودی است که در ستونهای جدول نمایش داده میشود.
2. در ستون آخر (عملیات)، آیکونهای ویرایش (**FaEdit**) و حذف (**FaTrash**) تنها برای نقش **ادمین (admin)** نمایش داده میشوند.
3. کاربران معمولی (**user**) فقط اطلاعات محصولات را مشاهده میکنند و به عملیات ویرایش و حذف دسترسی ندارند.
### نکات مهم
**نکته 1**
تفاوت بین نقشهای `admin` و `user` در این بخش فقط در نمایش آیکونهای ویرایش و حذف است.
اگر نقش کاربر `admin` باشد، این آیکونها نمایش داده میشوند؛ در غیر این صورت پنهان خواهند بود.
**نکته 2**
تغییرات مربوط به **جستوجو** یا **تغییر تعداد محصولات در هر صفحه**، مستقیماً بر دادههای نمایشدادهشده در **ProductTableBody** تأثیر میگذارند.
**نکته 3**
اگر هیچ محصولی یافت نشود، باید پیام زیر نمایش داده شود:
```plaintext
محصولی یافت نشد.
```
**نکته 4**
در هنگام بارگذاری دادهها، باید پیام زیر نمایش داده شود:
```plaintext
در حال بارگذاری...
```
**نکته 5**
با کلیک روی آیکون **ویرایش (FaEdit)** باید تابع
`setShowEditModal(true, product)`
فراخوانی شود تا مودال ویرایش باز شده و اطلاعات محصول انتخابشده در آن نمایش داده شود.
**نکته 6**
با کلیک روی آیکون **حذف (FaTrash)** باید تابع
`setShowDeleteConfirm(true, product)`
فراخوانی شود تا پنجرهی تأیید حذف نمایش داده شود.
<details class="purple">
<summary>**دکمهی حذف محصول**</summary>
1. وقتی روی دکمهی **حذف محصول** کلیک میکنیم، مودال **DeleteProductModal** باز میشود.
2. در این مودال از کاربر پرسیده میشود که آیا مطمئن است میخواهد محصول را حذف کند یا خیر.
### وظایف اصلی
1. **انصراف از عملیات**
+ اگر کاربر دکمهی «انصراف» را بزند، هیچ تغییری ایجاد نمیشود و مودال بسته خواهد شد.
2. **حذف محصول**
+ اگر کاربر دکمهی «حذف» را بزند:
+ محصول مورد نظر حذف میشود.
+ پس از موفقیتآمیز بودن عملیات، یک **refetch** انجام میشود تا جدول محصولات بروزرسانی شود.
</details>
<details class="purple">
<summary>**دکمهی ویرایش محصول در سطر هر محصول**</summary>
1. وقتی روی دکمهی **ویرایش محصول** کلیک میکنیم، مودال **EditProductModal** باز میشود.
2. داخل این مودال، کامپوننت **ProductForm** نمایش داده میشود و اطلاعات محصول انتخابشده در ورودیهای فرم مقداردهی اولیه میشوند.
### وظایف اصلی
1. **ویرایش اطلاعات محصول**
+ کاربر میتواند مقادیر فیلدهای فرم را تغییر دهد.
2. **بهروزرسانی دادهها**
+ پس از ثبت تغییرات، درخواست به سرور ارسال میشود.
+ پس از موفقیتآمیز بودن عملیات، باید یک **refetch** انجام شود تا جدول محصولات بروزرسانی شود.
### نکته
+ توجه کنید که کامپوننت **ProductForm** هم در مودال **AddProductModal** و هم در **EditProductModal** استفاده میشود.
</details>
</details>
<details class="yellow">
<summary>**کامپوننت ProductTableFooter**</summary>
+ در این بخش سه کامپوننت وجود دارد:
### 1. کامپوننت TotalCountDisplay
+ این کامپوننت وظیفهی **نمایش تعداد کل محصولات موجود** را دارد.
+ این عدد باید دقیقاً مطابق با تعداد محصولاتی باشد که از سمت سرور یا پرامیس داده دریافت میشوند.
### 2. کامپوننت PaginationControls
+ این کامپوننت مسئول **مدیریت صفحهبندی** است.
+ دادهها باید بهصورت صفحهبندیشده نمایش داده شوند.
+ دو دکمهی «>» و «<» باید وجود داشته باشند:
+ اگر کاربر در **صفحهی اول** قرار دارد، دکمهی «>» باید غیرفعال شود.
+ اگر کاربر در **صفحهی آخر** قرار دارد، دکمهی «<» باید غیرفعال شود.
### 3. کامپوننت PagesSizeSelector
+ این کامپوننت شامل یک **انتخابگر (selector)** برای تعیین تعداد آیتمها در هر صفحه است.
+ گزینههای انتخابی باید شامل **۵، ۱۰ یا ۱۵** آیتم در هر صفحه باشند.
+ با تغییر این مقدار:
+ تعداد کل صفحات دوباره محاسبه میشود.
+ دادههای نمایشدادهشده در جدول بهروزرسانی میشوند.
</details>
</details>
<details class="purple">
<summary>**کامپوننت ProductPage**</summary>
این کامپوننت شامل اجزای زیر است:
1. مودال AddProductModal
2. مودال EditProductModal
3. دیالوگ DeleteProductDialog
4. کامپوننت ProductPageHeader
5. کامپوننت ProductTable (در باکس راهنمای بالا، توضیح داده شد)
6. دکمهی خروج
<details class="yellow">
<summary>**دکمهی خروج**</summary>
با زدن این دکمه، باید عملیات **logout** انجام بشود.
</details>
<details class="yellow">
<summary>**کامپوننت ProductPageHeader**</summary>
در این کامپوننت دکمهی **افزودن محصول** قرار دارد که باید به شیوهی زیر کار کند.
1. وقتی روی دکمهی **افزودن محصول** کلیک میکنیم، باید مودال **AddProductModal** باز شود.
2. داخل این مودال، کامپوننت **ProductForm** نمایش داده میشود.
</details>
<details class="yellow">
<summary>**مودال AddProductModal**</summary>
این کامپوننت یک **مودال برای افزودن محصول جدید** است که شامل فرم ثبت محصول (`ProductForm`) میباشد.
با فراخوانی این کامپوننت، زمانی که `showAddModal` برابر با `true` باشد، یک مودال در مرکز صفحه نمایش داده میشود.
+ اگر مقدار `showAddModal` برابر با `false` باشد، مقدار `null` را ریترن کنید.
در صورت کلیک بر روی دکمهی ضربدر (✕)، مقدار `showAddModal` به `false` تغییر کرده و مودال بسته میشود.
پس از ثبت موفق محصول در فرم:
+ تابع `refetchProducts` برای **بهروزرسانی لیست محصولات** فراخوانی میشود.
+ مودال بهصورت خودکار بسته میشود.
</details>
<details class="yellow">
<summary>**مودال EditProductModal**</summary>
این کامپوننت یک **مودال برای ویرایش اطلاعات محصول موجود** است که از `ProductForm` برای نمایش و ویرایش دادهها استفاده میکند.
### توضیحات عملکرد
+ زمانی که `showEditModal` برابر با `true` باشد و پراپس `product` مقدار داشته باشد، مودال نمایش داده میشود.
+ اگر مقدار `showEditModal` یا `product` برابر با `false` باشد، مقدار `null` را ریترن کنید.
+ دادههای محصول انتخابشده از طریق پراپس `product` بهعنوان **مقادیر اولیه فرم** (`initialValues`) به `ProductForm` ارسال میشوند.
+ با کلیک بر روی دکمهی ضربدر (✕)، مودال بسته میشود.
+ پس از ثبت موفق تغییرات:
+ مودال بهصورت خودکار بسته میشود.
+ تابع `refetchProducts` برای **بهروزرسانی لیست محصولات** فراخوانی میشود.
</details>
<details class="yellow">
<summary>**دیالوگ DeleteProductDialog**</summary>
این کامپوننت یک **دیالوگ تأیید حذف محصول** را نمایش میدهد.
کاربر باید پیش از حذف نهایی محصول، این عملیات را تأیید کند.
### توضیحات عملکرد
+ زمانی که پراپس `showDeleteConfirm` برابر با `true` باشد و پراپس `product` مقدار داشته باشد، دیالوگ نمایش داده میشود.
+ در دیالوگ، نام محصول مورد نظر برای حذف نمایش داده میشود.
+ دو دکمه وجود دارد:
+ **انصراف:** دیالوگ را بدون انجام هیچ تغییری میبندد.
+ **حذف:** تابع `handleDelete` را اجرا کرده و درخواست حذف محصول را به API ارسال میکند.
### منطق عملکرد `handleDelete`
1. حالت **loading** فعال میشود تا از چندبار کلیک جلوگیری شود.
2. تابع `productApi.deleteProduct(product.id)` فراخوانی میشود.
3. در صورت موفقیت:
+ دیالوگ بسته میشود.
+ تابع `refetchProducts` برای **بهروزرسانی لیست محصولات** اجرا میشود.
4. در صورت بروز خطا، پیام خطا نمایش داده میشود.
5. پس از پایان عملیات (موفق یا ناموفق)، حالت **loading** غیرفعال میشود.
</details>
</details>
<details class="yellow">
<summary>**کامپوننت ProductForm**</summary>
شما در این کامپوننت باید تابع **handleSubmit** را پیادهسازی کنید.
### مراحل عملکرد
1. **اعتبارسنجی ورودیها**
+ بررسی میکند که فیلدهای `name`, `category`, `price` و `stock` پر شده باشند.
+ اگر یکی از فیلدها خالی باشد:
+ مقدار خطا (`error`) با متن `"لطفا همه فیلدها را پر کنید."` تنظیم میشود.
+ عملیات ذخیرهسازی متوقف میشود.
2. **شروع عملیات ذخیرهسازی**
+ مقدار `loading` روی `true` قرار میگیرد.
+ مقدار `error` پاک میشود تا پیامهای قبلی نمایش داده نشوند.
3. **فراخوانی api**
+ اگر مقدار **initialValue** موجود بود، محصول مورد نظر ویرایش میشود، اگر نه محصول جدید اضافه میشود.
4. **بروزرسانی بعد از موفقیت**
+ تابع `onSuccess()` فراخوانی میشود تا والد کامپوننت از موفقیت عملیات مطلع شود.
+ تابع `refetch()` از هوک `useProducts` صدا زده میشود تا لیست محصولات دوباره بارگذاری شود.
5. **مدیریت خطا**
+ اگر هنگام تماس با API خطایی رخ دهد، مقدار `error` با متن `"خطا در ذخیرهسازی محصول."` تنظیم میشود.
6. **پایان عملیات**
+ مقدار `loading` دوباره روی `false` قرار میگیرد تا دکمه ثبت فعال شود.
</details>
# **آنچه باید آپلود کنید**
+ **توجه:** پس از اعمال تغییرات، کل پروژه را _Zip_ کرده و آپلود کنید. **همانند پروژه اولیه در فایل زیپ شده نباید کد در پوشهی دیگری قرار بگیرد در غیر این صورت سیستم داوری فایل را شناسایی نکرده و نمرهای دریافت نخواهید کرد.**
+ **توجه:** تنها فایلهایی که در **ساختار پروژه** مشخص شدهاند، در سیستم داوری **مورد پذیرش** قرار خواهد گرفت و سایر تغییرات در سایر فایلها **بیتاثیر** خواهند بود.
+ **توجه:** به هیچ عنوان **نباید هیچ تغییری در استایلها** ایجاد کنید.
+ **توجه:** در برخی فایلها، **کامنتهایی برای راهنمایی** قرار داده شدهاند؛ طبق این راهنماییها عمل کنید.
+ **توجه:** لطفاً مواردی که در هر کامپوننت **ایمپورت و استفاده شدهاند** را حذف **نکنید** و همچنین **ترتیب یا جای کامپوننتها** را تغییر **ندهید.**