feat: адрес замера — 6 раздельных полей (город/улица/дом/кв/подъезд/этаж)

- measurements.js: renderClientPicker — одно поле адреса заменено на addr-grid
- добавлен локальный _splitAddr() — разбирает строку адреса обратно в поля
- при выборе клиента из пикера адрес автоматически раскладывается по полям
- каждое поле собирает state.address через readAndSaveAddr()
- index.html: cache bump → v=20260514l

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
wasrusgen 2026-05-15 22:13:20 +03:00
parent 016e3becdd
commit 0d2973ea77
2 changed files with 92 additions and 26 deletions

View File

@ -369,26 +369,94 @@ const Measurements = (function () {
/* ===================== Пикер клиента ===================== */
function renderClientPicker() {
// Разбивает строку адреса на поля (локальная копия splitAddress из clients.js)
function _splitAddr(s) {
if (!s) return { city: "Санкт-Петербург", street: "", house: "", apt: "", entrance: "", floor: "" };
s = s.trim();
const grab = (re) => { const m = s.match(re); if (m) { s = s.replace(m[0], ""); return m[1]; } return ""; };
const floor = grab(/,\s*этаж\s+([^\s,]+)/i);
const entrance = grab(/,\s*подъезд\s+([^\s,]+)/i);
const apt = grab(/,\s*кв\.?\s*([^\s,]+)/i);
const house = grab(/,\s*д\.?\s*([^\s,]+)/i);
s = s.replace(/,$/, "").trim();
const parts = s.split(",").map(p => p.trim()).filter(Boolean);
let city = "", street = "";
if (parts.length >= 2) { city = parts[0]; street = parts.slice(1).join(", "); }
else if (parts.length === 1) { city = parts[0]; }
if (!city) city = "Санкт-Петербург";
return { city, street, house, apt, entrance, floor };
}
const initParts = _splitAddr(state.address || "");
const wrap = el(`
<div class="client-picker-wrap">
<div class="form-row" id="pcChoiceRow"></div>
<div class="form-row">
<label class="field">
<span class="field-label">Адрес объекта</span>
<input type="text" data-bind="address" id="pcAddr"
value="${escAttr(state.address)}"
placeholder="СПб, пр. Просвещения, д. 87, кв. 12">
<div class="addr-grid">
<label class="field">
<span class="field-sublabel">Город</span>
<input type="text" id="pcCity" value="${escAttr(initParts.city)}" placeholder="Санкт-Петербург" autocomplete="address-level2">
</label>
<label class="field">
<span class="field-sublabel">Улица</span>
<input type="text" id="pcStreet" value="${escAttr(initParts.street)}" placeholder="пр. Просвещения" autocomplete="street-address">
</label>
<label class="field addr-house">
<span class="field-sublabel">Дом</span>
<input type="text" id="pcHouse" value="${escAttr(initParts.house)}" placeholder="87" inputmode="text">
</label>
<label class="field addr-apt">
<span class="field-sublabel">Кв./офис</span>
<input type="text" id="pcApt" value="${escAttr(initParts.apt)}" placeholder="12" inputmode="numeric">
</label>
<label class="field addr-entrance">
<span class="field-sublabel">Подъезд</span>
<input type="text" id="pcEntrance" value="${escAttr(initParts.entrance)}" placeholder="1" inputmode="numeric">
</label>
<label class="field addr-floor">
<span class="field-sublabel">Этаж</span>
<input type="text" id="pcFloor" value="${escAttr(initParts.floor)}" placeholder="3" inputmode="numeric">
</label>
</div>
<span class="field-error" id="pcAddrErr"></span>
</div>
</div>
`);
const choiceRow = wrap.querySelector("#pcChoiceRow");
const addrInput = wrap.querySelector("#pcAddr");
addrInput.addEventListener("input", e => {
state.address = e.target.value;
function readAndSaveAddr() {
const city = (wrap.querySelector("#pcCity").value || "").trim();
const street = (wrap.querySelector("#pcStreet").value || "").trim();
const house = (wrap.querySelector("#pcHouse").value || "").trim();
const apt = (wrap.querySelector("#pcApt").value || "").trim();
const entrance = (wrap.querySelector("#pcEntrance").value || "").trim();
const floor = (wrap.querySelector("#pcFloor").value || "").trim();
state.address = [
city, street,
house ? "д. " + house : "",
apt ? "кв. " + apt : "",
entrance ? "подъезд " + entrance : "",
floor ? "этаж " + floor : "",
].filter(Boolean).join(", ");
saveState();
}
function fillAddrFields(address) {
const p = _splitAddr(address || "");
wrap.querySelector("#pcCity").value = p.city;
wrap.querySelector("#pcStreet").value = p.street;
wrap.querySelector("#pcHouse").value = p.house;
wrap.querySelector("#pcApt").value = p.apt;
wrap.querySelector("#pcEntrance").value = p.entrance;
wrap.querySelector("#pcFloor").value = p.floor;
readAndSaveAddr();
}
["#pcCity","#pcStreet","#pcHouse","#pcApt","#pcEntrance","#pcFloor"].forEach(sel => {
wrap.querySelector(sel).addEventListener("input", readAndSaveAddr);
});
function refresh() {
@ -407,10 +475,8 @@ const Measurements = (function () {
`);
card.querySelector(".picker-change-btn").addEventListener("click", openOverlay);
choiceRow.appendChild(card);
if (!addrInput.value && pickedClient.address) {
addrInput.value = pickedClient.address;
state.address = pickedClient.address;
saveState();
if (!state.address && pickedClient.address) {
fillAddrFields(pickedClient.address);
}
} else {
const empty = el(`

View File

@ -12,14 +12,14 @@
<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;800&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&family=Caveat:wght@500;700&display=swap">
<script src="https://telegram.org/js/telegram-web-app.js"></script>
<link rel="stylesheet" href="assets/styles.css?v=20260514k">
<link rel="stylesheet" href="assets/podbor.css?v=20260514k">
<link rel="stylesheet" href="assets/styles.css?v=20260514l">
<link rel="stylesheet" href="assets/podbor.css?v=20260514l">
</head>
<body>
<!-- Splash — лого @wasrusgen1 + опилки (16) + вращающийся диск -->
<div class="loader splash" id="splash">
<div class="brand-logo-wrap">
<img class="brand-logo" src="assets/wasrusgen-logo.svg?v=20260514k" alt="@wasrusgen1">
<img class="brand-logo" src="assets/wasrusgen-logo.svg?v=20260514l" alt="@wasrusgen1">
<div class="splash-dust" aria-hidden="true">
<span class="dust d1"></span> <span class="dust d2"></span>
<span class="dust d3"></span> <span class="dust d4"></span>
@ -35,15 +35,15 @@
<div class="brand-tagline-gold">CRM</div>
</div>
<main id="app"></main>
<script src="assets/icons.js?v=20260514k"></script>
<script src="assets/podbor.config.js?v=20260514k"></script>
<script src="assets/podbor.picts.js?v=20260514k"></script>
<script src="assets/podbor.js?v=20260514k"></script>
<script src="assets/clients.js?v=20260514k"></script>
<script src="assets/zamer-picts.js?v=20260514k"></script>
<script src="assets/measurements.js?v=20260514k"></script>
<script src="assets/request.js?v=20260514k"></script>
<script src="assets/assembly.js?v=20260514k"></script>
<script src="assets/app.js?v=20260514k"></script>
<script src="assets/icons.js?v=20260514l"></script>
<script src="assets/podbor.config.js?v=20260514l"></script>
<script src="assets/podbor.picts.js?v=20260514l"></script>
<script src="assets/podbor.js?v=20260514l"></script>
<script src="assets/clients.js?v=20260514l"></script>
<script src="assets/zamer-picts.js?v=20260514l"></script>
<script src="assets/measurements.js?v=20260514l"></script>
<script src="assets/request.js?v=20260514l"></script>
<script src="assets/assembly.js?v=20260514l"></script>
<script src="assets/app.js?v=20260514l"></script>
</body>
</html>