docs(prompt): plan the multi-specialty UI work
Companion to clinicpro's doctor-multi-specialty-search prompt, which is already merged and now returns specialties[].parent_id plus descendant-aware specialty_id filtering. Records what the code inspection turned up, so the implementation does not rediscover it: data/specialties.json is a stale snapshot missing the five newest children of جراحی عمومی, which is why /specialties/جراح-گوارش 404s and why the parent/child UI cannot be built until it is synced at build time; the filters modal force-selects the first child when a group is picked, silently narrowing the search; and both poster variants slice specialties to a blind four, which overflows the fixed 1080x1350 frame. Not executed yet — committed so the plan is not carried around untracked. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,422 @@
|
||||
# نمایش چندتخصصی پزشک در کارت، فیلترها و پوستر
|
||||
|
||||
## پروژه
|
||||
|
||||
`nobat724_front` — سایت عمومی نوبتدهی.
|
||||
|
||||
**cross-repo.** پرامپت همتا: `clinicpro/.claude/prompt/doctor-multi-specialty-search.md`.
|
||||
**آن را اول اجرا کن.** وظیفهٔ ۳ اینجا به کلید `parent_id` در `specialties[]` پاسخ
|
||||
`GET /api/v1/doctors` نیاز دارد که همانجا اضافه میشود.
|
||||
|
||||
## زمینه
|
||||
|
||||
هر پزشک چند تخصص دارد و ساختار دو سطحی والد/فرزند است. نمونهٔ واقعی — دکتر محمدباقر جهانتاب
|
||||
شش تخصص دارد:
|
||||
|
||||
```
|
||||
13 جراحی عمومی (والد)
|
||||
14 جراحی پلاستیک و زیبایی
|
||||
167 جراحی لاپاراسکوپی
|
||||
168 جراح تیروئید
|
||||
169 جراح گوارش
|
||||
170 جراحی سرطانها
|
||||
```
|
||||
|
||||
## مشکل / هدف
|
||||
|
||||
چهار چیز در سایت عمومی میشکند.
|
||||
|
||||
### ۱. `data/specialties.json` کهنه است — ریشهٔ بقیهٔ مشکلات
|
||||
|
||||
فایل ثابت است و با دیتابیس همگام نیست:
|
||||
|
||||
- دیتابیس ۹۹ تخصص دارد، فایل ۹۴ تا.
|
||||
- پنج فرزندِ `جراحی عمومی` با شناسههای ۱۶۷ تا ۱۷۱ در فایل **نیستند** — دقیقاً همانهایی که
|
||||
این پزشک دارد.
|
||||
|
||||
این فایل در ۹ جا استفاده میشود: `app/sitemap.js`، `app/specialties/[slug]/page.js`،
|
||||
`app/doctor/[slug]/page.js`، `components/home/search/Fields.js`، `components/specialties/index.js`،
|
||||
`components/specialties/list/ItemSpecialties.js`، `components/doctor/index.js`،
|
||||
`components/clinics/index.js`، `helper/index.js`.
|
||||
|
||||
نتیجه: `/specialties/جراح-گوارش` برابر ۴۰۴ است، لینک breadcrumb صفحهٔ پزشک میشکند، و
|
||||
تخصص در سایتمپ نیست. تا این حل نشود، ساختن UI والد/فرزند ممکن نیست.
|
||||
|
||||
### ۲. انتخاب گروه، بیصدا به اولین زیرتخصص محدود میشود
|
||||
|
||||
کاربر `جراحی عمومی` را میزند و بدون اینکه بداند، فیلتر روی `جراحی پلاستیک و زیبایی` مینشیند.
|
||||
هیچ گزینهای برای «کل گروه» وجود ندارد.
|
||||
|
||||
### ۳. کارت پزشک همهٔ تخصصها را پشتسرهم چاپ میکند
|
||||
|
||||
در موبایل ارتفاع کارت باد میکند و شبکه بههم میریزد.
|
||||
|
||||
### ۴. پوستر سرریز میکند
|
||||
|
||||
چهار چیپ اول با نامهای بلند، به سه ردیف میروند، بخش hero بلند میشود و بخشهای پایین از
|
||||
کادر ثابت `1080×1350` با `overflow-hidden` بیرون میزنند.
|
||||
|
||||
## معیار پذیرش
|
||||
|
||||
- ✅ موفق: بعد از `npm run build`، فایل `data/specialties.json` هر ۹۹ تخصص فعال را با
|
||||
`parent_id` و `slug` دارد و `/specialties/جراح-گوارش` صفحه میدهد نه ۴۰۴.
|
||||
- ✅ موفق: در مودال فیلترها، انتخاب گروه `جراحی عمومی` گزینهٔ پیشفرض
|
||||
«همه تخصصهای جراحی عمومی» را میگذارد و درخواست با `specialty_id=13` میرود.
|
||||
- ✅ موفق: کارت دکتر جهانتاب در موبایل و دسکتاپ فقط `جراحی عمومی +5` نشان میدهد و ارتفاعش با
|
||||
کارت پزشک تکتخصصی یکی است.
|
||||
- ✅ موفق: کلیک روی `+5` فهرست کامل شش تخصص را در Popover نشان میدهد و **صفحهٔ پزشک را باز نمیکند**.
|
||||
- ✅ موفق: پوستر همان پزشک، هیچ محتوایی بیرون از کادر ۱۳۵۰ ندارد و تخصصها روی هم نمیافتند.
|
||||
- ❌ خطا: پزشک بدون هیچ تخصص → کارت بدون بخش تخصص و بدون `+0`، نه `undefined` و نه کرش.
|
||||
- ❌ خطا: اسکریپت همگامسازی وقتی API در دسترس نیست → build با پیام روشن شکست بخورد و فایل
|
||||
موجود را با آرایهٔ خالی بازنویسی **نکند**.
|
||||
- ⚠️ مرزی: پزشک با دقیقاً یک تخصص → فقط نام، بدون چیپ `+N`.
|
||||
- ⚠️ مرزی: پزشک با دو تخصص ریشهٔ متفاوت (مثلاً `جراحی عمومی` و `داخلی`) → اولین ریشه نمایش،
|
||||
بقیه در `+N`.
|
||||
- ⚠️ مرزی: پزشکی که فقط زیرتخصص دارد و هیچ ریشهای ندارد → اولین تخصص آرایه نمایش داده شود.
|
||||
- ⚠️ مرزی: پوستر پزشکی با نام تخصص خیلی بلند → بریدن روی مرز کلمه، نه وسط کلمه.
|
||||
|
||||
## فایلهای مرتبط
|
||||
|
||||
| فایل | نقش |
|
||||
|------|-----|
|
||||
| `scripts/sync-specialties.mjs` | **جدید** — ساخت `data/specialties.json` از API |
|
||||
| `package.json` | قلاب `prebuild` |
|
||||
| `lib/specialtyDisplay.js` | **جدید** — قاعدهٔ «تخصص اصلی» مشترک بین کارت و پوستر |
|
||||
| `helper/index.js` | `filterList()` خط ۲۷۰ |
|
||||
| `components/doctors/modal/Content.js` | انتخاب اجباری اولین فرزند |
|
||||
| `components/doctors/modal/form/index.js` | دو `CateSelector` |
|
||||
| `app/component/ItemDoctor.js` | کارت پزشک |
|
||||
| `app/component/PopoverDate.js` | الگوی موجود Popover — تقلید کن، از نو ننویس |
|
||||
| `components/doctor/poster/index.js` | پوستر تیره |
|
||||
| `components/doctor/poster/PosterLight.js` | پوستر روشن — همان باگ |
|
||||
| `components/doctors/search/SearchField.js` | placeholder کادر جستجو |
|
||||
|
||||
## وضعیت فعلی
|
||||
|
||||
### انتخاب اجباری اولین زیرتخصص
|
||||
|
||||
```js
|
||||
// components/doctors/modal/Content.js:5
|
||||
const changeSpecialty = (name, value) => {
|
||||
const newFilter = { ...filter, [name]: value };
|
||||
|
||||
if (name === "category") {
|
||||
if (value) {
|
||||
const filtered = specialties
|
||||
.filter((item) => item.parent_id)
|
||||
.filter((item) => String(item.parent_id) === String(value.id));
|
||||
newFilter.specialty = filtered[0] || null;
|
||||
} else {
|
||||
newFilter.specialty = null;
|
||||
}
|
||||
}
|
||||
|
||||
setFilter(newFilter);
|
||||
setDataInURL(newFilter);
|
||||
return newFilter;
|
||||
};
|
||||
```
|
||||
|
||||
و ساخت پارامتر، `specialty` را بر `category` ترجیح میدهد:
|
||||
|
||||
```js
|
||||
// helper/index.js:482
|
||||
if (newFilter.specialty?.id) {
|
||||
params.specialty_id = newFilter.specialty.id;
|
||||
} else if (newFilter.category?.id) {
|
||||
params.specialty_id = newFilter.category.id;
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
// helper/index.js:270
|
||||
export const filterList = (data) => {
|
||||
const parentList = specialties.filter((item) => !item.parent_id);
|
||||
const childrenList = specialties.filter((item) => item.parent_id);
|
||||
|
||||
const filteredChildrenList =
|
||||
data && data.category
|
||||
? childrenList.filter(
|
||||
(item) => String(item.parent_id) === String(data.category.id)
|
||||
)
|
||||
: [];
|
||||
|
||||
return {
|
||||
parentList: parentList,
|
||||
childrenList: filteredChildrenList,
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
### کارت — همه را پشتسرهم چاپ میکند
|
||||
|
||||
```jsx
|
||||
// app/component/ItemDoctor.js:46
|
||||
<TextLoading loading={loading} width={120} height={15}>
|
||||
<p className="text-[#616161] text-[14px] font-normal">
|
||||
تخصص:
|
||||
{doctor?.specialties?.map(
|
||||
(item, idx) =>
|
||||
`${item.name} ${doctor.specialties.length === idx + 1 ? "" : "|"} `
|
||||
)}
|
||||
</p>
|
||||
</TextLoading>
|
||||
```
|
||||
|
||||
### پوستر — برش کور روی عدد ۴
|
||||
|
||||
```jsx
|
||||
// components/doctor/poster/index.js:44 (و PosterLight.js:24 دقیقاً همین)
|
||||
const specialties = (data?.specialties ?? []).filter((s) => s?.name).slice(0, 4);
|
||||
```
|
||||
|
||||
```jsx
|
||||
// components/doctor/poster/index.js:132
|
||||
<div className="flex flex-wrap gap-[10px] mt-[16px]">
|
||||
{specialties.map((s, i) => (
|
||||
<span
|
||||
key={i}
|
||||
className="flex items-center rounded-full bg-[#F59E0B]/18 border border-[#F59E0B]/40 px-[18px] py-[9px]"
|
||||
>
|
||||
<span className="text-[#FCD9A0] text-[22px] font-semibold leading-none">
|
||||
{s.name}
|
||||
</span>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
```
|
||||
|
||||
کادر ثابت است، پس هر ردیف اضافه محتوای پایین را بیرون میاندازد:
|
||||
|
||||
```jsx
|
||||
// components/doctor/poster/index.js:81
|
||||
<div
|
||||
dir="rtl"
|
||||
className="relative w-[1080px] h-[1350px] overflow-hidden flex flex-col p-[64px] text-right"
|
||||
```
|
||||
|
||||
## وظایف
|
||||
|
||||
### ۱. اسکریپت همگامسازی `data/specialties.json`
|
||||
|
||||
فایل جدید `scripts/sync-specialties.mjs`.
|
||||
|
||||
منبع: `GET ${NEXT_PUBLIC_API_URL}/api/v1/specialties` — **عمومی است و توکن نمیخواهد**.
|
||||
بدون پارامتر `parent_id` همهٔ تخصصهای فعال را میدهد. پاسخ دولایه است:
|
||||
`{ success, data: { data: [...] } }`.
|
||||
|
||||
هر آیتم دقیقاً شکل فایل فعلی را دارد: `id` و `uuid` و `name` و `slug` و `status` و `weight`
|
||||
و `parent_id`.
|
||||
|
||||
```js
|
||||
// scripts/sync-specialties.mjs
|
||||
const API_URL = process.env.NEXT_PUBLIC_API_URL;
|
||||
const OUT = new URL("../data/specialties.json", import.meta.url);
|
||||
|
||||
const res = await fetch(`${API_URL}/api/v1/specialties`);
|
||||
if (!res.ok) throw new Error(`specialties sync failed: HTTP ${res.status}`);
|
||||
|
||||
const json = await res.json();
|
||||
const items = json?.data?.data ?? [];
|
||||
|
||||
// آرایهٔ خالی یعنی چیزی غلط است — فایل موجود را با آن بازنویسی نکن.
|
||||
if (!Array.isArray(items) || items.length === 0) {
|
||||
throw new Error("specialties sync returned an empty list; refusing to overwrite");
|
||||
}
|
||||
```
|
||||
|
||||
قبل از نوشتن، خروجی را با ترتیب پایدار مرتب کن (بر اساس `id`) تا diff فایل نویزی نشود،
|
||||
و با دو فاصله و `\n` انتهایی بنویس تا با شکل فعلی فایل بخواند.
|
||||
|
||||
در `package.json` قلاب بزن:
|
||||
|
||||
```json
|
||||
"prebuild": "node scripts/sync-specialties.mjs",
|
||||
```
|
||||
|
||||
`dev` را وصل نکن — توسعهٔ آفلاین نباید به API گره بخورد.
|
||||
|
||||
**نحوه تست:**
|
||||
```bash
|
||||
cd nobat724_front
|
||||
node scripts/sync-specialties.mjs
|
||||
python3 -c "import json;d=json.load(open('data/specialties.json'));print(len(d), [x['slug'] for x in d if x.get('parent_id')==13])"
|
||||
```
|
||||
باید ۹۹ و شامل `جراح-گوارش` باشد. سپس با `NEXT_PUBLIC_API_URL` غلط اجرا کن و مطمئن شو
|
||||
خطا میدهد و فایل دستنخورده میماند.
|
||||
بعد `npm run build` و باز کردن `/specialties/جراح-گوارش`.
|
||||
|
||||
### ۲. گزینهٔ «همه تخصصهای X»
|
||||
|
||||
`filterList` در `helper/index.js` یک آیتمِ «همه» به ابتدای `childrenList` اضافه کند که
|
||||
شناسهاش همان شناسهٔ والد است:
|
||||
|
||||
```js
|
||||
const filteredChildrenList =
|
||||
data && data.category
|
||||
? [
|
||||
// «همه» یعنی فیلتر روی خودِ گروه؛ بکاند specialty_id را به نوادگان گسترش میدهد.
|
||||
{ id: data.category.id, name: `همه تخصصهای ${data.category.name}`, parent_id: null },
|
||||
...childrenList.filter(
|
||||
(item) => String(item.parent_id) === String(data.category.id)
|
||||
),
|
||||
]
|
||||
: [];
|
||||
```
|
||||
|
||||
و در `Content.js` بهجای `filtered[0]`، همان آیتم «همه» انتخاب شود:
|
||||
|
||||
```js
|
||||
if (name === "category") {
|
||||
// پیشفرض «کل گروه» است، نه اولین زیرتخصص. انتخاب بیصدای اولین فرزند،
|
||||
// جستجوی کاربر را بدون اطلاعش تنگ میکرد.
|
||||
newFilter.specialty = value
|
||||
? { id: value.id, name: `همه تخصصهای ${value.name}` }
|
||||
: null;
|
||||
}
|
||||
```
|
||||
|
||||
`QueryForDoctorsReq` را دست نزن — چون شناسهٔ «همه» همان شناسهٔ والد است، همان مسیر فعلی
|
||||
`specialty_id` را درست میفرستد.
|
||||
|
||||
**نحوه تست:** unit test در `helper/specialtyFilter.test.js` برای `filterList` —
|
||||
با `category` برابر `جراحی عمومی` اولین آیتم `childrenList` باید `id` والد و عنوان
|
||||
«همه تخصصهای جراحی عمومی» داشته باشد؛ بدون `category` آرایه خالی بماند.
|
||||
سپس دستی: مودال فیلترها → گروه `جراحی عمومی` → در تب شبکه ببین `specialty_id=13` میرود
|
||||
و دکتر جهانتاب در نتایج هست.
|
||||
|
||||
### ۳. قاعدهٔ مشترک «تخصص اصلی»
|
||||
|
||||
فایل جدید `lib/specialtyDisplay.js`. کارت و پوستر هر دو از این میخوانند تا قاعده دو جا
|
||||
تکرار و واگرا نشود.
|
||||
|
||||
```js
|
||||
/**
|
||||
* تخصص «اصلی» و بقیه.
|
||||
*
|
||||
* ریشه (بدون parent_id) اصلی است چون عنوانی است که بیمار میشناسد و هنگام ذخیره در
|
||||
* بکاند خودکار به پزشک اضافه میشود، پس تقریباً همیشه وجود دارد. اگر ریشهای نبود،
|
||||
* اولین آیتم آرایه.
|
||||
*/
|
||||
export function splitSpecialties(list) {
|
||||
const items = (list ?? []).filter((s) => s?.name);
|
||||
if (items.length === 0) return { primary: null, rest: [] };
|
||||
|
||||
const primary = items.find((s) => !s.parent_id) ?? items[0];
|
||||
|
||||
return { primary, rest: items.filter((s) => s !== primary) };
|
||||
}
|
||||
```
|
||||
|
||||
**نحوه تست:** `lib/specialtyDisplay.test.js` — آرایهٔ خالی؛ تکتخصص؛ ریشه وسط آرایه؛
|
||||
هیچ ریشهای نبودن؛ آیتمِ بدون `name` که باید حذف شود.
|
||||
|
||||
### ۴. کارت پزشک — تخصص اصلی و `+N`
|
||||
|
||||
در `app/component/ItemDoctor.js` بلوک `<p>` تخصص با این جایگزین شود:
|
||||
|
||||
```jsx
|
||||
const { primary, rest } = splitSpecialties(doctor?.specialties);
|
||||
```
|
||||
|
||||
نمایش: نام `primary`، و اگر `rest.length > 0` یک چیپ کوچک `+{rest.length}` کنارش.
|
||||
کلیک روی چیپ، `Popover` باز کند با فهرست کامل (`primary` و `rest`).
|
||||
|
||||
الگوی `Popover` را از `app/component/PopoverDate.js` بردار، از صفر ننویس.
|
||||
|
||||
سه نکتهٔ اجباری:
|
||||
|
||||
- روی `onClick` چیپ حتماً `e.preventDefault()` و `e.stopPropagation()` بزن. کارت داخل
|
||||
`Link` است و بدون این، کلیک صفحهٔ پزشک را باز میکند.
|
||||
- چیپ باید `<button type="button">` باشد با `aria-label` روشن مثل
|
||||
`نمایش ${rest.length} تخصص دیگر`، نه `<span>` با `onClick`.
|
||||
- ارتفاع کارت نباید تغییر کند. نام `primary` در یک خط با `truncate` بماند.
|
||||
|
||||
استایل چیپ با توکنهای موجود همان فایل: `text-[12px]` و `rounded-[4px]` و
|
||||
`bg-[#F8F8FF]` و `text-[#616161]` — همان چیزی که بلوک امتیاز و رضایت استفاده میکند.
|
||||
رنگ یا کلاس تازه اضافه نکن.
|
||||
|
||||
**نحوه تست:** `npm run test` با یک تست کامپوننتی —
|
||||
پزشک با ۶ تخصص: متن `جراحی عمومی` هست و `+5` هست؛ کلیک روی `+5` هر شش نام را نشان میدهد؛
|
||||
پزشک با ۱ تخصص: هیچ `+` در DOM نیست؛ پزشک بدون تخصص: کرش نمیکند.
|
||||
سپس دستی در موبایل روی `/doctors` — ارتفاع کارت جهانتاب با کارت کناری یکی باشد.
|
||||
|
||||
### ۵. پوستر — والد درشت، زیرتخصصها یک خط متنی
|
||||
|
||||
**هر دو فایل** `components/doctor/poster/index.js` و `components/doctor/poster/PosterLight.js`.
|
||||
هر دو دقیقاً همان `slice(0, 4)` را دارند.
|
||||
|
||||
منطق پوستر عمداً با کارت فرق دارد: در چاپ نه کلیک هست نه Popover، پس `+N` بیمعناست.
|
||||
|
||||
در `lib/specialtyDisplay.js` یک تابع دوم بگذار:
|
||||
|
||||
```js
|
||||
/**
|
||||
* زیرتخصصها بهشکل یک خط متنی، بریده روی مرز کلمه با بودجهٔ کاراکتر.
|
||||
* پوستر کادر ثابت دارد و overflow-hidden است؛ هر ردیف اضافه، بخشهای پایین را بیرون میاندازد.
|
||||
*/
|
||||
export function posterSpecialtyLine(rest, budget = 90) {
|
||||
const names = rest.map((s) => s.name);
|
||||
const shown = [];
|
||||
let used = 0;
|
||||
|
||||
for (const name of names) {
|
||||
const cost = name.length + (shown.length ? 3 : 0); // ' · '
|
||||
if (used + cost > budget) break;
|
||||
shown.push(name);
|
||||
used += cost;
|
||||
}
|
||||
|
||||
const hidden = names.length - shown.length;
|
||||
|
||||
return { text: shown.join(" · "), hidden };
|
||||
}
|
||||
```
|
||||
|
||||
در پوستر: `primary` همان چیپ درشت فعلی بماند (فقط یکی، نه چهارتا).
|
||||
زیرش یک `<p>` با `text-[20px]` و رنگ کمرنگتر موجود (`#C7DBF2` در تیره، معادلش در روشن)
|
||||
که `text` را نشان میدهد و اگر `hidden > 0` بود `و {hidden} تخصص دیگر` را به آن میچسباند.
|
||||
|
||||
روی ظرف زیرتخصصها `max-h` بگذار معادل دو خط، تا حتی اگر بودجه اشتباه تنظیم شد،
|
||||
کادر سرریز نکند.
|
||||
|
||||
**نحوه تست:** تست واحد `posterSpecialtyLine` — بودجهٔ کوچک با نامهای بلند؛ آرایهٔ خالی
|
||||
(`text` تهی و `hidden` صفر)؛ یک نام بلندتر از کل بودجه (نباید وسط کلمه بریده شود).
|
||||
سپس دستی: پوستر دکتر جهانتاب را از صفحهٔ پزشک بساز و مطمئن شو در هر دو تم روشن و تیره
|
||||
هیچ چیزی بیرون از کادر نیست.
|
||||
|
||||
### ۶. placeholder کادر جستجو
|
||||
|
||||
```js
|
||||
// components/doctors/search/SearchField.js:58
|
||||
placeholder="جستجوی نام پزشک ..."
|
||||
```
|
||||
|
||||
به «جستجوی نام پزشک یا تخصص ...» تغییر کند. بکاند بعد از پرامپت همتا نام تخصص را هم میگردد
|
||||
و placeholder فعلی دروغ میشود.
|
||||
|
||||
**نحوه تست:** تایپ `جراح گوارش` در کادر و دیدن دکتر جهانتاب در نتایج. این تست فقط بعد از
|
||||
اجرای پرامپت بکاند معنا دارد.
|
||||
|
||||
## نکات مهم
|
||||
|
||||
- **ترتیب اجرا اجباری است.** وظیفهٔ ۳ و ۴ بدون `parent_id` در پاسخ API کار نمیکنند.
|
||||
اگر بکاند هنوز اجرا نشده، `splitSpecialties` همیشه `items[0]` را برمیگرداند و
|
||||
نتیجه ظاهراً درست ولی غیرقابلاتکا میشود.
|
||||
- **وظیفهٔ ۱ پیشنیاز وظیفهٔ ۲ است.** بدون فایلِ بهروز، فرزندهای ۱۶۷ تا ۱۷۱ در
|
||||
`filterList` نیستند و گزینهٔ «همه» روی گروهی مینشیند که فرزندانش را نمیبیند.
|
||||
- **یک قاعده، یک جا.** انتخاب «تخصص اصلی» فقط در `lib/specialtyDisplay.js`. اگر در
|
||||
`ItemDoctor` یا پوستر دوباره نوشته شود، فردا دو جا واگرا میشوند. این همان دلیل ساختن
|
||||
فایل است، نه abstraction برای آینده.
|
||||
- **کلیک داخل `Link`.** بدون `stopPropagation` روی چیپ `+N`، هر بار که کاربر تخصصها را
|
||||
میبیند به صفحهٔ پزشک پرت میشود. این را حتماً تست کن.
|
||||
- **پوستر دو فایل است.** `index.js` تیره و `PosterLight.js` روشن. اصلاح یکی و فراموشی
|
||||
دیگری، باگ را نصفه رها میکند.
|
||||
- **سئو.** `data/specialties.json` به `app/sitemap.js` هم خوراک میدهد. بعد از همگامسازی،
|
||||
سایتمپ تخصصهای تازه را میگیرد — این مطلوب است، ولی مطمئن شو اسکریپت در شکست، فایل را
|
||||
خالی نمیکند وگرنه سایتمپ کوچک میشود و صفحات از ایندکس میافتند.
|
||||
- **استایل.** MUI v5 و Tailwind و RTL و فونت Vazir. کلاس یا رنگ تازه اضافه نکن؛ از همان
|
||||
توکنهای موجود در همان فایل استفاده کن. راهحل با CSS موقت یا `!important` پذیرفته نیست.
|
||||
- **صفحهٔ جزئیات پزشک عمداً خارج از محدوده است.** breadcrumb شکستهاش عارضهٔ فایل کهنه است و
|
||||
با وظیفهٔ ۱ خودبهخود درست میشود. اگر بعد از وظیفهٔ ۱ باز هم شکسته بود، آیتم تازه به
|
||||
todo اضافه کن و گزارش بده.
|
||||
Reference in New Issue
Block a user