feat: Refactor specialty display logic and enhance specialty filtering
- Introduced SpecialtyChips component to manage the display of doctor's specialties with a primary specialty and a count of additional specialties. - Updated ItemDoctor component to utilize SpecialtyChips for better UI presentation. - Enhanced PosterLight and Poster components to display primary specialties and a text line for sub-specialties. - Implemented nextSpecialtyFilter function to improve specialty selection logic in the Content component. - Updated search functionality to allow searching by both doctor name and specialty. - Added new specialties to specialties.json for better coverage. - Created sync-specialties script to synchronize specialties data with the backend during build. - Added tests for new components and helper functions to ensure functionality and reliability.
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* همگامسازی `data/specialties.json` با دیتابیس، در زمان build.
|
||||
*
|
||||
* node scripts/sync-specialties.mjs
|
||||
*
|
||||
* فایل قبلاً یک اسنپشات دستی بود و عقب میماند: تخصصی که ادمین میساخت در سایت
|
||||
* نبود، پس صفحهاش ۴۰۴ میشد، در سایتمپ نمیآمد، و breadcrumb صفحهٔ پزشک به آن
|
||||
* لینک نمیداد. نُه مصرفکننده از همین فایل میخوانند، از `app/sitemap.js` تا
|
||||
* `helper/filterList`، پس منبع باید یکی و تازه بماند.
|
||||
*
|
||||
* فایل عمداً حذف نشد و runtime fetch جایش نیامد: `sitemap` و صفحات تخصص باید
|
||||
* ایستا بمانند و به دسترسبودن API در زمان درخواست گره نخورند.
|
||||
*
|
||||
* فقط به `build` وصل است، نه `dev` — توسعهٔ آفلاین نباید به API نیاز داشته باشد.
|
||||
*
|
||||
* بکاند محلی گواهی self-signed دارد، پس اجرای محلی مثل اسکریپت `dev` نیاز دارد:
|
||||
*
|
||||
* NODE_TLS_REJECT_UNAUTHORIZED=0 npm run build
|
||||
*
|
||||
* این متغیر عمداً داخل `prebuild` ست نشده تا build تولیدی اعتبارسنجی TLS را از
|
||||
* دست ندهد.
|
||||
*/
|
||||
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { dirname, join } from 'node:path';
|
||||
|
||||
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..');
|
||||
const OUT = join(ROOT, 'data/specialties.json');
|
||||
|
||||
/**
|
||||
* `.env` را Next میخواند، نه یک اسکریپت خام node. اینجا فقط برای اجرای محلی
|
||||
* خوانده میشود و متغیرهای واقعیِ محیط را بازنویسی نمیکند — در CI فایل نیست و
|
||||
* مقدار از پلتفرم میآید.
|
||||
*/
|
||||
function loadDotEnv() {
|
||||
const file = join(ROOT, '.env');
|
||||
if (!existsSync(file)) return;
|
||||
|
||||
for (const line of readFileSync(file, 'utf8').split('\n')) {
|
||||
const match = line.match(/^\s*([A-Z0-9_]+)\s*=\s*(.*)\s*$/);
|
||||
if (!match) continue;
|
||||
|
||||
const [, key, raw] = match;
|
||||
if (process.env[key] === undefined) {
|
||||
process.env[key] = raw.replace(/^["']|["']$/g, '');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loadDotEnv();
|
||||
|
||||
const API_URL = process.env.NEXT_PUBLIC_API_URL;
|
||||
|
||||
if (!API_URL) {
|
||||
throw new Error('sync-specialties: NEXT_PUBLIC_API_URL is not set');
|
||||
}
|
||||
|
||||
// `GET /api/v1/specialties` عمومی است و توکن نمیخواهد؛ بدون پارامتر `parent_id`
|
||||
// همهٔ تخصصهای فعال را میدهد، ریشهها و فرزندان با هم. پاسخ دولایه است.
|
||||
const res = await fetch(`${API_URL}/api/v1/specialties`);
|
||||
if (!res.ok) {
|
||||
throw new Error(`sync-specialties: HTTP ${res.status} from ${API_URL}`);
|
||||
}
|
||||
|
||||
const items = (await res.json())?.data?.data;
|
||||
|
||||
// لیست خالی یعنی چیزی غلط است — پاسخ عوض شده، دیتابیس خالی است، یا پشت پراکسی
|
||||
// نشستهایم. بازنویسی با آن، سایتمپ را آب میکند و صفحات تخصص را از ایندکس
|
||||
// میاندازد؛ شکستِ build از آن بهمراتب ارزانتر است.
|
||||
if (!Array.isArray(items) || items.length === 0) {
|
||||
throw new Error('sync-specialties: empty list; refusing to overwrite data/specialties.json');
|
||||
}
|
||||
|
||||
// ترتیب پایدار بر اساس id تا diff فایل نویزی نشود و مرور تغییرات ممکن بماند.
|
||||
const sorted = [...items].sort((a, b) => a.id - b.id);
|
||||
|
||||
const before = JSON.parse(readFileSync(OUT, 'utf8'));
|
||||
writeFileSync(OUT, `${JSON.stringify(sorted, null, 2)}\n`, 'utf8');
|
||||
|
||||
console.log(`sync-specialties: ${before.length} → ${sorted.length} specialties from ${API_URL}`);
|
||||
Reference in New Issue
Block a user