شما بهتازگی به تیم «اتاق فرمان» یک شرکت بزرگ اضافه شدهاید جایی که داشبورد مدیریتی، قلب تصمیمگیری روزانهی شرکت است.
اما یک مشکل جدی وجود دارد:
هر روز صدها اعلان از سیستمهای مختلف دریافت می شود از خطاهای حیاتی گرفته تا پیامهای اجتماعی و هشدارهای مالی.
مدیرها میگویند:
-
«اعلانهای مهم گم میشوند!»
-
«نمیفهمیم کدامها را خواندهایم!»
-
«وقتی دنبال یک چیز میگردیم، سیستم کند و گیج میشود!»
وظیفهی شما این است که اتاق فرمان را طوری پیادهسازی کنید که مثل یک اتاق کنترل حرفهای عمل کند:
-
اعلانها را نمایش دهد
-
فیلتر و مرتبسازی انجام دهد
-
وضعیت خوانده/نخوانده را مدیریت کند
-
و همهچیز را حتی بعد از رفرش صفحه به خاطر بسپارد
پروژهی اولیه آماده است و فقط منطق جاوااسکریپت داخل فایل main.js ناقص است.
ظاهر کلی برنامه به شکل زیر است:

پروژه اولیه را از این لینک دانلود کنید. ساختار فایلهای این پروژه به صورت زیر است:
initial_project/
├─ index.html
├─ styles.css
├─ main.js
├─ js/
└─ data.js
├─ fonts/
در فایل js/data.js تابع fetchNotificationsData() وجود دارد که با کمی تاخیر (۱۵۰ms) یک لیست اعلان برمیگرداند:
// Promise<Notification[]>
fetchNotificationsData()
هر اعلان چنین ساختاری دارد:
{
id: "n1",
title: "...",
body: "...",
category: "system" | "social" | "finance",
createdAt: 1700000000000
}
جزئیات پیادهسازی
دسترسی به المانها
المانها را با id های زیر پیدا کنید:
searchInputcategoryFiltersortSelectmarkAllReadunreadCountnotificationsList
توجه مهم: برخی از این المانها (و همچنین spinner) دارای data-testid هستند که در تستها استفاده میشونداز تغییر یا حذف data-testid ها خودداری کنید.
راهاندازی اولیه
در شروع برنامه:
- کنترلها (input/select/button) باید غیرفعال باشند.
fetchNotificationsData()را صدا بزنید و بعد از دریافت داده:- دادهها را در state نگه دارید
- کنترلها را فعال کنید
- لیست را رندر کنید (بر اساس فیلترهای فعلی)
رندر لیست
تابع renderNotifications(list) را پیادهسازی کنید:
اگر لیست خالی بود:
- یک
<li>با:-
کلاس
empty -
data-testid="empty" -
متن:
اعلانی یافت نشد
-
اگر آیتم وجود داشت:
برای هر اعلان:
-
یک
<li>با:-
کلاس
notif -
data-testid="notif-item" -
ویژگی
data-id="<id>"
-
-
اگر اعلان خواندهشده است:
- کلاس
is-readبه<li>اضافه شود
- کلاس
-
داخل هر آیتم دو دکمه وجود دارد:
-
دکمه پین (اختیاری)
-
data-testid="pin-btn" -
ویژگی
aria-pressed="true | false"
-
-
دکمه تغییر وضعیت خواندهشدن
data-testid="toggle-read-btn"
-
متن دکمهها آزاد است و در تستها بررسی نمیشود؛ فقط رفتار آنها مهم است.
شمارنده خواندهنشدهها
عدد داخل المان #unreadCount باید همیشه برابر باشد با:
تعداد اعلانهایی که در کل دادهها خواندهنشده هستند(نه فقط اعلانهای فیلترشده)
فیلترها
دو نوع فیلتر داریم:
- جستجو (Search)
- متن واردشده باید (case-insensitive) در
titleیاbodyشامل باشد (includes)
- دستهبندی (Category)
- اگر
categoryFilterمقدار داشته باشد، فقط همان دسته نمایش داده شود
مرتبسازی
دو حالت:
newest: جدیدترینها بالا (createdAt نزولی)oldest: قدیمیترینها بالا (createdAt صعودی)
قانون مهم: آیتمهای پینشده همیشه بالاتر از بقیه نمایش داده شوند (قبل از مرتبسازی زمانی).
Debounce + Spinner
تایپ در searchInput:
-
اگر هر دو فیلتر search و category خالی باشند:
-
فوراً رندر انجام شود
-
بدون spinner
-
هر debounce معلق لغو شود
-
-
در غیر این صورت:
-
فوراً spinner نمایش داده شود
-
فیلتر بعد از ۸۰۰ms از آخرین ورودی اعمال شود
-
تغییر categoryFilter:
-
اگر هر دو فیلتر خالی باشند:
- فوراً و بدون spinner رندر شود
-
در غیر این صورت:
-
فوراً spinner
-
اعمال فیلتر بعد از ۳۰۰ms
-
اگر کاربر قبل از پایان debounce دوباره ورودی بدهد، تایمر قبلی باید ریست شود.
Spinner
تابع showSpinner() باید:
-
یک
<li>با کلاسspinner-row -
و
data-testid="spinner" -
داخل لیست اعلانها نمایش دهد
نکات
- نصب/استفاده از هر کتابخانه یا فریمورک در کد اصلی ممنوع است (فقط JS خام).
- فقط مجاز به تغییر فایل
main.jsهستید. - از تغییر
data-testidها خودداری کنید تا تستها قابل اجرا بمانند.
آنچه باید آپلود کنید
فقط فایل main.js نهایی را ارسال کنید.
(فایل به صورت zip ارسال شود و ساختار باید به شکل زیر باشد)
answer.zip
└─ main.js