From 41dbaa66f413d67e1c8cf0f755844a21f72b0459 Mon Sep 17 00:00:00 2001 From: Codex Date: Mon, 7 Sep 2026 20:42:43 +0000 Subject: [PATCH] Restructure views and use Telegram native action button --- static/app.js | 84 +++++++++++++++++++++++++++++++++++++---------- static/index.html | 50 ++++++++++++++-------------- static/styles.css | 27 +++++++++++---- 3 files changed, 113 insertions(+), 48 deletions(-) diff --git a/static/app.js b/static/app.js index 2da58d0..cd091a7 100644 --- a/static/app.js +++ b/static/app.js @@ -12,6 +12,8 @@ const state = { kind: "income", board: "all", + currentView: "dashboardView", + previousView: "dashboardView", categoryFilter: "", summary: null, config: null, @@ -24,17 +26,19 @@ const $ = (selector) => document.querySelector(selector); const money = new Intl.NumberFormat("ru-RU", { style: "currency", currency: "RUB", maximumFractionDigits: 2 }); + function safeTelegram(action) { + try { action(); } catch (error) { console.warn("Telegram WebApp method failed", error); } + } + function initTelegram() { if (!tg) return; - const safely = (action) => { - try { action(); } catch (error) { console.warn("Telegram WebApp method failed", error); } - }; - safely(() => tg.ready()); - safely(() => tg.expand()); - safely(() => tg.enableClosingConfirmation?.()); - if (tg.isVersionAtLeast?.("6.1")) safely(() => tg.setBackgroundColor("#111111")); - if (tg.isVersionAtLeast?.("6.9")) safely(() => tg.setHeaderColor("#111111")); - if (tg.isVersionAtLeast?.("7.10")) safely(() => tg.setBottomBarColor("#111111")); + safeTelegram(() => tg.ready()); + safeTelegram(() => tg.expand()); + safeTelegram(() => tg.enableClosingConfirmation?.()); + if (tg.isVersionAtLeast?.("6.1")) safeTelegram(() => tg.setBackgroundColor("#111111")); + if (tg.isVersionAtLeast?.("6.9")) safeTelegram(() => tg.setHeaderColor("#111111")); + if (tg.isVersionAtLeast?.("7.7")) safeTelegram(() => tg.disableVerticalSwipes()); + if (tg.isVersionAtLeast?.("7.10")) safeTelegram(() => tg.setBottomBarColor("#111111")); } function showToast(message) { @@ -54,15 +58,59 @@ function bindNavigation() { document.querySelectorAll(".bottom-nav button").forEach((button) => { - button.addEventListener("click", () => { - document.querySelectorAll(".bottom-nav button, .view").forEach((item) => item.classList.remove("active")); - button.classList.add("active"); - $(`#${button.dataset.view}`).classList.add("active"); - if (button.dataset.view === "leaderboardView") loadLeaderboard(); - }); + button.addEventListener("click", () => showView(button.dataset.view)); }); } + function showView(viewId) { + document.querySelectorAll(".bottom-nav button, .view").forEach((item) => item.classList.remove("active")); + $(`#${viewId}`).classList.add("active"); + document.querySelector(`.bottom-nav button[data-view="${viewId}"]`)?.classList.add("active"); + state.currentView = viewId; + document.body.classList.toggle("form-open", viewId === "addView"); + updateNativeControls(); + window.scrollTo({ top: 0, behavior: "auto" }); + if (viewId === "leaderboardView") loadLeaderboard(); + } + + function openAddView() { + if (state.currentView !== "addView") state.previousView = state.currentView; + showView("addView"); + window.setTimeout(() => $("#amount").focus(), 0); + } + + function closeAddView() { + showView(state.previousView || "dashboardView"); + } + + function setupNativeControls() { + const isTelegramClient = Boolean(tg?.platform && tg.platform !== "unknown"); + if (!isTelegramClient || !tg?.MainButton) return; + safeTelegram(() => tg.MainButton.onClick(() => { + if (state.currentView === "addView") { + $("#transactionForm").requestSubmit(); + } else { + openAddView(); + } + })); + safeTelegram(() => tg.BackButton?.onClick(closeAddView)); + updateNativeControls(); + } + + function updateNativeControls() { + const isTelegramClient = Boolean(tg?.platform && tg.platform !== "unknown"); + if (!isTelegramClient || !tg?.MainButton) return; + const isForm = state.currentView === "addView"; + safeTelegram(() => tg.MainButton.setParams({ + text: isForm ? "Сохранить операцию" : "Добавить запись", + color: "#f0d28a", + text_color: "#17130b", + is_active: true, + is_visible: true, + })); + safeTelegram(() => isForm ? tg.BackButton?.show() : tg.BackButton?.hide()); + } + function fillCategories() { const select = $("#category"); const items = [...new Set([...categories[state.kind], ...state.customCategories[state.kind]])]; @@ -143,10 +191,12 @@ } if (!canSendViaTelegram()) return false; try { + safeTelegram(() => tg.MainButton?.showProgress()); tg.HapticFeedback?.notificationOccurred("success"); tg.sendData(encoded); return true; } catch (error) { + safeTelegram(() => tg.MainButton?.hideProgress()); console.error("Telegram sendData failed", error); showToast("Telegram не принял запись. Обновите приложение и попробуйте снова"); return false; @@ -190,9 +240,6 @@ $("#balanceValue").textContent = money.format(summary.balance_cents / 100); $("#incomeValue").textContent = money.format(summary.income_cents / 100); $("#expenseValue").textContent = money.format(summary.expense_cents / 100); - const percent = Number(summary.saved_percent); - $("#savedPercent").textContent = `${percent.toLocaleString("ru-RU", { maximumFractionDigits: 1 })}%`; - $("#savingProgress").style.width = `${Math.max(0, Math.min(100, percent))}%`; renderCategories(summary.categories || []); } @@ -369,6 +416,7 @@ initTelegram(); setIdentity(); bindNavigation(); + setupNativeControls(); bindForm(); bindLeaderboard(); fillCategories(); diff --git a/static/index.html b/static/index.html index a338741..949c570 100644 --- a/static/index.html +++ b/static/index.html @@ -6,9 +6,9 @@ Пожитки - + - +
@@ -24,25 +24,28 @@
-
-

Накоплено

- 0 ₽ -
- 0% -
-
- от всех поступлений -
+
+
+

Текущий баланс

+ 0 ₽ +
-
-
- -
Поступления0 ₽
-
-
- -
Расходы0 ₽
-
+
+
+ +
Поступления0 ₽
+
+
+ +
Расходы0 ₽
+
+
+
+
+ +
+
+

Движение денег

История

@@ -62,8 +65,8 @@
-
-
+
+

Новая запись

Движение денег

@@ -93,7 +96,6 @@ -

Telegram передаст запись боту вместе с вашим подтверждённым ID.

@@ -120,7 +122,7 @@ diff --git a/static/styles.css b/static/styles.css index 8fc4ee9..e4cd494 100644 --- a/static/styles.css +++ b/static/styles.css @@ -20,7 +20,13 @@ -webkit-tap-highlight-color: transparent; } -html, body { margin: 0; min-height: 100%; background: var(--bg); } +html, body { + margin: 0; + min-height: 100%; + background: var(--bg); + overscroll-behavior: none; + overscroll-behavior-y: none; +} body { position: relative; @@ -28,6 +34,7 @@ body { min-height: 100vh; min-height: 100dvh; padding: max(14px, env(safe-area-inset-top)) 18px calc(96px + env(safe-area-inset-bottom)); + overflow-x: hidden; background: transparent; } @@ -46,7 +53,7 @@ body::before { body::after { z-index: -1; - background: rgba(0, 0, 0, .16); + background: rgba(0, 0, 0, .2); } button, input, select { font: inherit; color: inherit; } @@ -63,7 +70,7 @@ button:focus { outline: none; } button:focus-visible { outline: 2px solid rgba(255, 255, 255, .72); outline-offset: 2px; } button:active { filter: brightness(.88); } -.shell { width: min(100%, 560px); margin: 0 auto; } +.shell { display: flex; flex-direction: column; width: min(100%, 560px); min-height: calc(var(--tg-viewport-stable-height, 100dvh) - 110px); margin: 0 auto; } .topbar { display: flex; align-items: center; justify-content: space-between; margin: 6px 2px 26px; text-shadow: 0 2px 14px rgba(0, 0, 0, .55); } .eyebrow { margin: 0 0 3px; text-transform: uppercase; letter-spacing: .12em; font-size: 10px; font-weight: 750; color: var(--muted); } h1, h2, p { margin-top: 0; } @@ -72,11 +79,16 @@ h2 { margin: 0; font-size: 23px; letter-spacing: -.025em; } .user-chip { display: flex; align-items: center; gap: 8px; max-width: 48%; padding: 5px 11px 5px 5px; border: 1px solid var(--line); border-radius: 999px; background: var(--surface); font-size: 12px; font-weight: 650; white-space: nowrap; overflow: hidden; } .mini-avatar { display: grid; flex: 0 0 auto; place-items: center; width: 29px; height: 29px; border-radius: 50%; background: rgba(0, 0, 0, .28); color: var(--text); } -.view { display: none; padding-top: clamp(120px, 17vh, 175px); } +.view { display: none; padding-top: 0; } .view.active { display: block; } +#dashboardView.active { display: flex; flex: 1; flex-direction: column; justify-content: flex-end; } +.dashboard-summary { width: 100%; margin-top: auto; } .hero-card { - min-height: 210px; + display: flex; + flex-direction: column; + justify-content: flex-end; + min-height: 190px; padding: 27px; border-radius: 29px; border: 1px solid var(--line); @@ -91,6 +103,8 @@ h2 { margin: 0; font-size: 23px; letter-spacing: -.025em; } .progress { flex: 1; height: 7px; overflow: hidden; border-radius: 99px; background: rgba(255,255,255,.2); } .progress i { display: block; width: 0; height: 100%; border-radius: inherit; background: #e8d487; transition: width .4s ease; } .hero-card small { display: block; margin-top: 7px; opacity: .55; font-size: 11px; } +.dashboard-summary .hero-card strong { margin-bottom: 0; } +.dashboard-summary .metric-grid { margin-bottom: 0; } .metric-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 11px; margin: 12px 0 28px; } .metric { display: flex; align-items: center; gap: 11px; min-width: 0; padding: 17px 15px; border: 1px solid var(--line); border-radius: 20px; background: var(--surface); } @@ -113,6 +127,7 @@ h2 { margin: 0; font-size: 23px; letter-spacing: -.025em; } .category-bar i { display: block; height: 100%; background: var(--accent); } .category-item.expense-item .category-bar i { background: var(--danger); } .history-heading { margin-top: 28px; } +.history-page-heading { margin: 5px 3px 24px; } .transaction-list { overflow: hidden; border: 1px solid var(--line); border-radius: 21px; background: var(--surface); } .transaction-row { display: grid; grid-template-columns: 40px 1fr auto; align-items: center; gap: 11px; padding: 13px 14px; border-bottom: 1px solid var(--line); } .transaction-row:last-child { border-bottom: 0; } @@ -168,7 +183,7 @@ h2 { margin: 0; font-size: 23px; letter-spacing: -.025em; } .bottom-nav button { display: grid; place-items: center; gap: 2px; padding: 3px; border: 0; background: transparent; color: var(--muted); font-size: 9px; font-weight: 650; } .bottom-nav button > span { font-size: 22px; line-height: 30px; } .bottom-nav button.active { color: var(--accent); } -.bottom-nav .add-mark { display: grid; place-items: center; width: 42px; height: 32px; border-radius: 12px; background: var(--accent); color: var(--accent-text); } +.form-open .bottom-nav { display: none; } .toast { position: fixed; z-index: 20; left: 50%; bottom: calc(92px + env(safe-area-inset-bottom)); max-width: calc(100% - 36px); padding: 11px 16px; border: 1px solid var(--line); border-radius: 12px; transform: translate(-50%, 20px); background: rgba(0, 0, 0, .62); color: var(--text); opacity: 0; pointer-events: none; transition: .2s ease; font-size: 12px; box-shadow: var(--shadow); backdrop-filter: blur(18px); -webkit-backdrop-filter: blur(18px); } .toast.show { opacity: 1; transform: translate(-50%, 0); }