Skip to content

About

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

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

SchedulePlanner — برنامه‌ریز هفتگی انتخاب واحد

اپلیکیشن تک‌صفحه‌ای برای چیدن برنامه‌ی هفتگیِ بدون تداخل از میان درس‌های ارائه‌شده‌ی دانشگاه. کاملاً سمت کلاینت (بدون بک‌اند)، فارسی و راست‌به‌چپ.

داده‌ی پیش‌فرض از PDF «دروس ارائه شده در نیمسال اول ۱۴۰۵–۱۴۰۶» دانشگاه صنعتی نوشیروانی بابل (گزارش ۱۱۰) استخراج شده است: ۱۶۹ درس، ۲۷۱ گروه — همه‌ی گروه‌های آموزشیِ دانشکده‌ی مهندسی برق و کامپیوتر (برق و کامپیوتر، الکترونیک، قدرت، کنترل، مخابرات، مهندسی پزشکی، کامپیوتر)، شاملِ درس‌های تحصیلات تکمیلی.

اجرا

npm install
npm run dev      # سرور توسعه
npm run build    # tsc + بیلد نهایی
npm run check      # تست‌های منطق (پارسر، حلال، تداخل‌سنجی، ZIP، توکن‌های CSS)
npm run check:xlsx # ساخت یک فایل xlsx نمونه برای بازبینی
npm run lint

پشته‌ی فنی

React + Vite + TypeScript، CSS خالص با توکن‌های سفارشی (بدون کتابخانه‌ی UI)، فونت Vazirmatn به‌صورت محلی، ذخیره‌سازی در localStorage.

ویژگی‌ها

۱) ایمپورت از جدول خام دانشگاه — src/lib/parseTable.ts

در صفحه‌ی «افزودن درس‌ها» می‌توانید HTML جدول سیستم انتخاب‌واحد را پیست کنید، فایل HTML صفحه را آپلود کنید، یا حتی متن ساده‌ی کپی‌شده را بدهید. پارسر:

  • شبکه‌ی جدول را با احتساب rowspan/colspan باز می‌کند (نام درس معمولاً روی چند ردیف کشیده شده است).
  • ستون‌ها را از سرصفحه تشخیص می‌دهد («شماره و گروه درس»، «نام درس»، «واحد»، «ظرفیت»، «نام استاد»، «زمان و مکان ارائه/ امتحان») و اگر سرصفحه پیدا نشد، با الگو حدس می‌زند و هشدار می‌دهد.
  • چند جلسه را از یک سلول جدا می‌کند: هر بازه‌ی «شروع-پایان» را پیدا می‌کند و نزدیک‌ترین نام روزِ پیش از آن را به آن نسبت می‌دهد. پس هم درس(ت): شنبه(۱۰:۰۵-۱۱:۳۰ کار می‌کند، هم شنبه ۱۰:۰۵-۱۱:۳۰، و هم چند خط پشت‌سرهم در یک سلول.
  • نام روزهای فارسی را به ایندکس عددی تبدیل می‌کند (۰=شنبه … ۶=جمعه). اسکن «بلندترین‌الگو-اول» است تا «یک‌شنبه» به اشتباه «شنبه» خوانده نشود؛ نیم‌فاصله، فاصله و ی/ک عربی هم یکسان‌سازی می‌شوند.
  • ارقام فارسی/عربی را برای محاسبه به لاتین و برای نمایش به فارسی برمی‌گرداند (src/lib/digits.ts).
  • گروه‌های هم‌کدِ قبل از _ را زیر یک Course دسته‌بندی می‌کند.
  • امتحان را از امتحان(۱۴۰۵.۱۰.۲۹) ساعت: ۰۸:۳۰-۱۰:۳۰ بیرون می‌کشد.
  • سپس جدول پیش‌نمایشِ قابل‌ویرایش نشان می‌دهد؛ ردیف‌های مشکوک زرد علامت می‌خورند و پیش از ذخیره‌ی نهایی می‌توانید همه‌چیز را دستی اصلاح کنید. جلسه‌ها به قالب متنیِ ساده‌ی «روز ساعت‌شروع-ساعت‌پایان | مکان» (هر خط یک جلسه) ویرایش می‌شوند و همان قالب دوباره خوانده می‌شود.

نکته‌ی جهت متن: جدول دانشگاه بازه‌ی ساعت را بسته‌به جهت‌دهیِ متن به هر دو ترتیب چاپ می‌کند (۱۵:۳۰-۱۷:۰۰ یا ۱۷:۰۰-۱۵:۳۰). پارسر جفت را بی‌ترتیب می‌گیرد و کوچک‌تر را شروع و بزرگ‌تر را پایان می‌گذارد، پس هر دو حالت درست خوانده می‌شود.

کارنامه‌ی تطبیق: «پاس‌شده» و «قابل اخذ» — src/lib/parseAudit.ts

در صفحه‌ی «کارنامه» می‌توانید کارنامه‌ی تطبیق برنامه‌ی آموزشی گلستان را پیست کنید. اپ از آن می‌خواند چه درس‌هایی را گذرانده‌اید و هر گروهِ نیاز چقدر کمبود دارد، بعد درس‌های ارائه‌شده‌ی این ترم را دسته‌بندی می‌کند.

پارسر ساختار سه‌لایه‌ی کارنامه را می‌خواند: سرصفحه‌ی گروه («دروس اصلي-مشترك | ۲۱ درس از ۲۴ درس برنامه | ۵۵ واحد از ۶۰ واحد برنامه»)، ردیف‌های درس (کد، نام، واحد، حالت «مصوب») و جدول تودرتوی ترم/نمره/نوع درس.

قاعده‌ی «اشباع گروه» — نکته‌ی اصلیِ منطق: اگر گروهی حد پایینش پر شده باشد، بقیه‌ی گزینه‌هایش هم «پاس‌شده/بی‌نیاز» حساب می‌شوند، حتی اگر گروه ۶ گزینه داشته باشد و شما فقط ۲ تا را گرفته باشید. چون گروه‌ها بازه دارند («۱ درس از ۰ تا ۶ درس»)، دو مفهوم از هم جدا شده‌اند: پر شدنِ حد پایین، و باقی‌ماندنِ ظرفیت.

وضعیت یعنی
passed خودِ این درس را گذرانده‌اید
needed گروهش کمبود دارد → لازم
optional حد پایین گروه پر است ولی ظرفیت دارد → اختیاریِ مجاز
covered گروه به سقف رسیده → گرفتنش چیزی اضافه نمی‌کند
unknown در کارنامه‌ی شما نیست (مثلاً درسِ رشته‌ی دیگر)

passed و covered زیر دسته‌ی «پاس‌شده»، و بقیه زیر «قابل اخذ» می‌نشینند. اگر یک درس در چند گروه فهرست شده باشد، سخت‌گیرانه‌ترین حالت برنده است.

خروجیِ کاربردی:

  • «چه چیزی لازم دارید»: فقط گروه‌های ناتمام، با کمبود واحد و نوار پیشرفت، و زیر هر گروه درس‌های ارائه‌شده‌ی همین ترم که به آن گروه حساب می‌شوند — با دکمه‌ی + برای افزودن مستقیم به انتخاب‌های ترم.
  • دسته‌بندی درس‌های ارائه‌شده به لازم / اختیاری / بیرون از برنامه / پاس‌شده. دو دسته‌ی آخر پیش‌فرض بسته‌اند تا فهرست شلوغ نشود.
  • در صفحه‌ی «انتخاب درس‌ها» هر درس تراشه‌ی وضعیت می‌گیرد و فیلتر «فقط لازم / فقط قابل اخذ / فقط پاس‌شده» اضافه می‌شود.

حریم خصوصی: از بخش مشخصات فقط رشته، مقطع، دانشکده، معدل و واحدها خوانده می‌شود. نام، شماره دانشجو، شماره ملی، شناسنامه، تاریخ تولد و عکسِ داخل کارنامه نه خوانده و نه ذخیره می‌شوند.

برای تست، python3 tools/check/make_audit_fixture.py یک کارنامه‌ی نمونه با ساختارِ واقعیِ گلستان می‌سازد (tools/check/fixtures/audit.html) که داده‌ی هویتی‌اش ساختگی است.

۲) انتخاب درس‌های مورد نیاز

فهرست همه‌ی درس‌ها با جست‌وجو روی نام درس، نام استاد و کد درس، فیلتر «کلاس در روزِ …» و فیلتر «فقط انتخاب‌شده‌ها». مجموع واحد انتخاب‌شده همیشه نمایش داده می‌شود.

۳) تولید خودکار ترکیب‌های بدون تداخل — src/lib/solver.ts

با عقب‌گرد (backtracking)، نه ضرب دکارتی: درس‌ها به ترتیب کم‌ترین تعداد گروه چیده می‌شوند (fail-fast) و هر گروه فقط با گروه‌های همان شاخه سنجیده می‌شود، پس شاخه‌های ناسازگار زود بریده می‌شوند.

  • سقف تولید ۵۰۰ ترکیب است؛ در صفحه ۱۲ ترکیبِ اول با دکمه‌ی «نمایش بیشتر» نشان داده می‌شود.
  • هر ترکیب یک بندانگشتی از جدول هفتگی است، با ناوبری قبلی/بعدی و شمارشگر «ترکیب ۱ از ۱۲»، و cross-fade ملایم موقع سوییچ.
  • کلیک روی هر ترکیب آن را در جدول هفتگی کامل (و با دکمه‌ی «نمایش تمام‌صفحه» به‌صورت تمام‌صفحه) باز می‌کند، همراه با کارت جزئیات هر درس و گروه‌های جایگزینِ علامت‌خورده بر اساس تداخل داشتن/نداشتن.
  • اگر هیچ ترکیبی نبود، مقصر دقیقاً نام برده می‌شود (diagnose()): درس‌های بدون گروه، جفت‌درس‌هایی که همه‌ی گروه‌هایشان دوبه‌دو متصادم‌اند (با ذکر شماره‌ی گروه و روز و ساعتِ جلسه‌ی متصادم)، و کوچک‌ترین مجموعه‌ای که با حذفش برنامه ساخته می‌شود — به‌همراه دکمه‌ی حذف همان درس.

۴) حالت دستی

کنار نام هر درس یک کشوی انتخاب گروه هست. تداخل جلوی انتخاب را نمی‌گیرد؛ فقط بلاک‌های متصادم در جدول هاشور می‌خورند و حاشیه‌ی هشدار می‌گیرند و بالای جدول خلاصه‌ی تداخل‌ها می‌آید. بلاک‌های متداخل کنار هم (در «لِین»های موازی) چیده می‌شوند تا هیچ‌کدام پنهان نشود. تاگل «حالت خودکار / حالت دستی» همیشه در دسترس است.

۵) نمایش جدول هفتگی

محور ساعت + ستون روزها (فقط روزهایی که کلاس دارند)، بلاک رنگی هر درس با نام و ساعت و استاد. کلیک روی هر بلاک به کارت جزئیات همان درس اسکرول می‌کند و آن را برجسته می‌کند.

پایداری داده

درس‌ها، انتخاب‌ها، حالت و گزینه‌ها در localStorage ذخیره می‌شوند. دکمه‌های خروجی/بازگردانی JSON برای پشتیبان‌گیری، و «بازگشت به داده‌ی پیش‌فرض».

فرم ورود و اطلاع‌رسانی — src/lib/identity.ts و src/lib/notify.ts

در اولین بازدید یک پاپ‌آپ (<dialog> بومی با showModal()) نام و نام خانوادگی می‌خواهد. تا این در localStorage نباشد، هیچ صفحه‌ای رندر نمی‌شود — نه غیرفعال، بلکه اصلاً در DOM نیست: تب‌ها هم ساخته نمی‌شوند. Esc و کلیک روی پس‌زمینه هم پاپ‌آپ را نمی‌بندند، چون بدون نام اجازه‌ی فعالیت نیست.

پس از ورود، نام و زمانِ ورود با ایموجی و متنِ فارسی به یک کانال تلگرام می‌رود:

🎓 ورود تازه به برنامه‌ریز هفتگی انتخاب واحد

👤 نام و نام خانوادگی: امیر محمدی
📅 تاریخ: سه‌شنبه ۲۴ شهریور ۱۴۰۵
⏰ ساعت: ۱۷:۲۱
💻 دستگاه: Chrome روی لینوکس
📚 داده‌ی درس‌ها: ۱۶۹ درس · ۲۷۱ گروه
🆕 اولین ورود با این نام

✅ کاربر وارد شد و می‌تواند برنامه‌ی هفتگی‌اش را بچیند.

راه‌اندازی

هیچ کاری لازم نیست. توکنِ بات و شناسه‌ی کانال داخلِ src/lib/notify.ts هستند، پس اپ از همان اولین بیلد پیام می‌فرستد.

برای عوض‌کردنشان لازم نیست کد را دست بزنید — .env.local (یا متغیرهای محیطیِ بیلد در ورسل/نتلیفای/…) اولویت دارد:

cp .env.example .env.local
#   VITE_TG_BOT_TOKEN=...       توکنی که BotFather می‌دهد
#   VITE_TG_CHAT_ID=-100...     شناسه‌ی عددی کانال (بات باید ادمینِ کانال باشد)

اگر پیام نرسید، تقریباً همیشه یعنی بات در کانال ادمین نیست.

⚠️ توکن در کد است و این یک تصمیمِ آگاهانه بود

توکن به‌خواستِ صریحِ صاحبِ پروژه مستقیم در src/lib/notify.ts نوشته شده و ریسکش پذیرفته شده است. پیامدهایش:

  • هر بازدیدکننده می‌تواند توکن را بردارد. این اپ بک‌اند ندارد؛ توکن داخلِ جاوااسکریپتِ بیلد است و با یک نگاه به سورسِ صفحه پیدا می‌شود. با آن می‌شود از طرفِ این بات پیام فرستاد، پیام پاک کرد، یا اطلاعاتِ کانال را خواند.
  • توکن در مخزنِ گیت است. اسکنرهایی که مخازنِ عمومی را برای کلید می‌گردند چنین توکنی را پیدا می‌کنند، پس فرض کنید دیر یا زود باید عوضش کنید.

اگر روزی خواستید بی‌ریسکش کنید: یک اندپوینتِ کوچکِ سمتِ سرور بگذارید که توکن را خودش نگه دارد (Vercel/Netlify/Cloudflare Functions یا هر چیزِ دیگر) و در src/lib/notify.ts فقط آدرسِ sendTelegram را به آن عوض کنید — بدنه‌ی درخواست همان می‌ماند. آن‌وقت توکن هرگز به مرورگر نمی‌رسد.

تصمیم‌های ریز

  • اعتبارسنجی، هم در فرم و هم در خواندنِ حافظه. sanitizeIdentity همان قاعده‌های فرم را روی مقدارِ localStorage هم اجرا می‌کند، پس یک نامِ دست‌کاری‌شده (خالی، تک‌حرفی، یا با رقم) در قفل را باز نمی‌کند.
  • نامِ فارسی درست پذیرفته می‌شود: نیم‌فاصله («علی‌رضا») و ی/ک عربی («يوسف») مجازند و دست‌کاری نمی‌شوند؛ رقم و نشانه‌گذاری نه.
  • فرستادنِ پیام هرگز جلوی ورود را نمی‌گیرد. تلگرام در ایران فیلتر است، پس پیام اول در صفِ localStorage می‌نشیند و در بارِ بعدیِ اپ دوباره تلاش می‌شود.
  • هر پیام پیش از فرستادن از صف برداشته می‌شود، نه بعد از آن. ترتیبِ عکسش یعنی دو اجرای هم‌زمانِ خالی‌کردنِ صف — چیزی که StrictMode در حالتِ توسعه واقعاً تولید می‌کند — یک پیام را دو بار به کانال می‌فرستد. این باگ در عمل رخ داد و آزمون دارد.
  • نامِ کاربر در متنِ HTML پیام escape می‌شود، وگرنه نامی مثل <b> پیام را می‌شکند و تلگرام آن را رد می‌کند.
  • «تغییر نام» پیامِ تازه نمی‌فرستد (ورودِ تازه نیست) و «خروج» فقط نام را پاک می‌کند؛ برنامه‌ها و انتخاب‌ها سر جایشان می‌مانند.

برنامه‌های ذخیره‌شده — src/lib/plans.ts

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

هر برنامه فقط یک عکس فوری از انتخاب‌ها است، نه از فهرست درس‌ها: درس‌های انتخاب‌شده، گروه‌های دستی، حالت خودکار/دستی، تاگل امتحان، و کارنامه‌ی تطبیق. کاتالوگِ درس‌ها مشترک می‌ماند و با جابه‌جا‌شدن بین برنامه‌ها عوض نمی‌شود.

کارنامه هم داخل برنامه ذخیره می‌شود؛ وگرنه وقتی برای دوستتان برنامه می‌چیدید و کارنامه‌ی او را وارد می‌کردید، کارنامه‌ی خودتان از بین می‌رفت.

چند تصمیمِ ریز که تفاوت ایجاد می‌کنند:

  • «تغییرِ ذخیره‌نشده» واقعاً یعنی تغییر. مقایسه روی یک عکسِ متعارف‌شده انجام می‌شود: ترتیبِ انتخاب‌ها بی‌اهمیت است و گروهِ دستیِ درسی که دیگر انتخاب نیست به حساب نمی‌آید. وگرنه نشانِ «ذخیره‌نشده» بی‌دلیل روشن می‌ماند و بعد از مدتی کاربر نادیده‌اش می‌گیرد.
  • بازکردنِ برنامه‌ی دیگر، وقتی چیزی ذخیره نشده، اول می‌پرسد — و در متنِ پرسش می‌گوید دقیقاً چه چیزی از بین می‌رود.
  • حذفِ برنامه، انتخاب‌های روی صفحه را پاک نمی‌کند؛ فقط آن‌ها را از برنامه جدا می‌کند تا بشود با نامی تازه ذخیره‌شان کرد.
  • نام تکراری خطا نمی‌دهد، خودش «۲» می‌گیرد.
  • حافظه‌ی دست‌کاری‌شده یا قدیمی، فقط برنامه‌های خرابش کنار گذاشته می‌شود (sanitizePlans) — نه اینکه کلِ فهرست دور ریخته شود.

برنامه‌ها داخل همان کلید localStorage می‌نشینند، پس خروجی/بازگردانی JSON هم خودبه‌خود شاملشان می‌شود.

موارد اختیاری که انجام شد

  • ✅ هشدار تداخل تاریخ امتحان (با تاگل «احتساب تداخل امتحان» که در حلال هم اعمال می‌شود)
  • ✅ جست‌وجو/فیلتر بر اساس استاد و روز
  • ✅ ریسپانسیو موبایل (بدون اسکرول افقی در عرض ۳۹۰px)
  • ✅ خروجی تصویری، خروجی اکسل و کدهای قابل‌کپی (پایین‌تر)

خروجی تصویری — src/lib/exportImage.ts

دکمه‌های «خروجی تصویر» و «کپی تصویر» در هر دو حالت خودکار و دستی (و در نمایش تمام‌صفحه) موجودند: اولی یک PNG با مقیاس ۲× ذخیره می‌کند، دومی همان تصویر را در کلیپ‌بورد می‌گذارد تا مستقیم در پیام‌رسان پیست شود.

جدول مستقیماً روی Canvas کشیده می‌شود، نه با عکس‌گرفتن از DOM:

  • مختصات همه‌ی بلاک‌ها را از قبل layoutWeek() حساب کرده، پس همان کدِ چیدمانِ صفحه تصویر را هم می‌سازد و هیچ کتابخانه‌ای (html2canvas و مانند آن) لازم نیست.
  • نتیجه در هر مقیاسی شارپ است و از سبک‌های صفحه (سایه، اسکرول، فونتِ نیم‌بارگذاری‌شده) مستقل است. پیش از ترسیم document.fonts.ready انتظار می‌شود تا متن با فونت جانشین کشیده نشود.
  • رنگ‌ها با getComputedStyle از همان توکن‌های تمِ فعال خوانده می‌شوند، پس تصویر در تم روشن و تاریک با چیزی که کاربر می‌بیند یکی است.
  • متن فارسی و راست‌به‌چپ با ctx.direction = 'rtl' و شکل‌دهیِ خودِ موتور مرورگر رندر می‌شود؛ ارقام فارسی می‌مانند.
  • تداخل در تصویر هم هاشور + کادر اناری می‌گیرد و بلاک‌های متداخل کنار هم چیده می‌شوند.
  • پایین تصویر راهنمای درس‌ها می‌آید (رنگ، گروه، استاد، تاریخ امتحان) — شاملِ درس‌های بدون زمان ثابت که در جدول بلاکی ندارند.
  • متنی که جا نشود با «…» علامت می‌خورد؛ متنِ بریده‌شده‌ی بی‌نشانه خواننده را گمراه می‌کند.

کدهای قابل‌کپی با یک کلیک

کدها با کادر نقطه‌چین نشان داده می‌شوند تا معلوم باشد قابل کلیک‌اند؛ با یک کلیک کپی می‌شوند و کادر لحظه‌ای سبز می‌شود.

  • در فهرست درس‌ها: کد درس.
  • در کارت جزئیات هر درس: کد گروه (مثل 1221019_02) — همان چیزی که در سیستم انتخاب‌واحد وارد می‌شود، نه فقط کد درس.
  • دکمه‌ی «کپی کدها» کدِ گروهِ همه‌ی درس‌های برنامه را یک‌جا و هر کدام در یک خط کپی می‌کند.

نمایش با ارقام فارسی است اما آنچه کپی می‌شود ارقام لاتینِ اصلی است، چون همان را باید در سیستم آموزش تایپ کرد. Clipboard API فقط در بستر امن (https یا localhost) کار می‌کند، پس در غیر آن به روش textarea + execCommand برمی‌گردیم تا روی http هم کار کند.

خروجی اکسل — src/lib/exportExcel.ts و src/lib/zip.ts

دکمه‌ی «خروجی اکسل» یک فایل .xlsx واقعی با سه برگه می‌سازد:

برگه محتوا
برنامه هفتگی جدول واقعی: ستون = روز، ردیف = بازه‌ی نیم‌ساعته، سلول‌ها ادغام‌شده
درس‌های برنامه یک ردیف برای هر درسِ برنامه، با کد گروه آماده‌ی کپی، و مجموع واحد
همه درس‌ها کل درس‌های ارائه‌شده، یک ردیف برای هر گروه

بدون هیچ کتابخانه‌ای. فایل xlsx در واقع یک ZIP از چند XML است (OOXML)، پس به‌جای افزودن SheetJS، zip.ts یک نویسنده‌ی ZIP بدون فشرده‌سازی (روش store، با CRC32) در حدود صد خط نوشته و exportExcel.ts همان چند XMLِ لازم را می‌سازد. متن‌ها inlineStr هستند تا sharedStrings لازم نشود.

  • هر سه برگه rightToLeft هستند، پس ستون A سمت راست می‌نشیند؛ سرصفحه هم freeze شده است.
  • واحد و ظرفیت به‌صورت عدد نوشته می‌شوند (نه متن)، تا در اکسل جمع‌پذیر باشند.
  • تداخل در برگه‌ی جدول با پرِ اناری و نشانه‌ی «⚠ تداخل» علامت می‌خورد.
  • ادغام سلول‌ها هرگز روی هم نمی‌افتد (فایل را خراب می‌کند): اگر دو جلسه در یک بازه شروع شوند، هر دو در یک سلول می‌نشینند و آن سلول ادغام نمی‌شود.
  • نویسه‌های کنترلیِ غیرمجاز در XML پاک می‌شوند، وگرنه فایل باز نمی‌شود.

درستیِ فایل با openpyxl بازبینی شده است (نه فقط «دانلود شد»): npm run check:xlsx یک نمونه می‌سازد تا با هر ابزاری باز و بررسی شود.

سیستم طراحی

تمِ روشن و واضح، با رنگ‌بندی گرفته‌شده از کاشی‌کاری لاجوردی–فیروزه‌ای:

نقش رنگ
بوم #f1f5fb — خاکستریِ کمی متمایل به آبیِ رنگِ اصلی (خنثیِ خالص نیست)
سطح #ffffff
رنگِ اصلی (لاجوردی) #2b4fc8 — فقط اکشن اصلی و حالت انتخاب
فیروزه‌ای #0b7f8c
زعفرانی (هشدار) #9a5a0b
اناری (تداخل) #c02742
متن #111b2e

اصول:

  • رنگ، اطلاعات است، نه تزئین. هر درس یک «فام» پایدار از فهرستی ۹ رنگیِ دستچین‌شده می‌گیرد؛ اشباع و روشنایی از توکن‌های تم می‌آید، پس یک فام در هر دو تم درست کار می‌کند. فام‌ها دست‌کم ۲۰ درجه از هم فاصله دارند و هیچ‌کدام نزدیک قرمزِ اناری نیست تا رنگِ درس با علامتِ تداخل اشتباه نشود.
  • حالت در «شکل» هم رمزگذاری می‌شود، نه فقط در رنگ: تداخل هم هاشور می‌گیرد، هم کادر اناری، هم تراشه‌ی «تداخل»؛ اعلان‌ها نوارِ شدت در لبه دارند.
  • رنگِ معنایی (خوب/هشدار/بحرانی) از رنگِ اصلی جداست.
  • همه‌چیز کارت نیست. کادر، پر، شعاع و سایه بر اساس نقش خرج می‌شوند: فهرست درس‌ها ردیف است، پنلِ تودرتو پرِ کم‌رنگ دارد، و سایه فقط جایی است که چیزی واقعاً بالا آمده.
  • چکیده پیش از جزئیات: نوار خلاصه‌ی بالای صفحه‌ی برنامه، تعداد درس، تداخل زمانی و امتحان و مجموع واحد را یک‌جا نشان می‌دهد؛ جزئیاتِ «کدام درس با کدام» پایین‌تر می‌آید.
  • ریلِ ترکیب‌ها کنارِ جدول است نه رویِ فضای خالی، تا با ۲ ترکیب فضای مرده نماند و با ۵۰ ترکیب هم خودش اسکرول شود.
  • تایپوگرافی: Vazirmatn متغیر (۱۰۰–۹۰۰) به‌صورت محلی از npm، در سه نقش: نمایشی (وزن ۸۰۰ با تراکینگ منفی)، متن (۴۰۰/۵۰۰) و برچسبِ ریز (۶۰۰ با letter-spacing). ارقام هرجا در ستون می‌نشینند tabular-nums می‌شوند. عمداً فونت دومی از CDN اضافه نشده تا اپ کاملاً آفلاین بماند.
  • هر دو تم طراحی شده‌اند. همه‌ی توکن‌ها اول در :root تعریف می‌شوند، بعد @media (prefers-color-scheme: dark) با گاردِ :root:not([data-theme='light']) و سپس :root[data-theme='dark'] بازنویسی‌شان می‌کنند — پس حالت پیش‌فرضِ «سیستم» (که هیچ data-theme روی ریشه نمی‌نشیند) هم درست رندر می‌شود.
  • انیمیشن‌ها کاربردی‌اند و کوتاه (۱۵۰ms): باز شدن گروه‌های جایگزین و گذارِ ملایم بین ترکیب‌ها. همه با احترام به prefers-reduced-motion.

بازتولید داده‌ی پیش‌فرض

src/data/seed.ts تولیدیِ ماشینی است؛ دست‌کاری‌اش نکنید:

pip install pdfplumber
python3 tools/extract.py path/to/report110-*.pdf   # → tools/raw_rows.json
python3 tools/build_seed.py                        # → tools/seed.json
python3 tools/gen_seed_ts.py                       # → src/data/seed.ts

این PDF متن را به ترتیب دیداری می‌نویسد، پس tools/extract.py متن فارسی را معکوس و شکل‌های نمایشی عربی را به حروف پایه برمی‌گرداند، در حالی که عددها و ساعت‌ها در همان ترتیب مختصات x درست‌اند؛ ستون‌ها هم از مختصات x نویسه‌ها تشخیص داده می‌شوند.

محدودیت شناخته‌شده: در خودِ PDF، شماره‌ی کلاسِ بعضی جلسه‌ها در لبه‌ی چپ صفحه بریده شده است (مثلاً «کلاس ۰۲» به‌جای «کلاس ۲۰۲»). این فقط فیلد location را تحت تأثیر می‌گذارد و در محاسبه‌ی تداخل نقشی ندارد؛ در پیش‌نمایش ایمپورت قابل اصلاح است.

ساختار

src/
  types.ts                 مدل داده: Session / Group / Course
  data/seed.ts             داده‌ی پیش‌فرض (تولیدی از PDF)
  lib/
    digits.ts              تبدیل ارقام فارسی↔لاتین، نرمال‌سازی متن فارسی
    days.ts                نام روزها ↔ ایندکس عددی (اسکن بلندترین‌الگو-اول)
    time.ts                محاسبه‌ی دقیقه، هم‌پوشانی بازه، مرتب‌سازی جفت ساعت
    conflicts.ts           تداخل جلسه/گروه/امتحان، جمع واحد
    parseTable.ts          پارسر جدول خام دانشگاه (ویژگی ۱)
    sessionText.ts         قالب متنیِ قابل‌ویرایش جلسه‌ها (رفت‌وبرگشتی)
    solver.ts              عقب‌گرد + تشخیص علتِ نبودِ ترکیب (ویژگی ۳)
    layout.ts              چیدمان جدول هفتگی و لِین‌بندیِ بلاک‌های متداخل
    storage.ts             localStorage و خروجی/بازگردانی JSON
    plans.ts               برنامه‌های ذخیره‌شده (عکسِ فوریِ انتخاب‌ها)
    identity.ts            نام کاربر و قفلِ ورود
    notify.ts              پیامِ ورود به تلگرام + صفِ تلاشِ دوباره
    parseAudit.ts          پارسر کارنامه‌ی تطبیق و محاسبه‌ی پاس‌شده/قابل اخذ
    exportImage.ts         ترسیم برنامه روی Canvas و خروجی PNG/کلیپ‌بورد
    exportExcel.ts         ساخت فایل xlsx (OOXML) با سه برگه
    zip.ts                 نویسنده‌ی ZIP بدون فشرده‌سازی، برای بسته‌ی xlsx
    clipboard.ts           کپی متن، با روش جانشین برای بسترِ ناامن
  components/              WeeklyGrid، CourseDetail، Diagnosis، CopyCode،
                           ExportButtons، PlanBar، LoginDialog، اجزای پایه
  pages/                   ImportPage، AuditPage، CoursesPage، SchedulePage
  styles/                  توکن‌های طراحی (رنگ/تایپ/فاصله) و CSS پایه
tools/                     خط لوله‌ی استخراج از PDF + تست‌های منطق

About

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

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages