diff --git a/public/service-worker.js b/public/service-worker.js
index 9252f3e..8b8308b 100644
--- a/public/service-worker.js
+++ b/public/service-worker.js
@@ -1,7 +1,7 @@
const isLocalhost = self.location.hostname === "localhost" || self.location.hostname === "127.0.0.1";
if (!isLocalhost) {
- const STATIC_CACHE = "construction-delivery-static-v132";
+ const STATIC_CACHE = "construction-delivery-static-v133";
const RUNTIME_CACHE = "construction-delivery-runtime-v119";
const APP_SHELL_URLS = ["/", "/client", "/index.html", "/manifest.webmanifest", "/icons/icon-192.png", "/icons/icon-512.png"];
diff --git a/src/components/admin/analytics/CommsFunnelBlock.jsx b/src/components/admin/analytics/CommsFunnelBlock.jsx
index 69b2a9e..7caca68 100644
--- a/src/components/admin/analytics/CommsFunnelBlock.jsx
+++ b/src/components/admin/analytics/CommsFunnelBlock.jsx
@@ -26,6 +26,11 @@ const CHANNEL_LABELS = {
manual: 'Менеджер',
link: 'Ссылка из SMS',
automation: 'Автоматически',
+ // Клиентские каналы из ссылки (/delivery/{hash}?src=...) — до 29.09.2026 не писались.
+ sms: 'SMS-ссылка',
+ client: 'Ссылка (прямой заход)',
+ push: 'Push',
+ messenger: 'Мессенджер',
};
const STATUS_LABELS = {
diff --git a/src/components/admin/analytics/CommsFunnelBlock.test.jsx b/src/components/admin/analytics/CommsFunnelBlock.test.jsx
index 3812e7b..813411b 100644
--- a/src/components/admin/analytics/CommsFunnelBlock.test.jsx
+++ b/src/components/admin/analytics/CommsFunnelBlock.test.jsx
@@ -80,6 +80,31 @@ describe("CommsFunnelBlock", () => {
expect(html).toContain("Ссылка из SMS");
});
+ it("подписывает клиентские каналы ссылки, а не показывает ключи БД", () => {
+ const clientChannels = renderToStaticMarkup(
+ ,
+ );
+ expect(clientChannels).toContain("SMS-ссылка");
+ expect(clientChannels).toContain("Ссылка (прямой заход)");
+ expect(clientChannels).toContain("Push");
+ expect(clientChannels).toContain("Мессенджер");
+ // сырые ключи источника наружу не уходят
+ expect(clientChannels).not.toContain(">client<");
+ expect(clientChannels).not.toContain(">messenger<");
+ });
+
it("перечисляет тех, кто не берёт трубку, и тех, кто открыл ссылку без подтверждения", () => {
expect(html).toContain("Не поднимают трубку");
expect(html).toContain("9787617568");
diff --git a/src/components/admin/analytics/OrderPathFunnelBlock.jsx b/src/components/admin/analytics/OrderPathFunnelBlock.jsx
index 2dbfc88..b4d1c41 100644
--- a/src/components/admin/analytics/OrderPathFunnelBlock.jsx
+++ b/src/components/admin/analytics/OrderPathFunnelBlock.jsx
@@ -34,6 +34,12 @@ const SOURCE_LABELS = {
link: 'Ссылка из SMS',
automation: 'Автоматически',
none: 'В обход (без даты)',
+ // Клиентские каналы из ссылки (/delivery/{hash}?src=...) — до 29.09.2026 не писались,
+ // поэтому в старых строках стоит пустой источник («В обход (без даты)»).
+ sms: 'SMS-ссылка',
+ client: 'Ссылка (прямой заход)',
+ push: 'Push',
+ messenger: 'Мессенджер',
};
const FunnelTooltip = ({ active, payload }) => {
diff --git a/src/components/admin/analytics/OrderPathFunnelBlock.test.jsx b/src/components/admin/analytics/OrderPathFunnelBlock.test.jsx
index 5d73e7b..f1b572f 100644
--- a/src/components/admin/analytics/OrderPathFunnelBlock.test.jsx
+++ b/src/components/admin/analytics/OrderPathFunnelBlock.test.jsx
@@ -65,6 +65,33 @@ describe("OrderPathFunnelBlock", () => {
expect(html).toContain("Доставка");
});
+ it("подписывает клиентские источники даты, а не показывает ключи БД", () => {
+ const withClientSources = renderToStaticMarkup(
+ ,
+ );
+ expect(withClientSources).toContain("SMS-ссылка");
+ expect(withClientSources).toContain("Ссылка (прямой заход)");
+ expect(withClientSources).toContain("Push");
+ expect(withClientSources).toContain("Мессенджер");
+ expect(withClientSources).not.toContain(">client<");
+ expect(withClientSources).not.toContain(">messenger<");
+ });
+
it("на пустом периоде пишет заглушку", () => {
const e = renderToStaticMarkup();
expect(e).toContain("За период заказов нет");