**فرانتیوم** *(Frontiom)* مثل همیشه گرسنه بود و واقعاً همون وقتها بود که میشد ترسناکترین صحنههایِ شرکت رو دید.
دقیقا مثل گرگینه که با دیدن ماه شکلش عوض میشه. یه روز گرم تابستون، فرانتیوم داشت کمکم گرسنه میشد که بکندیوم متوجه شد و سریع براش یه همبرگر خوشطعم سفارش داد. وقتی فرانتیوم داشت غذاشو میخورد فهمید چقدر دوست داره یه همبرگر لایهلایه رو؛ فکر کرد: «نگاه کن—نان، همبرگر، کاهو، پیاز، همبرگر، دوباره نان.»
فرانتیوم، که فرانتاند دولوپرِ خفنی بود، ناگهان به این فکر افتاد: **«چی میشه اگه برای سایتم یه منوی لایهلایه بسازم؟»**

# **پروژهی اولیه**
برای دانلود **پروژهی اولیه** روی [این لینک](/contest/assignments/84122/download_problem_initial_project/307256/) کلیک کنید.
<details class="red">
<summary>**ساختار فایلها**</summary>
```
hamburger/
├─ fonts/
├── <mark class="orange" title="این فایل را تکمیل کنید">index.html</mark>
├── <mark class="orange" title="این فایل را تکمیل کنید">index.js</mark>
└── <mark class="orange" title="این فایل را تکمیل کنید">styles.css</mark>
```
</details>
# **جزئیات پروژه**
در گیف زیر میتوانید عملکرد کلی برنامه را مشاهده کنید:

در این سؤال، قرار است با تمرکز بر مفهوم **ریسپانسیو** *(Responsive)*، منوی موجود را بر اساس توضیحات زیر در حالتهای مختلف نمایش دهید.
<details class="orange">
<summary>**فایل** `index.html`</summary>

</details>
<details class="blue">
<summary>**فایل** `styles.css`</summary>

### **بزرگتر از** `700px`**:**

+ **منو** *(Menu)* **نمایش داده شود** و **همبرگر منو** *(Hamburger Menu)* **نمایش داده نشود.**
### **کوچکتر از** `700px`**:**

+ **منو** *(Menu)* نمایش داده **نشود** و **همبرگر منو** *(Hamburger Menu)* **نمایش داده شود.**
</details>
<details class="yellow">
<summary>**فایل** `index.js`</summary>
شما باید با نوشتن کد مناسب، کاری کنید که با کلیک بر روی **آیکون سهخطی** موجود در navbar، منوی (mobile-menu) مانند تصویر زیر باز شود:

**توجه:** در فایل **styles.css** کلاسی با نام **show** تعریف شده است. شما باید هنگام کلیک بر روی **آیکون سهخطی** از این کلاس استفاده کنید تا منو باز و بسته شود.
</details>
# **آنچه باید آپلود کنید**
+ **توجه:** در این سوال شما مجاز به ایجاد تغییرات در هر سه فایلِ `index.html` ، `styles.css` و `index.js` هستید.
+ **توجه**: پس از اعمال تغییرات، کل پروژه را _Zip_ کرده و آپلود کنید. **همانند پروژه اولیه در فایل زیپ شده نباید کد در پوشهی دیگری قرار بگیرد در غیر این صورت سیستم داوری فایل را شناسایی نکرده و نمرهای دریافت نخواهید کرد.**
+ **توجه:** تنها فایلهایی که در **ساختار پروژه** مشخص شدهاند، در سیستم داوری **مورد پذیرش** قرار خواهد گرفت و سایر تغییرات در سایر فایلها **بیتاثیر** خواهند بود.
+ **توجه:** کدهای سیاساس خود را پس از کامنتی که در فایل نوشته شده است بنویسید.
کارهای امروز تمام شده بود. **فرانتیوم** همیشه وقتی از محل کار به سمت خانه میرفت، کمی دلگیر میشد. هندزفریاش را در گوش میگذاشت و مسیرِ شرکت تا خانه را پیاده طی میکرد. فردا روز تولدش بود، اما از وقتی برای تغییر و تحول در زندگیاش دور از خانواده تنها زندگی میکرد، این فکر که قرار است تولدی تنها داشته باشد، غمگینش میکرد.
در مسیر خانه یک سازفروشی بود. فرانتیوم همیشه محو نگاه کردن به آن ساز جادویی یعنی **آکاردئون** میشد. اما با خودش میگفت: «نباید چیزی برای خودم بخرم، باید پولم را جمع کنم. کاش کسی بود که روز تولدم آن ساز را به من هدیه میداد.» فردا وقتی وارد شرکت شد و به میز کارش رسید، چشمش به یک جعبهی کادوی بزرگ و یک کارت افتاد. روی کارت نوشته بود: «یادت باشه که بکندیوم خیلی دوستت داره :)»
فرانتیوم وقتی آکاردئون جادویی را دید، با خودش گفت: **«بذار قبل از اینکه ساز زدن رو شروع کنم، یک لیست آکاردئونی برای سایت خودم درست کنم.»**

# **پروژهی اولیه**
برای دانلود **پروژهی اولیه** روی [این لینک](/contest/assignments/84122/download_problem_initial_project/307255/) کلیک کنید.
<details class="red">
<summary>**ساختار فایلها**</summary>
```plaintext
accordion/
├─ fonts/
├─ index.html
├─ <mark class="orange" title="این فایل را تکمیل کنید">index.js</mark>
└─ styles.css
```
</details>
# **جزئیات پروژه**
هدف ما در این سوال، **پیادهسازی لیست آکاردئونی** است که میتوانید عملکرد کلی برنامه را در گیف زیر مشاهده کنید:

### وضعیت اولیهی آکاردئونها
در شروع بارگذاری صفحه، همهی آکاردئونها باید بسته باشند (کلاس `active` نداشته باشند و `max-height` آنها برابر با `0` پیکسل باشد).
باکسهای راهنما را مطالعه کنید و پیادهسازیها را مطابق با دستورالعملهای ارائهشده انجام دهید.
<details class="purple">
<summary>**پیادهسازی accordions_container** </summary>

</details>
<details class="purple">
<summary>**پیادهسازی accordion_body** </summary>

</details>
<details class="purple">
<summary>**پیادهسازی accordion_title** </summary>

</details>
<details class="purple">
<summary>**پیادهسازی accordion_content** </summary>

```plaintext
خیلی خوش اومدی به یک مسابقهی دیگهی کوئرایی، امیدوارم به اون نتیجهای که میخوای برسی و یادت باشه که همیشه تا لحظهی آخر تلاش بکنی ; )
```
</details>
<details class="brown">
<summary>**پیادهسازی click event** </summary>

</details>
<details class="brown">
<summary>**وضعیت log** </summary>
در **فایل** `index.html` **یک تگ با آیدی** `log` مشاهده میکنید.
شما باید با استفاده از `data-id` ای که پیشتر ساختید، وقتی آکادرئونی **باز** میشود، چنین محتوای متنیای نمایش دهید:
```plaintext
آکاردئون با دیتاست n باز شد
```
```plaintext
آکاردئون با دیتاست 2 باز شد
```
+ به عنوان مثال برای **باز شدن آکاردئون سوم،** متن نمایش داده شده به صورت بالا خواهد بود. **توجه داشته باشید** که اعداد آکاردئونها میبایست **حتما به صورت انگلیسی بوده،** همچنین متن نوشته شده باید **بدون هیچ تغییر اضافهای باشد.**
هنگام بسته شدن آکاردئونها نیز شما باید متنی به شکل زیر نمایش دهید:
```plaintext
آکاردئون با دیتاست n بسته شد
```
```plaintext
آکاردئون با دیتاست 1 بسته شد
```
+ به عنوان مثال برای **بسته شدن آکاردئون دوم،** متن نمایش داده شده به صورت بالا خواهد بود. **توجه داشته باشید** که اعداد آکاردئونها میبایست **حتما به صورت انگلیسی بوده،** همچنین متن نوشته شده باید **بدون هیچ تغییر اضافهای باشد.**

+ در صورتی که **آکاردئون اول باز** بوده و ما روی **آکاردئون سوم** کلیک کردیم، متون نمایش داده شده به شکل زیر خواهد بود:
```plaintext
آکاردئون با دیتاست 0 بسته شد
آکاردئون با دیتاست 2 باز شد
```

</details>
# **آنچه باید آپلود کنید**
+ **توجه**: پس از پیادهسازی موارد خواسته شده، **فایل** `index.js` را برای سیستم داوری ارسال کنید.
+ **توجه**: شما مجاز به افزودن فایل جدیدی در این ساختار **نیستید** و تنها باید تغییرات را در **فایل** `index.js` اعمال کنید.
+ **توجه:** برای هر قسمت از آکاردئون **دقیقاً همان** `id` و `class` ای را در نظر بگیرید که در بخش جزئیات پیادهسازی مشخص شده است؛ در غیر این صورت، **استایلها به درستی اعمال نمیشوند** و **نمرهای دریافت نخواهید کرد.**
+ **توجه:** در نهایت باید **دقیقاً ۶ آکاردئون** در `accordion_container` وجود داشته باشد؛ **نه کمتر و نه بیشتر.**

+ **توجه:** آکاردئون **فقط با کلیک روی** `accordion_title` **باز و بسته** میشود. اگر برای سایر بخشها رویدادی تعریف شود، **نمرهای تعلق نخواهد گرفت.**
+ **توجه:** برای هر المان مربوطه یک `data-attribute` **مقداردهی کنید که مقدار آن برابر با اندیس همان المان باشد.**
+ **توجه:** مقدار `data-id` برای آکاردئونها باید به ترتیب از ۰ تا ۵ تنظیم شود.
از اون روزی که بکندیوم ساز جادویی آکاردئون را به فرانتیوم هدیه داد، او مدام به این فکر میکرد که چطور میتواند بکندیوم را تحت تأثیر قرار بدهد. متوجه شده بود که بکندیوم به مباحث ساختمان داده علاقه دارد و همچنین میدانست که به بازی هم علاقهمند است. برای همین تصمیم گرفت یک بازی طراحی کند. داشت فکر میکرد که این بازی باید چطور باشد تا واقعا بکندیوم خوشحال شود. این بازی چیزی جز **بازی حدس عدد** نبود؛ بازیای که جوابش با جستوجوی باینری و در کمترین تعداد حدس ممکن به دست میآید.
حالا فرانتیوم مطمئن بود که بکندیوم قرار است خیلی خوشحال شود.

# **پروژه اولیه**
برای دانلود **پروژهی اولیه** روی [این لینک](/contest/assignments/84122/download_problem_initial_project/307254/) کلیک کنید.
<details class="red">
<summary>**ساختار فایلها**</summary>
```plaintext
game/
├─ fonts
├─ index.html
├─ <mark class="orange" title="این فایل را تکمیل کنید">index.js</mark>
└─ styles.css
```
</details>
# **جزئیات پروژه**
در گیف زیر میتوانید یک دور از بازی را مشاهده کنید

در این سوال، شما قرار است بازی جذاب **حدس عدد با استفاده از الگوریتم جستجوی باینری** را پیادهسازی کنید.
در ادامه، ابتدا با معرفی بازی و قوانین آن آشنا خواهید شد.
<details class="purple">
<summary>**معرفی بازی**</summary>

</details>
<details class="purple">
<summary>**شرح فرایند بازی**</summary>

<details class="yellow">
<summary>**امتیاز و بالاترین امتیاز**</summary>
> نتیجهی بازی بر اساس **امتیاز اولیهای** که در ابتدای بازی محاسبه میشود، تعیین خواهد شد.
ابتدا اندازهی بازه را با فرمول زیر محاسبه میکنیم:
$$
\text{rangeSize} = (\text{to} - \text{from}) + 1
$$
در این فرمول:
+ عدد انتهایی بازه را to در نظر گرفتیم.
+ عدد ابتدایی بازه را from در نظر گرفتیم.
+ افزودن عدد 1 به این اختلاف باعث میشود که بازه شامل هر دو عدد انتهایی نیز باشد. به عنوان مثال، اگر بازه از 1 تا 5 باشد، تعداد اعداد داخل بازه 5 است (نه 4).
سپس برای تعیین تعداد حداکثر تعداد حدسهای منطقی از مفهوم جستجوی دودویی (Binary Search) استفاده میکنیم:
$$
\text{maxGuesses} =
\begin{cases}
1, & \text{if } \text{rangeSize} \le 1 \\
\lfloor \log_2(\text{rangeSize}) \rfloor + 1, & \text{otherwise}
\end{cases}
$$
توضیح:
1. لگاریتم در پایهی ۲ به ما میگوید که برای یافتن عدد مورد نظر در یک بازه، با روش جستجوی دودویی چند مرحله نیاز است.
2. از آنجا که تعداد مراحل باید عددی صحیح باشد، از **کف لگاریتم** (floor) استفاده میکنیم.
3. در نهایت یک واحد به آن اضافه میکنیم تا حالت ابتدایی بازی نیز در محاسبه لحاظ شود.
برای مثال، اگر بازهی اعداد از ۱ تا ۱۰۰ باشد:
$$
\log_2(100) \approx 6.64
$$
بنابراین:
$$
\lfloor 6.64 \rfloor + 1 = 7
$$
یعنی بازیکن حداکثر با **۷ حدس** میتواند عدد صحیح را پیدا کند.
<details class="purple">
<summary>**شرط برد و باخت بازیکن**</summary>

</details>
<details class="purple">
<summary>**بالاترین امتیاز**</summary>

</details>
</details>
</details>
تمامی تگها و اجزایی که باید از آنها استفاده کرده و با **DOM** تعامل داشته باشید، در اینجا قابل مشاهده هستند.
<details class="green">
<summary>**تگها و اجزای بازی**</summary>

</details>
نکتهی بسیار مهم در این سوال، کنترل و نمایش پیامهای مختلف به کاربر است. با مشاهدهی جعبههای راهنما که در پایین قرار دارند، میتوانید عملکرد برنامه را درک کنید.
<details class="orange">
<summary>**معرفی تابع showToast**</summary>

<details class="purple">
<summary>**پیامهای عمومی**</summary>
+ در صورت وارد نکردن اعداد بازه یا غیرمنطقی بودن اعداد وارد شده متنی که باید نمایش داده بشود به شکل زیر است :
```plaintext
لطفا بازهی اعداد را به درستی در ورودیها وارد بکنید.
```
+ در صورت شروع بازی متنی که باید نمایش داده بشود به شکل زیر است :
```plaintext
بازی شروع شد
```
+ در صورت وارد کردن چیزی به جز عدد در ورودی مخصوص **حدس شما**:
```plaintext
لطفاً یک عدد معتبر حدس بزن.
```
</details>
<details class="green">
<summary>**پیامی که در صورت برد نمایش داده میشود**</summary>
زمانی که بازیکن برنده میشود:
```plaintext
بازی را بردید، برای بازی مجدد روی دکمهی بازی مجدد کلیک بکنید
```
</details>
<details class="purple">
<summary>**پیامی که در صورت باخت نمایش داده میشود**</summary>
زمانی که بازیکن میبازد :
```plaintext
بازی را باختید، برای بازی مجدد روی دکمهی بازی مجدد کلیک بکنید.
```
</details>
در گیف زیر پیامها را میتوانید پیامها را مشاهده کنید

</details>
<details class="orange">
<summary>**کنترل وضعیت گزارشها (report ها)**</summary>

- **حدس در حال انجام بازی:**
اگر عدد حدس زده شده بیشتر از عدد رندوم باشد:
- متن باکس راهنما به
```plaintext
عدد حدس زده شده بزرگ است
```
تغییر میکند.
اگر عدد حدس زده شده کمتر از عدد رندوم باشد:
- متن باکس راهنما به
```plaintext
عدد حدس زده شده کوچک است
```
تغییر میکند.
</details>
## نکات
+ دقت کنید که متنهای راهنما و مربوط به **toast** بسیار دقیق نوشته بشوند.
+ در گیف زیر میتوانید روند بازی که به باخت منتهی میشود را مشاهده بکنید:

+ در گیف زیر میتوانید روند بازی که به برد منتهی میشود را مشاهده بکنید:

# **آنچه باید آپلود کنید**
+ **توجه**: پس از پیادهسازی موارد خواسته شده، **فایل** `index.js` را برای سیستم داوری ارسال کنید.
+ **توجه**: شما مجاز به افزودن فایل جدیدی در این ساختار **نیستید** و تنها باید تغییرات را در **فایل** `index.js` اعمال کنید.
این روزها در شرکت، هر وقت کلمهی **«هدفون»** گفته میشد، همه یک نفر را به یاد میآوردند؛ اون کسی نبود جز **فرانتیوم.** فرانتیوم تنها زمانی که مجبور بود در جلسهای شرکت کند، هدفونش را از روی سر برمیداشت. حالا که آکاردئون داشت و بیش از هر زمان دیگری در دنیای موسیقی غرق بود، یک روز هنگام مرور آهنگهایش، سوال بسیار مهمی در ذهنش شکل گرفت: چرا تا الآن یک موزیک پلیر برای خودم نساختهام؟
مثل همیشه، فکر کردن به دنیای فرانت باعث شد ایدهی پروژهی جدیدی به ذهن فرانتیوم برسد: ساخت یک موزیک پلیر.

# **پروژه اولیه**
برای دانلود **پروژهی اولیه** روی [این لینک](/contest/assignments/84122/download_problem_initial_project/307253/) کلیک کنید.
<details class="red">
<summary>**ساختار فایلها**</summary>
```plaintext
music_player/
├─ fonts/
├─ <mark class="orange" title="این فایل را تکمیل کنید">data.js</mark>
├─ index.html
├─ <mark class="orange" title="این فایل را تکمیل کنید">main.js</mark>
└─ styles.css
```
</details>
# **جزئیات پروژه**
در گیف زیر میتوانید عملکر کلی برنامه را مشاهده کنید:

<details class="purple">
<summary>**پیادهسازی فایل data.js** </summary>
وقتی که به این فایل رجوع کنید سه پلیلیست مشاهده خواهید کرد، برای مثال:
<details class="red">
<summary>**اولین پلیلیست**</summary>
```js
const playlist1 = [
{
id: 1,
title: "Skyline Dreams",
artist: "Nova",
genre: "Chill",
duration: "3:45",
},
{
id: 2,
title: "Ocean Ride",
artist: "Wavey",
genre: "Electronic",
duration: "4:12",
},
{
id: 3,
title: "Midnight Sun",
artist: "Solar",
genre: "Pop",
duration: "2:58",
},
{
id: 4,
title: "Crystal Hearts",
artist: "Amber",
genre: "Indie",
duration: "3:33",
},
{
id: 5,
title: "Velvet Sky",
artist: "Luna",
genre: "Ambient",
duration: "3:10",
},
];
```
</details>

</details>
<details class="purple">
<summary>**پیادهسازی فایل main.js**</summary>
در ابتدای فایل تمام تگها و المانهایی که نیاز هست از آنها استفاده کنید، تعریف شدهاند.
```js
const allMenu = document.getElementById("menu-all");
const playlistMenu = document.getElementById("menu-playlist");
const favoritesMenu = document.getElementById("menu-favorites");
const trackTitle = document.getElementById("track-title");
const trackArtist = document.getElementById("track-artist");
const trackGenre = document.getElementById("track-genre");
const trackTime = document.getElementById("track-time");
const progressBar = document.getElementById("progress-bar");
const progressFill = document.getElementById("progress-fill");
const controls = {
play: document.getElementById("play"),
prev: document.getElementById("prev"),
next: document.getElementById("next"),
shuffle: document.getElementById("shuffle"),
favorite: document.getElementById("favorite")
};
let allTracks = [];
let currentTrackIndex = 0;
let activeTrackList = [];
let favorites = [];
let isShuffle = false;
let shuffledTrackList = [];
let timer = null;
let elapsedSeconds = 0;
let totalSeconds = 0;
let isPlaying = false;
```
## متغیرها

## توابع

</details>
<details class="purple">
<summary>**پیادهسازی منوی آهنگها**</summary>


<details class="red">
<summary>**ساختار منو در index.html** </summary>
```html
<aside class="menu">
<div class="menu-section">
<h3 class="toggle" data-section="all">
<i class="fa-solid fa-headphones"></i> All Tracks
</h3>
<ul class="menu-list" id="menu-all"></ul>
</div>
<div class="menu-section">
<h3 class="toggle" data-section="playlist">
<i class="fa-solid fa-folder-music"></i> Playlists
</h3>
<ul class="menu-list" id="menu-playlist"></ul>
</div>
<div class="menu-section">
<h3 class="toggle" data-section="favorites">
<i class="fa-regular fa-heart"></i> Favorites
</h3>
<ul class="menu-list" id="menu-favorites"></ul>
</div>
</aside>
```
1. **هر بخش از منو دارای یک** `<h3>` برای عنوان *(قابل کلیک)* و یک `<ul>` **برای نمایش لیست آیتمها** است.
2. **کلاسهای** `toggle` و `open` برای مدیریت *باز/بسته بودن* بخشها استفاده میشوند.
3. **آیتمهای آهنگها** به صورت `<li>` درون `<ul>`ها قرار میگیرند.
4. در بخش `📁 Playlists`، **هر لیست پخش میتواند شامل چند** `<ul>` زیرمجموعه باشد *(برای گروهبندی آهنگها)،* که هرکدام شامل `<li>`های مربوط به **آهنگهای همان دسته** هستند.
</details>
**نکته:** نام آهنگ و نام خواننده که در لیست رندر میشوند باید به صورت زیر نمایش داده شوند:
```
songName + " - " + singerName
```
</details>
<details class="purple">
<summary>**نمایش اطلاعات آهنگ**</summary>


</details>
<details class="purple">
<summary>**کنترلرهای موزیک پلیر**</summary>
## بخش **Progress Bar**
این بخش باید با زمان واقعی آهنگ هماهنگ باشد و قابلیت جابهجایی بین زمانهای مختلف آهنگ را فراهم کند. پس از پایان آهنگ، باید به آهنگ بعدی منتقل شود.
## آیکونها و عملکرد آن
1. **پخش و توقف (`fa-play` / `fa-pause`)**:
+ زمانی که آهنگ **در حال پخش** است، آیکون
<i class="fa-solid fa-pause"></i> (`fa-pause`) نمایش داده میشود و با کلیک بر روی آن، آهنگ متوقف میشود.
+ زمانی که آهنگ **متوقف** است، آیکون
<i class="fa-solid fa-play"></i> (`fa-play`) نمایش داده میشود و با کلیک، پخش آهنگ ادامه پیدا میکند.
2. **دکمهی بازگشت (`fa-backward-step`)**:
+ اگر **بیش از ۱۰ ثانیه** از آهنگ گذشته باشد، با کلیک به **ابتدای همان آهنگ** بازمیگردیم.
+ اگر **کمتر از ۱۰ ثانیه** گذشته باشد، با کلیک به **آهنگ قبلی** منتقل میشویم.
3. **دکمهی جلو (`fa-forward-step`)**:
+ با کلیک روی این دکمه، به **آهنگ بعدی** منتقل میشویم.
4. **دکمهی شافل (`fa-shuffle`)**:
+ با فعال کردن این دکمه، آهنگها **به صورت تصادفی (Random)** پخش میشوند.
+ با کلیک مجدد، پخش تصادفی غیرفعال میشود و ترتیب اصلی آهنگها بازمیگردد.
5. **دکمهی علاقهمندی (`fa-heart`)**:
+ در حالت عادی، آیکون <i class="fa-regular fa-heart"></i> (`fa-regular fa-heart`) نمایش داده میشود.
+ با کلیک، آهنگ به لیست _Favorites_ اضافه میشود و آیکون به
<i class="fa-solid fa-heart"></i> (`fa-solid fa-heart`) تغییر میکند.
+ با کلیک مجدد، آهنگ از لیست _Favorites_ حذف شده و آیکون دوباره به `fa-regular fa-heart` برمیگردد.

</details>
# **آنچه باید آپلود کنید**
+ **توجه**: پس از اعمال تغییرات، کل پروژه را _Zip_ کرده و آپلود کنید. **همانند پروژه اولیه در فایل زیپ شده نباید کد در پوشهی دیگری قرار بگیرد در غیر این صورت سیستم داوری فایل را شناسایی نکرده و نمرهای دریافت نخواهید کرد.**
+ **توجه:** تنها فایلهایی که در **ساختار پروژه** مشخص شدهاند، در سیستم داوری **مورد پذیرش** قرار خواهد گرفت و سایر تغییرات در سایر فایلها **بیتاثیر** خواهند بود.
+ **توجه:** پیش از آپلود پاسخ، کامنتهای موجود در فایل _main.js_ را حذف کنید.
حالا دیگه **آخرین روز سال** شده بود و **فرانتیوم** میخواست یک پروژه مناسب طراحی بکند، میخواست تواناییهای خود را بسنجد. توی خونهی مامانبزرگش ریاکتیوم در حالی که داشت به مامانبزرگ توی خونه تکونی کمک میکرد، چشمش خورد به جدول کلماتی که بابابزرگش حل میکرد.
حالا یک ایدهی خوب برای پیادهسازی به ذهنش رسیده بود، جدول اطلاعات ...

# **پروژه اولیه**
برای دانلود **پروژهی اولیه** روی [این لینک](/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_ کرده و آپلود کنید. **همانند پروژه اولیه در فایل زیپ شده نباید کد در پوشهی دیگری قرار بگیرد در غیر این صورت سیستم داوری فایل را شناسایی نکرده و نمرهای دریافت نخواهید کرد.**
+ **توجه:** تنها فایلهایی که در **ساختار پروژه** مشخص شدهاند، در سیستم داوری **مورد پذیرش** قرار خواهد گرفت و سایر تغییرات در سایر فایلها **بیتاثیر** خواهند بود.
+ **توجه:** به هیچ عنوان **نباید هیچ تغییری در استایلها** ایجاد کنید.
+ **توجه:** در برخی فایلها، **کامنتهایی برای راهنمایی** قرار داده شدهاند؛ طبق این راهنماییها عمل کنید.
+ **توجه:** لطفاً مواردی که در هر کامپوننت **ایمپورت و استفاده شدهاند** را حذف **نکنید** و همچنین **ترتیب یا جای کامپوننتها** را تغییر **ندهید.**