From b8d9ff937fc8fd61dfcfe30f9ef02474885fa937 Mon Sep 17 00:00:00 2001 From: wasrusgen Date: Wed, 13 May 2026 18:51:43 +0300 Subject: [PATCH] =?UTF-8?q?F:=20=D0=BA=D0=B0=D0=B1=D0=B8=D0=BD=D0=B5=D1=82?= =?UTF-8?q?=20=D0=B7=D0=B0=D0=BC=D0=B5=D1=80=D1=89=D0=B8=D0=BA=D0=B0=20?= =?UTF-8?q?=E2=80=94=20week-strip=20+=20=D0=B3=D1=80=D1=83=D0=BF=D0=BF?= =?UTF-8?q?=D0=B8=D1=80=D0=BE=D0=B2=D0=BA=D0=B0=20=D0=BF=D0=BE=20=D0=B4?= =?UTF-8?q?=D0=BD=D1=8F=D0=BC=20+=20=F0=9F=93=9E=20=D0=B7=D0=B2=D0=BE?= =?UTF-8?q?=D0=BD=D0=BE=D0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Шапка → 📅 strip недели → 📥 заявки группами. Week strip (компакт): - 7 дней начиная с понедельника текущей недели - Каждый день: название (Пн/Вт/...), число, полоса загрузки (высота = доля от max за неделю), счётчик замеров - Цвет полосы: 1-2 = walnut light, 3-4 = walnut, 5+ = #C0392B (перегруз) - Сегодня — выделен walnut-рамкой + warm-фоном; прошлые дни приглушены Группированный инбокс: ⚠️ Просрочено (scheduled_at в прошлом, но не completed) 🔥 Сегодня 📅 Завтра 🗓️ На неделе (до воскресенья) 📆 Позже 📞 Без даты (нужно согласовать) Каждый ряд: - Слева время (10:00 / Пт 15.05 14:00) — крупно, walnut, моно - Центр: ФИО клиента + адрес (truncate) - Справа: chevron → переход к заявке - Зелёная кнопка 📞 — звонок в один тап (tel: ссылка) Cache bust v=20260513zb. --- miniapp/assets/app.js | 205 +++++++++++++++++++++++++++++++------- miniapp/assets/podbor.css | 167 +++++++++++++++++++++++++++++++ miniapp/index.html | 22 ++-- 3 files changed, 347 insertions(+), 47 deletions(-) diff --git a/miniapp/assets/app.js b/miniapp/assets/app.js index 2db4beb..6880f01 100644 --- a/miniapp/assets/app.js +++ b/miniapp/assets/app.js @@ -448,35 +448,37 @@ async function renderStaff(me) { `)); - // Реальный инбокс — загружаем из /api/measurement_inbox + // Загружаем заявки и рендерим: week strip + сгруппированный инбокс + const stripPlaceholder = el(`
`); const inboxSection = el(`
-
📥 Входящие заявки на замер
+
📥 Заявки
`); + app.appendChild(stripPlaceholder); app.appendChild(inboxSection); if (caps.measurer) { try { const res = await fetch(`${BACKEND_URL}/api/measurement_inbox`, { method: "POST", - body: JSON.stringify({ initData: tg?.initData || "" }), + body: JSON.stringify({ + initData: tg?.initData || "", + initDataUnsafe: tg?.initDataUnsafe || null, + }), }); const data = await res.json(); const list = document.getElementById("inboxList"); if (!list) return; if (data.error) { list.innerHTML = `
Ошибка: ${data.error}
`; - } else if (!data.measurements || !data.measurements.length) { - list.innerHTML = ` -
- Заявок пока нет. Когда менеджер назначит замер — увидите здесь. -
- `; } else { - list.innerHTML = ""; - data.measurements.forEach(m => list.appendChild(renderInboxItem(m))); + const measurements = data.measurements || []; + // Week strip — заменяет placeholder + document.getElementById("weekStrip").replaceWith(renderWeekStrip(measurements)); + // Группированный инбокс + renderGroupedInbox(list, measurements); } } catch (e) { const list = document.getElementById("inboxList"); @@ -505,35 +507,166 @@ async function renderStaff(me) { } } -function renderInboxItem(m) { - const statusLabel = ({ - requested: "🟡 ждёт даты", - scheduled: "📅 назначен", - in_progress: "🔵 в работе", - })[m.status] || m.status; - // Когда: точная дата если назначена, иначе приблизительная - let whenText; - if (m.scheduled_at) { - whenText = "📅 " + formatDateHuman(m.scheduled_at); - } else { - whenText = "🕐 " + formatPreferredHuman(m); +/* ----------------- Группировка инбокса замерщика по дням ----------------- */ + +function _startOfDay(d) { + const x = new Date(d); + x.setHours(0, 0, 0, 0); + return x; +} + +function _daysBetween(a, b) { + return Math.round((_startOfDay(b) - _startOfDay(a)) / 86400000); +} + +function _groupForMeasurement(m, today, weekEnd) { + if (!m.scheduled_at) { + // Без даты — отделяем requested от scheduled (по идее scheduled без даты быть не должно) + return { key: "no_date", title: "📞 Без даты — нужно согласовать", order: 5 }; + } + const d = new Date(m.scheduled_at); + const diff = _daysBetween(today, d); + if (diff < 0) return { key: "overdue", title: "⚠️ Просрочено", order: 0 }; + if (diff === 0) return { key: "today", title: "🔥 Сегодня", order: 1 }; + if (diff === 1) return { key: "tomorrow", title: "📅 Завтра", order: 2 }; + if (d <= weekEnd) return { key: "this_week", title: "🗓️ На неделе", order: 3 }; + return { key: "later", title: "📆 Позже", order: 4 }; +} + +function renderGroupedInbox(container, measurements) { + container.innerHTML = ""; + if (!measurements.length) { + container.innerHTML = ` +
+ Заявок пока нет. Когда менеджер назначит замер — увидите здесь. +
+ `; + return; } - const item = el(` - + `); + const list = groupEl.querySelector(".inbox-group-list"); + g.items.forEach(m => list.appendChild(renderInboxItem(m, g.key))); + container.appendChild(groupEl); + } +} + +/* ----------------- Week strip — загрузка по дням ----------------- */ + +function renderWeekStrip(measurements) { + const today = _startOfDay(new Date()); + const dayIdx = (today.getDay() + 6) % 7; // Пн = 0 + const monday = new Date(today); + monday.setDate(today.getDate() - dayIdx); + const days = []; + for (let i = 0; i < 7; i++) { + const d = new Date(monday); + d.setDate(monday.getDate() + i); + days.push(d); + } + // Считаем сколько замеров на каждый день + const countByDay = days.map(d => { + const start = _startOfDay(d).getTime(); + const end = start + 86400000; + return measurements.filter(m => { + if (!m.scheduled_at) return false; + const t = new Date(m.scheduled_at).getTime(); + return t >= start && t < end; + }).length; + }); + const maxCount = Math.max(1, ...countByDay); + + const dayNames = ["Пн", "Вт", "Ср", "Чт", "Пт", "Сб", "Вс"]; + const section = el(` +
+
+ ${monday.getDate()}–${days[6].getDate()} ${monday.toLocaleString("ru-RU", { month: "long" })} +
+
+ ${days.map((d, i) => { + const cnt = countByDay[i]; + const heightPct = cnt ? Math.round((cnt / maxCount) * 100) : 0; + const isToday = _startOfDay(d).getTime() === today.getTime(); + const isPast = _startOfDay(d).getTime() < today.getTime(); + const loadClass = cnt >= 5 ? "load-hot" : cnt >= 3 ? "load-mid" : cnt > 0 ? "load-low" : "load-zero"; + return ` +
+
${dayNames[i]}
+
${d.getDate()}
+
+
${cnt || "—"}
+
+ `; + }).join("")} +
+
`); - item.addEventListener("click", () => { + return section; +} + +/* ----------------- Карточка заявки в инбоксе ----------------- */ + +function renderInboxItem(m, groupKey) { + // Когда: точное время если назначено + день недели для не-today + let timeLine; + if (m.scheduled_at) { + const d = new Date(m.scheduled_at); + const hh = String(d.getHours()).padStart(2, "0"); + const mi = String(d.getMinutes()).padStart(2, "0"); + if (groupKey === "today" || groupKey === "tomorrow") { + timeLine = `${hh}:${mi}`; + } else if (groupKey === "overdue") { + timeLine = `${String(d.getDate()).padStart(2,"0")}.${String(d.getMonth()+1).padStart(2,"0")} ${hh}:${mi}`; + } else { + const dayNames = ["Вс", "Пн", "Вт", "Ср", "Чт", "Пт", "Сб"]; + timeLine = `${dayNames[d.getDay()]} ${String(d.getDate()).padStart(2,"0")}.${String(d.getMonth()+1).padStart(2,"0")} ${hh}:${mi}`; + } + } else { + timeLine = formatPreferredHuman(m); + } + + const phoneClean = (m.client_phone || "").replace(/[^\d+]/g, ""); + const callHref = phoneClean ? `tel:${phoneClean}` : ""; + + const item = el(` +
+ + ${callHref + ? `📞` + : ""} +
+ `); + item.querySelector(".inbox-row-main").addEventListener("click", () => { haptic && haptic("impact"); location.hash = `#/inbox/${m.id}`; }); diff --git a/miniapp/assets/podbor.css b/miniapp/assets/podbor.css index 0e6722d..1ea9a8f 100644 --- a/miniapp/assets/podbor.css +++ b/miniapp/assets/podbor.css @@ -2538,6 +2538,173 @@ .checklist-md .cl-table th, .checklist-md .cl-table td { border: 1px solid rgba(107, 74, 43, 0.18); padding: 4px 8px; text-align: left; } .checklist-md .cl-table th { background: rgba(107, 74, 43, 0.08); font-weight: 600; } +/* ===== Кабинет замерщика: week strip + grouped inbox ===== */ + +/* Week strip — загрузка по дням */ +.cal-strip-block { + background: var(--card, #fff); + border: 1px solid rgba(107, 74, 43, 0.12); + border-radius: 14px; + padding: 12px 10px 10px; + margin-bottom: 14px; +} +.cal-strip-head { + font-family: var(--font-display, "Newsreader", serif); + font-style: italic; + font-size: 16px; + color: var(--ink, #1F1A14); + text-align: center; + margin-bottom: 10px; + text-transform: capitalize; +} +.cal-strip { + display: grid; + grid-template-columns: repeat(7, 1fr); + gap: 4px; +} +.cal-day { + display: flex; + flex-direction: column; + align-items: center; + padding: 6px 2px 8px; + border-radius: 8px; + background: var(--paper, #FBF7F0); + border: 1px solid transparent; + position: relative; +} +.cal-day.today { + border-color: var(--walnut, #6B4A2B); + background: var(--warm, rgba(107, 74, 43, 0.08)); +} +.cal-day.past { opacity: 0.5; } +.cal-day-name { + font-size: 9px; + font-family: var(--font-mono, "JetBrains Mono", monospace); + letter-spacing: 0.08em; + text-transform: uppercase; + color: var(--muted, #998877); + margin-bottom: 2px; +} +.cal-day-num { + font-size: 15px; + font-weight: 600; + color: var(--ink, #1F1A14); + line-height: 1; +} +.cal-day.today .cal-day-num { color: var(--walnut, #6B4A2B); } +.cal-day-bar { + width: 18px; + height: 28px; + margin: 6px 0 4px; + background: rgba(107, 74, 43, 0.08); + border-radius: 3px; + display: flex; + align-items: flex-end; + overflow: hidden; +} +.cal-day-bar .bar { + width: 100%; + border-radius: 3px; + transition: height 0.25s ease; +} +.cal-day-bar .bar.load-zero { background: transparent; } +.cal-day-bar .bar.load-low { background: rgba(107, 74, 43, 0.5); } +.cal-day-bar .bar.load-mid { background: var(--walnut, #6B4A2B); } +.cal-day-bar .bar.load-hot { background: #C0392B; } +.cal-day-count { + font-size: 10px; + font-family: var(--font-mono, "JetBrains Mono", monospace); + color: var(--muted, #998877); + letter-spacing: 0.04em; +} + +/* Grouped inbox */ +.inbox-group { margin-bottom: 14px; } +.inbox-group-head { + display: flex; + align-items: baseline; + gap: 6px; + margin: 12px 0 8px; + font-size: 13px; + font-weight: 600; + color: var(--ink, #1F1A14); + letter-spacing: 0.01em; +} +.inbox-group-head .count { + font-size: 11px; + color: var(--muted, #998877); + font-family: var(--font-mono, "JetBrains Mono", monospace); + font-weight: 400; +} +.inbox-group-list { + display: flex; + flex-direction: column; + gap: 6px; +} +.inbox-row { + display: flex; + gap: 8px; + background: var(--card, #fff); + border: 1px solid rgba(107, 74, 43, 0.14); + border-radius: 10px; + overflow: hidden; +} +.inbox-row-main { + flex: 1; + display: flex; + align-items: center; + gap: 12px; + padding: 10px 12px; + background: transparent; + border: none; + cursor: pointer; + text-align: left; + font-family: inherit; + min-width: 0; +} +.inbox-row-main:active { background: rgba(107, 74, 43, 0.06); } +.inbox-time { + font-family: var(--font-mono, "JetBrains Mono", monospace); + font-size: 13px; + font-weight: 600; + color: var(--walnut, #6B4A2B); + min-width: 50px; + text-align: center; +} +.inbox-row-body { flex: 1; min-width: 0; } +.inbox-client { + font-size: 14px; + font-weight: 500; + color: var(--ink, #1F1A14); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.inbox-addr { + font-size: 12px; + color: var(--muted, #998877); + margin-top: 2px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.inbox-arrow { + color: var(--muted, #998877); + font-size: 18px; +} +.inbox-call { + display: grid; + place-items: center; + width: 44px; + background: rgba(39, 174, 96, 0.08); + color: #27AE60; + text-decoration: none; + font-size: 18px; + flex-shrink: 0; + border-left: 1px solid rgba(107, 74, 43, 0.12); +} +.inbox-call:active { background: rgba(39, 174, 96, 0.18); } + /* ===== Кабинет сотрудника (замерщик/сборщик) ===== */ .staff-head { display: flex; diff --git a/miniapp/index.html b/miniapp/index.html index 43e87b7..f1b197e 100644 --- a/miniapp/index.html +++ b/miniapp/index.html @@ -12,8 +12,8 @@ - - + + @@ -31,14 +31,14 @@
Сделано с душой!
- - - - - - - - - + + + + + + + + +