Files
nobat724_front/.claude/prompt/doctor-poster-redesign.md

169 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# بازطراحی حرفه‌ای پوستر اشتراک‌گذاری پزشک (دانلود)
## پروژه
`nobat724_front`
## نقش
به‌عنوان متخصص UI/UX عمل کن. خروجی باید یک پوستر برند-محورِ تمیز، مدرن، خوانا و «قابل‌اشتراک در شبکه‌های اجتماعی» باشد — نه صرفاً یک باکس اطلاعات.
## زمینه
در صفحه‌ی عمومی پزشک (`/doctor/[slug]`) دکمه‌ی «اشتراک گذاری» یک Modal باز می‌کند و گزینه‌ی «دانلود» یک پوستر PDF از پزشک می‌سازد (via `html2canvas` + `jsPDF`). پوستر فعلی ساده و نامتوازن است: چیدمان دو ستونه‌ی خام، تایپوگرافی بدون سلسله‌مراتب، بدون slogan/امتیاز/شبکه‌های اجتماعی، لوگو خیلی کوچک، و ابعاد ثابت `600×700` که با محتوای متغیر (لیست خدمات/آدرس بلند) سرریز/نامرتب می‌شود.
مکانیزم آماده‌سازی تصویر قبلاً درست شده: `List.js` قبل از `html2canvas` منتظر لود همه‌ی `<img>`ها و آماده شدن QR می‌ماند، و عکس پروفایل از `DoctorAvatar` مشترک با صفحه می‌آید. **این بازطراحی فقط لایه‌ی بصری/چیدمان پوستر است؛ منطق دانلود و انتظار تصویر را نشکن.**
## هدف
پوستر را به بهترین شکل ممکن بازطراحی کن با این الزامات کاربر:
1. **لوگوی سایت حتماً حاضر و واضح باشد** (نه لوگوی ۳۰px گوشه) — در هدر به‌صورت برند، و به‌صورت واترمارک ظریف در فوتر.
2. کیفیت دانلود بالا و خروجی «شبکه‌اجتماعی‌پسند».
3. RTL، فونت Vazir، هماهنگ با هویت بصری سایت.
## فایل‌های مرتبط
| فایل | نقش |
|------|-----|
| `components/doctor/poster/index.js` | چیدمان اصلی پوستر — بازطراحی کامل |
| `components/doctor/poster/ImageProfile.js` | عکس پزشک (DoctorAvatar، ring) |
| `components/doctor/poster/QrImg.js` | QR + دامنه؛ `onReady` را نگه‌دار |
| `components/doctor/poster/Services.js` | لیست خدمات (`data.expertise`) |
| `components/doctor/poster/Address.js` | آدرس (`data.address[0].address`) |
| `components/doctor/poster/Telefon.js` | تلفن‌ها (`data.address[*].phone`) |
| `components/doctor/detailDoctor/Share.js` | کانتینر مخفی `hiddenRef` (ابعاد ثابت) + Modal |
| `components/doctor/detailDoctor/List.js` | `handleDownloadPDF` (html2canvas + jsPDF) — منطق را نگه‌دار، فقط ابعاد/نام فایل |
| `lib/getStateInfoClient.js` | `matchedCity` (شامل `logo_url`, `site_name`, `slogan`, `social_media`, `domain`, `contact_phone`) |
| `app/globals.css` | کلاس `.bg-poster` (خط ۸۳۸) و `.bg-stroke-circle` (خط ۸۴۶) |
| `data/city.json` | فیلدهای هر شهر: `logo_url`, `site_name`, `slogan`, `social_media`, `domain` |
| `public/assets/images/` | `logo.png`, `logo-2.png`, `poster.png`, `stroke-circle.png` |
## وضعیت فعلی
`components/doctor/poster/index.js` (چیدمان فعلی خام):
```jsx
<div className="bg-poster overflow-hidden !pt-[32px] p-[24px] relative flex justify-between items-start">
<div>
<div className="flex items-center justify-start gap-[5px]">
<Image src="/assets/images/logo.png" width={30} height={30} alt="logo" />
<p className="text-[#E7EEF6] text-[16px] font-bold !-translate-y-1">{siteName}</p>
</div>
<p className="mt-[40px] ... text-[20px] font-bold">{data?.name}</p>
<p className="text-[24px] mt-[20px] ...">تخصص: {/* specialties */}</p>
<Services data={data} />
<Address data={data} />
<Telefon data={data} />
</div>
<div className="flex flex-col justify-between items-end min-w-[200px]">
<ImageProfile data={data} />
<QrImg onReady={onQrReady} />
</div>
</div>
```
`components/doctor/detailDoctor/Share.js` (کانتینر مخفی — ابعاد ثابت فعلی):
```jsx
<div className="fixed min-w-[600px] min-h-[700px] w-[600px] h-[700px] top-0 left-0 opacity-0 pointer-events-none -z-10">
<div ref={hiddenRef}>
<Poster data={data} onQrReady={handleQrReady} />
</div>
</div>
```
`components/doctor/detailDoctor/List.js` (دانلود — این منطق را نگه‌دار):
```jsx
await waitForImages(element);
const canvas = await html2canvas(element, {
backgroundColor: "#ffffff", useCORS: true, scale: 2, imageTimeout: 15000,
});
const imgData = canvas.toDataURL("image/png");
const imgWidth = 210;
const pxPerMm = canvas.width / imgWidth;
const imgHeight = canvas.height / pxPerMm;
const pdf = new jsPDF({ orientation: "portrait", unit: "mm", format: [imgWidth, imgHeight] });
pdf.addImage(imgData, "PNG", 0, 0, imgWidth, imgHeight);
pdf.save("poster.pdf");
```
## داده‌های در دسترس
- پزشک (`data`): `name`, `specialties[].name`, `expertise[].name` (خدمات), `address[].address`, `address[].phone`, `experience`, `point` (امتیاز), `satisfaction`, `medical_system_code`, `img[0].url`, `social_media`.
- شهر (`getStateInfoClient().matchedCity`): `logo_url`, `site_name`, `slogan`, `domain`, `contact_phone`, `social_media` (instagram/telegram/whatsapp).
- QR: از `getStateInfoClient().fullUrl` (لینک همین صفحه).
## وظایف
### ۱. کانواس ثابت و باکیفیت
ابعاد پوستر را به یک کانواس ثابت با نسبت مناسب شبکه‌های اجتماعی ببر تا خروجی همیشه مرتب و پرکیفیت باشد. پیشنهاد: **نسبت ۴:۵ (مثلاً `1080×1350`)** یا استوری `1080×1920`. نسبت ۴:۵ برای پست اینستاگرام بهینه است.
- در `Share.js` ابعاد کانتینر مخفی را به همان کانواس تغییر بده (مثلاً `w-[1080px] h-[1350px]`). چون این عنصر مخفی و خارج از viewport رندر می‌شود، سایز بزرگ مشکلی ندارد.
- در `List.js` می‌توانی `scale` را روی `2` نگه داری (خروجی ۲۱۶۰×۲۷۰۰). اگر سایز کانواس را بزرگ کردی و خروجی سنگین شد، `scale: 1.5` هم قابل قبول است. نام فایل را معنادار کن: `` `poster-${data?.name ?? 'doctor'}.pdf` `` (کاراکترهای غیرمجاز را با `-` جایگزین کن).
### ۲. سیستم بصری (به‌عنوان UI/UX)
یک زبان بصری منسجم بساز؛ همه‌ی مقادیر زیر پیشنهادی‌اند، خودت به‌عنوان طراح تنظیم کن:
- **پس‌زمینه:** گرادیان برند (آبی تیره → آبی، هم‌خانواده با رنگ فعلی `#3987D4`/`#E7EEF6`). می‌توانی `.bg-poster` (تصویر `poster.png`) را نگه داری یا با گرادیان CSS جایگزین کنی. اگر گرادیان می‌سازی، از رنگ‌های rgb/hex استفاده کن (Tailwind v3، بدون oklch) تا `html2canvas` درست رندر کند.
- **کارت‌های شیشه‌ای/بخش‌بندی:** بخش خدمات/آدرس/تلفن را داخل کارت‌های نیمه‌شفاف با گوشه‌ی گرد و فاصله‌گذاری یکنواخت بگذار تا سلسله‌مراتب دیده شود.
- **تایپوگرافی:** سلسله‌مراتب واضح — نام پزشک بزرگ‌ترین (bold)، تخصص متوسط، برچسب بخش‌ها (خدمات/آدرس/تلفن) کوچک‌تر با آیکون. فونت فقط Vazir (از قبل در پروژه).
- **رنگ تأکید:** نارنجی موجود (`CircleOrangeSm`) برای بولت‌ها/CTA حفظ شود.
- **آواتار:** عکس دایره‌ای با حلقه‌ی ظریف (`.bg-stroke-circle` فعلی یا ring با border)، سایز بزرگ‌تر و به‌عنوان کانون بصری بالای پوستر.
### ۳. هدر برند + لوگو (الزام کاربر)
هدر شامل:
- **لوگوی سایت واضح**: از `matchedCity?.logo_url` استفاده کن؛ اگر `null` بود fallback به `/assets/images/logo.png` (یا `logo-2.png` هرکدام مناسب‌تر). اندازه‌ی محسوس (مثلاً ارتفاع ۴۸–۶۴px)، نه ۳۰px.
- `site_name` شهر + `domain` کنارش.
- در صورت وجود `slogan`، یک خط زیر لوگو.
مثال (pseudocode):
```jsx
const logoSrc = matchedCity?.logo_url || "/assets/images/logo.png";
// ...
<header className="flex items-center gap-3">
<img src={logoSrc} alt="logo" className="h-14 w-auto" />
<div className="flex flex-col">
<span className="text-[24px] font-bold text-white">{matchedCity?.site_name}</span>
<span className="text-[14px] text-[#B9D2EC]">{matchedCity?.domain}</span>
</div>
</header>
```
> نکته html2canvas: اگر `logo_url` از دامنه‌ی remote بیاید و آن سرور هدر CORS نفرستد، ممکن است در canvas آلوده/خالی شود. برای remote از `<img crossOrigin="anonymous">` استفاده کن و اگر لود نشد به لوگوی local برگرد (onError → fallback). لوگوی local همیشه امن است.
### ۴. بدنه — بخش‌های اطلاعاتی با مدیریت سرریز
- **هویت پزشک:** نام + برچسب تخصص‌ها به‌صورت chipهای کوچک (به‌جای متن پیوسته با `|`). ردیف badgeها: سابقه (`experience` سال)، امتیاز (`point` با آیکون ستاره)، رضایت (`satisfaction%`)، کد نظام (`medical_system_code`) — هرکدام موجود بود.
- **خدمات (`expertise`):** حداکثر N مورد (مثلاً ۶) نشان بده؛ اگر بیشتر بود «+X مورد دیگر». از overflow جلوگیری کن (کانواس ثابت است، محتوای متغیر نباید بشکند).
- **آدرس:** یک آدرس اصلی؛ متن بلند را با `line-clamp` (۲ خط) محدود کن.
- **تلفن:** شماره‌ی اصلی؛ اگر چند شماره بود حداکثر ۲ مورد. `dir="ltr"` برای شماره‌ها.
- همه‌ی این‌ها را طوری بچین که در ارتفاع ثابت کانواس جا شود؛ فضای خالی را با فاصله‌گذاری متوازن پر کن، نه با کشیدن یک بخش.
### ۵. فوتر — QR + CTA + واترمارک لوگو
- بلوک QR (همان `QrImg`) با یک برچسب CTA: «برای رزرو نوبت اسکن کنید» و دامنه زیر آن.
- آیکون/آی‌دی شبکه‌های اجتماعی از `matchedCity?.social_media` (اینستاگرام/تلگرام/واتساپ) اگر موجود بود.
- **واترمارک لوگو**: نسخه‌ی کم‌رنگ لوگو در گوشه/پس‌زمینه‌ی فوتر برای تأکید برند (الزام «لوگوی سایتم باید باشد»).
- `QrImg` باید `onReady` را همچنان صدا بزند (منطق انتظار دانلود در `List.js` به آن وابسته است) — این prop و رفتارش را نگه‌دار.
### ۶. حفظ سازگاری با پایپ‌لاین دانلود
- `List.js`: `waitForImages`, `imageTimeout`, `useCORS`, و گارد `qrReady` را نگه‌دار؛ فقط ابعاد format و نام فایل را در صورت نیاز به‌روزرسانی کن.
- `Share.js`: کانتینر باید همچنان **رندر‌شده ولی مخفی** بماند (`opacity-0 pointer-events-none -z-10`, نه `display:none`) تا تصاویر لود شوند و html2canvas بتواند snapshot بگیرد.
- `Poster` همچنان props `{ data, onQrReady }` را بگیرد.
## نکات مهم
- Tailwind v3 است (رنگ‌ها rgb/hex، بدون oklch) — از رنگ‌های مدرن غیرقابل‌رندر در html2canvas پرهیز کن.
- فونت فقط **Vazir**؛ فونت جدید اضافه نکن. مطمئن شو وزن‌های استفاده‌شده در `globals.css` تعریف شده‌اند (font-display swap) تا در snapshot درست بیایند.
- همه‌ی assetها **local** ترجیح داده شوند؛ برای remote (مثل `logo_url`) از `crossOrigin="anonymous"` + fallback استفاده کن.
- تصاویر `next/image` در پوستر: ترجیحاً `priority` بده تا lazy-load نشوند (مثل `ImageProfile`). برای المان‌های تزئینی می‌توانی از `<img>` ساده استفاده کنی.
- خروجی نهایی را **واقعی تست کن**: باز کردن Modal → دانلود → باز کردن PDF و بررسی: لوگو واضح، عکس پزشک، QR، خدمات/آدرس/تلفن مرتب، بدون سرریز، و چیدمان متوازن برای پزشکی با داده‌ی کم و پزشکی با داده‌ی زیاد (edge case).
- RTL: چیدمان و alignment راست‌به‌چپ درست باشد؛ شماره‌ها `ltr`.
- کیفیت: متن نباید تار شود — کانواس ثابت + `scale ≥ 1.5`.