feat: поиск клиентов на главном экране (фильтр по ФИО / телефону / договору)

- clients.js: renderList — поле поиска над списком, renderFiltered() фильтрует без API-запроса
- фильтрация: имя (подстрока), телефон (только цифры), номер договора
- счётчик обновляется: «Найдено N из M» при поиске, полная статистика без запроса
- podbor.css: .client-search-wrap, .client-search, .client-search-meta
- index.html: cache bump → v=20260514n

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
wasrusgen 2026-05-15 22:40:03 +03:00
parent bfd661575c
commit 715ac96de8
3 changed files with 79 additions and 22 deletions

View File

@ -408,6 +408,15 @@ const Clients = (function () {
});
root.appendChild(addBtn);
// Поиск (рендерится сразу, до загрузки)
const searchWrap = el(`
<div class="client-search-wrap">
<input class="client-search" type="search" placeholder="🔍 Поиск по имени, телефону, договору…" autocomplete="off">
</div>
`);
root.appendChild(searchWrap);
const searchInput = searchWrap.querySelector(".client-search");
const loading = el(`<div class="loader-inline"><div class="spinner"></div></div>`);
root.appendChild(loading);
@ -434,18 +443,42 @@ const Clients = (function () {
return;
}
const meta = el(`
<div class="kicker" style="margin-bottom:8px;">
${data.count} ${pluralize(data.count, "клиент", "клиента", "клиентов")} · ${countLeads(data.clients)} ${pluralize(countLeads(data.clients), "подбор", "подбора", "подборов")}
</div>
`);
root.appendChild(meta);
const metaEl = el(`<div class="kicker client-search-meta" style="margin-bottom:8px;"></div>`);
root.appendChild(metaEl);
const list = el(`<div class="client-list"></div>`);
for (const c of data.clients) {
list.appendChild(renderClientCard(c));
}
root.appendChild(list);
function renderFiltered(q) {
q = (q || "").trim().toLowerCase();
const qDigits = q.replace(/\D/g, "");
const filtered = q
? data.clients.filter(c => {
const nameMatch = (c.client_name || "").toLowerCase().includes(q);
const phoneMatch = qDigits && (c.client_phone || "").replace(/\D/g, "").includes(qDigits);
const contractMatch = (c.contract_no || "").toLowerCase().includes(q);
return nameMatch || phoneMatch || contractMatch;
})
: data.clients;
const n = filtered.length;
const total = data.clients.length;
metaEl.textContent = q
? `Найдено: ${n} из ${total}`
: `${total} ${pluralize(total, "клиент", "клиента", "клиентов")} · ${countLeads(data.clients)} ${pluralize(countLeads(data.clients), "подбор", "подбора", "подборов")}`;
list.innerHTML = "";
if (!filtered.length) {
list.innerHTML = `<div class="picker-empty-state" style="padding:32px 0;text-align:center;color:var(--muted);">Ничего не найдено</div>`;
return;
}
for (const c of filtered) {
list.appendChild(renderClientCard(c));
}
}
searchInput.addEventListener("input", () => renderFiltered(searchInput.value));
renderFiltered("");
}
function renderClientCard(c) {

View File

@ -3358,6 +3358,30 @@
border-top: 1px dashed var(--line);
}
/* ===== Поиск по списку клиентов ===== */
.client-search-wrap {
padding: 0 0 10px;
}
.client-search {
width: 100%;
padding: 10px 14px;
background: var(--paper, #FBF7F0);
border: 1.5px solid rgba(107,74,43,0.18);
border-radius: 10px;
font-family: inherit;
font-size: 14px;
color: var(--ink, #1F1A14);
box-sizing: border-box;
-webkit-appearance: none;
}
.client-search:focus {
outline: none;
border-color: var(--walnut, #6B4A2B);
background: white;
}
.client-search::placeholder { color: var(--muted, #998877); }
.client-search-meta { transition: color 0.15s; }
/* ===== Поля адреса (addr-grid) ===== */
.addr-grid {
display: grid;

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