mirror of
https://github.com/wasrusgen/zov-tech.git
synced 2026-06-03 15:44:47 +00:00
feat: add entrance+floor fields, fix geocoder false-positive on locality match
- clients.js: new client + edit client forms get подъезд and этаж fields (optional) - clients.js: address string includes подъезд/этаж when filled - clients.js: splitAddress parses подъезд/этаж from stored address string - clients.js: geocoder now checks result.kind — only shows ✓ for house/street precision; locality/province match shows warning "улица не найдена" without saving coords - podbor.css: addr-grid grows to 3 rows (город/улица, дом/кв, подъезд/этаж) - index.html: cache bump → v=20260514j Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
44799362c1
commit
bedef30465
@ -76,6 +76,14 @@ const Clients = (function () {
|
|||||||
<span class="field-sublabel">Кв./офис</span>
|
<span class="field-sublabel">Кв./офис</span>
|
||||||
<input type="text" id="ad_apt" placeholder="12" inputmode="numeric">
|
<input type="text" id="ad_apt" placeholder="12" inputmode="numeric">
|
||||||
</label>
|
</label>
|
||||||
|
<label class="field addr-entrance">
|
||||||
|
<span class="field-sublabel">Подъезд</span>
|
||||||
|
<input type="text" id="ad_entrance" placeholder="1" inputmode="numeric">
|
||||||
|
</label>
|
||||||
|
<label class="field addr-floor">
|
||||||
|
<span class="field-sublabel">Этаж</span>
|
||||||
|
<input type="text" id="ad_floor" placeholder="3" inputmode="numeric">
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<span class="field-error" id="errAddr"></span>
|
<span class="field-error" id="errAddr"></span>
|
||||||
<div class="geo-status" id="geoStatus"></div>
|
<div class="geo-status" id="geoStatus"></div>
|
||||||
@ -133,17 +141,24 @@ const Clients = (function () {
|
|||||||
});
|
});
|
||||||
const name = (form.querySelector("#fn").value || "").trim();
|
const name = (form.querySelector("#fn").value || "").trim();
|
||||||
const phoneRaw = (form.querySelector("#ph").value || "").trim();
|
const phoneRaw = (form.querySelector("#ph").value || "").trim();
|
||||||
const adCity = (form.querySelector("#ad_city").value || "").trim();
|
const adCity = (form.querySelector("#ad_city").value || "").trim();
|
||||||
const adStreet = (form.querySelector("#ad_street").value|| "").trim();
|
const adStreet = (form.querySelector("#ad_street").value || "").trim();
|
||||||
const adHouse = (form.querySelector("#ad_house").value || "").trim();
|
const adHouse = (form.querySelector("#ad_house").value || "").trim();
|
||||||
const adApt = (form.querySelector("#ad_apt").value || "").trim();
|
const adApt = (form.querySelector("#ad_apt").value || "").trim();
|
||||||
|
const adEntrance = (form.querySelector("#ad_entrance").value|| "").trim();
|
||||||
|
const adFloor = (form.querySelector("#ad_floor").value || "").trim();
|
||||||
const note = (form.querySelector("#nt").value || "").trim();
|
const note = (form.querySelector("#nt").value || "").trim();
|
||||||
const contract_no = (form.querySelector("#cn").value || "").trim();
|
const contract_no = (form.querySelector("#cn").value || "").trim();
|
||||||
const contract_date = (form.querySelector("#cd").value || "").trim();
|
const contract_date = (form.querySelector("#cd").value || "").trim();
|
||||||
|
|
||||||
// Собираем адрес из полей
|
// Собираем адрес из полей
|
||||||
const address = [adCity, adStreet, adHouse ? "д. " + adHouse : "", adApt ? "кв. " + adApt : ""]
|
const address = [
|
||||||
.filter(Boolean).join(", ");
|
adCity, adStreet,
|
||||||
|
adHouse ? "д. " + adHouse : "",
|
||||||
|
adApt ? "кв. " + adApt : "",
|
||||||
|
adEntrance ? "подъезд " + adEntrance : "",
|
||||||
|
adFloor ? "этаж " + adFloor : "",
|
||||||
|
].filter(Boolean).join(", ");
|
||||||
|
|
||||||
// Валидация
|
// Валидация
|
||||||
if (!name || name.length < 2) {
|
if (!name || name.length < 2) {
|
||||||
@ -179,9 +194,15 @@ const Clients = (function () {
|
|||||||
});
|
});
|
||||||
const geoData = await geoRes.json();
|
const geoData = await geoRes.json();
|
||||||
if (geoData.ok && geoData.result) {
|
if (geoData.ok && geoData.result) {
|
||||||
gps_lat = geoData.result.lat;
|
const kind = (geoData.result.kind || "").toLowerCase();
|
||||||
gps_lng = geoData.result.lng;
|
const precise = ["house", "street", "entrance", "building"].includes(kind);
|
||||||
geoEl.innerHTML = `<span class="geo-ok">✓ ${escHtml(geoData.result.formatted || address)}</span>`;
|
if (precise) {
|
||||||
|
gps_lat = geoData.result.lat;
|
||||||
|
gps_lng = geoData.result.lng;
|
||||||
|
geoEl.innerHTML = `<span class="geo-ok">✓ ${escHtml(geoData.result.formatted || address)}</span>`;
|
||||||
|
} else {
|
||||||
|
geoEl.innerHTML = `<span class="geo-warn">⚠ Улица не найдена — геокодер вернул «${escHtml(geoData.result.formatted || "")}». Проверьте написание улицы. Сохраняем без координат.</span>`;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
geoEl.innerHTML = `<span class="geo-warn">⚠ Адрес не найден в геокодере — проверьте написание. Сохраняем без координат.</span>`;
|
geoEl.innerHTML = `<span class="geo-warn">⚠ Адрес не найден в геокодере — проверьте написание. Сохраняем без координат.</span>`;
|
||||||
}
|
}
|
||||||
@ -240,23 +261,22 @@ const Clients = (function () {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Разбирает сохранённый адрес «Город, Улица, д. NN, кв. MM» обратно в поля.
|
// Разбирает сохранённый адрес «Город, Улица, д. NN, кв. MM, подъезд P, этаж F» обратно в поля.
|
||||||
function splitAddress(combined) {
|
function splitAddress(combined) {
|
||||||
if (!combined) return { city: "Санкт-Петербург", street: "", house: "", apt: "" };
|
if (!combined) return { city: "Санкт-Петербург", street: "", house: "", apt: "", entrance: "", floor: "" };
|
||||||
let s = combined.trim();
|
let s = combined.trim();
|
||||||
let apt = "";
|
const grab = (re) => { const m = s.match(re); if (m) { s = s.replace(m[0], ""); return m[1]; } return ""; };
|
||||||
const aptMatch = s.match(/,\s*кв\.?\s*([^\s,]+)/i);
|
const floor = grab(/,\s*этаж\s+([^\s,]+)/i);
|
||||||
if (aptMatch) { apt = aptMatch[1]; s = s.replace(aptMatch[0], ""); }
|
const entrance = grab(/,\s*подъезд\s+([^\s,]+)/i);
|
||||||
let house = "";
|
const apt = grab(/,\s*кв\.?\s*([^\s,]+)/i);
|
||||||
const houseMatch = s.match(/,\s*д\.?\s*([^\s,]+)/i);
|
const house = grab(/,\s*д\.?\s*([^\s,]+)/i);
|
||||||
if (houseMatch) { house = houseMatch[1]; s = s.replace(houseMatch[0], ""); }
|
|
||||||
s = s.replace(/,$/, "").trim();
|
s = s.replace(/,$/, "").trim();
|
||||||
const parts = s.split(",").map(p => p.trim()).filter(Boolean);
|
const parts = s.split(",").map(p => p.trim()).filter(Boolean);
|
||||||
let city = "", street = "";
|
let city = "", street = "";
|
||||||
if (parts.length >= 2) { city = parts[0]; street = parts.slice(1).join(", "); }
|
if (parts.length >= 2) { city = parts[0]; street = parts.slice(1).join(", "); }
|
||||||
else if (parts.length === 1) { city = parts[0]; }
|
else if (parts.length === 1) { city = parts[0]; }
|
||||||
if (!city) city = "Санкт-Петербург";
|
if (!city) city = "Санкт-Петербург";
|
||||||
return { city, street, house, apt };
|
return { city, street, house, apt, entrance, floor };
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizePhone(raw) {
|
function normalizePhone(raw) {
|
||||||
@ -751,6 +771,14 @@ const Clients = (function () {
|
|||||||
<span class="field-sublabel">Кв./офис</span>
|
<span class="field-sublabel">Кв./офис</span>
|
||||||
<input type="text" id="ed_apt" value="${escAttr(addrParts.apt)}" placeholder="12" inputmode="numeric">
|
<input type="text" id="ed_apt" value="${escAttr(addrParts.apt)}" placeholder="12" inputmode="numeric">
|
||||||
</label>
|
</label>
|
||||||
|
<label class="field addr-entrance">
|
||||||
|
<span class="field-sublabel">Подъезд</span>
|
||||||
|
<input type="text" id="ed_entrance" value="${escAttr(addrParts.entrance)}" placeholder="1" inputmode="numeric">
|
||||||
|
</label>
|
||||||
|
<label class="field addr-floor">
|
||||||
|
<span class="field-sublabel">Этаж</span>
|
||||||
|
<input type="text" id="ed_floor" value="${escAttr(addrParts.floor)}" placeholder="3" inputmode="numeric">
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<span class="field-error" id="ed_errAddr"></span>
|
<span class="field-error" id="ed_errAddr"></span>
|
||||||
<div class="geo-status" id="ed_geoStatus"></div>
|
<div class="geo-status" id="ed_geoStatus"></div>
|
||||||
@ -781,12 +809,14 @@ const Clients = (function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
form.querySelector("#ed_save").addEventListener("click", async () => {
|
form.querySelector("#ed_save").addEventListener("click", async () => {
|
||||||
const fn = form.querySelector("#ed_fn").value.trim();
|
const fn = form.querySelector("#ed_fn").value.trim();
|
||||||
const ph = form.querySelector("#ed_ph").value.trim();
|
const ph = form.querySelector("#ed_ph").value.trim();
|
||||||
const edCity = (form.querySelector("#ed_city").value || "").trim();
|
const edCity = (form.querySelector("#ed_city").value || "").trim();
|
||||||
const edStreet = (form.querySelector("#ed_street").value || "").trim();
|
const edStreet = (form.querySelector("#ed_street").value || "").trim();
|
||||||
const edHouse = (form.querySelector("#ed_house").value || "").trim();
|
const edHouse = (form.querySelector("#ed_house").value || "").trim();
|
||||||
const edApt = (form.querySelector("#ed_apt").value || "").trim();
|
const edApt = (form.querySelector("#ed_apt").value || "").trim();
|
||||||
|
const edEntrance = (form.querySelector("#ed_entrance").value || "").trim();
|
||||||
|
const edFloor = (form.querySelector("#ed_floor").value || "").trim();
|
||||||
const cno = form.querySelector("#ed_cno").value.trim();
|
const cno = form.querySelector("#ed_cno").value.trim();
|
||||||
const cdate = form.querySelector("#ed_cdate").value.trim();
|
const cdate = form.querySelector("#ed_cdate").value.trim();
|
||||||
const errName = form.querySelector("#ed_errName");
|
const errName = form.querySelector("#ed_errName");
|
||||||
@ -809,8 +839,13 @@ const Clients = (function () {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const address = [edCity, edStreet, edHouse ? "д. " + edHouse : "", edApt ? "кв. " + edApt : ""]
|
const address = [
|
||||||
.filter(Boolean).join(", ");
|
edCity, edStreet,
|
||||||
|
edHouse ? "д. " + edHouse : "",
|
||||||
|
edApt ? "кв. " + edApt : "",
|
||||||
|
edEntrance ? "подъезд " + edEntrance : "",
|
||||||
|
edFloor ? "этаж " + edFloor : "",
|
||||||
|
].filter(Boolean).join(", ");
|
||||||
|
|
||||||
const btn = form.querySelector("#ed_save");
|
const btn = form.querySelector("#ed_save");
|
||||||
btn.disabled = true; btn.textContent = "Проверяем адрес…";
|
btn.disabled = true; btn.textContent = "Проверяем адрес…";
|
||||||
@ -830,9 +865,15 @@ const Clients = (function () {
|
|||||||
});
|
});
|
||||||
const geoData = await geoRes.json();
|
const geoData = await geoRes.json();
|
||||||
if (geoData.ok && geoData.result) {
|
if (geoData.ok && geoData.result) {
|
||||||
gps_lat = geoData.result.lat;
|
const kind = (geoData.result.kind || "").toLowerCase();
|
||||||
gps_lng = geoData.result.lng;
|
const precise = ["house", "street", "entrance", "building"].includes(kind);
|
||||||
geoEl.innerHTML = `<span class="geo-ok">✓ ${escHtml(geoData.result.formatted || address)}</span>`;
|
if (precise) {
|
||||||
|
gps_lat = geoData.result.lat;
|
||||||
|
gps_lng = geoData.result.lng;
|
||||||
|
geoEl.innerHTML = `<span class="geo-ok">✓ ${escHtml(geoData.result.formatted || address)}</span>`;
|
||||||
|
} else {
|
||||||
|
geoEl.innerHTML = `<span class="geo-warn">⚠ Улица не найдена — геокодер вернул «${escHtml(geoData.result.formatted || "")}». Проверьте написание улицы. Сохраняем без координат.</span>`;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
geoEl.innerHTML = `<span class="geo-warn">⚠ Адрес не найден — сохраняем без координат.</span>`;
|
geoEl.innerHTML = `<span class="geo-warn">⚠ Адрес не найден — сохраняем без координат.</span>`;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3328,8 +3328,10 @@
|
|||||||
margin-top: 6px;
|
margin-top: 6px;
|
||||||
}
|
}
|
||||||
.addr-grid .field { margin: 0; }
|
.addr-grid .field { margin: 0; }
|
||||||
.addr-house { grid-column: 1; }
|
.addr-house { grid-column: 1; }
|
||||||
.addr-apt { grid-column: 2; }
|
.addr-apt { grid-column: 2; }
|
||||||
|
.addr-entrance { grid-column: 1; }
|
||||||
|
.addr-floor { grid-column: 2; }
|
||||||
.field-sublabel {
|
.field-sublabel {
|
||||||
display: block;
|
display: block;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
|
|||||||
@ -12,14 +12,14 @@
|
|||||||
<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;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">
|
<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>
|
<script src="https://telegram.org/js/telegram-web-app.js"></script>
|
||||||
<link rel="stylesheet" href="assets/styles.css?v=20260514i">
|
<link rel="stylesheet" href="assets/styles.css?v=20260514j">
|
||||||
<link rel="stylesheet" href="assets/podbor.css?v=20260514i">
|
<link rel="stylesheet" href="assets/podbor.css?v=20260514j">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<!-- Splash — лого @wasrusgen1 + опилки (16) + вращающийся диск -->
|
<!-- Splash — лого @wasrusgen1 + опилки (16) + вращающийся диск -->
|
||||||
<div class="loader splash" id="splash">
|
<div class="loader splash" id="splash">
|
||||||
<div class="brand-logo-wrap">
|
<div class="brand-logo-wrap">
|
||||||
<img class="brand-logo" src="assets/wasrusgen-logo.svg?v=20260514i" alt="@wasrusgen1">
|
<img class="brand-logo" src="assets/wasrusgen-logo.svg?v=20260514j" alt="@wasrusgen1">
|
||||||
<div class="splash-dust" aria-hidden="true">
|
<div class="splash-dust" aria-hidden="true">
|
||||||
<span class="dust d1"></span> <span class="dust d2"></span>
|
<span class="dust d1"></span> <span class="dust d2"></span>
|
||||||
<span class="dust d3"></span> <span class="dust d4"></span>
|
<span class="dust d3"></span> <span class="dust d4"></span>
|
||||||
@ -35,15 +35,15 @@
|
|||||||
<div class="brand-tagline-gold">CRM</div>
|
<div class="brand-tagline-gold">CRM</div>
|
||||||
</div>
|
</div>
|
||||||
<main id="app"></main>
|
<main id="app"></main>
|
||||||
<script src="assets/icons.js?v=20260514i"></script>
|
<script src="assets/icons.js?v=20260514j"></script>
|
||||||
<script src="assets/podbor.config.js?v=20260514i"></script>
|
<script src="assets/podbor.config.js?v=20260514j"></script>
|
||||||
<script src="assets/podbor.picts.js?v=20260514i"></script>
|
<script src="assets/podbor.picts.js?v=20260514j"></script>
|
||||||
<script src="assets/podbor.js?v=20260514i"></script>
|
<script src="assets/podbor.js?v=20260514j"></script>
|
||||||
<script src="assets/clients.js?v=20260514i"></script>
|
<script src="assets/clients.js?v=20260514j"></script>
|
||||||
<script src="assets/zamer-picts.js?v=20260514i"></script>
|
<script src="assets/zamer-picts.js?v=20260514j"></script>
|
||||||
<script src="assets/measurements.js?v=20260514i"></script>
|
<script src="assets/measurements.js?v=20260514j"></script>
|
||||||
<script src="assets/request.js?v=20260514i"></script>
|
<script src="assets/request.js?v=20260514j"></script>
|
||||||
<script src="assets/assembly.js?v=20260514i"></script>
|
<script src="assets/assembly.js?v=20260514j"></script>
|
||||||
<script src="assets/app.js?v=20260514i"></script>
|
<script src="assets/app.js?v=20260514j"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user