Files
nobat724_front/components/doctor/poster/PosterLight.js
T
hamed 56e264e44c 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.
2026-08-08 17:19:22 +03:30

221 lines
8.3 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useEffect, useState } from "react";
import DoctorAvatar from "@/app/component/DoctorAvatar";
import QrImg from "./QrImg";
import SiteLogo from "./SiteLogo";
import { getStateInfoClient } from "@/lib/getStateInfoClient";
import { posterSpecialtyLine, splitSpecialties } from "@/lib/specialtyDisplay";
import { doctorTitle } from "@/helper";
import StarDI from "@/components/icons/StarDI";
import { FEATURES } from "@/config/features";
const ACCENT = "#16528C";
const ACCENT_LIGHT = "#2E77C4";
const ORANGE = "#F17732";
function PosterLight({ data, onQrReady }) {
const [city, setCity] = useState(null);
useEffect(() => {
const { matchedCity } = getStateInfoClient();
setCity(matchedCity || null);
}, []);
// قرینهٔ پوستر تیره — همان دلیل: برش عددیِ ثابت با نام‌های بلند از کادر بیرون می‌زد.
const { primary, rest } = splitSpecialties(data?.specialties);
const subSpecialties = posterSpecialtyLine(rest, 78);
const services = (data?.expertise ?? []).filter((s) => s?.name);
const shownServices = services.slice(0, 8);
const moreServices = services.length - shownServices.length;
const rawAddress = data?.address?.[0]?.address;
const address =
rawAddress && rawAddress.length > 120
? `${rawAddress.slice(0, 120).trimEnd()}…`
: rawAddress;
const phones = (data?.address ?? [])
.map((a) => a?.phone)
.filter(Boolean)
.slice(0, 2);
const badges = [
Number(data?.experience) > 0 && { label: `${data.experience} سال تجربه` },
FEATURES.posterRatings && (data?.point ?? 0) > 0 && { icon: <StarDI />, label: `امتیاز ${data.point}` },
FEATURES.posterRatings && (data?.satisfaction ?? 0) > 0 && { label: `${data.satisfaction}% رضایت بیماران` },
].filter(Boolean);
return (
<div
dir="rtl"
className="relative w-[1080px] h-[1350px] overflow-hidden flex flex-col bg-[#F6F9FD] text-center"
>
{/* top brand band */}
<div
className="relative flex items-center justify-center py-[36px]"
style={{
background: `linear-gradient(135deg, ${ACCENT} 0%, ${ACCENT_LIGHT} 100%)`,
}}
>
<div className="flex items-center gap-[16px]">
<span className="flex items-center justify-center w-[72px] h-[72px] rounded-full bg-white">
<SiteLogo city={city} className="h-[48px] w-[48px]" />
</span>
<div className="flex flex-col items-start">
<span className="text-white text-[32px] font-extrabold leading-tight">
{city?.site_name || "نوبت ۷۲۴"}
</span>
<span className="text-[#CFE2F6] text-[19px] font-medium" dir="ltr">
{city?.domain || "nobat724.com"}
</span>
</div>
</div>
</div>
<div className="flex-1 px-[72px] flex flex-col items-center">
{/* avatar overlapping band */}
<div className="-mt-[8px] pt-[40px]">
<div
className="rounded-full p-[7px] bg-white"
style={{ boxShadow: "0 12px 40px rgba(22,82,140,.18)" }}
>
<DoctorAvatar
doctor={data}
size={230}
priority
className="rounded-full !object-cover w-[230px] h-[230px]"
/>
</div>
</div>
<h1 className="mt-[28px] text-[#12365C] text-[48px] font-extrabold leading-tight">
{doctorTitle(data?.name)}
</h1>
{primary && (
<div className="w-full flex justify-center mt-[16px]">
<span className="flex items-center shrink-0 rounded-full bg-[#E3EEF9] px-[20px] py-[9px]">
<span className="text-[#16528C] text-[23px] font-semibold leading-none whitespace-nowrap">
{primary.name}
</span>
</span>
</div>
)}
{/* زیرتخصص‌ها یک خط متنی — در چاپ نه کلیک هست نه Popover. */}
{subSpecialties.text && (
<p className="w-full mt-[12px] max-h-[80px] overflow-hidden text-[#3E6E9E] text-[20px] font-medium leading-[40px]">
{subSpecialties.text}
{subSpecialties.hidden > 0 && ` و ${subSpecialties.hidden} تخصص دیگر`}
</p>
)}
{badges.length > 0 && (
<div className="w-full flex flex-wrap justify-center items-center gap-[14px] mt-[22px]">
{badges.map((b, i) => (
<span key={i} className="flex items-center shrink-0 gap-[8px]">
{i > 0 && <span className="text-[#B8CCE0] text-[22px]">·</span>}
{b.icon}
<span className="text-[#4A6B8C] text-[22px] font-medium leading-none whitespace-nowrap">
{b.label}
</span>
</span>
))}
</div>
)}
{data?.medical_system_code && (
<p className="mt-[12px] text-[#7A93AC] text-[20px]">
کد نظام پزشکی:{" "}
<span dir="ltr" className="font-semibold text-[#4A6B8C]">
{data.medical_system_code}
</span>
</p>
)}
{/* services */}
{shownServices.length > 0 && (
<div className="w-full mt-[36px] rounded-[24px] bg-white border border-[#E2ECF5] p-[32px]">
<p className="text-[#12365C] text-[26px] font-bold mb-[20px]">خدمات</p>
<div className="flex flex-wrap justify-center gap-x-[12px] gap-y-[14px]">
{shownServices.map((s, i) => (
<span
key={i}
className="flex items-center gap-[10px] rounded-full bg-[#F6F9FD] border border-[#E2ECF5] px-[18px] py-[10px]"
>
<span
className="w-[9px] h-[9px] rounded-full shrink-0"
style={{ background: ORANGE }}
/>
<span className="text-[#33526F] text-[22px] leading-none whitespace-nowrap">
{s.name}
</span>
</span>
))}
{moreServices > 0 && (
<span className="text-[#7A93AC] text-[22px]">
+ {moreServices} خدمت دیگر
</span>
)}
</div>
</div>
)}
{/* address / phone */}
{(address || phones.length > 0) && (
<div className="w-full mt-[24px] rounded-[24px] bg-white border border-[#E2ECF5] p-[32px]">
{address && (
<p className="text-[#33526F] text-[23px] leading-[1.7] line-clamp-2">
<span className="font-bold text-[#12365C]">آدرس: </span>
{address}
</p>
)}
{phones.length > 0 && (
<p className={`text-[#33526F] text-[23px] ${address ? "mt-[16px]" : ""}`}>
<span className="font-bold text-[#12365C]">تلفن: </span>
{phones.map((p, i) => (
<span key={i} dir="ltr" className="font-semibold">
{p}
{i < phones.length - 1 ? " | " : ""}
</span>
))}
</p>
)}
</div>
)}
</div>
{/* footer */}
<div
className="relative mt-[28px] flex items-center justify-between px-[72px] py-[32px]"
style={{
background: `linear-gradient(135deg, ${ACCENT} 0%, ${ACCENT_LIGHT} 100%)`,
}}
>
<div className="flex items-center gap-[24px]">
<div className="rounded-[18px] bg-white p-[14px]">
<QrImg onReady={onQrReady} size={150} />
</div>
<div className="flex flex-col items-start">
<span className="text-white text-[28px] font-bold">
رزرو نوبت آنلاین اسکن کنید
</span>
<span className="text-[#CFE2F6] text-[21px] mt-[8px]" dir="ltr">
{city?.domain || "nobat724.com"}
</span>
</div>
</div>
<div
className="flex items-center rounded-full px-[26px] py-[13px]"
style={{ background: ORANGE }}
>
<span className="text-white text-[22px] font-bold leading-none">
نوبتدهی ۲۴ ساعته
</span>
</div>
</div>
</div>
);
}
export default PosterLight;