feat: Яндекс.Карты в карточке клиента + gps_lat/lng в API клиентов

- backend: _ensure_client получает gps_lat/gps_lng поля
- backend: при агрегации Measurements берём gps_lat/gps_lng для клиента
- clients.js: в шапке карточки кнопка «🗺 Карта» если есть координаты
- podbor.css: стили .client-detail-addr, .map-link-btn
- index.html: cache bump → v=20260514k

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
wasrusgen 2026-05-15 22:11:17 +03:00
parent bedef30465
commit 016e3becdd
4 changed files with 53 additions and 14 deletions

View File

@ -1069,6 +1069,8 @@ def _handle_clients(body: dict[str, Any]) -> dict[str, Any]:
"client_tg_id": ctg_id or None,
"client_phone": phone or "",
"address": "",
"gps_lat": "",
"gps_lng": "",
"client_no": "",
"contract_no": "",
"contract_date": "",
@ -1153,6 +1155,9 @@ def _handle_clients(body: dict[str, Any]) -> dict[str, Any]:
if contract_date and not c.get("contract_date"): c["contract_date"] = contract_date
if address and not c.get("address"): c["address"] = address
if client_phone and not c.get("client_phone"): c["client_phone"] = client_phone
gps_lat = (row.get("gps_lat") or "").strip()
gps_lng = (row.get("gps_lng") or "").strip()
if gps_lat and gps_lng and not c.get("gps_lat"): c["gps_lat"] = gps_lat; c["gps_lng"] = gps_lng
c["measurements_count"] = c.get("measurements_count", 0) + 1
# Замер не-draft = клиент в работе (requested/scheduled/completed)
if m_status and m_status != "draft":

View File

@ -510,8 +510,15 @@ const Clients = (function () {
const contractTag = client.contract_no
? `<div class="client-detail-meta">📋 договор ${escHtml(client.contract_no)}${client.contract_date ? ` · ${escHtml(client.contract_date)}` : ""}</div>`
: "";
const mapUrl = (client.gps_lat && client.gps_lng)
? `https://yandex.ru/maps/?ll=${client.gps_lng},${client.gps_lat}&z=17&pt=${client.gps_lng},${client.gps_lat},pm2rdm`
: "";
const addressTag = client.address
? `<div class="client-detail-meta">📍 ${escHtml(client.address)}</div>`
? `<div class="client-detail-meta client-detail-addr">
<span class="addr-text">📍 ${escHtml(client.address)}</span>${mapUrl
? `<a class="map-link-btn" href="${escAttr(mapUrl)}" target="_blank" rel="noopener">🗺 Карта</a>`
: ""}
</div>`
: "";
const statusTag = client.in_work
? ""

View File

@ -2085,6 +2085,33 @@
font-family: var(--font-mono, "JetBrains Mono", monospace);
margin-top: 4px;
}
.client-detail-addr {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
}
.client-detail-addr .addr-text {
flex: 1;
min-width: 0;
}
.map-link-btn {
flex-shrink: 0;
display: inline-flex;
align-items: center;
gap: 3px;
padding: 3px 9px;
background: rgba(107,74,43,0.08);
border: 1px solid rgba(107,74,43,0.20);
border-radius: 12px;
font-size: 11px;
font-weight: 600;
color: var(--walnut, #6B4A2B);
text-decoration: none;
font-family: inherit;
white-space: nowrap;
}
.map-link-btn:active { background: rgba(107,74,43,0.16); }
/* ===== Опасная зона удаления ===== */
.danger-zone {

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=20260514j">
<link rel="stylesheet" href="assets/podbor.css?v=20260514j">
<link rel="stylesheet" href="assets/styles.css?v=20260514k">
<link rel="stylesheet" href="assets/podbor.css?v=20260514k">
</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=20260514j" alt="@wasrusgen1">
<img class="brand-logo" src="assets/wasrusgen-logo.svg?v=20260514k" 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=20260514j"></script>
<script src="assets/podbor.config.js?v=20260514j"></script>
<script src="assets/podbor.picts.js?v=20260514j"></script>
<script src="assets/podbor.js?v=20260514j"></script>
<script src="assets/clients.js?v=20260514j"></script>
<script src="assets/zamer-picts.js?v=20260514j"></script>
<script src="assets/measurements.js?v=20260514j"></script>
<script src="assets/request.js?v=20260514j"></script>
<script src="assets/assembly.js?v=20260514j"></script>
<script src="assets/app.js?v=20260514j"></script>
<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>
</body>
</html>