mirror of
https://github.com/wasrusgen/zov-tech.git
synced 2026-06-03 16:24:50 +00:00
measurement workflow: приблизительная дата от менеджера
По процедуре пользователя: менеджер при создании заявки может не знать точной даты. Указывает диапазон, замерщик потом созванивается с клиентом и фиксирует точную дату. Менеджер при «Заказать замер» — радио-выбор: ○ Конкретная дата (открывает date + утром/днём/вечером) ○ Эта неделя ○ Следующая неделя ● Согласовать с клиентом (default) + поле «Уточнение по времени» (свободный текст) Замерщик в инбоксе: - Если scheduled_at заполнено → 📅 точная дата - Иначе → 🕐 приблизительная (эта неделя / след. неделя / 15.05 утром) + Note выводится после · - В DM-уведомлении строка «Когда: …» подсказывает что согласовать Замерщик в карточке заявки: - Если нет точной даты — отдельный блок «⏰ Когда удобно клиенту» с подсказкой «позвоните клиенту и согласуйте точную дату» - После назначения через datetime-input → блок исчезает Backend: 4 новые колонки preferred_type / preferred_date / preferred_time_of_day / preferred_note, добавлены в schema, serialize/deserialize в request + detail + inbox. Cache bust v=20260513u.
This commit is contained in:
parent
e37a5e723f
commit
fdce3b3c64
@ -606,6 +606,12 @@ def _measurement_columns() -> list[str]:
|
|||||||
"address", "client_name", "client_phone",
|
"address", "client_name", "client_phone",
|
||||||
# Поля Commit C (структура замера по чек-листу)
|
# Поля Commit C (структура замера по чек-листу)
|
||||||
"zamer_no", "zamer_date", "floor_base", "photos_meta",
|
"zamer_no", "zamer_date", "floor_base", "photos_meta",
|
||||||
|
# Поля для приблизительной даты от менеджера (Commit C2)
|
||||||
|
# preferred_type: specific | this_week | next_week | tbd
|
||||||
|
# preferred_date: ISO date если specific
|
||||||
|
# preferred_time_of_day: morning | day | evening
|
||||||
|
# preferred_note: «после звонка», «не раньше вторника», ...
|
||||||
|
"preferred_type", "preferred_date", "preferred_time_of_day", "preferred_note",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -637,6 +643,7 @@ def _row_for_measurement(measurement_id: str, ts: str, **fields) -> list[str]:
|
|||||||
"assigned_to_tg_id": "", "requested_by_tg_id": "", "scheduled_at": "",
|
"assigned_to_tg_id": "", "requested_by_tg_id": "", "scheduled_at": "",
|
||||||
"address": "", "client_name": "", "client_phone": "",
|
"address": "", "client_name": "", "client_phone": "",
|
||||||
"zamer_no": "", "zamer_date": "", "floor_base": "", "photos_meta": "",
|
"zamer_no": "", "zamer_date": "", "floor_base": "", "photos_meta": "",
|
||||||
|
"preferred_type": "", "preferred_date": "", "preferred_time_of_day": "", "preferred_note": "",
|
||||||
}
|
}
|
||||||
base.update(fields)
|
base.update(fields)
|
||||||
return [str(base.get(c, "")) for c in cols]
|
return [str(base.get(c, "")) for c in cols]
|
||||||
@ -1120,6 +1127,16 @@ def _handle_measurement_request(body: dict[str, Any]) -> dict[str, Any]:
|
|||||||
assigned_to = str(body.get("assigned_to_tg_id") or "").strip()
|
assigned_to = str(body.get("assigned_to_tg_id") or "").strip()
|
||||||
notes = (body.get("notes") or "").strip()
|
notes = (body.get("notes") or "").strip()
|
||||||
|
|
||||||
|
# Приблизительная дата визита (Commit C2)
|
||||||
|
preferred_type = (body.get("preferred_type") or "tbd").strip()
|
||||||
|
preferred_date = (body.get("preferred_date") or "").strip()
|
||||||
|
preferred_time_of_day = (body.get("preferred_time_of_day") or "").strip()
|
||||||
|
preferred_note = (body.get("preferred_note") or "").strip()
|
||||||
|
if preferred_type not in ("specific", "this_week", "next_week", "tbd"):
|
||||||
|
preferred_type = "tbd"
|
||||||
|
if preferred_time_of_day not in ("morning", "day", "evening", ""):
|
||||||
|
preferred_time_of_day = ""
|
||||||
|
|
||||||
if not client_name or not client_phone:
|
if not client_name or not client_phone:
|
||||||
return {"error": "missing_client_info", "hint": "client_name and client_phone are required"}
|
return {"error": "missing_client_info", "hint": "client_name and client_phone are required"}
|
||||||
|
|
||||||
@ -1144,19 +1161,27 @@ def _handle_measurement_request(body: dict[str, Any]) -> dict[str, Any]:
|
|||||||
client_name=client_name,
|
client_name=client_name,
|
||||||
client_phone=client_phone,
|
client_phone=client_phone,
|
||||||
notes=notes,
|
notes=notes,
|
||||||
|
preferred_type=preferred_type,
|
||||||
|
preferred_date=preferred_date,
|
||||||
|
preferred_time_of_day=preferred_time_of_day,
|
||||||
|
preferred_note=preferred_note,
|
||||||
))
|
))
|
||||||
|
|
||||||
# Уведомление назначенному замерщику
|
# Уведомление назначенному замерщику
|
||||||
if assigned_to:
|
if assigned_to:
|
||||||
|
timing_line = _format_preferred_human(
|
||||||
|
preferred_type, preferred_date, preferred_time_of_day, preferred_note
|
||||||
|
)
|
||||||
tg.send_message(
|
tg.send_message(
|
||||||
int(assigned_to),
|
int(assigned_to),
|
||||||
f"📐 <b>Новая заявка на замер</b>\n\n"
|
f"📐 <b>Новая заявка на замер</b>\n\n"
|
||||||
f"Клиент: <b>{client_name}</b>\n"
|
f"Клиент: <b>{client_name}</b>\n"
|
||||||
f"Телефон: <code>{client_phone}</code>\n"
|
f"Телефон: <code>{client_phone}</code>\n"
|
||||||
f"Адрес: {address or '—'}\n"
|
f"Адрес: {address or '—'}\n"
|
||||||
|
f"Когда: {timing_line}\n"
|
||||||
f"От менеджера: {user.get('full_name') or tg_id}\n\n"
|
f"От менеджера: {user.get('full_name') or tg_id}\n\n"
|
||||||
f"{notes if notes else ''}\n"
|
f"{notes if notes else ''}\n"
|
||||||
f"Откройте кабинет — назначьте дату."
|
f"Откройте кабинет — согласуйте точную дату с клиентом."
|
||||||
)
|
)
|
||||||
|
|
||||||
sheets.log_event("measurement_requested", tg_id, {
|
sheets.log_event("measurement_requested", tg_id, {
|
||||||
@ -1206,6 +1231,10 @@ def _handle_measurement_inbox(body: dict[str, Any]) -> dict[str, Any]:
|
|||||||
"notes": row.get("notes", ""),
|
"notes": row.get("notes", ""),
|
||||||
"manager_tg_id": row.get("manager_tg_id", ""),
|
"manager_tg_id": row.get("manager_tg_id", ""),
|
||||||
"requested_by_tg_id": row.get("requested_by_tg_id", ""),
|
"requested_by_tg_id": row.get("requested_by_tg_id", ""),
|
||||||
|
"preferred_type": row.get("preferred_type", ""),
|
||||||
|
"preferred_date": row.get("preferred_date", ""),
|
||||||
|
"preferred_time_of_day": row.get("preferred_time_of_day", ""),
|
||||||
|
"preferred_note": row.get("preferred_note", ""),
|
||||||
})
|
})
|
||||||
# Назначенная дата → первая; затем requested без даты
|
# Назначенная дата → первая; затем requested без даты
|
||||||
def _sort_key(item):
|
def _sort_key(item):
|
||||||
@ -1308,6 +1337,34 @@ def _format_date_human(iso: str) -> str:
|
|||||||
return iso
|
return iso
|
||||||
|
|
||||||
|
|
||||||
|
def _format_preferred_human(p_type: str, p_date: str, p_tod: str, p_note: str) -> str:
|
||||||
|
"""Приблизительная дата от менеджера в человекочитаемом виде."""
|
||||||
|
tod_map = {"morning": "утром", "day": "днём", "evening": "вечером"}
|
||||||
|
if p_type == "specific":
|
||||||
|
date_part = p_date
|
||||||
|
if p_date:
|
||||||
|
try:
|
||||||
|
from datetime import datetime as _dt
|
||||||
|
date_part = _dt.strptime(p_date, "%Y-%m-%d").strftime("%d.%m.%Y")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
parts = []
|
||||||
|
if date_part:
|
||||||
|
parts.append(date_part)
|
||||||
|
if p_tod in tod_map:
|
||||||
|
parts.append(tod_map[p_tod])
|
||||||
|
s = " ".join(parts) if parts else "конкретная дата"
|
||||||
|
elif p_type == "this_week":
|
||||||
|
s = "эта неделя"
|
||||||
|
elif p_type == "next_week":
|
||||||
|
s = "следующая неделя"
|
||||||
|
else:
|
||||||
|
s = "согласовать с клиентом"
|
||||||
|
if p_note:
|
||||||
|
s += f" · {p_note}"
|
||||||
|
return s
|
||||||
|
|
||||||
|
|
||||||
def _handle_measurement_detail(body: dict[str, Any]) -> dict[str, Any]:
|
def _handle_measurement_detail(body: dict[str, Any]) -> dict[str, Any]:
|
||||||
"""Возвращает один замер целиком — для детальной страницы и печати."""
|
"""Возвращает один замер целиком — для детальной страницы и печати."""
|
||||||
cfg = get_config()
|
cfg = get_config()
|
||||||
@ -1372,6 +1429,11 @@ def _handle_measurement_detail(body: dict[str, Any]) -> dict[str, Any]:
|
|||||||
"zamer_date": row.get("zamer_date", ""),
|
"zamer_date": row.get("zamer_date", ""),
|
||||||
"floor_base": row.get("floor_base", ""),
|
"floor_base": row.get("floor_base", ""),
|
||||||
"photos_meta": _safe_json(row.get("photos_meta", "")),
|
"photos_meta": _safe_json(row.get("photos_meta", "")),
|
||||||
|
# Приблизительная дата от менеджера (Commit C2)
|
||||||
|
"preferred_type": row.get("preferred_type", ""),
|
||||||
|
"preferred_date": row.get("preferred_date", ""),
|
||||||
|
"preferred_time_of_day": row.get("preferred_time_of_day", ""),
|
||||||
|
"preferred_note": row.get("preferred_note", ""),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -511,7 +511,13 @@ function renderInboxItem(m) {
|
|||||||
scheduled: "📅 назначен",
|
scheduled: "📅 назначен",
|
||||||
in_progress: "🔵 в работе",
|
in_progress: "🔵 в работе",
|
||||||
})[m.status] || m.status;
|
})[m.status] || m.status;
|
||||||
const sched = m.scheduled_at ? formatDateHuman(m.scheduled_at) : "дата не назначена";
|
// Когда: точная дата если назначена, иначе приблизительная
|
||||||
|
let whenText;
|
||||||
|
if (m.scheduled_at) {
|
||||||
|
whenText = "📅 " + formatDateHuman(m.scheduled_at);
|
||||||
|
} else {
|
||||||
|
whenText = "🕐 " + formatPreferredHuman(m);
|
||||||
|
}
|
||||||
|
|
||||||
const item = el(`
|
const item = el(`
|
||||||
<button class="lead-item" style="text-align:left;">
|
<button class="lead-item" style="text-align:left;">
|
||||||
@ -521,7 +527,7 @@ function renderInboxItem(m) {
|
|||||||
${escHtml(m.address || "адрес не указан")}
|
${escHtml(m.address || "адрес не указан")}
|
||||||
</div>
|
</div>
|
||||||
<div class="lead-id" style="font-size:11px; color:var(--muted); margin-top:2px;">
|
<div class="lead-id" style="font-size:11px; color:var(--muted); margin-top:2px;">
|
||||||
${escHtml(sched)} · ${statusLabel}
|
${escHtml(whenText)} · ${statusLabel}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="lead-arrow">${ICONS.chevron || "›"}</div>
|
<div class="lead-arrow">${ICONS.chevron || "›"}</div>
|
||||||
@ -534,6 +540,33 @@ function renderInboxItem(m) {
|
|||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatPreferredHuman(m) {
|
||||||
|
const todMap = { morning: "утром", day: "днём", evening: "вечером" };
|
||||||
|
const t = m.preferred_type || "tbd";
|
||||||
|
const parts = [];
|
||||||
|
if (t === "specific") {
|
||||||
|
if (m.preferred_date) {
|
||||||
|
try {
|
||||||
|
const d = new Date(m.preferred_date);
|
||||||
|
parts.push(`${String(d.getDate()).padStart(2,"0")}.${String(d.getMonth()+1).padStart(2,"0")}`);
|
||||||
|
} catch (e) { parts.push(m.preferred_date); }
|
||||||
|
}
|
||||||
|
if (m.preferred_time_of_day && todMap[m.preferred_time_of_day]) {
|
||||||
|
parts.push(todMap[m.preferred_time_of_day]);
|
||||||
|
}
|
||||||
|
if (!parts.length) parts.push("конкретная дата");
|
||||||
|
} else if (t === "this_week") {
|
||||||
|
parts.push("эта неделя");
|
||||||
|
} else if (t === "next_week") {
|
||||||
|
parts.push("следующая неделя");
|
||||||
|
} else {
|
||||||
|
parts.push("согласовать с клиентом");
|
||||||
|
}
|
||||||
|
let s = parts.join(" ");
|
||||||
|
if (m.preferred_note) s += " · " + m.preferred_note;
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
function formatDateHuman(iso) {
|
function formatDateHuman(iso) {
|
||||||
if (!iso) return "—";
|
if (!iso) return "—";
|
||||||
try {
|
try {
|
||||||
@ -607,6 +640,20 @@ async function renderInboxDetail(measurementId) {
|
|||||||
</div>
|
</div>
|
||||||
`));
|
`));
|
||||||
|
|
||||||
|
// Приблизительная дата от менеджера (если точной ещё нет — это подсказка)
|
||||||
|
if (!m.scheduled_at && (m.preferred_type || m.preferred_note)) {
|
||||||
|
const prefText = formatPreferredHuman(m);
|
||||||
|
app.appendChild(el(`
|
||||||
|
<section class="block preferred-block">
|
||||||
|
<div class="block-head">⏰ Когда удобно клиенту (от менеджера)</div>
|
||||||
|
<div style="padding:12px 4px;color:var(--ink);font-size:15px;font-weight:500;">${escHtml(prefText)}</div>
|
||||||
|
<div style="padding:0 4px 4px;color:var(--muted);font-size:12px;">
|
||||||
|
Позвоните клиенту и согласуйте точную дату — она появится ниже.
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
`));
|
||||||
|
}
|
||||||
|
|
||||||
// Заметки от менеджера
|
// Заметки от менеджера
|
||||||
if (m.notes) {
|
if (m.notes) {
|
||||||
app.appendChild(el(`
|
app.appendChild(el(`
|
||||||
|
|||||||
@ -2066,6 +2066,49 @@
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ===== Заявка на замер: выбор «когда удобно» ===== */
|
||||||
|
.preferred-options {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 8px;
|
||||||
|
margin: 8px 0 6px;
|
||||||
|
}
|
||||||
|
.pref-opt {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
background: var(--card, #fff);
|
||||||
|
border: 1px solid var(--line-strong, rgba(15, 15, 14, 0.16));
|
||||||
|
border-radius: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
transition: background 0.12s, border-color 0.12s;
|
||||||
|
}
|
||||||
|
.pref-opt input[type="radio"] {
|
||||||
|
margin: 0;
|
||||||
|
accent-color: var(--walnut, #6B4A2B);
|
||||||
|
}
|
||||||
|
.pref-opt input[type="radio"]:checked + .pref-label {
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--walnut, #6B4A2B);
|
||||||
|
}
|
||||||
|
.pref-opt:has(input:checked) {
|
||||||
|
background: var(--warm, rgba(107, 74, 43, 0.08));
|
||||||
|
border-color: var(--walnut, #6B4A2B);
|
||||||
|
}
|
||||||
|
.pref-label {
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--ink, #1F1A14);
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Блок «когда удобно» в карточке замерщика */
|
||||||
|
.preferred-block {
|
||||||
|
background: var(--warm, rgba(107, 74, 43, 0.08));
|
||||||
|
border-left: 3px solid var(--walnut, #6B4A2B);
|
||||||
|
}
|
||||||
|
|
||||||
/* ===== Замер: фото с тегами ===== */
|
/* ===== Замер: фото с тегами ===== */
|
||||||
.podbor-header .podbor-help {
|
.podbor-header .podbor-help {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
|
|||||||
@ -10,6 +10,11 @@ const MeasurementRequest = (function () {
|
|||||||
address: "",
|
address: "",
|
||||||
assigned_to_tg_id: "",
|
assigned_to_tg_id: "",
|
||||||
notes: "",
|
notes: "",
|
||||||
|
// Приблизительная дата визита
|
||||||
|
preferred_type: "tbd", // specific | this_week | next_week | tbd
|
||||||
|
preferred_date: "",
|
||||||
|
preferred_time_of_day: "", // morning | day | evening | ""
|
||||||
|
preferred_note: "",
|
||||||
};
|
};
|
||||||
let measurers = [];
|
let measurers = [];
|
||||||
|
|
||||||
@ -18,7 +23,10 @@ const MeasurementRequest = (function () {
|
|||||||
document.body.classList.remove("has-bottom-nav");
|
document.body.classList.remove("has-bottom-nav");
|
||||||
const oldNav = document.getElementById("bottom-nav");
|
const oldNav = document.getElementById("bottom-nav");
|
||||||
if (oldNav) oldNav.remove();
|
if (oldNav) oldNav.remove();
|
||||||
state = { client_name: "", client_phone: "", address: "", assigned_to_tg_id: "", notes: "" };
|
state = {
|
||||||
|
client_name: "", client_phone: "", address: "", assigned_to_tg_id: "", notes: "",
|
||||||
|
preferred_type: "tbd", preferred_date: "", preferred_time_of_day: "", preferred_note: "",
|
||||||
|
};
|
||||||
render();
|
render();
|
||||||
loadMeasurers();
|
loadMeasurers();
|
||||||
}
|
}
|
||||||
@ -67,6 +75,49 @@ const MeasurementRequest = (function () {
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="section-head" style="margin-top:18px;"><span class="label">⏰ Когда удобно клиенту</span></div>
|
||||||
|
<div class="preferred-options">
|
||||||
|
<label class="pref-opt">
|
||||||
|
<input type="radio" name="prefType" value="specific" data-pref="type">
|
||||||
|
<span class="pref-label">Конкретная дата</span>
|
||||||
|
</label>
|
||||||
|
<label class="pref-opt">
|
||||||
|
<input type="radio" name="prefType" value="this_week" data-pref="type">
|
||||||
|
<span class="pref-label">Эта неделя</span>
|
||||||
|
</label>
|
||||||
|
<label class="pref-opt">
|
||||||
|
<input type="radio" name="prefType" value="next_week" data-pref="type">
|
||||||
|
<span class="pref-label">Следующая неделя</span>
|
||||||
|
</label>
|
||||||
|
<label class="pref-opt">
|
||||||
|
<input type="radio" name="prefType" value="tbd" data-pref="type" checked>
|
||||||
|
<span class="pref-label">Согласовать с клиентом</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row two-col" id="prefSpecificBox" style="display:none;">
|
||||||
|
<label class="field">
|
||||||
|
<span class="field-label">Дата</span>
|
||||||
|
<input type="date" data-pref="date">
|
||||||
|
</label>
|
||||||
|
<label class="field">
|
||||||
|
<span class="field-label">Время дня</span>
|
||||||
|
<select data-pref="time_of_day">
|
||||||
|
<option value="">не важно</option>
|
||||||
|
<option value="morning">утром</option>
|
||||||
|
<option value="day">днём</option>
|
||||||
|
<option value="evening">вечером</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<label class="field">
|
||||||
|
<span class="field-label">Уточнение по времени</span>
|
||||||
|
<input type="text" data-pref="note" placeholder="например: после звонка, не раньше вторника">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<label class="field">
|
<label class="field">
|
||||||
<span class="field-label">Заметки для замерщика</span>
|
<span class="field-label">Заметки для замерщика</span>
|
||||||
@ -96,6 +147,23 @@ const MeasurementRequest = (function () {
|
|||||||
state[e.target.dataset.bind] = e.target.value;
|
state[e.target.dataset.bind] = e.target.value;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
// Радио-кнопки + поля приблизительной даты
|
||||||
|
node.querySelectorAll("[data-pref]").forEach(inp => {
|
||||||
|
const key = inp.dataset.pref;
|
||||||
|
const mapKey = "preferred_" + key;
|
||||||
|
inp.addEventListener("change", e => {
|
||||||
|
const val = e.target.type === "radio" ? e.target.value : e.target.value;
|
||||||
|
state[mapKey] = val;
|
||||||
|
if (key === "type") togglePrefSpecific(node);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
togglePrefSpecific(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
function togglePrefSpecific(node) {
|
||||||
|
const box = node.querySelector("#prefSpecificBox");
|
||||||
|
if (!box) return;
|
||||||
|
box.style.display = state.preferred_type === "specific" ? "" : "none";
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadMeasurers() {
|
async function loadMeasurers() {
|
||||||
@ -151,11 +219,17 @@ const MeasurementRequest = (function () {
|
|||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
initData: tg?.initData || "",
|
initData: tg?.initData || "",
|
||||||
|
initDataUnsafe: tg?.initDataUnsafe || null,
|
||||||
client_name: name,
|
client_name: name,
|
||||||
client_phone: phone,
|
client_phone: phone,
|
||||||
address: state.address || "",
|
address: state.address || "",
|
||||||
assigned_to_tg_id: state.assigned_to_tg_id || "",
|
assigned_to_tg_id: state.assigned_to_tg_id || "",
|
||||||
notes: state.notes || "",
|
notes: state.notes || "",
|
||||||
|
// Приблизительная дата визита
|
||||||
|
preferred_type: state.preferred_type || "tbd",
|
||||||
|
preferred_date: state.preferred_date || "",
|
||||||
|
preferred_time_of_day: state.preferred_time_of_day || "",
|
||||||
|
preferred_note: state.preferred_note || "",
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|||||||
@ -12,8 +12,8 @@
|
|||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Geist:wght@400;500;600&family=Newsreader:ital,wght@0,400..600;1,400..600&family=Instrument+Serif:ital@0;1&family=JetBrains+Mono:wght@400;500&family=Cormorant+Garamond:ital,wght@1,400;1,500;1,600&display=swap">
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Geist:wght@400;500;600&family=Newsreader:ital,wght@0,400..600;1,400..600&family=Instrument+Serif:ital@0;1&family=JetBrains+Mono:wght@400;500&family=Cormorant+Garamond:ital,wght@1,400;1,500;1,600&display=swap">
|
||||||
<script src="https://telegram.org/js/telegram-web-app.js"></script>
|
<script src="https://telegram.org/js/telegram-web-app.js"></script>
|
||||||
<link rel="stylesheet" href="assets/styles.css?v=20260513t">
|
<link rel="stylesheet" href="assets/styles.css?v=20260513u">
|
||||||
<link rel="stylesheet" href="assets/podbor.css?v=20260513t">
|
<link rel="stylesheet" href="assets/podbor.css?v=20260513u">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<!-- Splash — за пределами #app, render-функции его не смывают -->
|
<!-- Splash — за пределами #app, render-функции его не смывают -->
|
||||||
@ -31,13 +31,13 @@
|
|||||||
<div class="loader-tagline">Сделано с душой!</div>
|
<div class="loader-tagline">Сделано с душой!</div>
|
||||||
</div>
|
</div>
|
||||||
<main id="app"></main>
|
<main id="app"></main>
|
||||||
<script src="assets/icons.js?v=20260513t"></script>
|
<script src="assets/icons.js?v=20260513u"></script>
|
||||||
<script src="assets/podbor.config.js?v=20260513t"></script>
|
<script src="assets/podbor.config.js?v=20260513u"></script>
|
||||||
<script src="assets/podbor.picts.js?v=20260513t"></script>
|
<script src="assets/podbor.picts.js?v=20260513u"></script>
|
||||||
<script src="assets/podbor.js?v=20260513t"></script>
|
<script src="assets/podbor.js?v=20260513u"></script>
|
||||||
<script src="assets/clients.js?v=20260513t"></script>
|
<script src="assets/clients.js?v=20260513u"></script>
|
||||||
<script src="assets/measurements.js?v=20260513t"></script>
|
<script src="assets/measurements.js?v=20260513u"></script>
|
||||||
<script src="assets/request.js?v=20260513t"></script>
|
<script src="assets/request.js?v=20260513u"></script>
|
||||||
<script src="assets/app.js?v=20260513t"></script>
|
<script src="assets/app.js?v=20260513u"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user