mirror of
https://github.com/wasrusgen/zov-tech.git
synced 2026-06-03 16:24:50 +00:00
flow: упрощённая заявка + 3 чёткие стадии у замерщика
По цепочке менеджер→замерщик→замер:
Менеджер «Заказать замер»:
- ФИО, телефон, адрес, кому назначить
- Одно поле «Примечание» (рекомендации по дате + особенности)
Убраны radio-buttons specific/this_week/next_week — слишком сложно.
Точную дату всё равно согласует замерщик с клиентом.
Замерщик в карточке заявки — 3 чёткие стадии:
1. ЕСЛИ статус requested (дата не назначена):
- Блок «📞 Согласовать дату с клиентом»
- Подсказка «Позвоните клиенту и зафиксируйте»
- datetime-local + кнопка «Назначить»
2. ЕСЛИ статус scheduled (дата уже есть):
- Блок «📅 Замер назначен» крупно (Newsreader 22pt italic)
- Кнопка «Изменить дату» — разворачивает скрытую форму
- ОСНОВНАЯ кнопка «📐 Начать замер» (большая, primary, 16pt)
- До «Начать замер» чек-листа не видно
Чек-лист (📋 в шапке) теперь живёт ТОЛЬКО в мастере замера
(когда нажали «Начать замер»). До этого момента не отвлекает.
Backend: DM при создании заявки шлёт только примечание
(без расшифровки preferred_type).
Cache bust v=20260513z.
This commit is contained in:
parent
9e23239f57
commit
366625be66
@ -1196,18 +1196,15 @@ def _handle_measurement_request(body: dict[str, Any]) -> dict[str, Any]:
|
|||||||
|
|
||||||
# Уведомление назначенному замерщику
|
# Уведомление назначенному замерщику
|
||||||
if assigned_to:
|
if assigned_to:
|
||||||
timing_line = _format_preferred_human(
|
note_line = f"\nПримечание: {preferred_note}" if preferred_note else ""
|
||||||
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}"
|
||||||
f"От менеджера: {user.get('full_name') or tg_id}\n\n"
|
f"{note_line}\n\n"
|
||||||
f"{notes if notes else ''}\n"
|
|
||||||
f"Откройте кабинет — согласуйте точную дату с клиентом."
|
f"Откройте кабинет — согласуйте точную дату с клиентом."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -541,6 +541,9 @@ function renderInboxItem(m) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function formatPreferredHuman(m) {
|
function formatPreferredHuman(m) {
|
||||||
|
// Теперь приоритет — текст из preferred_note (свободная форма).
|
||||||
|
// Старые записи с preferred_type/date/time_of_day выводятся как fallback.
|
||||||
|
if (m.preferred_note) return m.preferred_note;
|
||||||
const todMap = { morning: "утром", day: "днём", evening: "вечером" };
|
const todMap = { morning: "утром", day: "днём", evening: "вечером" };
|
||||||
const t = m.preferred_type || "tbd";
|
const t = m.preferred_type || "tbd";
|
||||||
const parts = [];
|
const parts = [];
|
||||||
@ -562,9 +565,7 @@ function formatPreferredHuman(m) {
|
|||||||
} else {
|
} else {
|
||||||
parts.push("согласовать с клиентом");
|
parts.push("согласовать с клиентом");
|
||||||
}
|
}
|
||||||
let s = parts.join(" ");
|
return parts.join(" ");
|
||||||
if (m.preferred_note) s += " · " + m.preferred_note;
|
|
||||||
return s;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatDateHuman(iso) {
|
function formatDateHuman(iso) {
|
||||||
@ -640,62 +641,102 @@ async function renderInboxDetail(measurementId) {
|
|||||||
</div>
|
</div>
|
||||||
`));
|
`));
|
||||||
|
|
||||||
// Приблизительная дата от менеджера (если точной ещё нет — это подсказка)
|
// Примечание от менеджера (рекомендации по дате, особенности доступа)
|
||||||
if (!m.scheduled_at && (m.preferred_type || m.preferred_note)) {
|
if (m.preferred_note) {
|
||||||
const prefText = formatPreferredHuman(m);
|
|
||||||
app.appendChild(el(`
|
app.appendChild(el(`
|
||||||
<section class="block preferred-block">
|
<section class="block preferred-block">
|
||||||
<div class="block-head">⏰ Когда удобно клиенту (от менеджера)</div>
|
<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:12px 4px;color:var(--ink);font-size:14.5px;line-height:1.5;">${escHtml(m.preferred_note).replace(/\n/g, "<br>")}</div>
|
||||||
<div style="padding:0 4px 4px;color:var(--muted);font-size:12px;">
|
|
||||||
Позвоните клиенту и согласуйте точную дату — она появится ниже.
|
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
`));
|
`));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Заметки от менеджера
|
// Блок логистики (подъезд, GPS, парковка) — заполняется на месте
|
||||||
if (m.notes) {
|
|
||||||
app.appendChild(el(`
|
|
||||||
<section class="block">
|
|
||||||
<div class="block-head">Заметки от менеджера</div>
|
|
||||||
<div style="padding:12px 4px;color:var(--ink-2);font-size:14px;">${escHtml(m.notes).replace(/\n/g, "<br>")}</div>
|
|
||||||
</section>
|
|
||||||
`));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Блок логистики — заполняется замерщиком/сборщиком на месте
|
|
||||||
app.appendChild(renderLogisticsBlock(m));
|
app.appendChild(renderLogisticsBlock(m));
|
||||||
|
|
||||||
// Блок «назначить дату» (если ещё requested) или «изменить дату» (если scheduled)
|
// Блок даты замера — две версии в зависимости от статуса
|
||||||
const isScheduled = m.status === "scheduled";
|
const isScheduled = m.status === "scheduled" && m.scheduled_at;
|
||||||
const schedSection = el(`
|
if (isScheduled) {
|
||||||
<section class="block">
|
// Дата назначена — показываем её крупно + кнопка «Изменить»
|
||||||
<div class="block-head">${isScheduled ? "Дата замера" : "Назначить дату"}</div>
|
const dateSection = el(`
|
||||||
<div style="padding:6px 0 0;">
|
<section class="block date-set-block">
|
||||||
|
<div class="block-head">📅 Замер назначен</div>
|
||||||
|
<div class="date-set-value">${escHtml(formatDateHuman(m.scheduled_at))}</div>
|
||||||
|
<div class="podbor-cta-row">
|
||||||
|
<button class="btn-secondary" id="changeDate" type="button">Изменить дату</button>
|
||||||
|
</div>
|
||||||
|
<div class="date-set-form" id="changeDateForm" style="display:none;">
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<label class="field">
|
<label class="field">
|
||||||
<span class="field-label">Дата и время визита</span>
|
<span class="field-label">Новая дата и время</span>
|
||||||
<input type="datetime-local" id="schedInput" value="${m.scheduled_at ? toDatetimeLocalValue(m.scheduled_at) : ""}">
|
<input type="datetime-local" id="schedInput" value="${toDatetimeLocalValue(m.scheduled_at)}">
|
||||||
<span class="field-hint" id="schedHint">${isScheduled ? "Согласовано — можно изменить" : "Согласуйте с клиентом, потом выберите тут"}</span>
|
|
||||||
<span class="field-error" id="schedError"></span>
|
<span class="field-error" id="schedError"></span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="podbor-cta-row">
|
<div class="podbor-cta-row">
|
||||||
<button class="btn-primary" id="saveSched">${isScheduled ? "Изменить дату" : "Назначить"}</button>
|
<button class="btn-secondary" id="cancelChange" type="button">Отмена</button>
|
||||||
|
<button class="btn-primary" id="saveSched" type="button">Сохранить</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
`);
|
`);
|
||||||
app.appendChild(schedSection);
|
app.appendChild(dateSection);
|
||||||
|
dateSection.querySelector("#changeDate").addEventListener("click", () => {
|
||||||
|
dateSection.querySelector("#changeDateForm").style.display = "";
|
||||||
|
dateSection.querySelector("#changeDate").style.display = "none";
|
||||||
|
});
|
||||||
|
dateSection.querySelector("#cancelChange").addEventListener("click", () => {
|
||||||
|
dateSection.querySelector("#changeDateForm").style.display = "none";
|
||||||
|
dateSection.querySelector("#changeDate").style.display = "";
|
||||||
|
});
|
||||||
|
dateSection.querySelector("#saveSched").addEventListener("click", () => saveScheduleDate(measurementId, dateSection));
|
||||||
|
|
||||||
schedSection.querySelector("#saveSched").addEventListener("click", async () => {
|
// ОСНОВНАЯ кнопка — начать замер (открывает мастер с чек-листом)
|
||||||
const input = schedSection.querySelector("#schedInput");
|
const startSection = el(`
|
||||||
const errorEl = schedSection.querySelector("#schedError");
|
<div class="podbor-cta-row" style="margin-top:20px;">
|
||||||
errorEl.textContent = "";
|
<button class="btn-primary" id="startMeasure" style="font-size:16px;padding:14px 20px;">📐 Начать замер</button>
|
||||||
|
</div>
|
||||||
|
<div class="muted" style="text-align:center;font-size:12px;margin-top:8px;">
|
||||||
|
Чек-лист, фото и заметки откроются после нажатия.
|
||||||
|
</div>
|
||||||
|
`);
|
||||||
|
app.appendChild(startSection);
|
||||||
|
startSection.querySelector("#startMeasure").addEventListener("click", () => {
|
||||||
|
haptic && haptic("impact");
|
||||||
|
location.hash = `#/measure?id=${measurementId}`;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Дата не назначена — основной шаг: согласовать и назначить
|
||||||
|
const dateSection = el(`
|
||||||
|
<section class="block">
|
||||||
|
<div class="block-head">📞 Согласовать дату с клиентом</div>
|
||||||
|
<div style="padding:8px 4px;color:var(--muted);font-size:13px;">
|
||||||
|
Позвоните клиенту, договоритесь о точной дате и времени, затем зафиксируйте здесь.
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<label class="field">
|
||||||
|
<span class="field-label">Дата и время визита</span>
|
||||||
|
<input type="datetime-local" id="schedInput">
|
||||||
|
<span class="field-error" id="schedError"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="podbor-cta-row">
|
||||||
|
<button class="btn-primary" id="saveSched" type="button">Назначить</button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
`);
|
||||||
|
app.appendChild(dateSection);
|
||||||
|
dateSection.querySelector("#saveSched").addEventListener("click", () => saveScheduleDate(measurementId, dateSection));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveScheduleDate(measurementId, section) {
|
||||||
|
const input = section.querySelector("#schedInput");
|
||||||
|
const errorEl = section.querySelector("#schedError");
|
||||||
|
if (errorEl) errorEl.textContent = "";
|
||||||
const val = input.value;
|
const val = input.value;
|
||||||
if (!val) {
|
if (!val) {
|
||||||
errorEl.textContent = "Укажите дату и время";
|
if (errorEl) errorEl.textContent = "Укажите дату и время";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const iso = new Date(val).toISOString();
|
const iso = new Date(val).toISOString();
|
||||||
@ -704,35 +745,22 @@ async function renderInboxDetail(measurementId) {
|
|||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
initData: tg?.initData || "",
|
initData: tg?.initData || "",
|
||||||
|
initDataUnsafe: tg?.initDataUnsafe || null,
|
||||||
measurement_id: measurementId,
|
measurement_id: measurementId,
|
||||||
scheduled_at: iso,
|
scheduled_at: iso,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
errorEl.textContent = "Ошибка: " + data.error;
|
if (errorEl) errorEl.textContent = "Ошибка: " + data.error;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
haptic && haptic("success");
|
haptic && haptic("success");
|
||||||
tg?.showAlert?.("Дата назначена — менеджер уведомлён.");
|
tg?.showAlert?.("Дата сохранена — менеджер уведомлён.");
|
||||||
renderInboxDetail(measurementId); // перерисовать
|
renderInboxDetail(measurementId); // перерисовать с новым статусом
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
errorEl.textContent = "Сеть: " + e.message;
|
if (errorEl) errorEl.textContent = "Сеть: " + e.message;
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
// Кнопка «Сделать замер» (только если назначено или прямо сейчас)
|
|
||||||
const measureBtn = el(`
|
|
||||||
<div class="podbor-cta-row" style="margin-top:16px;">
|
|
||||||
<button class="btn-primary" id="goMeasure">📐 Сделать замер сейчас</button>
|
|
||||||
</div>
|
|
||||||
`);
|
|
||||||
measureBtn.querySelector("#goMeasure").addEventListener("click", () => {
|
|
||||||
haptic && haptic("impact");
|
|
||||||
// Передаём measurement_id чтобы wizard работал в update-mode
|
|
||||||
location.hash = `#/measure?id=${measurementId}`;
|
|
||||||
});
|
|
||||||
app.appendChild(measureBtn);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderLogisticsBlock(m) {
|
function renderLogisticsBlock(m) {
|
||||||
|
|||||||
@ -2183,6 +2183,25 @@
|
|||||||
border-left: 3px solid var(--walnut, #6B4A2B);
|
border-left: 3px solid var(--walnut, #6B4A2B);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Блок «дата назначена» */
|
||||||
|
.date-set-block {
|
||||||
|
background: linear-gradient(180deg, rgba(0, 62, 126, 0.04), transparent);
|
||||||
|
border-left: 3px solid var(--accent-1, #003E7E);
|
||||||
|
}
|
||||||
|
.date-set-block .date-set-value {
|
||||||
|
font-family: var(--font-display, "Newsreader", serif);
|
||||||
|
font-style: italic;
|
||||||
|
font-size: 22px;
|
||||||
|
color: var(--accent-1, #003E7E);
|
||||||
|
padding: 8px 4px 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.date-set-block .date-set-form {
|
||||||
|
border-top: 1px dashed rgba(0, 62, 126, 0.2);
|
||||||
|
padding-top: 12px;
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
/* ===== Логистика (подъезд, GPS, парковка) ===== */
|
/* ===== Логистика (подъезд, GPS, парковка) ===== */
|
||||||
.logistics-block .block-head {
|
.logistics-block .block-head {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@ -9,11 +9,8 @@ const MeasurementRequest = (function () {
|
|||||||
client_phone: "",
|
client_phone: "",
|
||||||
address: "",
|
address: "",
|
||||||
assigned_to_tg_id: "",
|
assigned_to_tg_id: "",
|
||||||
notes: "",
|
// Одно поле «Примечание» — рекомендации по дате замера + особенности.
|
||||||
// Приблизительная дата визита
|
// Замерщик увидит это в карточке заявки и согласует точное время с клиентом.
|
||||||
preferred_type: "tbd", // specific | this_week | next_week | tbd
|
|
||||||
preferred_date: "",
|
|
||||||
preferred_time_of_day: "", // morning | day | evening | ""
|
|
||||||
preferred_note: "",
|
preferred_note: "",
|
||||||
};
|
};
|
||||||
let measurers = [];
|
let measurers = [];
|
||||||
@ -24,8 +21,8 @@ const MeasurementRequest = (function () {
|
|||||||
const oldNav = document.getElementById("bottom-nav");
|
const oldNav = document.getElementById("bottom-nav");
|
||||||
if (oldNav) oldNav.remove();
|
if (oldNav) oldNav.remove();
|
||||||
state = {
|
state = {
|
||||||
client_name: "", client_phone: "", address: "", assigned_to_tg_id: "", notes: "",
|
client_name: "", client_phone: "", address: "", assigned_to_tg_id: "",
|
||||||
preferred_type: "tbd", preferred_date: "", preferred_time_of_day: "", preferred_note: "",
|
preferred_note: "",
|
||||||
};
|
};
|
||||||
render();
|
render();
|
||||||
loadMeasurers();
|
loadMeasurers();
|
||||||
@ -75,53 +72,11 @@ 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">
|
<div class="form-row">
|
||||||
<label class="field">
|
<label class="field">
|
||||||
<span class="field-label">Уточнение по времени</span>
|
<span class="field-label">Примечание</span>
|
||||||
<input type="text" data-pref="note" placeholder="например: после звонка, не раньше вторника">
|
<textarea data-bind="preferred_note" rows="3" placeholder="например: эта неделя после звонка, не раньше вторника, удобно утром, газ/электро, особые условия доступа"></textarea>
|
||||||
</label>
|
<span class="field-hint">Рекомендации по дате + особенности. Точную дату согласует замерщик с клиентом.</span>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-row">
|
|
||||||
<label class="field">
|
|
||||||
<span class="field-label">Заметки для замерщика</span>
|
|
||||||
<textarea data-bind="notes" rows="3" placeholder="газ/электро, особые условия доступа, ниши под технику, ..."></textarea>
|
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -147,23 +102,6 @@ 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() {
|
||||||
@ -224,12 +162,9 @@ const MeasurementRequest = (function () {
|
|||||||
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 || "",
|
// Примечание (рекомендации по дате + особенности) — единое поле
|
||||||
// Приблизительная дата визита
|
|
||||||
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 || "",
|
preferred_note: state.preferred_note || "",
|
||||||
|
preferred_type: "tbd",
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
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=20260513y">
|
<link rel="stylesheet" href="assets/styles.css?v=20260513z">
|
||||||
<link rel="stylesheet" href="assets/podbor.css?v=20260513y">
|
<link rel="stylesheet" href="assets/podbor.css?v=20260513z">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<!-- Splash — за пределами #app, render-функции его не смывают -->
|
<!-- Splash — за пределами #app, render-функции его не смывают -->
|
||||||
@ -31,14 +31,14 @@
|
|||||||
<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=20260513y"></script>
|
<script src="assets/icons.js?v=20260513z"></script>
|
||||||
<script src="assets/podbor.config.js?v=20260513y"></script>
|
<script src="assets/podbor.config.js?v=20260513z"></script>
|
||||||
<script src="assets/podbor.picts.js?v=20260513y"></script>
|
<script src="assets/podbor.picts.js?v=20260513z"></script>
|
||||||
<script src="assets/podbor.js?v=20260513y"></script>
|
<script src="assets/podbor.js?v=20260513z"></script>
|
||||||
<script src="assets/clients.js?v=20260513y"></script>
|
<script src="assets/clients.js?v=20260513z"></script>
|
||||||
<script src="assets/zamer-picts.js?v=20260513y"></script>
|
<script src="assets/zamer-picts.js?v=20260513z"></script>
|
||||||
<script src="assets/measurements.js?v=20260513y"></script>
|
<script src="assets/measurements.js?v=20260513z"></script>
|
||||||
<script src="assets/request.js?v=20260513y"></script>
|
<script src="assets/request.js?v=20260513z"></script>
|
||||||
<script src="assets/app.js?v=20260513y"></script>
|
<script src="assets/app.js?v=20260513z"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user