Files
nobat724_front/.claude/prompt/clinic-contact-info-fix.md
T

11 KiB
Raw Blame History

اصلاح بخش «اطلاعات تماس» صفحه کلینیک

پروژه

nobat724_front

زمینه

صفحه عمومی کلینیک (/clinic/[uuid]) یک کارت «اطلاعات تماس» دارد که روزهای کاری، تلفن، آدرس و نقشه را نشان می‌دهد. روی نمونه واقعی:

http://karaj-nobat.localhost:3000/clinic/bcb00726-2343-4d63-90c6-d0175cc74591

پاسخ واقعی API (GET /api/v1/clinic/{uuid} روی https://clinic-pro.ddev.site) این است:

{
  "name": "کلینیک تست QA",
  "title": "کلینیک تست QA",
  "phone": "03531234567",
  "phone_number": "03531234567",
  "city":  [{ "id": "105", "name": "کرج",   "parent": "5" }],
  "state": [{ "id": "5",   "name": "البرز" }],
  "location": "یزد، خیابان تست QA",
  "map": { "latitude": null, "longitude": null },
  "24_7": false,
  "field_working_days": null
}

اطلاعات تماسی که روی صفحه رندر می‌شود با این داده هم‌خوان نیست.

مشکل / هدف

۱. شهر/استان اصلاً نمایش داده نمی‌شود. API فیلدهای city[0].name و state[0].name را می‌فرستد ولی UI فقط رشتهٔ آزاد location را چاپ می‌کند. نتیجه: کاربر روی دامنهٔ کرج آدرس «یزد، خیابان تست QA» می‌بیند بدون هیچ نشانه‌ای از شهر واقعی کلینیک (کرج/البرز).

۲. تلفن خام رندر می‌شود. 03531234567 بدون هیچ جداکننده‌ای نمایش داده می‌شود و مقدار href="tel:..." هم مستقیم از همان رشته ساخته می‌شود (اگر مقدار DB فاصله یا - داشته باشد، tel: خراب می‌شود).

۳. ناسازگاری منبع تلفن بین UI و JSON-LD. کامپوننت Detail.js از phone_number || phone می‌خواند اما JSON-LD در page.js فقط clinic.phone را می‌خواند. اگر یکی پر و دیگری خالی باشد، صفحه و structured data دو چیز متفاوت می‌گویند.

۴. PostalAddress در JSON-LD ناقص است. فقط streetAddress و addressCountry دارد؛ addressLocality (شهر) و addressRegion (استان) ندارند در حالی که داده‌اش موجود است.

۵. کارت خالی. وقتی field_working_days و phone و location همه null باشند و مختصات هم نباشد، Detail مقدار null برمی‌گرداند و mapQuery خالی است — ولی کارت <div className="border ... rounded-[16px] ..."> همچنان رندر می‌شود و یک باکس خالی روی صفحه می‌ماند.

فایل‌های مرتبط

فایل نقش
nobat724_front/components/clinic/components/contact/Detail.js رندر ردیف‌های روزهای کاری / تلفن / آدرس
nobat724_front/components/clinic/components/contact/index.js کارت اطلاعات تماس + نقشه + دکمه مسیریابی
nobat724_front/components/clinic/components/contact/ContentDetail.js ردیف head/detail (تغییر ندارد)
nobat724_front/app/clinic/[slug]/page.js JSON-LD نوع MedicalClinic (فیلدهای telephone و address)

وضعیت فعلی

components/clinic/components/contact/Detail.js:

function Detail({ data }) {
  const workingDays = data?.["24_7"]
    ? "۲۴ ساعته، تمام روزهای هفته"
    : data?.field_working_days?.trim();
  const phone = (data?.phone_number || data?.phone || "").trim();
  const address = data?.location?.trim();

  if (!workingDays && !phone && !address) return null;

  return (
    <ul className="flex flex-col items-start justify-start gap-[16px]">
      {workingDays && (
        <ContentDetail head="روزهای کاری: " detail={workingDays} />
      )}
      {phone && (
        <ContentDetail
          head="تلفن: "
          detail={
            <a href={`tel:${phone}`} dir="ltr" className="hover:text-[#5559CE]">
              {phone}
            </a>
          }
        />
      )}
      {address && <ContentDetail head="آدرس: " detail={address} />}
    </ul>
  );
}

app/clinic/[slug]/page.js (بخش JSON-LD):

...(clinic.phone && { telephone: clinic.phone }),
...(clinic.location && {
  address: {
    "@type": "PostalAddress",
    streetAddress: clinic.location,
    addressCountry: "IR",
  },
}),

وظایف

۱. ساخت helper مشترک برای اطلاعات تماس کلینیک

فایل جدید nobat724_front/lib/clinicContact.js بساز تا هم UI و هم JSON-LD از یک منبع بخوانند (رفع مشکل ۳):

// lib/clinicContact.js

/** API هر دو کلید را می‌فرستد؛ یکی ممکن است null باشد. یک منبع واحد. */
export function getClinicPhone(clinic) {
  return (clinic?.phone_number || clinic?.phone || "").toString().trim();
}

/** فقط رقم — برای href="tel:" تا فاصله/خط تیرهٔ داخل DB لینک را خراب نکند. */
export function telHref(phone) {
  const digits = (phone || "").replace(/[^\d+]/g, "");
  return digits ? `tel:${digits}` : null;
}

/** 03531234567 → +983531234567 برای schema.org telephone */
export function toE164Ir(phone) {
  const d = (phone || "").replace(/\D/g, "");
  if (!d) return null;
  if (d.startsWith("98")) return `+${d}`;
  if (d.startsWith("0")) return `+98${d.slice(1)}`;
  return `+98${d}`;
}

export function getClinicCity(clinic) {
  return clinic?.city?.[0]?.name?.trim() || null;
}

export function getClinicState(clinic) {
  return clinic?.state?.[0]?.name?.trim() || null;
}

/**
 * آدرس نمایشی: «استان، شهر — نشانی».
 * اگر خودِ location قبلاً نام شهر را داشته باشد دوباره تکرار نمی‌شود.
 */
export function getClinicAddress(clinic) {
  const street = clinic?.location?.trim() || "";
  const city = getClinicCity(clinic);
  const state = getClinicState(clinic);

  const parts = [];
  if (state && !street.includes(state)) parts.push(state);
  if (city && !street.includes(city)) parts.push(city);
  if (street) parts.push(street);

  return parts.length ? parts.join("، ") : null;
}

/** آیا اصلاً چیزی برای نمایش در کارت تماس هست؟ */
export function hasClinicContact(clinic) {
  return Boolean(
    clinic?.["24_7"] ||
      clinic?.field_working_days?.trim() ||
      getClinicPhone(clinic) ||
      getClinicAddress(clinic) ||
      (clinic?.map?.latitude && clinic?.map?.longitude)
  );
}

۲. اصلاح Detail.js

  • تلفن و آدرس را از helper بگیر.
  • tel: را از telHref() بساز؛ اگر null بود فقط متن ساده رندر کن (نه لینک شکسته).
  • شهر/استان را در ردیف آدرس نشان بده.
import ContentDetail from "./ContentDetail";
import { getClinicPhone, telHref, getClinicAddress } from "@/lib/clinicContact";

function Detail({ data }) {
  const workingDays = data?.["24_7"]
    ? "۲۴ ساعته، تمام روزهای هفته"
    : data?.field_working_days?.trim();
  const phone = getClinicPhone(data);
  const href = telHref(phone);
  const address = getClinicAddress(data);

  if (!workingDays && !phone && !address) return null;

  return (
    <ul className="flex flex-col items-start justify-start gap-[16px]">
      {workingDays && <ContentDetail head="روزهای کاری: " detail={workingDays} />}
      {phone && (
        <ContentDetail
          head="تلفن: "
          detail={
            href ? (
              <a href={href} dir="ltr" className="hover:text-[#5559CE]">
                {phone}
              </a>
            ) : (
              <span dir="ltr">{phone}</span>
            )
          }
        />
      )}
      {address && <ContentDetail head="آدرس: " detail={address} />}
    </ul>
  );
}

۳. جلوگیری از کارت خالی در contact/index.js

در ابتدای Contact، اگر hasClinicContact(data) نادرست بود null برگردان تا باکس border-dar خالی رندر نشود:

import { hasClinicContact } from "@/lib/clinicContact";

function Contact({ data }) {
  const [open, setOpen] = useState(false);
  // ...
  if (!hasClinicContact(data)) return null;
  // ...
}

هوک‌ها باید قبل از این return صدا زده شوند (قانون hooks) — useState را بالای شرط نگه دار.

همچنین در ساخت mapQuery، به‌جای data.location از getClinicAddress(data) استفاده کن تا وقتی مختصات نیست، کوئری نقشه شامل شهر/استان باشد و پین به شهر درست بیفتد (نمونهٔ فعلی: location = "یزد، خیابان تست QA" ولی شهر واقعی «کرج» است — بدون شهر، نقشه یزد را نشان می‌دهد).

۴. تکمیل JSON-LD در app/clinic/[slug]/page.js

import {
  getClinicPhone,
  toE164Ir,
  getClinicCity,
  getClinicState,
} from "@/lib/clinicContact";

// ...
const clinicPhone = getClinicPhone(clinic);
const clinicCity = getClinicCity(clinic);
const clinicState = getClinicState(clinic);

const jsonLd = clinic ? {
  // ...
  ...(clinicPhone && { telephone: toE164Ir(clinicPhone) }),
  ...((clinic.location || clinicCity) && {
    address: {
      "@type": "PostalAddress",
      ...(clinic.location && { streetAddress: clinic.location.trim() }),
      ...(clinicCity && { addressLocality: clinicCity }),
      ...(clinicState && { addressRegion: clinicState }),
      addressCountry: "IR",
    },
  }),
  // ...
} : null;

نکات مهم

  • Server/Client: contact/index.js کلاینت است (useStatelib/clinicContact.js باید pure و بدون وابستگی به next/headers بماند تا هم در Server Component (page.js) و هم در Client Component قابل import باشد.
  • مقدار null رشته‌ای: API برای فیلدهای پرنشده null می‌فرستد؛ هیچ‌جا مستقیم داخل template string نگذار (کامنت موجود در Detail.js همین را هشدار می‌دهد) — همهٔ helperها باید null برگردانند نه رشتهٔ خالیِ درج‌شده.
  • 24_7 کلید عددی‌شروع است — همیشه با bracket notation (clinic["24_7"]) خوانده شود.
  • تکرار شهر: بعضی رکوردها نام شهر را داخل خود location دارند؛ منطق getClinicAddress باید تکرار را حذف کند (تست: location="کرج، بلوار..." + city="کرج" → خروجی نباید «کرج، کرج، بلوار...» باشد).
  • JSON-LD sanitize: خروجی همچنان باید از safeJsonLd() عبور کند (الگوی فعلی page.js).
  • تغییر backend لازم نیست — همهٔ فیلدها (city, state, phone, phone_number, location) در پاسخ فعلی API موجودند.
  • بررسی رگرسیون: اگر صفحهٔ پزشک (components/doctor/...) هم آدرس کلینیک را همین‌طور رندر می‌کند، فقط گزارش بده — در این تسک تغییرش نده.