diff --git a/miniapp/assets/app.js b/miniapp/assets/app.js index 6880f01..dbedcff 100644 --- a/miniapp/assets/app.js +++ b/miniapp/assets/app.js @@ -116,74 +116,35 @@ function pluralRu(n, forms) { return forms[2]; } -function renderManagerHome(me) { - // === MOCK DATA (Этап 1 — визуал, без реального backend) === - const firstName = (me.user?.full_name || "").split(/\s+/)[0] || "Артём"; - const todayTask = { - time: "15:30", - tag: "ЗАМЕР", - client: "А. Пестова", - address: "ЖК Сады Пекина, корп. 3", - phone: "+7 999 000-00-00", - }; - const projects = [ - { name: "Семья Иваниковых", address: "ул. Орджоникидзе, 14 — 47", stage: "Согласование", date: "14 мая", progress: 0.40, statusLabel: "Ожидает клиента", statusKind: "waiting" }, - { name: "Кабанова И. С.", address: "Никольская набережная, 20", stage: "Производство", date: "21 мая", progress: 0.60, statusLabel: "В работе", statusKind: "active" }, - { name: "Карелин А.", address: "посёлок Сосновый, дом 4", stage: "Замер", date: "сегодня", progress: 0.10, statusLabel: "Срочно", statusKind: "urgent" }, - { name: "Петросян Г.", address: "ул. Лесная, 18 — 12", stage: "Доставка", date: "16 мая", progress: 0.85, statusLabel: "В работе", statusKind: "active" }, - { name: "Тимирясов И.", address: "пос. Барвиха, дом 8", stage: "Монтаж", date: "11 мая", progress: 0.95, statusLabel: "Завершается", statusKind: "active" }, - ]; - const unreadChats = 2; - const tasksTodayCount = todayTask ? 1 : 0; - const taskWord = pluralRu(tasksTodayCount, ["замер", "замера", "замеров"]); - const phraseTail = tasksTodayCount === 0 ? "ничего на сегодня" : `${tasksTodayCount === 1 ? "один" : tasksTodayCount} ${taskWord} сегодня`; +async function renderManagerHome(me) { + const firstName = (me.user?.full_name || "").split(/\s+/)[0] || "Менеджер"; - // === RENDER === app.innerHTML = ""; document.body.classList.add("has-bottom-nav"); - // Greeting - app.appendChild(el(` + // Greeting + bell (placeholder) + const greetingEl = el(`
${timeOfDay()}
-
${firstName},
- ${phraseTail} +
${firstName},
+ смотрим день…
-
- `)); + `); + app.appendChild(greetingEl); - // Hero task - if (todayTask) { - app.appendChild(el(` -
-
- - На сегодня${todayTask.time} - - ${todayTask.tag} -
-
${todayTask.client}
-
${todayTask.address}
-
- - ${ICONS.phone} -
-
- `)); - } + // Контейнер для «Сегодня» — наполнится после загрузки + const todayContainer = el(`
`); + app.appendChild(todayContainer); // Quick actions const quickActions = [ - { icon: "user", title: "Клиенты", subtitle: "История подборов", href: "#/clients" }, - { icon: "package", title: "Подбор техники", subtitle: "Встройка + AI", href: "#/podbor" }, - { icon: "ruler", title: "Заказать замер", subtitle: "Назначить замерщика", href: "#/request" }, - { icon: "camera", title: "Замер сейчас", subtitle: "Заполнить вручную", href: "#/measure" }, + { icon: "user", title: "Клиенты", subtitle: "История + хронология", href: "#/clients" }, + { icon: "package", title: "Подбор техники", subtitle: "Встройка + AI", href: "#/podbor" }, + { icon: "ruler", title: "Заказать замер", subtitle: "Назначить замерщика", href: "#/request" }, + { icon: "camera", title: "Замер сейчас", subtitle: "Заполнить вручную", href: "#/measure" }, ]; app.appendChild(el(`
Быстрые действия
`)); const grid = el(`
`); @@ -204,36 +165,228 @@ function renderManagerHome(me) { }); app.appendChild(grid); - // Active projects - app.appendChild(el(` -
- Активные проекты · ${projects.length} - Все + // Активные проекты — будет наполняться позже из реальных данных + const projectsContainer = el(`
`); + app.appendChild(projectsContainer); + + renderBottomNav("home", { unreadChats: 0 }); + + // Параллельно грузим реальные данные + try { + const res = await fetch(`${BACKEND_URL}/api/measurements`, { + method: "POST", + body: JSON.stringify({ + initData: tg?.initData || "", + initDataUnsafe: tg?.initDataUnsafe || null, + }), + }); + const data = await res.json(); + const measurements = (data.measurements || []); + + renderManagerToday(todayContainer, measurements, firstName, greetingEl); + renderManagerProjects(projectsContainer, measurements); + } catch (e) { + todayContainer.innerHTML = `
Не удалось загрузить данные: ${escHtml(e.message)}
`; + } +} + +function renderManagerToday(container, measurements, firstName, greetingEl) { + const today = _startOfDay(new Date()); + const tomorrow = new Date(today); tomorrow.setDate(tomorrow.getDate() + 1); + + // Сегодня = scheduled_at сегодня и не completed + const todayEvents = []; + const overdueEvents = []; + const noDateEvents = []; + + for (const m of measurements) { + if (m.status === "completed") continue; + if (m.scheduled_at) { + const d = new Date(m.scheduled_at); + if (_startOfDay(d).getTime() === today.getTime()) { + todayEvents.push(m); + } else if (d < new Date()) { + overdueEvents.push(m); + } + } else if (m.status === "requested") { + // Заявка без даты — нужно подсказать замерщику + noDateEvents.push(m); + } + } + todayEvents.sort((a, b) => (a.scheduled_at || "").localeCompare(b.scheduled_at || "")); + + // Обновляем приветствие + const cnt = todayEvents.length; + let tail; + if (cnt === 0) { + tail = overdueEvents.length + ? `${overdueEvents.length} ${pluralRu(overdueEvents.length, ["просрочка", "просрочки", "просрочек"])}` + : "ничего на сегодня"; + } else { + const word = pluralRu(cnt, ["замер", "замера", "замеров"]); + tail = `${cnt === 1 ? "один" : cnt} ${word} сегодня`; + } + const headline = greetingEl.querySelector("#greetingHeadline"); + if (headline) headline.innerHTML = `${escHtml(firstName)},
${escHtml(tail)}`; + + container.innerHTML = ""; + + // HERO — первое событие сегодня + if (todayEvents.length > 0) { + const m = todayEvents[0]; + const d = new Date(m.scheduled_at); + const hh = String(d.getHours()).padStart(2, "0"); + const mi = String(d.getMinutes()).padStart(2, "0"); + const phoneClean = (m.client_phone || "").replace(/[^\d+]/g, ""); + const hero = el(` +
+
+ На сегодня${hh}:${mi} + ЗАМЕР +
+
${escHtml(m.client_name || "Без имени")}
+
${escHtml(m.address || "адрес не указан")}
+
+ + ${phoneClean ? `${ICONS.phone || "📞"}` : ""} +
+
+ `); + hero.querySelector("#heroOpen").addEventListener("click", () => { + haptic("impact"); + location.hash = `#/clients/measurement/${m.id}`; + }); + container.appendChild(hero); + } + + // Срочно: просрочки + if (overdueEvents.length > 0) { + container.appendChild(el(`
⚠️ Срочно · ${overdueEvents.length}
`)); + const list = el(`
`); + overdueEvents.slice(0, 5).forEach(m => list.appendChild(renderTodayItem(m, "overdue"))); + container.appendChild(list); + } + + // Остальные на сегодня (кроме первого, который в hero) + if (todayEvents.length > 1) { + container.appendChild(el(`
📅 Ещё сегодня · ${todayEvents.length - 1}
`)); + const list = el(`
`); + todayEvents.slice(1).forEach(m => list.appendChild(renderTodayItem(m, "today"))); + container.appendChild(list); + } + + // Заявки без даты — напомнить созвониться с замерщиком + if (noDateEvents.length > 0) { + container.appendChild(el(`
📞 Без даты · ${noDateEvents.length}
`)); + const list = el(`
`); + noDateEvents.slice(0, 5).forEach(m => list.appendChild(renderTodayItem(m, "no_date"))); + container.appendChild(list); + } + + if (todayEvents.length === 0 && overdueEvents.length === 0 && noDateEvents.length === 0) { + container.appendChild(el(` +
+
Свободный день
+
Замеров на сегодня нет.
Можно поработать с клиентами или заказать новые замеры.
+
+ `)); + } +} + +function renderTodayItem(m, kind) { + const phoneClean = (m.client_phone || "").replace(/[^\d+]/g, ""); + const callHref = phoneClean ? `tel:${phoneClean}` : ""; + let timeText = "—"; + 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 (kind === "overdue") { + timeText = `${String(d.getDate()).padStart(2,"0")}.${String(d.getMonth()+1).padStart(2,"0")} ${hh}:${mi}`; + } else { + timeText = `${hh}:${mi}`; + } + } else if (kind === "no_date") { + timeText = "?"; + } + const row = el(` +
+ + ${callHref ? `📞` : ""} +
+ `); + row.querySelector(".inbox-row-main").addEventListener("click", () => { + haptic && haptic("impact"); + location.hash = `#/clients/measurement/${m.id}`; + }); + return row; +} + +function renderManagerProjects(container, measurements) { + // Активные проекты = все замеры менеджера с любым статусом кроме completed/archived в обозримой перспективе. + // Берём последние 5 по дате создания. + const active = (measurements || []) + .filter(m => m.status !== "archived") + .sort((a, b) => (b.created_at || "").localeCompare(a.created_at || "")) + .slice(0, 5); + + container.innerHTML = ""; + if (!active.length) return; + + container.appendChild(el(` +
+ Активные проекты · ${active.length}
`)); const list = el(`
`); - projects.forEach(p => { + for (const m of active) { + const stage = ({ + requested: "Заявка на замер", + scheduled: "Замер назначен", + in_progress: "Замер в работе", + completed: "Замер выполнен", + })[m.status] || m.status; + const statusKind = m.status === "completed" ? "active" + : m.status === "requested" ? "waiting" + : m.status === "scheduled" ? "active" + : "waiting"; + const dateLabel = m.scheduled_at + ? new Date(m.scheduled_at).toLocaleDateString("ru-RU", { day: "numeric", month: "short" }) + : (m.created_at ? formatDateHuman(m.created_at).slice(0, 10) : "—"); + const progress = ({ + requested: 0.15, + scheduled: 0.35, + in_progress: 0.55, + completed: 0.75, + })[m.status] || 0.10; const card = el(`
-
${p.name}
- ${p.statusLabel} +
${escHtml(m.client_name || "Без имени")}
+ ${statusKind === "waiting" ? "Ожидает" : "В работе"}
-
${p.address}
-
+
${escHtml(m.address || "адрес не указан")}
+
- ${p.stage} - ${p.date} + ${stage} + ${dateLabel}
`); - card.addEventListener("click", () => { haptic("impact"); tg?.showAlert?.(`Проект «${p.name}» — скоро`); }); + card.addEventListener("click", () => { + haptic("impact"); + location.hash = `#/clients/measurement/${m.id}`; + }); list.appendChild(card); - }); - app.appendChild(list); - - // Bottom nav (fixed, outside #app) - renderBottomNav("home", { unreadChats }); + } + container.appendChild(list); } function renderBottomNav(active, opts = {}) { diff --git a/miniapp/assets/podbor.css b/miniapp/assets/podbor.css index 1ea9a8f..84c782b 100644 --- a/miniapp/assets/podbor.css +++ b/miniapp/assets/podbor.css @@ -2538,6 +2538,14 @@ .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; } +/* ===== Главная менеджера: список «На сегодня» ===== */ +.today-list { display: flex; flex-direction: column; gap: 6px; } +.inbox-row.overdue { + border-color: rgba(192, 57, 43, 0.35); + background: rgba(192, 57, 43, 0.04); +} +.inbox-row.overdue .inbox-time { color: #C0392B; } + /* ===== Кабинет замерщика: week strip + grouped inbox ===== */ /* Week strip — загрузка по дням */ diff --git a/miniapp/index.html b/miniapp/index.html index f1b197e..8671065 100644 --- a/miniapp/index.html +++ b/miniapp/index.html @@ -12,8 +12,8 @@ - - + + @@ -31,14 +31,14 @@
Сделано с душой!
- - - - - - - - - + + + + + + + + +