/* === Global modals, cookie banner, mobile CTA bar === */
/* Event-driven: dispatch a CustomEvent on window to open any modal from anywhere */
const { useState: useStateM, useEffect: useEffectM, useRef: useRefM } = React;
/* ---------- Booking modal ---------- */
function BookingModal() {
const [open, setOpen] = useStateM(false);
const [trigger, setTrigger] = useStateM("default");
const [form, setForm] = useStateM({ name: "", phone: "", consent: false });
const [errors, setErrors] = useStateM({});
const [submitting, setSubmitting] = useStateM(false);
const [submitted, setSubmitted] = useStateM(false);
useEffectM(() => {
const onOpen = (e) => {
setTrigger((e.detail && e.detail.trigger) || "default");
setSubmitted(false);
setForm({ name: "", phone: "", consent: false });
setErrors({});
setOpen(true);
};
window.addEventListener("open-booking", onOpen);
return () => window.removeEventListener("open-booking", onOpen);
}, []);
useEffectM(() => {
document.body.classList.toggle("modal-open", open);
const onKey = (e) => { if (e.key === "Escape") setOpen(false); };
window.addEventListener("keydown", onKey);
return () => window.removeEventListener("keydown", onKey);
}, [open]);
if (!open) return null;
const set = (k) => (e) => {
const v = e.target.type === "checkbox" ? e.target.checked : e.target.value;
setForm({ ...form, [k]: v });
if (errors[k]) setErrors({ ...errors, [k]: null });
};
const submit = async (ev) => {
ev.preventDefault();
const e = {};
if (!form.name.trim() || form.name.trim().length < 2) e.name = "Укажите имя";
if (!/^[+\d][\d\s()\-]{8,}$/.test(form.phone.trim())) e.phone = "Введите телефон в формате +7…";
if (!form.consent) e.consent = "Без согласия не можем продолжить";
if (Object.keys(e).length) { setErrors(e); return; }
setSubmitting(true);
try {
const body = new URLSearchParams();
body.set("name", form.name.trim());
body.set("phone", form.phone.trim());
body.set("consent", "true");
body.set("source", trigger || "booking");
const r = await fetch("/api/booking", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: body.toString(),
});
const data = await r.json().catch(() => ({}));
if (!r.ok || data.ok === false) {
const fieldErrs = {};
(data.errors || []).forEach((k) => {
if (k === "name") fieldErrs.name = "Проверьте имя";
else if (k === "phone") fieldErrs.phone = "Проверьте телефон";
else if (k === "consent") fieldErrs.consent = "Без согласия не можем продолжить";
});
if (!Object.keys(fieldErrs).length) fieldErrs.phone = r.status === 429 ? "Слишком много запросов — попробуйте позже" : "Не удалось отправить — позвоните по телефону";
setErrors(fieldErrs);
setSubmitting(false);
return;
}
setSubmitting(false);
setSubmitted(true);
} catch (err) {
setErrors({ phone: "Сеть недоступна — позвоните по телефону" });
setSubmitting(false);
}
};
const titles = {
default: { eyebrow: "Запись", title: "Бесплатная консультация" },
method: { eyebrow: "Метод", title: "Узнать, подходит ли мне реконструкция" },
cta: { eyebrow: "Запись", title: "Записаться на ближайшее время" },
snapshot:{ eyebrow: "Снимки", title: "Получить заключение по МРТ/КТ" },
};
const t = titles[trigger] || titles.default;
return (
e.target === e.currentTarget && setOpen(false)}>
setOpen(false)} aria-label="Закрыть">×
{submitted ? (
<>
✓
Заявка принята
Спасибо, {form.name || "пациент"}. Координатор перезвонит на номер{" "}
{form.phone} {" "}
в течение рабочего дня (8:00–20:00, без выходных).
Пока ждёте — можно прислать МРТ/КТ на e-mail:{" "}
med4424@yandex.ru
.
setOpen(false)} style={{ alignSelf: "flex-start", marginTop: 8 }}>
Хорошо
>
) : (
)}
);
}
/* ---------- Indication detail modal ---------- */
function IndicationModal() {
const [openKey, setOpenKey] = useStateM(null);
useEffectM(() => {
const onOpen = (e) => setOpenKey(e.detail.key);
window.addEventListener("open-indication", onOpen);
return () => window.removeEventListener("open-indication", onOpen);
}, []);
useEffectM(() => {
document.body.classList.toggle("modal-open", !!openKey);
const onKey = (e) => { if (e.key === "Escape") setOpenKey(null); };
window.addEventListener("keydown", onKey);
return () => window.removeEventListener("keydown", onKey);
}, [openKey]);
if (!openKey) return null;
const details = window.INDICATION_DETAILS || {};
const data = details[openKey];
if (!data) return null;
return (
e.target === e.currentTarget && setOpenKey(null)}>
setOpenKey(null)} aria-label="Закрыть">×
Что мы лечим
{data.title}
Признаки и симптомы
{data.bullets.map((b, i) => (
))}
{
setOpenKey(null);
setTimeout(() => window.dispatchEvent(new CustomEvent("open-booking", { detail: { trigger: "default" } })), 200);
}}
>
Записаться на консультацию →
+7 (812) 907-50-50
);
}
/* ---------- Doctor profile modal ---------- */
function DoctorModal() {
const [open, setOpen] = useStateM(false);
const [activeDoc, setActiveDoc] = useStateM(null);
useEffectM(() => {
const onOpen = () => setOpen(true);
window.addEventListener("open-doctor", onOpen);
return () => window.removeEventListener("open-doctor", onOpen);
}, []);
useEffectM(() => {
document.body.classList.toggle("modal-open", open);
const onKey = (e) => {
if (e.key === "Escape") {
if (activeDoc) setActiveDoc(null);
else setOpen(false);
}
};
window.addEventListener("keydown", onKey);
return () => window.removeEventListener("keydown", onKey);
}, [open, activeDoc]);
if (!open) return null;
// Реальные сканы документов с pervaya-medklinika.ru/yastrebov/
// Подписи на сайте клиники отсутствуют — даны нейтральные.
const documents = [
{ id: "vk", title: "Удостоверение о высшей квалификационной категории", image: "doctor-certs/cert-VK-preview.jpg", pdf: "doctor-certs/cert-VK.pdf" },
{ id: "diplom1", title: "Диплом · медицинский институт ПетрГУ, 2001", image: "doctor-certs/cert-04.jpg" },
{ id: "diplom2", title: "Диплом ординатуры · нейрохирургия, РНХИ им. А. Л. Поленова, 2006", image: "doctor-certs/cert-05.jpg" },
{ id: "diplom3", title: "Диплом / удостоверение об образовании", image: "doctor-certs/cert-06.jpg" },
{ id: "diplom4", title: "Диплом / удостоверение об образовании", image: "doctor-certs/cert-07.jpg" },
{ id: "diplom5", title: "Диплом / удостоверение об образовании", image: "doctor-certs/cert-08.jpg" },
{ id: "cert1", title: "Сертификат специалиста", image: "doctor-certs/cert-01.jpg" },
{ id: "cert2", title: "Сертификат специалиста", image: "doctor-certs/cert-02.jpg" },
{ id: "cert3", title: "Сертификат специалиста", image: "doctor-certs/cert-03.jpg" },
];
return (
e.target === e.currentTarget && setOpen(false)}>
setOpen(false)} aria-label="Закрыть">×
Главный врач клиники
Ястребов Роман Александрович
Нейрохирург
Вертебролог
Артролог
Ортопед-травматолог
Высшая категория
О враче
Главный врач «Первой Медицинской Клиники», нейрохирург высшей квалификационной
категории. Стаж более {new Date().getFullYear() - 2006} лет. Закончил медицинский институт ПетрГУ
в 2001 году, прошёл интернатуру в Республиканской больнице
им. В. А. Баранова (Петрозаводск, 2002), ординатуру по нейрохирургии
в РНХИ им. профессора А. Л. Поленова (2006).
Авторская методика — реконструкция межпозвонкового диска под мониторинговой
3D-навигацией. Проводит процедуру лично — таких специалистов в России
не много. Профильные направления: нейрохирургия, вертебрология, артрология,
ортопедия-травматология.
Образование и опыт
2001
Медицинский институт ПетрГУ
2002
Интернатура · Республиканская больница им. В. А. Баранова, Петрозаводск
2002–2004
Больница скорой медицинской помощи, Петрозаводск
2004–2006
РНХИ им. профессора А. Л. Поленова · ординатура и работа
2006–2016
Практика в частных клиниках Санкт-Петербурга
Сейчас
Главный врач «Первой Медицинской Клиники». Реконструкция межпозвонковых дисков под 3D-навигацией
Документы и сертификаты
Сканы с официального сайта клиники. Нажмите на документ —
откроется полная версия. Оригиналы доступны для просмотра в клинике.
{documents.map((d) => (
setActiveDoc(d)}
>
{d.image ? (
) : (
)}
))}
{
setOpen(false);
setTimeout(() => window.dispatchEvent(new CustomEvent("open-booking", { detail: { trigger: "default" } })), 200);
}}
>
Записаться к врачу →
+7 (812) 907-50-50
{activeDoc && (
setActiveDoc(null)}>
{ e.stopPropagation(); setActiveDoc(null); }}
aria-label="Закрыть"
>×
e.stopPropagation()}>
{activeDoc.image ? (
) : (
Скан документа загрузит администратор клиники.
Оригинал доступен для просмотра в клинике
при первом приёме.
)}
)}
);
}
function EmailModal() {
const [open, setOpen] = useStateM(false);
useEffectM(() => {
const onOpen = () => setOpen(true);
window.addEventListener("open-email", onOpen);
return () => window.removeEventListener("open-email", onOpen);
}, []);
// Любой клик по ссылке на e-mail клиники открывает эту модалку (кнопка «открыть почту» с ?subject не затрагивается)
useEffectM(() => {
const onClick = (e) => {
const a = e.target.closest && e.target.closest('a[href="mailto:med4424@yandex.ru"]');
if (a) { e.preventDefault(); setOpen(true); }
};
document.addEventListener("click", onClick);
return () => document.removeEventListener("click", onClick);
}, []);
useEffectM(() => {
document.body.classList.toggle("modal-open", open);
const onKey = (e) => { if (e.key === "Escape") setOpen(false); };
window.addEventListener("keydown", onKey);
return () => window.removeEventListener("keydown", onKey);
}, [open]);
const [copied, setCopied] = useStateM(false);
const copyEmail = () => {
const text = "med4424@yandex.ru";
const fallback = () => {
try {
const ta = document.createElement("textarea");
ta.value = text;
ta.setAttribute("readonly", "");
ta.style.cssText = "position:fixed;top:0;left:0;width:1px;height:1px;opacity:0;";
document.body.appendChild(ta);
ta.focus();
ta.select();
ta.setSelectionRange(0, text.length);
document.execCommand("copy");
document.body.removeChild(ta);
} catch (e) {}
};
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(text).catch(fallback);
} else {
fallback();
}
setCopied(true);
setTimeout(() => setCopied(false), 2000);
};
if (!open) return null;
const subject = encodeURIComponent("Снимки на консультацию");
const body = encodeURIComponent(
"Здравствуйте!\n\nПрилагаю имеющиеся МРТ/КТ и описание жалоб.\n\nИмя:\nТелефон:\nДиагноз/жалобы:\n\nСпасибо."
);
return (
e.target === e.currentTarget && setOpen(false)}>
setOpen(false)} aria-label="Закрыть">×
Заочная консультация
Пришлите МРТ или КТ на e-mail
Прикрепите снимки (DICOM, JPG или ссылку на облако) и опишите жалобы.
Врач посмотрит в течение 1–2 рабочих дней и скажет, подходит ли
вам процедура и какова ориентировочная стоимость. Бесплатно, без обязательств.
Отправляя снимки, вы соглашаетесь на обработку медицинских данных
в целях получения консультации согласно ФЗ-152 и ст. 13 ФЗ-323.
);
}
/* ---------- Cookie banner ---------- */
function CookieBanner() {
const [visible, setVisible] = useStateM(false);
useEffectM(() => {
try {
const v = localStorage.getItem("pmk-cookies");
if (!v) setVisible(true);
} catch (e) {
setVisible(true);
}
}, []);
const ack = (level) => {
try { localStorage.setItem("pmk-cookies", level); } catch (e) {}
setVisible(false);
};
if (!visible) return null;
return (
Сайт использует cookies для корректной работы и анализа посещений.
Продолжая просмотр, вы соглашаетесь с {" "}
политикой обработки персональных данных {" "}
и использованием cookie-файлов в соответствии с ФЗ-152.
ack("essential")}>Только необходимые
ack("all")}>Принять все
);
}
/* ---------- Mobile sticky CTA bar ---------- */
function MobileCtaBar() {
return (
);
}
/* ---------- Inline CTA card (drop into sections) ---------- */
function CtaCard({ title, text, primaryLabel = "Записаться", trigger = "default", secondary = "email", call = false }) {
return (
Следующий шаг
{title}
{text &&
{text}
}
window.dispatchEvent(new CustomEvent("open-booking", { detail: { trigger } }))}
>
{primaryLabel} →
{secondary === "email" && (
window.dispatchEvent(new CustomEvent("open-email"))}
>
Прислать МРТ на e-mail
)}
Позвонить
);
}
/* ---------- Legal section (drop into footer) ---------- */
function LegalSection() {
return (
!
Имеются противопоказания. Необходима консультация специалиста.
{" "}
Информация на сайте носит ознакомительный характер и не является
публичной офертой (ст. 437 ГК РФ). Медицинская реклама размещена
в соответствии со ст. 24 ФЗ «О рекламе».
Реквизиты клиники
ООО «Е.М.К.» — Первая Медицинская Клиника
ИНН 7838486399 · КПП 783801001
ОГРН 1137847066862
190013, Санкт-Петербург, ул. Рузовская, д. 29, литера А, пом. 6-Н
Лицензия и документы
Медицинская деятельность осуществляется на основании лицензии
№ window.dispatchEvent(new CustomEvent("open-license"))} className="mono" style={{ color: "var(--bg)", background: "none", border: 0, borderBottom: "1px solid currentColor", padding: 0, font: "inherit", cursor: "pointer" }}>ЛО-78-01-011038 ,
выданной Комитетом по здравоохранению Санкт-Петербурга. Учредительные
документы и перечень работ/услуг предоставляются согласно ст. 79 ФЗ-323
«Об основах охраны здоровья граждан в РФ».
Персональные данные · cookies
Обработка данных проводится в соответствии с ФЗ-152
«О персональных данных». Подробнее — в
политике конфиденциальности .
Cookie-файлы используются для аналитики и улучшения работы сайта.
Права пациента
Право на информированное добровольное согласие, отказ от медицинского
вмешательства, выбор врача и клиники, врачебную тайну — гарантируются
ст. 19–22 ФЗ-323. Жалобы и претензии — на med4424@yandex.ru .
);
}
/* ---------- License modal ---------- */
function LicenseModal() {
const [open, setOpen] = useStateM(false);
useEffectM(() => {
const onOpen = () => setOpen(true);
window.addEventListener("open-license", onOpen);
return () => window.removeEventListener("open-license", onOpen);
}, []);
useEffectM(() => {
document.body.classList.toggle("modal-open", open);
const onKey = (e) => { if (e.key === "Escape") setOpen(false); };
window.addEventListener("keydown", onKey);
return () => window.removeEventListener("keydown", onKey);
}, [open]);
if (!open) return null;
return (
e.target === e.currentTarget && setOpen(false)}>
setOpen(false)} aria-label="Закрыть">×
Лицензия
№ ЛО-78-01-011038
На осуществление медицинской деятельности. ООО «Е.М.К.» — Первая Медицинская Клиника.
Выдана Комитетом по здравоохранению Санкт-Петербурга.
Нажмите на изображение, чтобы открыть в полном размере
);
}
/* ---------- Root: mount all globals ---------- */
function GlobalUI() {
return (
<>
>
);
}
/* ---------- Helper: open booking from any onClick ---------- */
function openBooking(trigger = "default") {
window.dispatchEvent(new CustomEvent("open-booking", { detail: { trigger } }));
}
function openEmail() {
window.dispatchEvent(new CustomEvent("open-email"));
}
Object.assign(window, {
BookingModal, EmailModal, CookieBanner, MobileCtaBar,
CtaCard, LegalSection, GlobalUI, openBooking, openEmail,
});