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

پروژهی اولیه
برای دانلود پروژهی اولیه روی این لینک کلیک کنید.
ساختار فایلها
samane/
├─ public/
│ └─ vite.svg
├─ src/
│ ├─ api/
│ │ ├─ attendance.api.ts
│ │ ├─ client.ts
│ │ ├─ products.api.ts
│ │ └─ users.api.ts
│ ├─ app/
│ │ ├─ App.tsx
│ │ └─ routes.tsx
│ ├─ components/
│ │ ├─ Layout/
│ │ │ ├─ MainLayout.tsx
│ │ │ └─ Sidebar.tsx
│ │ ├─ Modal/
│ │ │ └─ GenericModal.tsx
│ │ └─ Table/
│ │ └─ GenericTable.tsx
│ ├─ features/
│ │ ├─ dashboard/
│ │ │ ├─ DashboardPage.tsx
│ │ │ ├─ EntryModal.tsx
│ │ │ ├─ ExitModal.tsx
│ │ │ └─ useAttendance.ts
│ │ ├─ products/
│ │ │ ├─ ProductFormModal.tsx
│ │ │ ├─ ProductPage.tsx
│ │ │ └─ useProducts.ts
│ │ └─ users/
│ │ ├─ starter/
│ │ │ ├─ UserFormModal_starter.tsx
│ │ │ └─ useUsers_starter.ts
│ │ ├─ UserFormModal.tsx
│ │ ├─ UsersPage.tsx
│ │ └─ useUsers.ts
│ ├─ types/
│ │ └─ index.ts
│ ├─ index.css
│ └─ main.tsx
├─ eslint.config.js
├─ index.html
├─ package.json
├─ README.md
├─ tsconfig.json
├─ tsconfig.node.json
└─ vite.config.ts
پیشنمایشهای پیادهسازی
در گیفهای زیر، میتوانید تمام عملکرد برنامه را مشاهده کنید:



جزئیات پیادهسازی
فایل index.ts
در این فایل شما باید تایپهای مورد نیاز پروژه را تعریف کنید، چرا که در ادامه در تمامی کامپوننتها، فرمها و APIها از این تایپها استفاده خواهد شد.
کامنتهای موجود در فایل به طور دقیق مشخص کردهاند که هر تایپ چه پراپرتیهایی نیاز دارد و نوع دادهی آنها چیست. بنابراین با دقت و صرف زمان مناسب، تایپها را به درستی و کامل تعریف کنید تا در مراحل بعدی پروژه با خطاهای تایپاسکریپتی مواجه نشوید.
نکات مهم هنگام پیادهسازی:
-
از نامهای پیشنهادی در کامنتها استفاده کنید تا هماهنگی با سایر بخشها حفظ شود.
-
برای مقادیر محدود، مثل جنسیت، حتماً نوع محدود (union type) تعریف کنید تا مقدار دیگری پذیرفته نشود.
-
پراپرتیهای اختیاری را با
?مشخص کنید. -
تایپهای مرتبط با پاسخ API یا payload ها باید دقیقاً مشابه کامنتها تعریف شوند تا سایر بخشها بتوانند بدون خطا با آنها کار کنند.
توجه: پس از تکمیل تایپها حتما تمام کامنتها را پاک کنید!
کامپوننت main.tsx
-
یک
QueryClientبسازید و آن را باQueryClientProviderدر بالاترین سطح پروژه قرار دهید. -
بعد از اینکار، میتوانید در کل پروژه از
useQuery,useMutationو سایر hooks های React Query استفاده کنید. -
این ساختار پایه برای مدیریت state های async و دادههای API است و نیازی به تغییر
Appیا فایلهای دیگر نیست.
پیادهسازی Routes.ts و App.tsx
فایل Routes.ts
وظیفه:
-
پروژه را به router وصل میکند تا مسیریابی در اپلیکیشن کار کند.
-
بالاترین سطح اپلیکیشن را مدیریت میکند و
RouterProviderازreact-router-domرا رندر میکند.
نکات مهم:
routerکه از فایلroutes.tsxصادر شده، باید بهRouterProviderداده شود.
کامپوننت App.tsx
وظیفه:
-
تعریف مسیرهای اپلیکیشن و تعیین component هایی که در هر مسیر نمایش داده میشوند.
-
استفاده از
createBrowserRouterبرای مدیریت مسیرهای SPA.
نکات مهم:
-
مسیر اصلی
/باMainLayoutنمایش داده میشود. -
MainLayoutشامل نوار کناری، header و فضای نمایش صفحات است. -
مسیرهای داخلی:
-
/→DashboardPage -
/users→UsersPage -
/products→ProductPage
-
-
مسیرها باید با
childrenزیرMainLayoutتعریف شوند تا layout ثابت بماند.
دایرکتوری api
فایل client.ts
پیادهسازی axiosClient
هدف
ساخت یک کلاینت استاندارد Axios برای مدیریت یکپارچهی درخواستها و پاسخهای API. این کلاینت باید در تمام بخشهای پروژه برای ارتباط با سرور استفاده شود و مسئول مدیریت خطاها نیز باشد.
مقدمه
axios یک کتابخانهی HTTP Client برای مرورگر و Node.js است که:
-
از
Promiseپشتیبانی میکند. -
امکان ارسال درخواستهای
GET,POST,PUT,DELETEو ... را فراهم میکند. -
با TypeScript سازگار است و قابلیت تایپ کردن
requestوresponseدارد.
گامهای پیادهسازی
- ایجاد کلاینت Axios
ابتدا یک کلاینت Axios با تنظیمات پایه بسازید:
baseURL
تمام مسیرهای API باید با مقدار /api آغاز شوند. مثلاً اگر axiosClient.get("/users") فراخوانی شود، مسیر نهایی /api/users خواهد بود.
headers
هدر پیشفرض تمام درخواستها باید شامل "Content-Type": "application/json" باشد.
-
افزودن Interceptor برای پاسخها
-
در
axiosمیتوانید از interceptors استفاده کنید تا قبل یا بعد از ارسال درخواست، منطق خاصی اجرا کنید. -
در این پروژه، فقط نیاز است interceptor پاسخ (response interceptor) را پیادهسازی کنید.
-
این interceptor باید:
-
در حالت موفق، پاسخ را بدون تغییر بازگرداند.
-
در حالت خطا، جزئیات خطا (مثلاً
error.response) را در کنسول چاپ کند تا در زمان توسعه قابل بررسی باشد. -
سپس، خطا را بازگرداند تا در لایههای بالاتر (مثلاً در React Query) بتوان آن را مدیریت کرد.
-
-
-
برگرداندن مقدار نهایی
- در پایان،
axiosClientرا export کنید تا در سایر بخشهای پروژه قابل استفاده باشد.
- در پایان،
نکته
در این فایل نیازی به مدیریت توکن یا احراز هویت نیست. هدف تنها ایجاد یک ساختار اولیهی استاندارد برای ارسال درخواستها و هندل کردن خطاهاست.
فایل users.api.ts
این فایل شامل سه تابع اصلی برای مدیریت دادههای کاربر بهصورت mock (شبیهسازیشده) است. هدف از این فایل، فراهم کردن رابطی ساده برای دریافت، اضافهکردن و ریستکردن لیست کاربران است.
تابع getUsers
توضیح:
این تابع برای دریافت لیست کاربران استفاده میشود.
در صورت ارسال پارامتر q، کاربران بر اساس نام یا نام خانوادگی فیلتر میشوند.
ورودی:
q(اختیاری): رشتهای برای جستوجو در نام یا نام خانوادگی.
خروجی:
Promise<User[]>→ لیستی از کاربران (بهصورت async).
تابع createUser
توضیح: برای افزودن کاربر جدید به لیست استفاده میشود. تابع بهصورت خودکار شناسه (id) تولید کرده و کاربر جدید را به آرایه اضافه میکند.
ورودی:
newUser: شیئی شامل مشخصات کاربر بهجز فیلدid(زیراidبهصورت خودکار ساخته میشود)
خروجی:
Promise<User>→ کاربر ایجاد شده همراه باidجدید.
تابع resetUsers
توضیح: لیست کاربران را به حالت اولیه (۱۰ کاربر اول) برمیگرداند. در واقع تمام کاربران جدید اضافهشده حذف میشوند.
نکته: تمام توابع بهصورت شبیهسازیشده دارای تأخیر (delay) هستند تا رفتار واقعی API را تداعی کنند.
فایل products.api.ts
این فایل شامل توابعی برای مدیریت دادههای محصولات بهصورت mock است.
تابع getProducts
توضیح:
این تابع لیست کامل محصولات را برمیگرداند.
همه دادهها از یک آرایهی شبیهسازیشده (mockProducts) خوانده میشوند.
ورودی: ندارد.
خروجی:
Promise<Product[]>→ لیستی از تمام محصولات.
تابع createProduct
توضیح:
برای افزودن محصول جدید استفاده میشود.
شناسه (id) بهصورت خودکار تولید شده و محصول جدید به لیست افزوده میشود.
ورودی:
newProduct: شیئی شامل نام و قیمت محصول (بدونid).
خروجی:
Promise<Product>→ محصول ایجادشده همراه باidتصادفی.
تابع resetProducts
توضیح: لیست محصولات را به حالت اولیه بازمیگرداند. تمام محصولاتی که بعداً اضافه شدهاند حذف میشوند و دادهها به آرایهی اولیه برمیگردند.
نکته: همهی توابع شامل تأخیر مصنوعی هستند تا رفتار واقعی درخواستهای شبکه را شبیهسازی کنند
فایل attendance.api.ts
این فایل مسئول مدیریت ورود و خروج کاربران و محاسبهی هزینهی حضور آنهاست.
همچنین ارتباط بین کاربران (user)، محصولات (product) و سوابق حضور (attendance) را شبیهسازی میکند.
ثابتها و تنظیمات
const BASE_FEE_PER_HOUR = 10000;
هر کاربر بهازای هر ساعت حضور باید مبلغ ۱۰٬۰۰۰ تومان پرداخت کند.
هزینه بر اساس دقیقه محاسبه میشود (ratePerMinute = BASE_FEE_PER_HOUR / 60).
تابع getPresentUsers
توضیح: لیست تمام کاربرانی را برمیگرداند که وارد شدهاند اما هنوز خارج نشدهاند.
ورودی: ندارد.
خروجی:
Promise<AttendanceRecord[]>→ لیست کاربران حاضر.
تابع enterUser
توضیح: ثبت ورود یک کاربر جدید به سیستم. اگر کاربر هنوز خارج نشده باشد، خطا میدهد.
ورودی:
-
payload.userId→ شناسهی کاربر. -
payload.enteredAt(اختیاری) → زمان ورود (در صورت عدم ارسال، زمان فعلی ثبت میشود).
خروجی:
Promise<AttendanceRecord>→ رکورد ثبتشدهی ورود.
تابع exitUser
توضیح: ثبت خروج کاربر از سیستم و محاسبهی هزینهی نهایی. در زمان خروج، مدت حضور و محصولات خریداریشده لحاظ میشوند.
مراحل عملکرد تابع:
-
بررسی میکند که رکورد حضور وجود داشته باشد و خروج قبلاً ثبت نشده باشد.
-
مدت حضور (برحسب دقیقه) محاسبه میشود.
-
هزینهی پایه بر اساس زمان محاسبه میشود.
-
قیمت محصولات انتخابشده (از
getProducts()) جمع زده میشود. -
مجموع کل (
total) =baseFee + productsTotal. -
مقدار خروج (
exitedAt) و محصولات (productIds) در رکورد ذخیره میشوند.
تابع resetAttendance
توضیح: تمام سوابق حضور را پاک کرده و سیستم را به حالت اولیه بازمیگرداند.
نکته: مثل سایر فایلهای API، همهی توابع شامل تأخیر شبیهسازیشده هستند
دایرکتوری components
فایل Layout
کامپوننت MainLayout
در این فایل، ساختار اصلی صفحه تعریف شده است که شامل دو بخش اصلی میباشد:
-
Sidebarبرای نمایش منوی کناری -
Contentبرای نمایش محتوای متغیر (داینامیک)
در قسمت مشخصشده با کامنت Content، باید محتوای هر صفحه (مثلاً داشبورد، کاربران، تنظیمات و ...) بهصورت داینامیک نمایش داده شود.
هدف این است که هنگام تغییر مسیر (Route)، فقط بخش محتوایی بهروز شود و ساختار کلی صفحه (مثل Sidebar) ثابت بماند.
کامپوننت Sidebar.tsx
در این فایل باید منوی کناری (Sidebar) را از نظر منطق عملکرد پیادهسازی کنید. تمام ساختار و استایل مورد نیاز از قبل آماده است، اما هیچ رفتار یا دادهی منطقی وجود ندارد.
هدف:
نمایش یک منوی ناوبری در سمت راست صفحه که کاربر بتواند با کلیک بر روی هر آیتم، بین صفحات مختلف جابهجا شود و آیتم فعال مشخص باشد.
گامهای پیادهسازی
1. ایمپورتهای مورد نیاز
از کتابخانههای زیر استفاده کنید:
-
useNavigateوuseLocationازreact-router-domبرای مدیریت مسیرها -
آیکونهای مورد نیاز از
@mui/icons-material(مثلاًDashboardIcon,PeopleIcon,InventoryIcon)
2. ایجاد آرایه آیتمها
در داخل کامپوننت، آرایهای به نام items بسازید که شامل آبجکتهایی با ساختار زیر باشد:
{
text: string; // عنوان آیتم (مثلاً "کاربران")
icon: JSX.Element; // آیکون مربوطه
path: string; // مسیر صفحه (مثلاً "/users")
}
به عنوان مثال، سه آیتم با مسیرهای زیر نیاز است:
-
/برای صفحهی داشبورد -
/usersبرای صفحهی کاربران -
/productsبرای صفحهی هزینههای مازاد
3. رندر کردن آیتمها
درون تگ <List>، از متد map برای نمایش تمام آیتمها استفاده کنید.
در هر ListItemButton:
-
مسیر فعلی را با
location.pathnameمقایسه کنید تا آیتم فعال (selected) مشخص شود. -
هنگام کلیک، با استفاده از
navigate(path)کاربر را به صفحهی جدید هدایت کنید.
4. نکته مهم
شما نباید ساختار، استایل یا layout را تغییر دهید. فقط منطق مربوط به:
-
ایجاد آرایه آیتمها
-
تشخیص مسیر فعال
-
جابهجایی بین صفحات را پیادهسازی کنید.
فایل Modal
فایل Table
در این بخش باید یک کامپوننت عمومی جدول (Generic Table) پیادهسازی کنید که بتواند هر نوع دادهای را بر اساس ستونهای دادهشده نمایش دهد.
هدف:
ساخت کامپوننتی که با دریافت آرایهای از ستونها و دادهها، جدول را بهصورت داینامیک نمایش دهد. این کامپوننت باید بتواند برای هر نوع دادهای (Users، Products، Records و...) قابل استفاده باشد.
مشخصات Props
1. columns: Column[]
هر ستون شامل دو مقدار است:
{
key: string; // کلید مربوط به فیلد داده (مثلاً "name" یا "price")
label: string; // عنوان نمایشی در هدر جدول
}
2. data: T[]
آرایهای از آبجکتها که هرکدام یک ردیف جدول هستند.
هر شیء میتواند شامل هر کلید دلخواهی باشد (بنابراین باید از Generic Type استفاده کنید).
وظیفه شما
درون جدول، باید موارد زیر را پیادهسازی کنید:
بخش سرستون (TableHead)
-
با استفاده از
columns.mapتمام ستونها را رندر کنید. -
مقدار
col.labelباید در هر سلول نمایش داده شود. -
جهت متن در سلولها باید راستچین باشد (
align="right").
بخش بدنه (TableBody)
-
با استفاده از
data.map، تمام سطرهای جدول را ایجاد کنید. -
برای هر ردیف (
row) ازcolumns.mapاستفاده کنید تا ترتیب ستونها مطابقcolumnsباشد. -
مقدار هر سلول باید از
row[col.key]خوانده شود.
نکات مهم
-
جدول باید کاملاً داینامیک باشد — هیچ ستونی نباید بهصورت دستی نوشته شود.
-
برای کلیدهای
TableRowوTableCellاز مقادیر منحصربهفرد (مثل index یا key) استفاده کنید. -
نوع دادهی ورودی را بهصورت Generic تعریف کنید تا برای هر مدل داده قابل استفاده باشد:
const GenericTable = <T extends Record<string, any>>({...}: GenericTableProps<T>)
نتیجه مورد انتظار
هنگام استفاده از این کامپوننت به شکل زیر:
<GenericTable
columns={[
{ key: "name", label: "نام" },
{ key: "price", label: "قیمت" },
]}
data={[
{ name: "آب معدنی", price: 2000 },
{ name: "چیپس", price: 3000 },
]}
/>
دایرکتوری features
در این بخش سه ویژگی (Feature) با نامهای زیر وجود دارد:
-
users
-
products
-
dashboard
در ادامه، هر سه دایرکتوری را بهصورت جداگانه بررسی میکنیم و تمام مواردی را که باید پیادهسازی کنید، توضیح خواهیم داد.
هوکها
در هر فایل باید یکسری هوک نوشته شوند که آنها را شرح میدهیم
فایل useUsers.ts
در این فایل، دو هوک مرتبط با مدیریت کاربران باید پیادهسازی شوند:
-
useUsers -
useCreateUser
هر دو هوک از کتابخانهی React Query (TanStack Query) برای مدیریت دادههای سمت سرور و بهروزرسانی خودکار UI استفاده میکنند.
پیادهسازی هوک useUsers
در این بخش، باید از هوک useQuery مربوط به React Query استفاده کنید.
مراحل کلی به این صورت است:
-
ابتدا یک کلید ثابت برای کش دادهها با نام مثلاً
"users"تعریف کنید. -
سپس از
useQueryبرای فراخوانی تابعی مانندgetUsersاستفاده کنید که دادهها را از API برمیگرداند. -
اگر در ورودی هوک یک مقدار جستجو (
q) دریافت میکنید، آن را هم درqueryKeyلحاظ کنید تا کش برای هر مقدار جستجو منحصربهفرد باشد. -
در نهایت، هوک باید دادهها، وضعیت بارگذاری و خطا را برگرداند.
نکته:
تابع getUsers را از مسیر ../../api/users.api ایمپورت کنید و نوع دادهی خروجی را از User تعریفشده در ../../types بگیرید.
پیادهسازی هوک useCreateUser
در این بخش باید از هوک useMutation استفاده کنید تا امکان افزودن کاربر جدید فراهم شود.
مراحل پیشنهادی:
-
از
useQueryClientبرای دسترسی به کش سراسری React Query استفاده کنید. -
از
useMutationبرای فراخوانی تابعی مانندcreateUserبهره بگیرید. این تابع باید اطلاعات کاربر جدید را به API ارسال کند. -
در بخش
onSuccess، با استفاده ازinvalidateQueriesکش مربوط به کلید"users"را پاک کنید تا پس از افزودن کاربر جدید، لیست کاربران بهروز شود.
نکته:
برای ورودی تابع ایجاد کاربر، از نوع Omit<User, "id"> استفاده کنید تا شناسه (id) که توسط سرور ایجاد میشود، ارسال نگردد.
فایل useProducts.ts
در این فایل باید دو هوک برای مدیریت دادههای مربوط به محصولات پیادهسازی شوند:
-
useProductsبرای دریافت فهرست محصولات از سرور -
useCreateProductبرای ایجاد محصول جدید و بهروزرسانی دادهها پس از آن
پیادهسازی هوک useProducts
در این قسمت، باید با استفاده از useQuery دادههای مربوط به محصولات را از API دریافت کنید.
مراحل کلی:
-
ابتدا یک کلید کش برای دادههای محصولات با نام
"products"تعریف کنید تا React Query دادهها را بر اساس آن ذخیره و مدیریت کند. -
سپس از
useQueryبرای فراخوانی تابعgetProductsاستفاده کنید. این تابع دادهها را از سرور برمیگرداند. -
هوک باید دادهها، وضعیت بارگذاری (
isLoading)، خطا (error) و سایر وضعیتهای مربوط به Query را برگرداند تا در کامپوننتهای دیگر قابل استفاده باشد.
نکته:
تابع getProducts باید از مسیر ../../api/products.api ایمپورت شود و نوع دادهی خروجی آن Product[] باشد که از مسیر ../../types در دسترس است.
پیادهسازی هوک useCreateProduct
در این بخش باید هوکی برای ایجاد محصول جدید پیادهسازی کنید. برای این کار از useMutation استفاده میشود.
مراحل پیشنهادی:
-
از
useQueryClientبرای دسترسی به کش سراسری React Query استفاده کنید. -
از
useMutationبرای ارسال دادههای محصول جدید با تابعcreateProductبهره بگیرید. -
در قسمت
onSuccess، با استفاده ازinvalidateQueriesکش مربوط به"products"را پاک کنید تا پس از ایجاد محصول، لیست محصولات بهروزرسانی شود.
نکته:
در این بخش باید نوع ورودی تابع ایجاد محصول را بهصورت Omit<Product, "id"> تعریف کنید، چون شناسه محصول توسط سرور ساخته میشود.
فایل useAttendance.ts
در این فایل چهار هوک مهم برای مدیریت حضور و غیاب کاربران وجود دارد:
-
usePresentUsers→ دریافت لیست کاربران حاضر -
useEnterUser→ ثبت ورود کاربر -
useExitUser→ ثبت خروج کاربر -
useResetAttendance→ ریست کردن دادههای حضور و غیاب
پیادهسازی هوک usePresentUsers
-
این هوک برای دریافت تمام رکوردهای حاضر کاربران استفاده میشود.
-
باید از
useQueryاستفاده کنید و یک کلید کش سراسری تعریف کنید، مثلاً"attendance". -
تابع
queryFnبایدgetPresentUsersباشد تا دادهها را از سرور بگیرد. -
هوک باید وضعیت بارگذاری (
isLoading)، دادهها (data) و خطا (error) را برگرداند تا در کامپوننتها قابل استفاده باشد.
پیادهسازی هوک useEnterUser
-
این هوک برای ثبت ورود یک کاربر جدید است.
-
از
useMutationاستفاده کنید و تابعmutationFnآنenterUserباشد. -
بعد از موفقیت، باید کش مربوط به
"attendance"را باinvalidateQueriesتازه کنید تا لیست کاربران حاضر بروز شود. -
نوع ورودی دادهها (
payload) باید مطابقEnterPayloadباشد.
پیادهسازی هوک useExitUser
-
مشابه
useEnterUserاست ولی برای ثبت خروج کاربران. -
mutationFnاینجا تابعexitUserاست و بعد از موفقیت کش"attendance"باید تازه شود. -
نوع دادهی ورودی
ExitPayloadو خروجیExitResponseاست.
پیادهسازی هوک useResetAttendance
-
این هوک برای ریست کردن کل دادههای حضور و غیاب استفاده میشود.
-
از
useMutationاستفاده کنید و تابعmutationFnآنresetAttendanceباشد. -
پس از موفقیت، کش
"attendance"را باinvalidateQueriesتازه کنید. -
این هوک هیچ دادهای به عنوان ورودی نمیگیرد و خروجی آن هم
voidاست.
ارسال پاسخ برای این سؤال