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

حالا یک ایده‌ی خوب برای پیاده‌سازی به ذهنش رسیده بود، جدول اطلاعات ...

توضیح تصویر

پروژه اولیه

برای دانلود پروژه‌ی اولیه روی این لینک کلیک کنید.

ساختار فایل‌ها

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

راه اندازی

  • پس از دانلود کردن فایل پروژه اولیه، آن را از حالت فشرده خارج کنید.

  • سپس در ترمینال خود دستور npm install را اجرا کنید.

  • در نهایت پروژه را با استفاده از دستور npm run dev اجرا کنید.

در صورت نیاز از دستور npm install --force برای نصب وابستگی‌ها استفاده کنید.

جزئیات پیاده‌سازی

دایرکتوری api

فایل productApi.ts

در این فایل، نوع داده‌ای (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)

فایل logout.ts

در این فایل اندپوینتی نوشته شده است که با استفاده از آن عملیات خروج انجام می‌شود.

فایل createToken.ts

در این فایل، تابعی با نام createToken پیاده‌سازی شده است که وظیفه دارد پس از ورود موفق کاربر، دو مقدار access token و refresh token را بازگرداند.

هدف تابع

این تابع با بررسی اطلاعات کاربری (نام کاربری و رمز عبور) که در آرایه‌ی fakeUsers از پیش تعریف شده‌اند، احراز هویت را شبیه‌سازی می‌کند. در صورت معتبر بودن اطلاعات ورودی، توکن‌ها تولید و بازگردانده می‌شوند؛ در غیر این صورت، خطایی با پیام «نام کاربری یا رمز عبور اشتباه است» برگردانده می‌شود.

اطلاعات ورود برای این دو کاربر از پیش تعریف شده است:

{
   username: "admin",
   password : "admin123",
   role : "admin as const"
}

{
    username: "user",
    password: "user123",
    role: "user" as const
}

هر کاربر پس از ورود، با توجه به نقش (role) خود به مسیر (route) متفاوتی هدایت می‌شود.

مسیرها (Routes) و دسترسی‌ها

مسیر adminTableRoute

  • در ProductPageHeader، دکمه‌ی افزودن محصول نمایش داده می‌شود و ادمین می‌تواند محصولات جدید اضافه کند.

  • در ProductTableRow، دو آیکون ویرایش و حذف وجود دارد که امکان ویرایش یا حذف محصول را فراهم می‌کنند.

مسیر userTableRoute

  • در این مسیر، امکانات مدیریتی برای کاربر عادی نمایش داده نمی‌شوند.

  • کاربر عادی تنها مجاز است محصولات را مشاهده (Read) کند.

نکات ورود (Login)

  • تا زمانی که عملیات ورود انجام نشده باشد، کاربر اجازه‌ی مشاهده‌ی صفحات داخلی را ندارد.

  • در صورت تلاش برای دسترسی مستقیم به آدرس‌های دیگر، کاربر به صفحه‌ی Login هدایت (redirect) می‌شود.

  • پس از ورود موفق، بسته به نقش کاربر:

    • کاربر ادمین → هدایت به adminTableRoute

    • کاربر عادی → هدایت به userTableRoute

فرایند ورود کاربر

تمامی اطلاعات مربوط به ورود کاربر، شامل access token، refresh token و role، باید با استفاده از Context API مدیریت شده و در Local Storage ذخیره شوند.

دلیل این کار آن است که تا زمانی که کاربر از سیستم خارج نشده (Log out) یا رفرش توکن (refresh token) او منقضی نشده باشد، نباید صفحه‌ی Login مجدداً نمایش داده شود. نمایش دوباره‌ی فرم ورود در این شرایط، از نظر تجربه‌ی کاربری (UX) مناسب نیست و باعث ناهماهنگی در جریان کاربر می‌شود.

فایل‌هایی که باید تکمیل شوند

فایل AppRoutes.ts

در این فایل قرار است مسیرهای اصلی برنامه (Routes) بر اساس وضعیت ورود و نقش کاربر تنظیم شوند. هدف این است که تنها کاربران مجاز بتوانند به مسیرهای مربوط به خود دسترسی داشته باشند.

  • از React Router برای تعریف مسیرها استفاده می‌شود.

  • با استفاده از useAuth() از AuthContext، وضعیت ورود (isLoggedIn) و نقش (role) کاربر دریافت می‌شود.

منطق پیاده‌سازی:

  1. اگر کاربر وارد نشده باشد:

    • تنها مسیر /login باید در دسترس باشد.

    • سایر مسیرها باید با استفاده از <Navigate> به /login هدایت شوند.

  2. اگر کاربر وارد شده باشد:

    • اگر نقش او admin باشد → به مسیر /adminTableRoute هدایت شده و تنها مسیرهای مخصوص ادمین نمایش داده می‌شوند.

    • اگر نقش او user باشد → به مسیر /userTableRoute هدایت شده و تنها مسیرهای مخصوص کاربر معمولی نمایش داده می‌شوند.

فایل AuthContext.tsx

در این فایل قرار است سیستم احراز هویت (Authentication) کاربران پیاده‌سازی شود. هدف این است که با استفاده از Context API، وضعیت ورود کاربر، نقش او و توکن‌ها در سطح کل اپلیکیشن مدیریت شود.

  • AuthContext نقش یک Store مرکزی را دارد که اطلاعات مربوط به ورود کاربر را نگهداری کرده و آن را برای سایر بخش‌های برنامه (مانند صفحات و کامپوننت‌ها) در دسترس قرار می‌دهد.

این context شامل اطلاعات زیر خواهد بود:

  • وضعیت ورود (isLoggedIn)

  • نقش کاربر (role)

  • توکن‌ها (accessToken و refreshToken)

  • توابع ورود و خروج (login و logout)

  1. تعریف تابع login برای ذخیره‌ی اطلاعات ورود در Local Storage و به‌روزرسانی stateها.

  2. تعریف تابع logout که با صدا زدن logoutApi() کاربر را از سیستم خارج می‌کند و اطلاعات را از Local Storage حذف می‌کند.

فایل LoginPage.tsx

  1. با استفاده از useState، مقادیر ورودی فرم را مدیریت کنید.

  2. از تابع handleLogin برای ارسال فرم استفاده کنید و توجه داشته باشید که هنگام سابمیت نباید صفحه رفرش شود.

فایل useLogin.ts

در این فایل قرار است یک هوک سفارشی (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 ذخیره و نمایش داده شود.

  • توجه: ساختار فایل App.tsx را تغییر ندهید!

پیاده‌سازی هوک useProducts

هوک 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 کنید و در جاهایی که نیاز هست از آن استفاده کنید.

نمایش اطاعات در جدول

  • جدول ما 3 قسمت با این اسامی دارد :

کامپوننت ProductTableHeader.tsx

در این بخش، ستون‌های جدول محصولات به‌ترتیب زیر نمایش داده می‌شوند:

  1. نام محصول (productName)

  2. دسته‌بندی (productCategory)

  3. قیمت (productPrice)

  4. موجودی (stock)

  5. عملیات (operation)

همچنین کامپوننت ProductTableControls در این قسمت قرار دارد که شامل فیلد جست‌وجو و دکمه‌ی مرتب‌سازی است.

قابلیت جست‌وجو

  • باید قابلیت جست‌وجو بر اساس نام محصول پیاده‌سازی شود.

  • محتوای بخش بدنه‌ی جدول (tbody) با تغییر مقدار جست‌وجو به‌روزرسانی خواهد شد.

قابلیت مرتب‌سازی

  • دکمه‌ی مرتب‌سازی با کلیک بین سه حالت جابه‌جا می‌شود:
    1. مرتب‌سازی صعودی (ارزان‌ترین ← گران‌ترین)

    2. مرتب‌سازی نزولی (گران‌ترین ← ارزان‌ترین)

    3. حالت پیش‌فرض (بدون مرتب‌سازی)

در کد مربوطه، متن دکمه بسته به وضعیت فعلی به‌شکل زیر تغییر می‌کند:

{sortByPrice === "asc"
  ? "مرتب‌سازی: ارزان‌ترین ← گران‌ترین"
  : sortByPrice === "desc"
  ? "مرتب‌سازی: گران‌ترین ← ارزان‌ترین"
  : "مرتب‌سازی بر اساس قیمت"}

کامپوننت ProductTableBody.tsx

در این بخش، لیست محصولات دریافت‌شده باید به‌صورت ردیف‌های جداگانه در جدول نمایش داده شود. این داده‌ها در کامپوننت ProductTableBody نمایش داده می‌شوند.

شرح عملکرد

  1. هر محصول شامل اطلاعاتی مانند نام، دسته‌بندی، قیمت و موجودی است که در ستون‌های جدول نمایش داده می‌شود.

  2. در ستون آخر (عملیات)، آیکون‌های ویرایش (FaEdit) و حذف (FaTrash) تنها برای نقش ادمین (admin) نمایش داده می‌شوند.

  3. کاربران معمولی (user) فقط اطلاعات محصولات را مشاهده می‌کنند و به عملیات ویرایش و حذف دسترسی ندارند.

نکات مهم

نکته 1 تفاوت بین نقش‌های admin و user در این بخش فقط در نمایش آیکون‌های ویرایش و حذف است. اگر نقش کاربر admin باشد، این آیکون‌ها نمایش داده می‌شوند؛ در غیر این صورت پنهان خواهند بود.

نکته 2 تغییرات مربوط به جست‌وجو یا تغییر تعداد محصولات در هر صفحه، مستقیماً بر داده‌های نمایش‌داده‌شده در ProductTableBody تأثیر می‌گذارند.

نکته 3 اگر هیچ محصولی یافت نشود، باید پیام زیر نمایش داده شود:

 محصولی یافت نشد.

نکته 4 در هنگام بارگذاری داده‌ها، باید پیام زیر نمایش داده شود:

در حال بارگذاری...

نکته 5 با کلیک روی آیکون ویرایش (FaEdit) باید تابع setShowEditModal(true, product) فراخوانی شود تا مودال ویرایش باز شده و اطلاعات محصول انتخاب‌شده در آن نمایش داده شود.

نکته 6

با کلیک روی آیکون حذف (FaTrash) باید تابع setShowDeleteConfirm(true, product) فراخوانی شود تا پنجره‌ی تأیید حذف نمایش داده شود.

دکمه‌ی حذف محصول

  1. وقتی روی دکمه‌ی حذف محصول کلیک می‌کنیم، مودال DeleteProductModal باز می‌شود.

  2. در این مودال از کاربر پرسیده می‌شود که آیا مطمئن است می‌خواهد محصول را حذف کند یا خیر.

وظایف اصلی

  1. انصراف از عملیات

    • اگر کاربر دکمه‌ی «انصراف» را بزند، هیچ تغییری ایجاد نمی‌شود و مودال بسته خواهد شد.
  2. حذف محصول

    • اگر کاربر دکمه‌ی «حذف» را بزند:
      • محصول مورد نظر حذف می‌شود.

      • پس از موفقیت‌آمیز بودن عملیات، یک refetch انجام می‌شود تا جدول محصولات بروزرسانی شود.

دکمه‌ی ویرایش محصول در سطر هر محصول

  1. وقتی روی دکمه‌ی ویرایش محصول کلیک می‌کنیم، مودال EditProductModal باز می‌شود.

  2. داخل این مودال، کامپوننت ProductForm نمایش داده می‌شود و اطلاعات محصول انتخاب‌شده در ورودی‌های فرم مقداردهی اولیه می‌شوند.

وظایف اصلی

  1. ویرایش اطلاعات محصول

    • کاربر می‌تواند مقادیر فیلدهای فرم را تغییر دهد.
  2. به‌روزرسانی داده‌ها

    • پس از ثبت تغییرات، درخواست به سرور ارسال می‌شود.

    • پس از موفقیت‌آمیز بودن عملیات، باید یک refetch انجام شود تا جدول محصولات بروزرسانی شود.

نکته

  • توجه کنید که کامپوننت ProductForm هم در مودال AddProductModal و هم در EditProductModal استفاده می‌شود.

کامپوننت ProductTableFooter

  • در این بخش سه کامپوننت وجود دارد:

1. کامپوننت TotalCountDisplay

  • این کامپوننت وظیفه‌ی نمایش تعداد کل محصولات موجود را دارد.

  • این عدد باید دقیقاً مطابق با تعداد محصولاتی باشد که از سمت سرور یا پرامیس داده دریافت می‌شوند.

2. کامپوننت PaginationControls

  • این کامپوننت مسئول مدیریت صفحه‌بندی است.

  • داده‌ها باید به‌صورت صفحه‌بندی‌شده نمایش داده شوند.

  • دو دکمه‌ی «>» و «<» باید وجود داشته باشند:

    • اگر کاربر در صفحه‌ی اول قرار دارد، دکمه‌ی «>» باید غیرفعال شود.

    • اگر کاربر در صفحه‌ی آخر قرار دارد، دکمه‌ی «<» باید غیرفعال شود.

3. کامپوننت PagesSizeSelector

  • این کامپوننت شامل یک انتخاب‌گر (selector) برای تعیین تعداد آیتم‌ها در هر صفحه است.

  • گزینه‌های انتخابی باید شامل ۵، ۱۰ یا ۱۵ آیتم در هر صفحه باشند.

  • با تغییر این مقدار:

    • تعداد کل صفحات دوباره محاسبه می‌شود.

    • داده‌های نمایش‌داده‌شده در جدول به‌روزرسانی می‌شوند.

کامپوننت ProductPage

این کامپوننت شامل اجزای زیر است:

  1. مودال AddProductModal
  2. مودال EditProductModal
  3. دیالوگ DeleteProductDialog
  4. کامپوننت ProductPageHeader
  5. کامپوننت ProductTable (در باکس راهنمای بالا، توضیح داده شد)
  6. دکمه‌ی خروج

دکمه‌ی خروج

با زدن این دکمه، باید عملیات logout انجام بشود.

کامپوننت ProductPageHeader

در این کامپوننت دکمه‌ی افزودن محصول قرار دارد که باید به شیوه‌ی زیر کار کند.

  1. وقتی روی دکمه‌ی افزودن محصول کلیک می‌کنیم، باید مودال AddProductModal باز شود.
  2. داخل این مودال، کامپوننت ProductForm نمایش داده می‌شود.

مودال AddProductModal

این کامپوننت یک مودال برای افزودن محصول جدید است که شامل فرم ثبت محصول (ProductForm) می‌باشد.

با فراخوانی این کامپوننت، زمانی که showAddModal برابر با true باشد، یک مودال در مرکز صفحه نمایش داده می‌شود.

  • اگر مقدار showAddModal برابر با false باشد، مقدار null را ریترن کنید.

در صورت کلیک بر روی دکمه‌ی ضربدر (✕)، مقدار showAddModal به false تغییر کرده و مودال بسته می‌شود.

پس از ثبت موفق محصول در فرم:

  • تابع refetchProducts برای به‌روزرسانی لیست محصولات فراخوانی می‌شود.

  • مودال به‌صورت خودکار بسته می‌شود.

مودال EditProductModal

این کامپوننت یک مودال برای ویرایش اطلاعات محصول موجود است که از ProductForm برای نمایش و ویرایش داده‌ها استفاده می‌کند.

توضیحات عملکرد

  • زمانی که showEditModal برابر با true باشد و پراپس product مقدار داشته باشد، مودال نمایش داده می‌شود.

  • اگر مقدار showEditModal یا product برابر با false باشد، مقدار null را ریترن کنید.

  • داده‌های محصول انتخاب‌شده از طریق پراپس product به‌عنوان مقادیر اولیه فرم (initialValues) به ProductForm ارسال می‌شوند.

  • با کلیک بر روی دکمه‌ی ضربدر (✕)، مودال بسته می‌شود.

  • پس از ثبت موفق تغییرات:

    • مودال به‌صورت خودکار بسته می‌شود.

    • تابع refetchProducts برای به‌روزرسانی لیست محصولات فراخوانی می‌شود.

دیالوگ DeleteProductDialog

این کامپوننت یک دیالوگ تأیید حذف محصول را نمایش می‌دهد. کاربر باید پیش از حذف نهایی محصول، این عملیات را تأیید کند.

توضیحات عملکرد

  • زمانی که پراپس showDeleteConfirm برابر با true باشد و پراپس product مقدار داشته باشد، دیالوگ نمایش داده می‌شود.

  • در دیالوگ، نام محصول مورد نظر برای حذف نمایش داده می‌شود.

  • دو دکمه وجود دارد:

    • انصراف: دیالوگ را بدون انجام هیچ تغییری می‌بندد.

    • حذف: تابع handleDelete را اجرا کرده و درخواست حذف محصول را به API ارسال می‌کند.

منطق عملکرد handleDelete

  1. حالت loading فعال می‌شود تا از چندبار کلیک جلوگیری شود.

  2. تابع productApi.deleteProduct(product.id) فراخوانی می‌شود.

  3. در صورت موفقیت:

    • دیالوگ بسته می‌شود.

    • تابع refetchProducts برای به‌روزرسانی لیست محصولات اجرا می‌شود.

  4. در صورت بروز خطا، پیام خطا نمایش داده می‌شود.

  5. پس از پایان عملیات (موفق یا ناموفق)، حالت loading غیرفعال می‌شود.

کامپوننت ProductForm

شما در این کامپوننت باید تابع handleSubmit را پیاده‌سازی کنید.

مراحل عملکرد

  1. اعتبارسنجی ورودی‌ها

    • بررسی می‌کند که فیلدهای name, category, price و stock پر شده باشند.

    • اگر یکی از فیلدها خالی باشد:

      • مقدار خطا (error) با متن "لطفا همه فیلدها را پر کنید." تنظیم می‌شود.

      • عملیات ذخیره‌سازی متوقف می‌شود.

  2. شروع عملیات ذخیره‌سازی

  • مقدار loading روی true قرار می‌گیرد.

  • مقدار error پاک می‌شود تا پیام‌های قبلی نمایش داده نشوند.

  1. فراخوانی api
  • اگر مقدار initialValue موجود بود، محصول مورد نظر ویرایش می‌شود، اگر نه محصول جدید اضافه می‌شود.
  1. بروزرسانی بعد از موفقیت

    • تابع onSuccess() فراخوانی می‌شود تا والد کامپوننت از موفقیت عملیات مطلع شود.

    • تابع refetch() از هوک useProducts صدا زده می‌شود تا لیست محصولات دوباره بارگذاری شود.

  2. مدیریت خطا

    • اگر هنگام تماس با API خطایی رخ دهد، مقدار error با متن "خطا در ذخیره‌سازی محصول." تنظیم می‌شود.
  3. پایان عملیات

    • مقدار loading دوباره روی false قرار می‌گیرد تا دکمه ثبت فعال شود.

آن‌چه باید آپلود کنید

  • توجه: پس از اعمال تغییرات، کل پروژه را Zip کرده و آپلود کنید. همانند پروژه اولیه در فایل زیپ شده نباید کد در پوشه‌ی دیگری قرار بگیرد در غیر این صورت سیستم داوری فایل را شناسایی نکرده و نمره‌ای دریافت نخواهید کرد.

  • توجه: تنها فایل‌هایی که در ساختار پروژه مشخص شده‌اند، در سیستم داوری مورد پذیرش قرار خواهد گرفت و سایر تغییرات در سایر فایل‌ها بی‌تاثیر خواهند بود.

  • توجه: به هیچ عنوان نباید هیچ تغییری در استایل‌ها ایجاد کنید.

  • توجه: در برخی فایل‌ها، کامنت‌هایی برای راهنمایی قرار داده شده‌اند؛ طبق این راهنمایی‌ها عمل کنید.

  • توجه: لطفاً مواردی که در هر کامپوننت ایمپورت و استفاده شده‌اند را حذف نکنید و همچنین ترتیب یا جای کامپوننت‌ها را تغییر ندهید.

ارسال پاسخ برای این سؤال
فایلی انتخاب نشده است.