Replace public legal files and switch booking to a contact request without a schedule.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@moniki/web",
|
||||
"version": "1.0.5",
|
||||
"version": "1.0.6",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev -p 3000",
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,37 @@
|
||||
import { createInquiry } from "@/lib/api";
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
function asText(value: unknown) {
|
||||
return typeof value === "string" ? value.trim() : "";
|
||||
}
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const body = (await request.json()) as Record<string, unknown>;
|
||||
const name = asText(body.name ?? body.patientName);
|
||||
const email = asText(body.email);
|
||||
const phone = asText(body.phone);
|
||||
const branchId = asText(body.branchId);
|
||||
const serviceId = asText(body.serviceId) || undefined;
|
||||
const doctorId = asText(body.doctorId) || undefined;
|
||||
|
||||
if (!name || !email || !phone || !branchId || (!serviceId && !doctorId)) {
|
||||
return NextResponse.json({ error: "invalid" }, { status: 400 });
|
||||
}
|
||||
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
||||
return NextResponse.json({ error: "email" }, { status: 400 });
|
||||
}
|
||||
|
||||
try {
|
||||
const inquiry = await createInquiry({
|
||||
name,
|
||||
email,
|
||||
phone,
|
||||
branchId,
|
||||
serviceId,
|
||||
doctorId,
|
||||
});
|
||||
return NextResponse.json(inquiry);
|
||||
} catch {
|
||||
return NextResponse.json({ error: "unavailable" }, { status: 503 });
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import { Button } from "@/components/ui/Button";
|
||||
import { Card } from "@/components/ui/Card";
|
||||
import { Input } from "@/components/ui/Input";
|
||||
import { Select } from "@/components/ui/Select";
|
||||
import type { Branch, Doctor, Service, Slot } from "@/lib/demo-data";
|
||||
import type { Branch, Doctor, Service } from "@/lib/demo-data";
|
||||
import { matchesQuery } from "@/lib/search";
|
||||
import { cn, formatPrice, stripPriceListIndex } from "@/lib/utils";
|
||||
import { useTranslations, useLocale } from "next-intl";
|
||||
@@ -21,7 +21,6 @@ type Props = {
|
||||
const steps = [
|
||||
{ id: "service", labelKey: "stepService" },
|
||||
{ id: "branch", labelKey: "stepBranch" },
|
||||
{ id: "slot", labelKey: "stepSlot" },
|
||||
{ id: "confirm", labelKey: "stepConfirm" },
|
||||
] as const;
|
||||
|
||||
@@ -40,10 +39,8 @@ export function BookingWizard({
|
||||
const [services, setServices] = useState<Service[]>([]);
|
||||
const [serviceQuery, setServiceQuery] = useState("");
|
||||
const [branchId, setBranchId] = useState(initialBranchId || "");
|
||||
const [date, setDate] = useState(() => new Date().toISOString().slice(0, 10));
|
||||
const [slots, setSlots] = useState<Slot[]>([]);
|
||||
const [slotId, setSlotId] = useState("");
|
||||
const [name, setName] = useState("");
|
||||
const [email, setEmail] = useState("");
|
||||
const [phone, setPhone] = useState("");
|
||||
const [done, setDone] = useState(false);
|
||||
const [error, setError] = useState("");
|
||||
@@ -61,7 +58,6 @@ export function BookingWizard({
|
||||
const service = services.find((s) => s.id === serviceId);
|
||||
const doctor = doctors.find((d) => d.id === doctorId);
|
||||
const branch = branches.find((b) => b.id === branchId);
|
||||
const slot = slots.find((s) => s.id === slotId);
|
||||
|
||||
const visibleServices = useMemo(() => {
|
||||
const list = !serviceQuery.trim()
|
||||
@@ -81,54 +77,36 @@ export function BookingWizard({
|
||||
return doctors.filter((d) => d.services.includes(serviceId));
|
||||
}, [doctors, serviceId]);
|
||||
|
||||
async function loadSlots(nextDate: string, nextBranch: string) {
|
||||
const params = new URLSearchParams({
|
||||
date: nextDate,
|
||||
branchId: nextBranch,
|
||||
});
|
||||
if (doctorId) params.set("doctorId", doctorId);
|
||||
if (serviceId) params.set("serviceId", serviceId);
|
||||
const res = await fetch(`/api/slots?${params}`);
|
||||
const data = (await res.json()) as Slot[];
|
||||
setSlots(data);
|
||||
setSlotId("");
|
||||
}
|
||||
|
||||
function canNext() {
|
||||
if (step === 0) return Boolean(serviceId || doctorId);
|
||||
if (step === 1) return Boolean(branchId);
|
||||
if (step === 2) return Boolean(slotId);
|
||||
return Boolean(name && phone);
|
||||
return Boolean(name.trim() && email.trim() && phone.trim());
|
||||
}
|
||||
|
||||
async function onNext() {
|
||||
setError("");
|
||||
if (step === 1 && branchId) {
|
||||
await loadSlots(date, branchId);
|
||||
}
|
||||
if (step < steps.length - 1) {
|
||||
setStep((s) => s + 1);
|
||||
return;
|
||||
}
|
||||
startTransition(async () => {
|
||||
try {
|
||||
const res = await fetch("/api/bookings", {
|
||||
const res = await fetch("/api/inquiries", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
serviceId: serviceId || undefined,
|
||||
doctorId: doctorId || undefined,
|
||||
branchId,
|
||||
slotId,
|
||||
datetime: slot?.start,
|
||||
patientName: name,
|
||||
phone,
|
||||
name: name.trim(),
|
||||
email: email.trim(),
|
||||
phone: phone.trim(),
|
||||
}),
|
||||
});
|
||||
if (!res.ok) throw new Error("fail");
|
||||
setDone(true);
|
||||
} catch {
|
||||
setError(t("confirm"));
|
||||
setError(t("error"));
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -177,7 +155,7 @@ export function BookingWizard({
|
||||
<button
|
||||
key={s.id}
|
||||
type="button"
|
||||
onClick={() => setServiceId(s.id)}
|
||||
onClick={() => setServiceId(s.id === serviceId ? "" : s.id)}
|
||||
className={cn(
|
||||
"flex w-full items-center justify-between gap-3 border-b border-[var(--border)] px-3 py-2 text-left text-sm last:border-b-0",
|
||||
serviceId === s.id
|
||||
@@ -233,53 +211,6 @@ export function BookingWizard({
|
||||
) : null}
|
||||
|
||||
{step === 2 ? (
|
||||
<>
|
||||
<Input
|
||||
type="date"
|
||||
label={t("selectDate")}
|
||||
value={date}
|
||||
onChange={async (e) => {
|
||||
const v = e.target.value;
|
||||
setDate(v);
|
||||
if (branchId) await loadSlots(v, branchId);
|
||||
}}
|
||||
/>
|
||||
<div>
|
||||
<p className="mb-2 text-sm font-medium text-[var(--neutral-700)]">{t("selectTime")}</p>
|
||||
{slots.filter((s) => s.available).length === 0 ? (
|
||||
<p className="text-sm text-[var(--text-muted)]">{t("noSlots")}</p>
|
||||
) : (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{slots
|
||||
.filter((s) => s.available)
|
||||
.map((s) => {
|
||||
const label = new Date(s.start).toLocaleTimeString([], {
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
});
|
||||
return (
|
||||
<button
|
||||
key={s.id}
|
||||
type="button"
|
||||
onClick={() => setSlotId(s.id)}
|
||||
className={cn(
|
||||
"rounded-[10px] border px-3 py-2 text-sm font-medium",
|
||||
slotId === s.id
|
||||
? "border-[var(--brand-500)] bg-[var(--brand-600)] text-white"
|
||||
: "border-[var(--border)] hover:border-[var(--brand-300)]",
|
||||
)}
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
) : null}
|
||||
|
||||
{step === 3 ? (
|
||||
<>
|
||||
<div className="rounded-[10px] bg-[var(--neutral-50)] p-4 text-sm">
|
||||
<p className="font-semibold">{t("summary")}</p>
|
||||
@@ -287,11 +218,16 @@ export function BookingWizard({
|
||||
{service ? <li>{stripPriceListIndex(service.title)}</li> : null}
|
||||
{doctor ? <li>{doctor.name}</li> : null}
|
||||
{branch ? <li>{branch.name}</li> : null}
|
||||
{slot ? <li>{new Date(slot.start).toLocaleString()}</li> : null}
|
||||
{service ? <li>{formatPrice(service.price, locale)}</li> : null}
|
||||
</ul>
|
||||
</div>
|
||||
<Input label={t("patientName")} value={name} onChange={(e) => setName(e.target.value)} required />
|
||||
<Input
|
||||
type="email"
|
||||
label={t("email")}
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
required
|
||||
/>
|
||||
<Input label={t("phone")} value={phone} onChange={(e) => setPhone(e.target.value)} required />
|
||||
</>
|
||||
) : null}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Card } from "@/components/ui/Card";
|
||||
import {
|
||||
FALLBACK_PUBLIC_CONTRACT_HREF,
|
||||
FALLBACK_PUBLIC_DOCUMENTS,
|
||||
getPublicDocuments,
|
||||
type PublicDocument,
|
||||
} from "@/lib/api";
|
||||
@@ -19,16 +19,7 @@ function fileLabel(item: PublicDocument) {
|
||||
export async function PublicDocumentsList({ locale }: { locale: string }) {
|
||||
const t = await getTranslations("legal.documentsPage");
|
||||
const docs = await getPublicDocuments(locale);
|
||||
const items: PublicDocument[] = docs.length
|
||||
? docs
|
||||
: [
|
||||
{
|
||||
id: 0,
|
||||
title: t("contractTitle"),
|
||||
href: FALLBACK_PUBLIC_CONTRACT_HREF,
|
||||
filename: "Dogovor-platnie-2026.docx",
|
||||
},
|
||||
];
|
||||
const items: PublicDocument[] = docs.length ? docs : FALLBACK_PUBLIC_DOCUMENTS;
|
||||
|
||||
return (
|
||||
<ul className="space-y-4">
|
||||
|
||||
+51
-4
@@ -46,15 +46,15 @@ function withLocale(query: URLSearchParams, locale?: string | null) {
|
||||
return query;
|
||||
}
|
||||
|
||||
type FetchOptions = RequestInit & { token?: string | null; fallback?: unknown };
|
||||
type FetchOptions = RequestInit & { token?: string | null; fallback?: unknown; timeoutMs?: number };
|
||||
|
||||
async function cmsFetch<T>(path: string, options: FetchOptions = {}): Promise<T> {
|
||||
const { token, fallback, ...init } = options;
|
||||
const { token, fallback, timeoutMs, ...init } = options;
|
||||
const headers = new Headers(init.headers);
|
||||
headers.set("Content-Type", "application/json");
|
||||
if (token) headers.set("Authorization", `Bearer ${token}`);
|
||||
|
||||
const timeout = AbortSignal.timeout(5000);
|
||||
const timeout = AbortSignal.timeout(timeoutMs ?? 5000);
|
||||
const signal = init.signal ? AbortSignal.any([init.signal, timeout]) : timeout;
|
||||
|
||||
try {
|
||||
@@ -767,6 +767,31 @@ export async function createBooking(payload: {
|
||||
});
|
||||
}
|
||||
|
||||
export async function createInquiry(payload: {
|
||||
name: string;
|
||||
email: string;
|
||||
phone: string;
|
||||
branchId: string;
|
||||
serviceId?: string;
|
||||
doctorId?: string;
|
||||
}): Promise<{ id: string }> {
|
||||
const data: Record<string, unknown> = {
|
||||
name: payload.name,
|
||||
email: payload.email,
|
||||
phone: payload.phone,
|
||||
branch: Number(payload.branchId) || payload.branchId,
|
||||
status: "new",
|
||||
};
|
||||
if (payload.serviceId) data.service = Number(payload.serviceId) || payload.serviceId;
|
||||
if (payload.doctorId) data.doctor = Number(payload.doctorId) || payload.doctorId;
|
||||
return cmsFetch(`/api/custom/inquiries`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify(data),
|
||||
cache: "no-store",
|
||||
timeoutMs: 20000,
|
||||
});
|
||||
}
|
||||
|
||||
export async function cancelBooking(id: string): Promise<void> {
|
||||
const token = await getAuthToken();
|
||||
await cmsFetch(`/api/bookings/${id}`, {
|
||||
@@ -1059,7 +1084,29 @@ export type PublicDocument = {
|
||||
issuedAt?: string;
|
||||
};
|
||||
|
||||
export const FALLBACK_PUBLIC_CONTRACT_HREF = "/documents/Dogovor-platnie-2026.docx";
|
||||
export const FALLBACK_PUBLIC_DOCUMENTS: PublicDocument[] = [
|
||||
{
|
||||
id: 1,
|
||||
title: "Согласие пациента на обработку персональных данных",
|
||||
href: "/documents/Soglasie-personalnye-dannye.rtf",
|
||||
filename: "Soglasie-personalnye-dannye.rtf",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title:
|
||||
"Информированное добровольное согласие пациента (законного представителя пациента) на медицинское вмешательство, предоставление сведений, составляющих врачебную тайну, и консультацию врача-специалиста с применением телемедицинских технологий",
|
||||
href: "/documents/IDS-meditsinskoe-vmeshatelstvo.docx",
|
||||
filename: "IDS-meditsinskoe-vmeshatelstvo.docx",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: "Договор на оказание платных медицинских услуг (с физическим лицом)",
|
||||
href: "/documents/Dogovor-platnye-uslugi-fiz-litso.docx",
|
||||
filename: "Dogovor-platnye-uslugi-fiz-litso.docx",
|
||||
},
|
||||
];
|
||||
|
||||
export const FALLBACK_PUBLIC_CONTRACT_HREF = FALLBACK_PUBLIC_DOCUMENTS[2].href;
|
||||
|
||||
export async function getPublicDocuments(locale = "ru"): Promise<PublicDocument[]> {
|
||||
try {
|
||||
|
||||
@@ -119,25 +119,27 @@
|
||||
},
|
||||
"booking": {
|
||||
"title": "Анлайн-запіс",
|
||||
"subtitle": "Абярыце паслугу ці ўрача, філіял і зручны час",
|
||||
"subtitle": "Абярыце паслугу ці ўрача, філіял і пакіньце кантакты — мы звяжамся з вамі",
|
||||
"stepService": "Паслуга / ўрач",
|
||||
"stepBranch": "Філіял",
|
||||
"stepSlot": "Дата і час",
|
||||
"stepConfirm": "Пацверджанне",
|
||||
"stepConfirm": "Кантакты",
|
||||
"selectService": "Абярыце паслугу",
|
||||
"selectDoctor": "Абярыце ўрача (неабавязкова)",
|
||||
"selectDoctor": "Абярыце ўрача",
|
||||
"selectBranch": "Абярыце філіял",
|
||||
"selectDate": "Дата",
|
||||
"selectTime": "Даступны час",
|
||||
"noSlots": "Няма свабодных слотаў на гэтую дату",
|
||||
"confirm": "Пацвердзіць запіс",
|
||||
"success": "Запіс створаны",
|
||||
"successHint": "Мы дашлём напамін на email. Кіраваць запісам можна ў асабістым кабінеце.",
|
||||
"confirm": "Адправіць заяўку",
|
||||
"success": "Вялікі дзякуй!",
|
||||
"successHint": "Мы абавязкова звяжамся з вамі ў бліжэйшы час і ўдакладнім усю інфармацыю.",
|
||||
"error": "Не ўдалося адправіць заяўку. Патэлефануйце нам або паспрабуйце яшчэ раз.",
|
||||
"next": "Далей",
|
||||
"back": "Назад",
|
||||
"patientName": "ПІБ",
|
||||
"email": "E-mail",
|
||||
"phone": "Тэлефон",
|
||||
"summary": "Зводка запісу",
|
||||
"summary": "Зводка заяўкі",
|
||||
"searchService": "Знайдзіце паслугу па назве або кодзе"
|
||||
},
|
||||
"branches": {
|
||||
@@ -236,8 +238,8 @@
|
||||
"doctorsSubtitle": "Урачы з вялікім клінічным досведам",
|
||||
"allDoctors": "Усе ўрачы",
|
||||
"allReviews": "Усе водгукі",
|
||||
"ctaTitle": "Запішыцеся на зручны час",
|
||||
"ctaSubtitle": "Анлайн-запіс 24/7, напаміны і аплата праз СБП",
|
||||
"ctaTitle": "Пакіньце заяўку на прыём",
|
||||
"ctaSubtitle": "Абярыце паслугу ці ўрача — мы звяжамся з вамі і ўдакладнім дэталі",
|
||||
"branchesTitle": "Нашы адрасы",
|
||||
"branchesSubtitle": "Дзве зручныя лакацыі для прыёму",
|
||||
"newsTitle": "Навіны клінікі",
|
||||
@@ -312,7 +314,7 @@
|
||||
"subtitle": "Дагаворы і іншыя файлы для пацыентаў платных паслуг.",
|
||||
"intro": "Спампуйце актуальныя дакументы па платных медыцынскіх паслугах МОНІКІ.",
|
||||
"download": "Спампаваць",
|
||||
"contractTitle": "Дагавор на аказанне платных медыцынскіх паслуг (2026)"
|
||||
"contractTitle": "Дагавор на аказанне платных медыцынскіх паслуг (з фізічнай асобай)"
|
||||
},
|
||||
"discounts": {
|
||||
"title": "Зніжкі і льготы",
|
||||
|
||||
@@ -119,25 +119,27 @@
|
||||
},
|
||||
"booking": {
|
||||
"title": "Online booking",
|
||||
"subtitle": "Choose a service or doctor, branch, and time slot",
|
||||
"subtitle": "Choose a service or doctor and a branch, then leave your contacts — we will call you back",
|
||||
"stepService": "Service / doctor",
|
||||
"stepBranch": "Branch",
|
||||
"stepSlot": "Date & time",
|
||||
"stepConfirm": "Confirm",
|
||||
"stepConfirm": "Contacts",
|
||||
"selectService": "Select a service",
|
||||
"selectDoctor": "Select a doctor (optional)",
|
||||
"selectDoctor": "Select a doctor",
|
||||
"selectBranch": "Select a branch",
|
||||
"selectDate": "Date",
|
||||
"selectTime": "Available times",
|
||||
"noSlots": "No slots available for this date",
|
||||
"confirm": "Confirm booking",
|
||||
"success": "Booking created",
|
||||
"successHint": "We will email a reminder. Manage bookings in your account.",
|
||||
"confirm": "Send request",
|
||||
"success": "Thank you!",
|
||||
"successHint": "We will contact you shortly and confirm all the details.",
|
||||
"error": "Could not send the request. Please call us or try again.",
|
||||
"next": "Next",
|
||||
"back": "Back",
|
||||
"patientName": "Full name",
|
||||
"email": "E-mail",
|
||||
"phone": "Phone",
|
||||
"summary": "Booking summary",
|
||||
"summary": "Request summary",
|
||||
"searchService": "Find a service by name or code"
|
||||
},
|
||||
"branches": {
|
||||
@@ -236,8 +238,8 @@
|
||||
"doctorsSubtitle": "Doctors with extensive clinical experience",
|
||||
"allDoctors": "All doctors",
|
||||
"allReviews": "All reviews",
|
||||
"ctaTitle": "Book a convenient time",
|
||||
"ctaSubtitle": "Online booking 24/7, reminders, and SBP payments",
|
||||
"ctaTitle": "Leave a booking request",
|
||||
"ctaSubtitle": "Choose a service or doctor — we will contact you to confirm the details",
|
||||
"branchesTitle": "Our locations",
|
||||
"branchesSubtitle": "Two convenient campuses for appointments",
|
||||
"newsTitle": "Clinic news",
|
||||
@@ -312,7 +314,7 @@
|
||||
"subtitle": "Contracts and other files for paid-service patients.",
|
||||
"intro": "Download current documents for paid medical services at MONIKI.",
|
||||
"download": "Download",
|
||||
"contractTitle": "Paid medical services contract (2026)"
|
||||
"contractTitle": "Paid medical services contract (with an individual)"
|
||||
},
|
||||
"discounts": {
|
||||
"title": "Discounts and benefits",
|
||||
|
||||
@@ -119,25 +119,27 @@
|
||||
},
|
||||
"booking": {
|
||||
"title": "Онлайн жазылуу",
|
||||
"subtitle": "Кызмат же дарыгерди, филиалды жана ыңгайлуу убакытты тандаңыз",
|
||||
"subtitle": "Кызмат же дарыгерди жана филиалды тандап, байланышыңызды калтырыңыз — биз сиз менен байланышабыз",
|
||||
"stepService": "Кызмат / дарыгер",
|
||||
"stepBranch": "Филиал",
|
||||
"stepSlot": "Дата жана убакыт",
|
||||
"stepConfirm": "Ырастоо",
|
||||
"stepConfirm": "Байланыш",
|
||||
"selectService": "Кызматты тандаңыз",
|
||||
"selectDoctor": "Дарыгерди тандаңыз (милдеттүү эмес)",
|
||||
"selectDoctor": "Дарыгерди тандаңыз",
|
||||
"selectBranch": "Филиалды тандаңыз",
|
||||
"selectDate": "Дата",
|
||||
"selectTime": "Жеткиликтүү убакыт",
|
||||
"noSlots": "Бул датага бош слот жок",
|
||||
"confirm": "Жазылууну ырастоо",
|
||||
"success": "Жазылуу түзүлдү",
|
||||
"successHint": "Эскертүүнү emailге жөнөтөбүз. Жазылууну жеке кабинеттен башкарыңыз.",
|
||||
"confirm": "Өтүнмө жөнөтүү",
|
||||
"success": "Чоң рахмат!",
|
||||
"successHint": "Биз жакын арада сиз менен байланышып, бардык маалыматты тактайбыз.",
|
||||
"error": "Өтүнмө жөнөтүлгөн жок. Бизге чалыңыз же кайра аракет кылыңыз.",
|
||||
"next": "Андан ары",
|
||||
"back": "Артка",
|
||||
"patientName": "Аты-жөнү",
|
||||
"email": "E-mail",
|
||||
"phone": "Телефон",
|
||||
"summary": "Жазылуу жыйынтыгы",
|
||||
"summary": "Өтүнмөнүн жыйынтыгы",
|
||||
"searchService": "Кызматты аталышы же коду боюнча табыңыз"
|
||||
},
|
||||
"branches": {
|
||||
@@ -236,8 +238,8 @@
|
||||
"doctorsSubtitle": "Чоң клиникалык тажрыйбалуу дарыгерлер",
|
||||
"allDoctors": "Бардык дарыгерлер",
|
||||
"allReviews": "Бардык пикирлер",
|
||||
"ctaTitle": "Ыңгайлуу убакытка жазылыңыз",
|
||||
"ctaSubtitle": "Онлайн жазылуу 24/7, эскертүүлөр жана СБП төлөмү",
|
||||
"ctaTitle": "Кабыл алуу өтүнмөсүн калтырыңыз",
|
||||
"ctaSubtitle": "Кызмат же дарыгерди тандаңыз — биз сиз менен байланышып, деталдарды тактайбыз",
|
||||
"branchesTitle": "Биздин даректер",
|
||||
"branchesSubtitle": "Кабыл алуу үчүн эки ыңгайлуу жайгашкан жер",
|
||||
"newsTitle": "Клиника жаңылыктары",
|
||||
@@ -312,7 +314,7 @@
|
||||
"subtitle": "Акылуу кызматтардын бейтаптары үчүн келишимдер жана башка файлдар.",
|
||||
"intro": "МОНИКИнин акылуу медициналык кызматтары боюнча актуалдуу документтерди жүктөп алыңыз.",
|
||||
"download": "Жүктөө",
|
||||
"contractTitle": "Акылуу медициналык кызматтарды көрсөтүү келишими (2026)"
|
||||
"contractTitle": "Акылуу медициналык кызматтарды көрсөтүү келишими (жеке жак менен)"
|
||||
},
|
||||
"discounts": {
|
||||
"title": "Арзандатуулар жана жеңилдиктер",
|
||||
|
||||
@@ -119,26 +119,28 @@
|
||||
},
|
||||
"booking": {
|
||||
"title": "Онлайн-запись",
|
||||
"subtitle": "Выберите услугу или врача, филиал и удобное время",
|
||||
"subtitle": "Выберите услугу или врача, филиал и оставьте контакты — мы свяжемся с вами",
|
||||
"stepService": "Услуга / врач",
|
||||
"stepBranch": "Филиал",
|
||||
"stepSlot": "Дата и время",
|
||||
"stepConfirm": "Подтверждение",
|
||||
"stepConfirm": "Контакты",
|
||||
"selectService": "Выберите услугу",
|
||||
"searchService": "Найдите услугу по названию или коду",
|
||||
"selectDoctor": "Выберите врача (необязательно)",
|
||||
"selectDoctor": "Выберите врача",
|
||||
"selectBranch": "Выберите филиал",
|
||||
"selectDate": "Дата",
|
||||
"selectTime": "Доступное время",
|
||||
"noSlots": "Нет свободных слотов на эту дату",
|
||||
"confirm": "Подтвердить запись",
|
||||
"success": "Запись создана",
|
||||
"successHint": "Мы отправим напоминание на email. Управлять записью можно в личном кабинете.",
|
||||
"confirm": "Отправить заявку",
|
||||
"success": "Большое спасибо!",
|
||||
"successHint": "Мы обязательно свяжемся с вами в ближайшее время и уточним всю информацию.",
|
||||
"error": "Не удалось отправить заявку. Позвоните нам или попробуйте ещё раз.",
|
||||
"next": "Далее",
|
||||
"back": "Назад",
|
||||
"patientName": "ФИО",
|
||||
"email": "E-mail",
|
||||
"phone": "Телефон",
|
||||
"summary": "Сводка записи"
|
||||
"summary": "Сводка заявки"
|
||||
},
|
||||
"branches": {
|
||||
"title": "Филиалы",
|
||||
@@ -236,8 +238,8 @@
|
||||
"doctorsSubtitle": "Врачи с большим клиническим опытом",
|
||||
"allDoctors": "Все врачи",
|
||||
"allReviews": "Все отзывы",
|
||||
"ctaTitle": "Запишитесь на удобное время",
|
||||
"ctaSubtitle": "Онлайн-запись 24/7, напоминания и оплата через СБП",
|
||||
"ctaTitle": "Оставьте заявку на приём",
|
||||
"ctaSubtitle": "Выберите услугу или врача — мы свяжемся с вами и уточним детали",
|
||||
"branchesTitle": "Наши адреса",
|
||||
"branchesSubtitle": "Два удобных локации для приёма",
|
||||
"newsTitle": "Новости клиники",
|
||||
@@ -312,7 +314,7 @@
|
||||
"subtitle": "Договоры и другие файлы для пациентов платных услуг.",
|
||||
"intro": "Скачайте актуальные документы по платным медицинским услугам МОНИКИ.",
|
||||
"download": "Скачать",
|
||||
"contractTitle": "Договор на оказание платных медицинских услуг (2026)"
|
||||
"contractTitle": "Договор на оказание платных медицинских услуг (с физическим лицом)"
|
||||
},
|
||||
"discounts": {
|
||||
"title": "Скидки и льготы",
|
||||
|
||||
@@ -119,25 +119,27 @@
|
||||
},
|
||||
"booking": {
|
||||
"title": "Сабти онлайн",
|
||||
"subtitle": "Хизмат ё духтур, филиал ва вақти қулайро интихоб кунед",
|
||||
"subtitle": "Хизмат ё духтур ва филиалро интихоб кунед ва тамос гузоред — мо бо шумо тамос мегирем",
|
||||
"stepService": "Хизмат / духтур",
|
||||
"stepBranch": "Филиал",
|
||||
"stepSlot": "Сана ва вақт",
|
||||
"stepConfirm": "Тасдиқ",
|
||||
"stepConfirm": "Тамос",
|
||||
"selectService": "Хизматро интихоб кунед",
|
||||
"selectDoctor": "Духтурро интихоб кунед (ихтиёрӣ)",
|
||||
"selectDoctor": "Духтурро интихоб кунед",
|
||||
"selectBranch": "Филиалро интихоб кунед",
|
||||
"selectDate": "Сана",
|
||||
"selectTime": "Вақтҳои дастрас",
|
||||
"noSlots": "Барои ин сана слот нест",
|
||||
"confirm": "Сабтро тасдиқ кунед",
|
||||
"success": "Сабт эҷод шуд",
|
||||
"successHint": "Ёдраскунӣ ба email мефиристем. Сабтро дар кабинети шахсӣ идора кунед.",
|
||||
"confirm": "Фиристодани дархост",
|
||||
"success": "Ташаккури зиёд!",
|
||||
"successHint": "Мо ҳатман дар наздиктарин вақт бо шумо тамос мегирем ва тамоми маълумотро дақиқ мекунем.",
|
||||
"error": "Дархост фиристода нашуд. Ба мо занг занед ё бори дигар кӯшиш кунед.",
|
||||
"next": "Минбаъд",
|
||||
"back": "Бозгашт",
|
||||
"patientName": "Ному насаб",
|
||||
"email": "E-mail",
|
||||
"phone": "Телефон",
|
||||
"summary": "Хулосаи сабт",
|
||||
"summary": "Хулосаи дархост",
|
||||
"searchService": "Хизматро бо ном ё рамз ҷустуҷӯ кунед"
|
||||
},
|
||||
"branches": {
|
||||
@@ -236,8 +238,8 @@
|
||||
"doctorsSubtitle": "Духтурон бо таҷрибаи зиёди клиникӣ",
|
||||
"allDoctors": "Ҳамаи духтурон",
|
||||
"allReviews": "Ҳамаи шарҳҳо",
|
||||
"ctaTitle": "Ба вақти қулай сабт кунед",
|
||||
"ctaSubtitle": "Сабти онлайн 24/7, ёдраскунӣ ва пардохти СБП",
|
||||
"ctaTitle": "Дархости қабулро гузоред",
|
||||
"ctaSubtitle": "Хизмат ё духтурро интихоб кунед — мо бо шумо тамос гирифта ҷузъиётро дақиқ мекунем",
|
||||
"branchesTitle": "Суроғаҳои мо",
|
||||
"branchesSubtitle": "Ду макони қулай барои қабул",
|
||||
"newsTitle": "Хабарҳои клиника",
|
||||
@@ -312,7 +314,7 @@
|
||||
"subtitle": "Шартномаҳо ва дигар файлҳо барои беморони хизматрасонии пулакӣ.",
|
||||
"intro": "Ҳуҷҷатҳои актуалии хизматрасонии тиббии пулакии МОНИКИ-ро боргирӣ кунед.",
|
||||
"download": "Боргирӣ",
|
||||
"contractTitle": "Шартнома дар бораи расонидани хизматрасонии тиббии пулакӣ (2026)"
|
||||
"contractTitle": "Шартнома дар бораи расонидани хизматрасонии тиббии пулакӣ (бо шахси воқеӣ)"
|
||||
},
|
||||
"discounts": {
|
||||
"title": "Тахфиф ва имтиёзҳо",
|
||||
|
||||
@@ -119,25 +119,27 @@
|
||||
},
|
||||
"booking": {
|
||||
"title": "在线预约",
|
||||
"subtitle": "选择服务或医生、分院和合适时间",
|
||||
"subtitle": "选择服务或医生、分院并留下联系方式,我们会与您联系",
|
||||
"stepService": "服务 / 医生",
|
||||
"stepBranch": "分院",
|
||||
"stepSlot": "日期与时间",
|
||||
"stepConfirm": "确认",
|
||||
"stepConfirm": "联系方式",
|
||||
"selectService": "选择服务",
|
||||
"selectDoctor": "选择医生(可选)",
|
||||
"selectDoctor": "选择医生",
|
||||
"selectBranch": "选择分院",
|
||||
"selectDate": "日期",
|
||||
"selectTime": "可预约时间",
|
||||
"noSlots": "该日期暂无空档",
|
||||
"confirm": "确认预约",
|
||||
"success": "预约已创建",
|
||||
"successHint": "我们将发送邮件提醒。可在个人中心管理预约。",
|
||||
"confirm": "提交申请",
|
||||
"success": "非常感谢!",
|
||||
"successHint": "我们会尽快与您联系并确认全部信息。",
|
||||
"error": "申请未能发送。请致电我们或稍后重试。",
|
||||
"next": "下一步",
|
||||
"back": "返回",
|
||||
"patientName": "姓名",
|
||||
"email": "E-mail",
|
||||
"phone": "电话",
|
||||
"summary": "预约摘要",
|
||||
"summary": "申请摘要",
|
||||
"searchService": "按名称或编码查找服务"
|
||||
},
|
||||
"branches": {
|
||||
@@ -236,8 +238,8 @@
|
||||
"doctorsSubtitle": "拥有丰富临床经验的医生",
|
||||
"allDoctors": "全部医生",
|
||||
"allReviews": "全部评价",
|
||||
"ctaTitle": "预约合适时间",
|
||||
"ctaSubtitle": "全天候在线预约、提醒与 SBP 支付",
|
||||
"ctaTitle": "提交就诊申请",
|
||||
"ctaSubtitle": "选择服务或医生,我们会与您联系并确认细节",
|
||||
"branchesTitle": "我们的地址",
|
||||
"branchesSubtitle": "两个便捷就诊地点",
|
||||
"newsTitle": "医院新闻",
|
||||
@@ -312,7 +314,7 @@
|
||||
"subtitle": "付费服务患者可下载的合同及其他文件。",
|
||||
"intro": "下载莫尼基付费医疗服务的现行文件。",
|
||||
"download": "下载",
|
||||
"contractTitle": "付费医疗服务合同(2026)"
|
||||
"contractTitle": "付费医疗服务合同(自然人)"
|
||||
},
|
||||
"discounts": {
|
||||
"title": "折扣与优惠",
|
||||
|
||||
Reference in New Issue
Block a user