@inspirer-dev/crm-dashboard 1.0.90 → 1.0.91
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/admin/src/hooks/api/index.ts +17 -0
- package/admin/src/hooks/api/use-manual-pushes.ts +177 -0
- package/admin/src/lib/react-query/query-keys.ts +5 -0
- package/admin/src/pages/HomePage/HomePage.css +17 -0
- package/admin/src/pages/HomePage/components/ManualPushesView.tsx +560 -0
- package/admin/src/pages/HomePage/components/index.ts +1 -0
- package/admin/src/pages/HomePage/index.tsx +14 -3
- package/dist/_chunks/{index-LXBoz7PC.mjs → index-B_cJDVsP.mjs} +481 -7
- package/dist/_chunks/{index-DRXMKPXI.js → index-D6jz5MgX.js} +478 -4
- package/dist/admin/index.js +1 -1
- package/dist/admin/index.mjs +1 -1
- package/dist/server/index.js +222 -2
- package/dist/server/index.mjs +222 -2
- package/dist/style.css +17 -0
- package/package.json +1 -1
- package/server/src/controllers/controller.ts +64 -0
- package/server/src/routes/index.ts +55 -0
- package/server/src/services/service.ts +134 -2
|
@@ -7,6 +7,7 @@ const icons = require("@strapi/icons");
|
|
|
7
7
|
const styledComponents = require("styled-components");
|
|
8
8
|
const reactQuery = require("@tanstack/react-query");
|
|
9
9
|
const reactQueryDevtools = require("@tanstack/react-query-devtools");
|
|
10
|
+
const admin = require("@strapi/strapi/admin");
|
|
10
11
|
const recharts = require("recharts");
|
|
11
12
|
const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
|
|
12
13
|
const React__default = /* @__PURE__ */ _interopDefault(React);
|
|
@@ -33,7 +34,10 @@ const crmKeys = {
|
|
|
33
34
|
antiSpamLogsWithFilters: (filters, page) => [...crmKeys.antiSpamLogs(), "list", { filters, page }],
|
|
34
35
|
campaigns: () => [...crmKeys.all, "campaigns"],
|
|
35
36
|
segments: () => [...crmKeys.all, "segments"],
|
|
36
|
-
templates: () => [...crmKeys.all, "templates"]
|
|
37
|
+
templates: () => [...crmKeys.all, "templates"],
|
|
38
|
+
manualPushTemplates: () => [...crmKeys.all, "manual-pushes", "templates"],
|
|
39
|
+
manualPushHistory: () => [...crmKeys.all, "manual-pushes", "history"],
|
|
40
|
+
manualPushStats: (manualPushId) => [...crmKeys.all, "manual-pushes", "stats", manualPushId]
|
|
37
41
|
};
|
|
38
42
|
const QueryProvider = ({ children }) => {
|
|
39
43
|
return /* @__PURE__ */ jsxRuntime.jsxs(reactQuery.QueryClientProvider, { client: queryClient, children: [
|
|
@@ -275,6 +279,72 @@ const useABTestData = (filters) => {
|
|
|
275
279
|
staleTime: 2 * 60 * 1e3
|
|
276
280
|
});
|
|
277
281
|
};
|
|
282
|
+
const PLUGIN_BASE = "/crm-dashboard";
|
|
283
|
+
const extractBackendError = (data) => {
|
|
284
|
+
if (!data) return "Unknown error";
|
|
285
|
+
if (data.backend?.message) return data.backend.message;
|
|
286
|
+
if (typeof data.backend === "string") return data.backend;
|
|
287
|
+
if (data.error) return data.error;
|
|
288
|
+
return JSON.stringify(data);
|
|
289
|
+
};
|
|
290
|
+
const useManualPushTemplates = () => {
|
|
291
|
+
const { get } = admin.useFetchClient();
|
|
292
|
+
return reactQuery.useQuery({
|
|
293
|
+
queryKey: crmKeys.manualPushTemplates(),
|
|
294
|
+
queryFn: async () => {
|
|
295
|
+
const { data } = await get(`${PLUGIN_BASE}/manual-pushes/templates`);
|
|
296
|
+
if (data?.error) throw new Error(data.error);
|
|
297
|
+
return data?.data ?? [];
|
|
298
|
+
},
|
|
299
|
+
staleTime: 60 * 1e3
|
|
300
|
+
});
|
|
301
|
+
};
|
|
302
|
+
const useManualPushHistory = () => {
|
|
303
|
+
const { get } = admin.useFetchClient();
|
|
304
|
+
return reactQuery.useQuery({
|
|
305
|
+
queryKey: crmKeys.manualPushHistory(),
|
|
306
|
+
queryFn: async () => {
|
|
307
|
+
const { data } = await get(`${PLUGIN_BASE}/manual-pushes/history`, {
|
|
308
|
+
params: { limit: 50 }
|
|
309
|
+
});
|
|
310
|
+
if (Array.isArray(data)) return data;
|
|
311
|
+
if (data?.data && Array.isArray(data.data)) return data.data;
|
|
312
|
+
return [];
|
|
313
|
+
},
|
|
314
|
+
staleTime: 15 * 1e3,
|
|
315
|
+
refetchInterval: 10 * 1e3
|
|
316
|
+
});
|
|
317
|
+
};
|
|
318
|
+
const useDispatchManualPush = () => {
|
|
319
|
+
const { post } = admin.useFetchClient();
|
|
320
|
+
const queryClient2 = reactQuery.useQueryClient();
|
|
321
|
+
return reactQuery.useMutation({
|
|
322
|
+
mutationFn: async (payload) => {
|
|
323
|
+
const { data } = await post(`${PLUGIN_BASE}/manual-pushes/dispatch`, payload);
|
|
324
|
+
if (data?.error) throw new Error(extractBackendError(data));
|
|
325
|
+
return data;
|
|
326
|
+
},
|
|
327
|
+
onSuccess: (_data, variables) => {
|
|
328
|
+
if (!variables.dryRun) {
|
|
329
|
+
queryClient2.invalidateQueries({ queryKey: crmKeys.manualPushHistory() });
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
};
|
|
334
|
+
const useDispatchTestManualPush = () => {
|
|
335
|
+
const { post } = admin.useFetchClient();
|
|
336
|
+
const queryClient2 = reactQuery.useQueryClient();
|
|
337
|
+
return reactQuery.useMutation({
|
|
338
|
+
mutationFn: async (payload) => {
|
|
339
|
+
const { data } = await post(`${PLUGIN_BASE}/manual-pushes/dispatch-test`, payload);
|
|
340
|
+
if (data?.error) throw new Error(extractBackendError(data));
|
|
341
|
+
return data;
|
|
342
|
+
},
|
|
343
|
+
onSuccess: () => {
|
|
344
|
+
queryClient2.invalidateQueries({ queryKey: crmKeys.manualPushHistory() });
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
};
|
|
278
348
|
function useStagedFilters(initialState) {
|
|
279
349
|
const [draft, setDraftState] = React.useState(initialState);
|
|
280
350
|
const [applied, setApplied] = React.useState(initialState);
|
|
@@ -3101,7 +3171,401 @@ const StatsView = () => {
|
|
|
3101
3171
|
] });
|
|
3102
3172
|
};
|
|
3103
3173
|
const StatsView$1 = React.memo(StatsView);
|
|
3104
|
-
const homePageStyles = ".dashboard-container {\n --crm-bg-primary: #ffffff;\n --crm-bg-secondary: #f6f6f9;\n --crm-bg-tertiary: #eaeaef;\n --crm-text-primary: #32324d;\n --crm-text-secondary: #666687;\n --crm-text-muted: #8e8ea9;\n --crm-border: #dcdce4;\n --crm-border-light: #eaeaef;\n --crm-shadow: rgba(0, 0, 0, 0.08);\n --crm-shadow-strong: rgba(0, 0, 0, 0.12);\n --crm-accent: #4945ff;\n --crm-accent-light: #d9d8ff;\n --crm-success: #059669;\n --crm-success-light: #d1fae5;\n --crm-warning: #d97706;\n --crm-warning-light: #fef3c7;\n --crm-danger: #dc2626;\n --crm-danger-light: #fee2e2;\n --crm-header-gradient: linear-gradient(135deg, #4945ff 0%, #7b79ff 100%);\n --crm-chart-grid: #e0e0e0;\n --crm-chart-text: #666687;\n --crm-table-header-bg: #f6f6f9;\n --crm-table-row-hover: #f0f0ff;\n --crm-table-row-stripe: #fafafc;\n --tab-icon-stats-gradient: linear-gradient(135deg, #e0e7ff 0%, #c7d2fe 100%);\n --tab-icon-logs-gradient: linear-gradient(135deg, #d1fae5 0%, #a7f3d0 100%);\n --tab-icon-antispam-gradient: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);\n\n min-height: 100vh;\n background: var(--crm-bg-secondary);\n}\n\n.dashboard-container.theme-dark {\n --crm-bg-primary: #212134;\n --crm-bg-secondary: #181826;\n --crm-bg-tertiary: #2a2a42;\n --crm-text-primary: #ffffff;\n --crm-text-secondary: #a5a5ba;\n --crm-text-muted: #7b7b98;\n --crm-border: #3d3d5c;\n --crm-border-light: #2a2a42;\n --crm-shadow: rgba(0, 0, 0, 0.3);\n --crm-shadow-strong: rgba(0, 0, 0, 0.4);\n --crm-accent: #a5a3ff;\n --crm-accent-light: rgba(123, 121, 255, 0.2);\n --crm-success: #34d399;\n --crm-success-light: rgba(16, 185, 129, 0.2);\n --crm-warning: #fbbf24;\n --crm-warning-light: rgba(245, 158, 11, 0.2);\n --crm-danger: #f87171;\n --crm-danger-light: rgba(239, 68, 68, 0.2);\n --crm-header-gradient: linear-gradient(135deg, #2a2a5c 0%, #3d3d80 100%);\n --crm-chart-grid: #3d3d5c;\n --crm-chart-text: #a5a5ba;\n --crm-table-header-bg: #2a2a42;\n --crm-table-row-hover: #2a2a50;\n --crm-table-row-stripe: #1e1e32;\n --tab-icon-stats-gradient: linear-gradient(135deg, rgba(123, 121, 255, 0.2) 0%, rgba(99, 102, 241, 0.3) 100%);\n --tab-icon-logs-gradient: linear-gradient(135deg, rgba(16, 185, 129, 0.2) 0%, rgba(52, 211, 153, 0.3) 100%);\n --tab-icon-antispam-gradient: linear-gradient(135deg, rgba(245, 158, 11, 0.2) 0%, rgba(251, 191, 36, 0.3) 100%);\n}\n\n.dashboard-header {\n background: var(--crm-header-gradient);\n padding: 24px 32px;\n border-radius: 0 0 16px 16px;\n margin-bottom: 24px;\n box-shadow: 0 4px 24px var(--crm-shadow-strong);\n}\n\n.dashboard-header h1 {\n color: white;\n font-size: 28px;\n font-weight: 700;\n margin: 0 0 4px 0;\n}\n\n.dashboard-header p {\n color: rgba(255, 255, 255, 0.85);\n font-size: 14px;\n margin: 0;\n}\n\n.tab-navigation {\n display: flex;\n gap: 12px;\n padding: 0 24px;\n margin-bottom: 16px;\n}\n\n.tab-card {\n flex: 1;\n display: flex;\n align-items: center;\n gap: 14px;\n padding: 18px 20px;\n background: var(--crm-bg-primary);\n border: 2px solid transparent;\n border-radius: 12px;\n cursor: pointer;\n transition: all 0.2s ease;\n box-shadow: 0 2px 8px var(--crm-shadow);\n position: relative;\n overflow: hidden;\n}\n\n.tab-card::before {\n content: '';\n position: absolute;\n left: 0;\n top: 0;\n bottom: 0;\n width: 4px;\n background: transparent;\n transition: background 0.2s ease;\n}\n\n.tab-card:hover {\n transform: translateY(-2px);\n box-shadow: 0 8px 24px var(--crm-shadow-strong);\n}\n\n.tab-card.active {\n border-color: var(--tab-color, var(--crm-accent));\n box-shadow: 0 4px 16px var(--crm-shadow-strong);\n}\n\n.tab-card.active::before {\n background: var(--tab-color, var(--crm-accent));\n}\n\n.tab-icon {\n width: 44px;\n height: 44px;\n border-radius: 10px;\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n transition: transform 0.2s ease;\n}\n\n.tab-card:hover .tab-icon {\n transform: scale(1.05);\n}\n\n.tab-icon.stats {\n background: var(--tab-icon-stats-gradient);\n color: var(--crm-accent);\n}\n\n.tab-icon.logs {\n background: var(--tab-icon-logs-gradient);\n color: var(--crm-success);\n}\n\n.tab-icon.antispam {\n background: var(--tab-icon-antispam-gradient);\n color: var(--crm-warning);\n}\n\n.tab-content-wrapper {\n display: flex;\n flex-direction: column;\n gap: 2px;\n flex: 1;\n min-width: 0;\n}\n\n.tab-label {\n font-size: 15px;\n font-weight: 600;\n color: var(--crm-text-primary);\n margin: 0;\n}\n\n.tab-description {\n font-size: 12px;\n color: var(--crm-text-muted);\n margin: 0;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.tab-badge {\n display: flex;\n align-items: center;\n justify-content: center;\n min-width: 28px;\n height: 28px;\n padding: 0 10px;\n border-radius: 14px;\n font-size: 13px;\n font-weight: 600;\n flex-shrink: 0;\n transition: all 0.2s ease;\n}\n\n.tab-badge.stats {\n background: var(--crm-accent-light);\n color: var(--crm-accent);\n}\n\n.tab-badge.logs {\n background: var(--crm-success-light);\n color: var(--crm-success);\n}\n\n.tab-badge.antispam {\n background: var(--crm-warning-light);\n color: var(--crm-warning);\n}\n\n.tab-card.active .tab-badge.stats {\n background: var(--crm-accent);\n color: white;\n}\n\n.tab-card.active .tab-badge.logs {\n background: var(--crm-success);\n color: white;\n}\n\n.tab-card.active .tab-badge.antispam {\n background: var(--crm-warning);\n color: white;\n}\n\n.tab-content {\n margin: 0 24px;\n padding: 24px;\n background: var(--crm-bg-primary);\n border-radius: 16px;\n box-shadow: 0 4px 16px var(--crm-shadow);\n}\n\n.loading-container {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 64px;\n gap: 16px;\n color: var(--crm-text-muted);\n}\n\n.loading-spinner {\n width: 40px;\n height: 40px;\n border: 3px solid var(--crm-border);\n border-top-color: var(--crm-accent);\n border-radius: 50%;\n animation: spin 0.8s linear infinite;\n}\n\n@keyframes spin {\n to {\n transform: rotate(360deg);\n }\n}\n\n/* Stats Cards */\n.stats-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));\n gap: 16px;\n margin-bottom: 24px;\n}\n\n.stat-card {\n background: var(--crm-bg-primary);\n border-radius: 12px;\n padding: 20px;\n border: 1px solid var(--crm-border-light);\n box-shadow: 0 2px 8px var(--crm-shadow);\n transition: all 0.2s ease;\n}\n\n.stat-card:hover {\n transform: translateY(-2px);\n box-shadow: 0 6px 16px var(--crm-shadow-strong);\n}\n\n.stat-card-header {\n display: flex;\n align-items: center;\n gap: 12px;\n margin-bottom: 12px;\n}\n\n.stat-card-icon {\n width: 40px;\n height: 40px;\n border-radius: 10px;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.stat-card-icon.primary {\n background: var(--crm-accent-light);\n color: var(--crm-accent);\n}\n\n.stat-card-icon.success {\n background: var(--crm-success-light);\n color: var(--crm-success);\n}\n\n.stat-card-icon.warning {\n background: var(--crm-warning-light);\n color: var(--crm-warning);\n}\n\n.stat-card-icon.danger {\n background: var(--crm-danger-light);\n color: var(--crm-danger);\n}\n\n.stat-card-value {\n font-size: 28px;\n font-weight: 700;\n color: var(--crm-text-primary);\n line-height: 1.2;\n}\n\n.stat-card-label {\n font-size: 13px;\n color: var(--crm-text-muted);\n margin-top: 4px;\n}\n\n.stat-card-trend {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n font-size: 12px;\n font-weight: 600;\n padding: 2px 8px;\n border-radius: 12px;\n margin-top: 8px;\n}\n\n.stat-card-trend.up {\n background: var(--crm-success-light);\n color: var(--crm-success);\n}\n\n.stat-card-trend.down {\n background: var(--crm-danger-light);\n color: var(--crm-danger);\n}\n\n.stat-card-trend.neutral {\n background: var(--crm-bg-tertiary);\n color: var(--crm-text-muted);\n}\n\n.export-dropdown {\n position: relative;\n display: inline-block;\n}\n\n.export-dropdown-menu {\n position: absolute;\n top: 100%;\n right: 0;\n margin-top: 4px;\n background: var(--crm-bg-primary);\n border: 1px solid var(--crm-border);\n border-radius: 8px;\n box-shadow: 0 4px 16px var(--crm-shadow-strong);\n min-width: 140px;\n z-index: 100;\n overflow: hidden;\n}\n\n.export-dropdown-item {\n display: flex;\n align-items: center;\n gap: 8px;\n width: 100%;\n padding: 10px 14px;\n border: none;\n background: none;\n font-size: 13px;\n color: var(--crm-text-primary);\n cursor: pointer;\n transition: background 0.15s ease;\n}\n\n.export-dropdown-item:hover {\n background: var(--crm-bg-tertiary);\n}\n\n.export-dropdown-item svg {\n width: 16px;\n height: 16px;\n color: var(--crm-text-muted);\n}\n\n.granularity-select {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.granularity-select label {\n font-size: 12px;\n color: var(--crm-text-muted);\n white-space: nowrap;\n}\n\n.granularity-pills {\n display: flex;\n gap: 2px;\n background: var(--crm-bg-tertiary);\n border-radius: 6px;\n padding: 2px;\n}\n\n.granularity-pill {\n padding: 4px 10px;\n font-size: 11px;\n font-weight: 600;\n border: none;\n border-radius: 4px;\n cursor: pointer;\n transition: all 0.15s ease;\n background: transparent;\n color: var(--crm-text-muted);\n}\n\n.granularity-pill.active {\n background: var(--crm-accent);\n color: white;\n}\n\n.granularity-pill:hover:not(.active) {\n background: var(--crm-bg-secondary);\n color: var(--crm-text-primary);\n}\n\n.compare-toggle {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 13px;\n color: var(--crm-text-secondary);\n cursor: pointer;\n}\n\n.compare-toggle input {\n width: 16px;\n height: 16px;\n accent-color: var(--crm-accent);\n}\n\n.compare-toggle:hover {\n color: var(--crm-text-primary);\n}\n\n.metrics-toggle {\n display: flex;\n gap: 12px;\n margin-top: 12px;\n padding-top: 12px;\n border-top: 1px solid var(--crm-border-light);\n}\n\n.metrics-toggle label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 12px;\n color: var(--crm-text-secondary);\n cursor: pointer;\n}\n\n.metrics-toggle input {\n width: 14px;\n height: 14px;\n}\n\n.metrics-toggle label:hover {\n color: var(--crm-text-primary);\n}\n\n/* Chart Container */\n.chart-container {\n background: var(--crm-bg-primary);\n border-radius: 12px;\n padding: 24px;\n border: 1px solid var(--crm-border-light);\n box-shadow: 0 2px 8px var(--crm-shadow);\n}\n\n.chart-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 20px;\n}\n\n.chart-title {\n font-size: 16px;\n font-weight: 600;\n color: var(--crm-text-primary);\n}\n\n.chart-subtitle {\n font-size: 13px;\n color: var(--crm-text-muted);\n margin-top: 2px;\n}\n\n/* Table Styles */\n.crm-table-wrapper {\n background: var(--crm-bg-primary);\n border-radius: 12px;\n border: 1px solid var(--crm-border-light);\n box-shadow: 0 2px 8px var(--crm-shadow);\n overflow: hidden;\n}\n\n.crm-table {\n width: 100%;\n border-collapse: collapse;\n}\n\n.crm-table thead {\n background: var(--crm-table-header-bg);\n}\n\n.crm-table th {\n padding: 14px 16px;\n text-align: left;\n font-size: 11px;\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n color: var(--crm-text-secondary);\n border-bottom: 1px solid var(--crm-border);\n}\n\n.crm-table td {\n padding: 14px 16px;\n font-size: 14px;\n color: var(--crm-text-primary);\n border-bottom: 1px solid var(--crm-border-light);\n}\n\n.crm-table tbody tr {\n transition: background-color 0.15s ease;\n}\n\n.crm-table tbody tr:hover {\n background: var(--crm-table-row-hover);\n}\n\n.crm-table tbody tr:nth-child(even) {\n background: var(--crm-table-row-stripe);\n}\n\n.crm-table tbody tr:nth-child(even):hover {\n background: var(--crm-table-row-hover);\n}\n\n.crm-table tbody tr:last-child td {\n border-bottom: none;\n}\n\n/* Status Badge */\n.status-badge {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 4px 10px;\n border-radius: 6px;\n font-size: 12px;\n font-weight: 600;\n}\n\n.status-badge::before {\n content: '';\n width: 6px;\n height: 6px;\n border-radius: 50%;\n}\n\n.status-badge.sent {\n background: var(--crm-success-light);\n color: var(--crm-success);\n}\n\n.status-badge.sent::before {\n background: var(--crm-success);\n}\n\n.status-badge.planned {\n background: var(--crm-warning-light);\n color: var(--crm-warning);\n}\n\n.status-badge.planned::before {\n background: var(--crm-warning);\n}\n\n.status-badge.failed {\n background: var(--crm-danger-light);\n color: var(--crm-danger);\n}\n\n.status-badge.failed::before {\n background: var(--crm-danger);\n}\n\n.status-badge.cancelled {\n background: var(--crm-bg-tertiary);\n color: var(--crm-text-muted);\n}\n\n.status-badge.cancelled::before {\n background: var(--crm-text-muted);\n}\n\n.status-badge.warning {\n background: var(--crm-warning-light);\n color: var(--crm-warning);\n}\n\n.status-badge.warning::before {\n background: var(--crm-warning);\n}\n\n/* Pagination */\n.pagination {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 16px 0;\n}\n\n.pagination-info {\n font-size: 13px;\n color: var(--crm-text-muted);\n}\n\n.pagination-controls {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.pagination-btn {\n padding: 8px 16px;\n border-radius: 8px;\n font-size: 13px;\n font-weight: 500;\n background: var(--crm-bg-primary);\n border: 1px solid var(--crm-border);\n color: var(--crm-text-primary);\n cursor: pointer;\n transition: all 0.15s ease;\n}\n\n.pagination-btn:hover:not(:disabled) {\n background: var(--crm-accent-light);\n border-color: var(--crm-accent);\n color: var(--crm-accent);\n}\n\n.pagination-btn:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.pagination-current {\n padding: 8px 12px;\n font-size: 13px;\n font-weight: 600;\n color: var(--crm-text-primary);\n}\n\n/* Empty State */\n.empty-state {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 48px 24px;\n text-align: center;\n}\n\n.empty-state-icon {\n width: 64px;\n height: 64px;\n border-radius: 16px;\n background: var(--crm-bg-tertiary);\n display: flex;\n align-items: center;\n justify-content: center;\n margin-bottom: 16px;\n color: var(--crm-text-muted);\n}\n\n.empty-state-title {\n font-size: 16px;\n font-weight: 600;\n color: var(--crm-text-primary);\n margin-bottom: 4px;\n}\n\n.empty-state-description {\n font-size: 14px;\n color: var(--crm-text-muted);\n}\n\n/* Section Header in table wrapper */\n.crm-table-wrapper .section-header,\n.crm-table-wrapper > div:first-child {\n padding: 16px 20px;\n}\n\n/* Funnel grid responsive */\n.funnel-grid {\n display: grid;\n grid-template-columns: repeat(5, 1fr);\n gap: 12px;\n}\n\n@media (max-width: 1200px) {\n .stats-grid {\n grid-template-columns: repeat(3, 1fr) !important;\n }\n\n .funnel-grid {\n grid-template-columns: repeat(3, 1fr);\n }\n}\n\n@media (max-width: 1024px) {\n .tab-navigation {\n flex-direction: column;\n gap: 8px;\n padding: 0 16px;\n }\n\n .tab-card {\n padding: 14px 16px;\n }\n\n .tab-description {\n display: none;\n }\n\n .tab-content {\n margin: 0 16px;\n border-radius: 12px;\n }\n\n .stats-grid {\n grid-template-columns: repeat(2, 1fr) !important;\n }\n\n .funnel-grid {\n grid-template-columns: repeat(2, 1fr);\n }\n\n .chart-container {\n padding: 16px;\n }\n}\n\n@media (max-width: 768px) {\n .stats-grid {\n grid-template-columns: repeat(2, 1fr) !important;\n }\n\n .crm-table th,\n .crm-table td {\n padding: 10px 12px;\n font-size: 13px;\n }\n}\n\n@media (max-width: 640px) {\n .dashboard-header {\n padding: 20px;\n border-radius: 0 0 12px 12px;\n }\n\n .dashboard-header h1 {\n font-size: 22px;\n }\n\n .tab-icon {\n width: 36px;\n height: 36px;\n }\n\n .tab-label {\n font-size: 14px;\n }\n\n .stats-grid {\n grid-template-columns: 1fr !important;\n }\n\n .funnel-grid {\n grid-template-columns: 1fr;\n }\n\n .stat-card-value {\n font-size: 24px;\n }\n\n .chart-container {\n padding: 12px;\n }\n}\n\n/* Today Widget */\n.today-widget {\n background: var(--crm-bg-primary);\n border-radius: 12px;\n padding: 20px;\n border: 1px solid var(--crm-border-light);\n box-shadow: 0 4px 16px var(--crm-shadow);\n}\n\n.today-widget.loading {\n min-height: 160px;\n display: flex;\n flex-direction: column;\n}\n\n.today-widget-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 16px;\n}\n\n.today-widget-title {\n font-size: 13px;\n font-weight: 700;\n letter-spacing: 0.5px;\n text-transform: uppercase;\n color: var(--crm-text-primary);\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.live-dot {\n width: 8px;\n height: 8px;\n background: var(--crm-success);\n border-radius: 50%;\n animation: pulse 2s ease-in-out infinite;\n box-shadow: 0 0 8px var(--crm-success);\n}\n\n@keyframes pulse {\n 0%, 100% { opacity: 1; transform: scale(1); }\n 50% { opacity: 0.7; transform: scale(1.2); }\n}\n\n.today-widget-comparison {\n font-size: 11px;\n color: var(--crm-text-muted);\n background: var(--crm-bg-tertiary);\n padding: 4px 10px;\n border-radius: 12px;\n}\n\n.today-widget-metrics {\n display: grid;\n grid-template-columns: repeat(2, 1fr);\n gap: 10px;\n margin-bottom: 16px;\n}\n\n.today-metric {\n text-align: center;\n padding: 12px 8px;\n background: var(--crm-bg-secondary);\n border-radius: 10px;\n border: 1px solid var(--crm-border-light);\n transition: border-color 0.2s ease, background-color 0.2s ease;\n}\n\n.today-metric:hover {\n border-color: var(--crm-border);\n background: var(--crm-bg-tertiary);\n}\n\n.today-metric-value {\n font-size: 22px;\n font-weight: 700;\n line-height: 1.2;\n color: var(--crm-text-primary);\n}\n\n.today-metric-label {\n font-size: 11px;\n color: var(--crm-text-muted);\n margin-top: 4px;\n font-weight: 500;\n}\n\n.trend-indicator {\n display: inline-flex;\n align-items: center;\n font-size: 11px;\n font-weight: 600;\n margin-top: 6px;\n padding: 3px 8px;\n border-radius: 10px;\n}\n\n.trend-indicator.up {\n background: var(--crm-success-light);\n color: var(--crm-success);\n}\n\n.trend-indicator.down {\n background: var(--crm-danger-light);\n color: var(--crm-danger);\n}\n\n.trend-neutral {\n display: inline-flex;\n font-size: 11px;\n font-weight: 600;\n margin-top: 6px;\n padding: 3px 8px;\n border-radius: 10px;\n background: var(--crm-bg-tertiary);\n color: var(--crm-text-muted);\n}\n\n.today-widget-footer {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding-top: 12px;\n border-top: 1px solid var(--crm-border-light);\n font-size: 12px;\n}\n\n.today-rates {\n display: flex;\n gap: 16px;\n}\n\n.today-rates span {\n color: var(--crm-text-muted);\n font-weight: 500;\n}\n\n.today-rates span strong {\n color: var(--crm-text-primary);\n font-weight: 700;\n}\n\n.today-updated {\n color: var(--crm-text-muted);\n font-size: 11px;\n}\n\n.today-widget-loading {\n flex: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 14px;\n color: var(--crm-text-muted);\n}\n\n/* Cohort Heat Map */\n.cohort-heatmap {\n background: var(--crm-bg-primary);\n border-radius: 12px;\n padding: 24px;\n border: 1px solid var(--crm-border-light);\n}\n\n.cohort-table-wrapper {\n overflow-x: auto;\n margin: 16px 0;\n}\n\n.cohort-table {\n width: 100%;\n border-collapse: collapse;\n font-size: 12px;\n}\n\n.cohort-header-cell {\n padding: 10px 8px;\n text-align: center;\n font-weight: 600;\n color: var(--crm-text-secondary);\n background: var(--crm-bg-secondary);\n border-bottom: 1px solid var(--crm-border);\n}\n\n.cohort-period-header {\n min-width: 60px;\n}\n\n.cohort-date-cell {\n padding: 10px 12px;\n font-weight: 600;\n color: var(--crm-text-primary);\n background: var(--crm-bg-secondary);\n white-space: nowrap;\n}\n\n.cohort-users-cell {\n padding: 10px 12px;\n text-align: right;\n color: var(--crm-text-secondary);\n background: var(--crm-bg-secondary);\n}\n\n.cohort-cell {\n padding: 10px 8px;\n text-align: center;\n font-weight: 600;\n font-size: 11px;\n transition: all 0.15s ease;\n cursor: default;\n}\n\n.cohort-cell:hover {\n filter: brightness(1.1);\n z-index: 1;\n position: relative;\n}\n\n.cohort-legend {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 12px;\n font-size: 11px;\n color: var(--crm-text-muted);\n margin-top: 12px;\n}\n\n.cohort-legend-gradient {\n width: 120px;\n height: 8px;\n border-radius: 4px;\n background: linear-gradient(90deg, rgba(239, 68, 68, 0.6) 0%, rgba(245, 158, 11, 0.6) 50%, rgba(16, 185, 129, 0.8) 100%);\n}\n\n.cohort-loading {\n text-align: center;\n padding: 32px;\n color: var(--crm-text-muted);\n}\n\n/* A/B Tests */\n.ab-tests-section {\n background: var(--crm-bg-primary);\n border-radius: 12px;\n padding: 24px;\n border: 1px solid var(--crm-border-light);\n}\n\n.ab-tests-list {\n display: flex;\n flex-direction: column;\n gap: 16px;\n}\n\n.ab-test-card {\n background: var(--crm-bg-secondary);\n border-radius: 10px;\n padding: 20px;\n border: 1px solid var(--crm-border-light);\n}\n\n.ab-campaign-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 16px;\n}\n\n.ab-test-name {\n font-size: 15px;\n font-weight: 600;\n color: var(--crm-text-primary);\n}\n\n.ab-block-count {\n font-size: 12px;\n font-weight: 500;\n color: var(--crm-text-muted);\n background: var(--crm-bg-tertiary);\n padding: 2px 10px;\n border-radius: 10px;\n}\n\n.ab-blocks-list {\n display: flex;\n flex-direction: column;\n gap: 12px;\n}\n\n.ab-message-block {\n background: var(--crm-bg-primary);\n border-radius: 8px;\n padding: 16px;\n border: 1px solid var(--crm-border-light);\n}\n\n.ab-block-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 12px;\n}\n\n.ab-block-name {\n font-size: 13px;\n font-weight: 600;\n color: var(--crm-text-secondary);\n}\n\n.ab-test-status {\n font-size: 12px;\n font-weight: 600;\n display: flex;\n align-items: center;\n gap: 6px;\n}\n\n.ab-test-variants {\n display: grid;\n grid-template-columns: 1fr auto 1fr;\n gap: 16px;\n align-items: center;\n}\n\n.ab-variant {\n background: var(--crm-bg-secondary);\n border-radius: 8px;\n padding: 16px;\n border: 2px solid transparent;\n transition: all 0.2s ease;\n}\n\n.ab-variant.winner {\n border-color: var(--crm-success);\n box-shadow: 0 0 0 4px rgba(16, 185, 129, 0.1);\n}\n\n.ab-variant-header {\n display: flex;\n align-items: center;\n gap: 8px;\n margin-bottom: 12px;\n}\n\n.ab-variant-name {\n font-weight: 600;\n color: var(--crm-text-primary);\n}\n\n.ab-variant-tag {\n font-size: 10px;\n font-weight: 600;\n padding: 2px 8px;\n border-radius: 10px;\n text-transform: uppercase;\n}\n\n.ab-variant-tag.control {\n background: var(--crm-bg-tertiary);\n color: var(--crm-text-muted);\n}\n\n.ab-variant-tag.treatment {\n background: var(--crm-accent-light);\n color: var(--crm-accent);\n}\n\n.ab-variant-stats {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 12px;\n}\n\n.ab-stat {\n text-align: center;\n}\n\n.ab-stat-value {\n font-size: 18px;\n font-weight: 700;\n color: var(--crm-text-primary);\n}\n\n.ab-stat-label {\n font-size: 10px;\n color: var(--crm-text-muted);\n margin-top: 2px;\n}\n\n.ab-vs {\n font-size: 12px;\n font-weight: 700;\n color: var(--crm-text-muted);\n text-align: center;\n}\n\n.ab-test-analysis {\n display: flex;\n gap: 24px;\n margin-top: 16px;\n padding-top: 16px;\n border-top: 1px solid var(--crm-border-light);\n}\n\n.ab-analysis-item {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 13px;\n}\n\n.ab-analysis-label {\n color: var(--crm-text-muted);\n}\n\n.ab-analysis-value {\n font-weight: 600;\n color: var(--crm-text-primary);\n}\n\n.ab-analysis-value.positive {\n color: var(--crm-success);\n}\n\n.ab-analysis-value.negative {\n color: var(--crm-danger);\n}\n\n.ab-analysis-value.confidence-high {\n color: var(--crm-success);\n}\n\n.ab-analysis-value.confidence-medium {\n color: var(--crm-success);\n}\n\n.ab-analysis-value.confidence-low {\n color: var(--crm-warning);\n}\n\n.ab-tests-loading {\n text-align: center;\n padding: 32px;\n color: var(--crm-text-muted);\n}\n\n/* Multi-variant A/B Tests */\n.ab-test-variants-multi {\n display: flex;\n align-items: stretch;\n gap: 8px;\n flex-wrap: wrap;\n}\n\n.ab-vs-small {\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 10px;\n font-weight: 700;\n color: var(--crm-text-muted);\n padding: 0 4px;\n}\n\n.ab-variant-compact {\n flex: 1;\n min-width: 140px;\n max-width: 200px;\n background: var(--crm-bg-secondary);\n border-radius: 8px;\n padding: 12px;\n border: 2px solid transparent;\n transition: all 0.2s ease;\n}\n\n.ab-variant-compact.winner {\n border-color: var(--crm-success);\n box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.1);\n}\n\n.ab-variant-compact .ab-variant-header {\n margin-bottom: 8px;\n flex-wrap: wrap;\n gap: 4px;\n}\n\n.ab-variant-compact .ab-variant-name {\n font-size: 13px;\n}\n\n.ab-variant-stats-compact {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.ab-stat-row {\n display: flex;\n justify-content: space-between;\n align-items: center;\n font-size: 12px;\n}\n\n.ab-stat-row .ab-stat-label {\n color: var(--crm-text-muted);\n font-size: 11px;\n}\n\n.ab-stat-row .ab-stat-value {\n font-weight: 600;\n color: var(--crm-text-primary);\n font-size: 12px;\n}\n\n.ab-variant-lift {\n margin-top: 8px;\n padding-top: 8px;\n border-top: 1px solid var(--crm-border-light);\n text-align: center;\n font-size: 12px;\n font-weight: 600;\n}\n\n.ab-variant-lift .positive {\n color: var(--crm-success);\n}\n\n.ab-variant-lift .negative {\n color: var(--crm-danger);\n}\n\n.ab-test-analysis-compact {\n display: flex;\n align-items: center;\n gap: 8px;\n margin-top: 12px;\n padding-top: 12px;\n border-top: 1px solid var(--crm-border-light);\n font-size: 12px;\n}\n\n.ab-test-analysis-compact .ab-analysis-label {\n color: var(--crm-text-muted);\n}\n\n.ab-test-analysis-compact .ab-analysis-value {\n font-weight: 600;\n}\n\n@media (max-width: 640px) {\n .ab-test-variants-multi {\n flex-direction: column;\n }\n\n .ab-variant-compact {\n max-width: none;\n }\n\n .ab-vs-small {\n padding: 4px 0;\n }\n}\n\n/* Send Time Heat Map */\n.sendtime-heatmap {\n background: var(--crm-bg-primary);\n border-radius: 12px;\n padding: 24px;\n border: 1px solid var(--crm-border-light);\n}\n\n.sendtime-best {\n text-align: right;\n}\n\n.sendtime-best-label {\n font-size: 11px;\n color: var(--crm-text-muted);\n}\n\n.sendtime-best-value {\n font-size: 16px;\n font-weight: 700;\n color: var(--crm-success);\n}\n\n.sendtime-best-rate {\n font-size: 12px;\n color: var(--crm-success);\n}\n\n.sendtime-grid-wrapper {\n overflow-x: auto;\n margin: 16px 0;\n}\n\n.sendtime-grid {\n display: grid;\n grid-template-columns: 60px repeat(7, 1fr);\n gap: 2px;\n min-width: 400px;\n}\n\n.sendtime-corner {\n background: transparent;\n}\n\n.sendtime-day-header {\n text-align: center;\n font-size: 11px;\n font-weight: 600;\n color: var(--crm-text-secondary);\n padding: 8px 4px;\n}\n\n.sendtime-hour-label {\n font-size: 10px;\n color: var(--crm-text-muted);\n display: flex;\n align-items: center;\n justify-content: flex-end;\n padding-right: 8px;\n}\n\n.sendtime-cell {\n aspect-ratio: 1;\n border-radius: 4px;\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: default;\n transition: all 0.15s ease;\n min-height: 32px;\n}\n\n.sendtime-cell:hover {\n filter: brightness(1.15);\n z-index: 1;\n}\n\n.sendtime-cell.best {\n box-shadow: 0 0 0 2px var(--crm-success);\n}\n\n.sendtime-cell-value {\n font-size: 9px;\n font-weight: 600;\n color: inherit;\n}\n\n.sendtime-legend {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 12px;\n font-size: 11px;\n color: var(--crm-text-muted);\n margin-top: 12px;\n}\n\n.sendtime-legend-gradient {\n width: 120px;\n height: 8px;\n border-radius: 4px;\n background: linear-gradient(90deg, rgba(239, 68, 68, 0.6) 0%, rgba(245, 158, 11, 0.6) 50%, rgba(16, 185, 129, 0.8) 100%);\n}\n\n.sendtime-loading {\n text-align: center;\n padding: 32px;\n color: var(--crm-text-muted);\n}\n\n/* Segment Table */\n.segment-table-section {\n background: var(--crm-bg-primary);\n border-radius: 12px;\n padding: 24px;\n border: 1px solid var(--crm-border-light);\n}\n\n.segment-cards {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));\n gap: 16px;\n}\n\n.segment-card {\n background: var(--crm-bg-secondary);\n border-radius: 10px;\n padding: 16px;\n border-left: 4px solid var(--crm-text-muted);\n transition: all 0.2s ease;\n}\n\n.segment-card:hover {\n transform: translateY(-2px);\n box-shadow: 0 4px 12px var(--crm-shadow);\n}\n\n.segment-card-header {\n display: flex;\n align-items: center;\n gap: 8px;\n margin-bottom: 12px;\n}\n\n.segment-icon {\n font-size: 18px;\n}\n\n.segment-name {\n font-size: 14px;\n font-weight: 600;\n color: var(--crm-text-primary);\n flex: 1;\n}\n\n.segment-users {\n font-size: 12px;\n font-weight: 600;\n padding: 2px 8px;\n border-radius: 10px;\n}\n\n.segment-metrics {\n display: grid;\n grid-template-columns: repeat(4, 1fr);\n gap: 8px;\n margin-bottom: 12px;\n}\n\n.segment-metric {\n text-align: center;\n}\n\n.segment-metric-value {\n font-size: 16px;\n font-weight: 700;\n color: var(--crm-text-primary);\n}\n\n.segment-metric-label {\n font-size: 10px;\n color: var(--crm-text-muted);\n}\n\n.segment-bar-wrapper {\n height: 4px;\n background: var(--crm-bg-tertiary);\n border-radius: 2px;\n overflow: hidden;\n}\n\n.segment-bar {\n height: 100%;\n border-radius: 2px;\n transition: width 0.3s ease;\n}\n\n.segment-loading {\n text-align: center;\n padding: 32px;\n color: var(--crm-text-muted);\n}\n\n/* Goal Tracker */\n.goal-tracker {\n background: var(--crm-bg-primary);\n border-radius: 12px;\n padding: 20px;\n border: 1px solid var(--crm-border-light);\n}\n\n.goal-tracker-header {\n display: flex;\n justify-content: space-between;\n align-items: flex-start;\n margin-bottom: 16px;\n}\n\n.goal-edit-btn {\n background: transparent;\n border: none;\n font-size: 16px;\n cursor: pointer;\n padding: 4px 8px;\n border-radius: 6px;\n transition: background 0.15s ease;\n}\n\n.goal-edit-btn:hover {\n background: var(--crm-bg-tertiary);\n}\n\n.goal-items {\n display: flex;\n flex-direction: column;\n gap: 16px;\n}\n\n.goal-item {\n padding: 12px;\n background: var(--crm-bg-secondary);\n border-radius: 8px;\n}\n\n.goal-item-header {\n display: flex;\n align-items: center;\n gap: 8px;\n margin-bottom: 8px;\n}\n\n.goal-icon {\n font-size: 16px;\n}\n\n.goal-name {\n font-size: 13px;\n font-weight: 600;\n color: var(--crm-text-primary);\n flex: 1;\n}\n\n.goal-progress-badge {\n font-size: 11px;\n font-weight: 700;\n padding: 2px 8px;\n border-radius: 10px;\n}\n\n.goal-bar-wrapper {\n height: 6px;\n background: var(--crm-bg-tertiary);\n border-radius: 3px;\n overflow: hidden;\n margin-bottom: 8px;\n}\n\n.goal-bar {\n height: 100%;\n border-radius: 3px;\n transition: width 0.3s ease;\n}\n\n.goal-values {\n display: flex;\n align-items: center;\n gap: 4px;\n font-size: 12px;\n}\n\n.goal-current {\n font-weight: 600;\n color: var(--crm-text-primary);\n}\n\n.goal-separator {\n color: var(--crm-text-muted);\n}\n\n.goal-target {\n color: var(--crm-text-secondary);\n}\n\n.goal-target-input {\n width: 80px;\n padding: 2px 6px;\n border: 1px solid var(--crm-border);\n border-radius: 4px;\n font-size: 12px;\n background: var(--crm-bg-primary);\n color: var(--crm-text-primary);\n}\n\n.goal-target-input:focus {\n outline: none;\n border-color: var(--crm-accent);\n}\n\n/* Enhanced Stats Layout */\n.stats-header-row {\n display: grid;\n grid-template-columns: 320px 1fr;\n gap: 16px;\n margin-bottom: 24px;\n}\n\n@media (max-width: 1024px) {\n .stats-header-row {\n grid-template-columns: 1fr;\n }\n\n .ab-test-variants {\n grid-template-columns: 1fr;\n }\n\n .ab-vs {\n padding: 8px 0;\n }\n\n .ab-test-analysis {\n flex-wrap: wrap;\n gap: 12px;\n }\n}\n\n@media (max-width: 768px) {\n .segment-metrics {\n grid-template-columns: repeat(2, 1fr);\n }\n}\n";
|
|
3174
|
+
const stripHtml = (html) => html.replace(/<br\s*\/?>/gi, "\n").replace(/<\/p>/gi, "\n").replace(/<[^>]+>/g, "").replace(/ /g, " ").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").trim();
|
|
3175
|
+
const truncate = (text, n) => text.length > n ? `${text.slice(0, n)}…` : text;
|
|
3176
|
+
const formatDateTime = (iso) => new Date(iso).toLocaleString("ru-RU", {
|
|
3177
|
+
day: "2-digit",
|
|
3178
|
+
month: "2-digit",
|
|
3179
|
+
year: "numeric",
|
|
3180
|
+
hour: "2-digit",
|
|
3181
|
+
minute: "2-digit"
|
|
3182
|
+
});
|
|
3183
|
+
const parseUserIdList = (raw) => {
|
|
3184
|
+
if (!raw.trim()) return [];
|
|
3185
|
+
return raw.split(/[\s,]+/).map((t) => t.trim()).filter(Boolean).map((t) => Number(t)).filter((n) => Number.isInteger(n) && n > 0);
|
|
3186
|
+
};
|
|
3187
|
+
const ManualPushesView = () => {
|
|
3188
|
+
const { data: templates = [], isLoading, error, refetch } = useManualPushTemplates();
|
|
3189
|
+
const { data: history = [], isLoading: historyLoading } = useManualPushHistory();
|
|
3190
|
+
const [modalState, setModalState] = React.useState(null);
|
|
3191
|
+
const sortedTemplates = React.useMemo(
|
|
3192
|
+
() => [...templates].sort(
|
|
3193
|
+
(a, b) => a.updatedAt < b.updatedAt ? 1 : a.updatedAt > b.updatedAt ? -1 : 0
|
|
3194
|
+
),
|
|
3195
|
+
[templates]
|
|
3196
|
+
);
|
|
3197
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { padding: 4, children: [
|
|
3198
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", paddingBottom: 4, children: [
|
|
3199
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
3200
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "beta", children: "Ручные рассылки" }),
|
|
3201
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { color: "var(--crm-text-secondary)", fontSize: 13, marginTop: 4 }, children: "Опубликованные в Strapi шаблоны Manual Push. Отправка не происходит автоматически — используйте кнопки ниже." })
|
|
3202
|
+
] }),
|
|
3203
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "tertiary", onClick: () => refetch(), children: "Обновить" })
|
|
3204
|
+
] }),
|
|
3205
|
+
isLoading && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "center", padding: 8, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Loader, { children: "Загрузка шаблонов…" }) }),
|
|
3206
|
+
error && /* @__PURE__ */ jsxRuntime.jsx(
|
|
3207
|
+
designSystem.Box,
|
|
3208
|
+
{
|
|
3209
|
+
padding: 4,
|
|
3210
|
+
background: "danger100",
|
|
3211
|
+
hasRadius: true,
|
|
3212
|
+
style: { border: "1px solid var(--crm-danger)" },
|
|
3213
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { textColor: "danger700", children: [
|
|
3214
|
+
"Не удалось загрузить шаблоны: ",
|
|
3215
|
+
error.message
|
|
3216
|
+
] })
|
|
3217
|
+
}
|
|
3218
|
+
),
|
|
3219
|
+
!isLoading && !error && sortedTemplates.length === 0 && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 6, background: "neutral100", hasRadius: true, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", children: "Шаблоны не найдены. Создайте запись в Content Manager → Manual Push и опубликуйте её." }) }),
|
|
3220
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3221
|
+
"div",
|
|
3222
|
+
{
|
|
3223
|
+
style: {
|
|
3224
|
+
display: "grid",
|
|
3225
|
+
gridTemplateColumns: "repeat(auto-fill, minmax(360px, 1fr))",
|
|
3226
|
+
gap: 16
|
|
3227
|
+
},
|
|
3228
|
+
children: sortedTemplates.map((t) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
3229
|
+
TemplateCard,
|
|
3230
|
+
{
|
|
3231
|
+
template: t,
|
|
3232
|
+
onSendTest: () => setModalState({ template: t, mode: "test" }),
|
|
3233
|
+
onDispatch: () => setModalState({ template: t, mode: "segment" })
|
|
3234
|
+
},
|
|
3235
|
+
t.documentId
|
|
3236
|
+
))
|
|
3237
|
+
}
|
|
3238
|
+
),
|
|
3239
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { paddingTop: 8, children: [
|
|
3240
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "beta", children: "Последние отправки" }),
|
|
3241
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { paddingTop: 3, children: historyLoading ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "center", padding: 4, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Loader, { children: "Загрузка истории…" }) }) : history.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 4, background: "neutral100", hasRadius: true, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", children: "Пока ни одной отправки." }) }) : /* @__PURE__ */ jsxRuntime.jsx(HistoryTable, { history, templates }) })
|
|
3242
|
+
] }),
|
|
3243
|
+
modalState && /* @__PURE__ */ jsxRuntime.jsx(DispatchModal, { state: modalState, onClose: () => setModalState(null) })
|
|
3244
|
+
] });
|
|
3245
|
+
};
|
|
3246
|
+
const TemplateCard = ({ template, onSendTest, onDispatch }) => {
|
|
3247
|
+
const preview = truncate(stripHtml(template.body || ""), 220);
|
|
3248
|
+
const hasTestUsers = template.testUserIds.length > 0;
|
|
3249
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3250
|
+
designSystem.Box,
|
|
3251
|
+
{
|
|
3252
|
+
background: "neutral0",
|
|
3253
|
+
hasRadius: true,
|
|
3254
|
+
padding: 4,
|
|
3255
|
+
shadow: "tableShadow",
|
|
3256
|
+
style: { display: "flex", flexDirection: "column", gap: 12 },
|
|
3257
|
+
children: [
|
|
3258
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "space-between", alignItems: "flex-start", gap: 2, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [
|
|
3259
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "delta", ellipsis: true, children: template.name }),
|
|
3260
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 1, paddingTop: 1, children: [
|
|
3261
|
+
template.locales.map((l) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { backgroundColor: "primary200", textColor: "primary600", children: l.toUpperCase() }, l)),
|
|
3262
|
+
template.image && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { backgroundColor: "secondary200", textColor: "secondary700", children: "IMG" }),
|
|
3263
|
+
template.buttonUrl && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { backgroundColor: "alternative200", textColor: "alternative700", children: "BTN" })
|
|
3264
|
+
] })
|
|
3265
|
+
] }) }),
|
|
3266
|
+
template.image?.url && /* @__PURE__ */ jsxRuntime.jsx(
|
|
3267
|
+
"div",
|
|
3268
|
+
{
|
|
3269
|
+
style: {
|
|
3270
|
+
background: "var(--crm-bg-tertiary)",
|
|
3271
|
+
borderRadius: 8,
|
|
3272
|
+
overflow: "hidden",
|
|
3273
|
+
maxHeight: 120,
|
|
3274
|
+
display: "flex",
|
|
3275
|
+
justifyContent: "center",
|
|
3276
|
+
alignItems: "center"
|
|
3277
|
+
},
|
|
3278
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3279
|
+
"img",
|
|
3280
|
+
{
|
|
3281
|
+
src: template.image.url,
|
|
3282
|
+
alt: template.image.alternativeText || template.name,
|
|
3283
|
+
style: { maxWidth: "100%", maxHeight: 120, objectFit: "cover" }
|
|
3284
|
+
}
|
|
3285
|
+
)
|
|
3286
|
+
}
|
|
3287
|
+
),
|
|
3288
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3289
|
+
"div",
|
|
3290
|
+
{
|
|
3291
|
+
style: {
|
|
3292
|
+
color: "var(--crm-text-secondary)",
|
|
3293
|
+
fontSize: 13,
|
|
3294
|
+
whiteSpace: "pre-wrap",
|
|
3295
|
+
lineHeight: 1.4,
|
|
3296
|
+
minHeight: 40
|
|
3297
|
+
},
|
|
3298
|
+
children: preview || /* @__PURE__ */ jsxRuntime.jsx("em", { style: { opacity: 0.6 }, children: "Тело шаблона пустое" })
|
|
3299
|
+
}
|
|
3300
|
+
),
|
|
3301
|
+
template.buttonLabel && template.buttonUrl && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { fontSize: 12, color: "var(--crm-text-muted)" }, children: [
|
|
3302
|
+
"Кнопка: ",
|
|
3303
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: template.buttonLabel }),
|
|
3304
|
+
" → ",
|
|
3305
|
+
truncate(template.buttonUrl, 48)
|
|
3306
|
+
] }),
|
|
3307
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { fontSize: 12, color: "var(--crm-text-muted)" }, children: [
|
|
3308
|
+
"testUserIds: ",
|
|
3309
|
+
hasTestUsers ? template.testUserIds.join(", ") : "—"
|
|
3310
|
+
] }),
|
|
3311
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, justifyContent: "flex-end", paddingTop: 1, children: [
|
|
3312
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3313
|
+
designSystem.Button,
|
|
3314
|
+
{
|
|
3315
|
+
variant: "tertiary",
|
|
3316
|
+
disabled: !hasTestUsers,
|
|
3317
|
+
onClick: onSendTest,
|
|
3318
|
+
title: hasTestUsers ? "" : "Заполните testUserIds в Strapi",
|
|
3319
|
+
children: "Тест-отправка"
|
|
3320
|
+
}
|
|
3321
|
+
),
|
|
3322
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "default", onClick: onDispatch, children: "Отправить…" })
|
|
3323
|
+
] })
|
|
3324
|
+
]
|
|
3325
|
+
}
|
|
3326
|
+
);
|
|
3327
|
+
};
|
|
3328
|
+
const HistoryTable = ({ history, templates }) => {
|
|
3329
|
+
const nameByDocId = React.useMemo(() => {
|
|
3330
|
+
const map = /* @__PURE__ */ new Map();
|
|
3331
|
+
templates.forEach((t) => map.set(t.documentId, t.name));
|
|
3332
|
+
return map;
|
|
3333
|
+
}, [templates]);
|
|
3334
|
+
return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { background: "neutral0", hasRadius: true, shadow: "tableShadow", style: { overflow: "hidden" }, children: /* @__PURE__ */ jsxRuntime.jsxs("table", { style: { width: "100%", borderCollapse: "collapse", fontSize: 13 }, children: [
|
|
3335
|
+
/* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { style: { background: "var(--crm-table-header-bg)", textAlign: "left" }, children: [
|
|
3336
|
+
/* @__PURE__ */ jsxRuntime.jsx("th", { style: { padding: "10px 12px" }, children: "Отправлено" }),
|
|
3337
|
+
/* @__PURE__ */ jsxRuntime.jsx("th", { style: { padding: "10px 12px" }, children: "Шаблон" }),
|
|
3338
|
+
/* @__PURE__ */ jsxRuntime.jsx("th", { style: { padding: "10px 12px", textAlign: "right" }, children: "В очереди" }),
|
|
3339
|
+
/* @__PURE__ */ jsxRuntime.jsx("th", { style: { padding: "10px 12px", textAlign: "right" }, children: "Отпр." }),
|
|
3340
|
+
/* @__PURE__ */ jsxRuntime.jsx("th", { style: { padding: "10px 12px", textAlign: "right" }, children: "Ошибки" }),
|
|
3341
|
+
/* @__PURE__ */ jsxRuntime.jsx("th", { style: { padding: "10px 12px", textAlign: "right" }, children: "Отмен." }),
|
|
3342
|
+
/* @__PURE__ */ jsxRuntime.jsx("th", { style: { padding: "10px 12px" }, children: "Кем" })
|
|
3343
|
+
] }) }),
|
|
3344
|
+
/* @__PURE__ */ jsxRuntime.jsx("tbody", { children: history.map((row) => /* @__PURE__ */ jsxRuntime.jsxs("tr", { style: { borderTop: "1px solid var(--crm-border-light)" }, children: [
|
|
3345
|
+
/* @__PURE__ */ jsxRuntime.jsx("td", { style: { padding: "10px 12px", whiteSpace: "nowrap" }, children: formatDateTime(row.dispatchedAt) }),
|
|
3346
|
+
/* @__PURE__ */ jsxRuntime.jsx("td", { style: { padding: "10px 12px" }, children: nameByDocId.get(row.strapiDocumentId) || row.strapiDocumentId }),
|
|
3347
|
+
/* @__PURE__ */ jsxRuntime.jsx("td", { style: { padding: "10px 12px", textAlign: "right" }, children: row.totalQueued }),
|
|
3348
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3349
|
+
"td",
|
|
3350
|
+
{
|
|
3351
|
+
style: {
|
|
3352
|
+
padding: "10px 12px",
|
|
3353
|
+
textAlign: "right",
|
|
3354
|
+
color: "var(--crm-success)"
|
|
3355
|
+
},
|
|
3356
|
+
children: row.counts.sent
|
|
3357
|
+
}
|
|
3358
|
+
),
|
|
3359
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3360
|
+
"td",
|
|
3361
|
+
{
|
|
3362
|
+
style: {
|
|
3363
|
+
padding: "10px 12px",
|
|
3364
|
+
textAlign: "right",
|
|
3365
|
+
color: "var(--crm-danger)"
|
|
3366
|
+
},
|
|
3367
|
+
children: row.counts.failed
|
|
3368
|
+
}
|
|
3369
|
+
),
|
|
3370
|
+
/* @__PURE__ */ jsxRuntime.jsx("td", { style: { padding: "10px 12px", textAlign: "right" }, children: row.counts.cancelled }),
|
|
3371
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3372
|
+
"td",
|
|
3373
|
+
{
|
|
3374
|
+
style: {
|
|
3375
|
+
padding: "10px 12px",
|
|
3376
|
+
color: "var(--crm-text-muted)",
|
|
3377
|
+
whiteSpace: "nowrap"
|
|
3378
|
+
},
|
|
3379
|
+
children: row.dispatchedBy || "—"
|
|
3380
|
+
}
|
|
3381
|
+
)
|
|
3382
|
+
] }, row.manualPushId)) })
|
|
3383
|
+
] }) });
|
|
3384
|
+
};
|
|
3385
|
+
const DispatchModal = ({ state, onClose }) => {
|
|
3386
|
+
const { template, mode } = state;
|
|
3387
|
+
const isTest = mode === "test";
|
|
3388
|
+
const dispatchMutation = useDispatchManualPush();
|
|
3389
|
+
const dispatchTestMutation = useDispatchTestManualPush();
|
|
3390
|
+
const [dispatchedBy, setDispatchedBy] = React.useState("");
|
|
3391
|
+
const [includeRu, setIncludeRu] = React.useState(false);
|
|
3392
|
+
const [includeEn, setIncludeEn] = React.useState(false);
|
|
3393
|
+
const [userIdsRaw, setUserIdsRaw] = React.useState("");
|
|
3394
|
+
const [excludeOptedOut, setExcludeOptedOut] = React.useState(true);
|
|
3395
|
+
const [dryRunResult, setDryRunResult] = React.useState(null);
|
|
3396
|
+
const [confirmed, setConfirmed] = React.useState(false);
|
|
3397
|
+
const reset = () => {
|
|
3398
|
+
setDispatchedBy("");
|
|
3399
|
+
setIncludeRu(false);
|
|
3400
|
+
setIncludeEn(false);
|
|
3401
|
+
setUserIdsRaw("");
|
|
3402
|
+
setExcludeOptedOut(true);
|
|
3403
|
+
setDryRunResult(null);
|
|
3404
|
+
setConfirmed(false);
|
|
3405
|
+
};
|
|
3406
|
+
const close = () => {
|
|
3407
|
+
reset();
|
|
3408
|
+
onClose();
|
|
3409
|
+
};
|
|
3410
|
+
const buildPayload = (dryRun) => {
|
|
3411
|
+
const userIds = parseUserIdList(userIdsRaw);
|
|
3412
|
+
const languages = [];
|
|
3413
|
+
if (includeRu) languages.push("ru");
|
|
3414
|
+
if (includeEn) languages.push("en");
|
|
3415
|
+
return {
|
|
3416
|
+
strapiDocumentId: template.documentId,
|
|
3417
|
+
segment: {
|
|
3418
|
+
...userIds.length > 0 ? { userIds } : {},
|
|
3419
|
+
...languages.length > 0 ? { languages } : {},
|
|
3420
|
+
excludeOptedOut
|
|
3421
|
+
},
|
|
3422
|
+
dryRun,
|
|
3423
|
+
...dispatchedBy ? { dispatchedBy } : {}
|
|
3424
|
+
};
|
|
3425
|
+
};
|
|
3426
|
+
const onDryRun = async () => {
|
|
3427
|
+
setDryRunResult(null);
|
|
3428
|
+
try {
|
|
3429
|
+
const res = await dispatchMutation.mutateAsync(buildPayload(true));
|
|
3430
|
+
setDryRunResult(res.totalQueued);
|
|
3431
|
+
} catch {
|
|
3432
|
+
}
|
|
3433
|
+
};
|
|
3434
|
+
const onConfirm = async () => {
|
|
3435
|
+
if (isTest) {
|
|
3436
|
+
try {
|
|
3437
|
+
await dispatchTestMutation.mutateAsync({
|
|
3438
|
+
strapiDocumentId: template.documentId,
|
|
3439
|
+
...dispatchedBy ? { dispatchedBy } : {}
|
|
3440
|
+
});
|
|
3441
|
+
close();
|
|
3442
|
+
} catch {
|
|
3443
|
+
}
|
|
3444
|
+
return;
|
|
3445
|
+
}
|
|
3446
|
+
if (!confirmed) {
|
|
3447
|
+
setConfirmed(true);
|
|
3448
|
+
return;
|
|
3449
|
+
}
|
|
3450
|
+
try {
|
|
3451
|
+
await dispatchMutation.mutateAsync(buildPayload(false));
|
|
3452
|
+
close();
|
|
3453
|
+
} catch {
|
|
3454
|
+
}
|
|
3455
|
+
};
|
|
3456
|
+
const mutation = isTest ? dispatchTestMutation : dispatchMutation;
|
|
3457
|
+
const err = mutation.error;
|
|
3458
|
+
return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Root, { open: true, onOpenChange: (open) => !open && close(), children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Modal.Content, { style: { width: 640, maxWidth: "95vw" }, children: [
|
|
3459
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Header, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Modal.Title, { children: [
|
|
3460
|
+
isTest ? "Тест-отправка" : "Отправка",
|
|
3461
|
+
": ",
|
|
3462
|
+
template.name
|
|
3463
|
+
] }) }),
|
|
3464
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Body, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 4, alignItems: "stretch", children: [
|
|
3465
|
+
isTest ? /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { padding: 3, background: "neutral100", hasRadius: true, children: [
|
|
3466
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "omega", children: [
|
|
3467
|
+
"Будет отправлено пользователям из ",
|
|
3468
|
+
/* @__PURE__ */ jsxRuntime.jsx("code", { children: "testUserIds" }),
|
|
3469
|
+
":"
|
|
3470
|
+
] }),
|
|
3471
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { marginTop: 8, fontFamily: "monospace", fontSize: 13 }, children: template.testUserIds.join(", ") }),
|
|
3472
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { marginTop: 8, fontSize: 12, color: "var(--crm-text-muted)" }, children: "Anti-spam, opt-out и priority конфликты для тест-отправки пропускаются." })
|
|
3473
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
3474
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { children: [
|
|
3475
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: "Языки (опц.)" }),
|
|
3476
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 4, paddingTop: 1, children: [
|
|
3477
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3478
|
+
designSystem.Checkbox,
|
|
3479
|
+
{
|
|
3480
|
+
checked: includeRu,
|
|
3481
|
+
onCheckedChange: (v) => setIncludeRu(Boolean(v)),
|
|
3482
|
+
children: "Русский"
|
|
3483
|
+
}
|
|
3484
|
+
),
|
|
3485
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3486
|
+
designSystem.Checkbox,
|
|
3487
|
+
{
|
|
3488
|
+
checked: includeEn,
|
|
3489
|
+
onCheckedChange: (v) => setIncludeEn(Boolean(v)),
|
|
3490
|
+
children: "English"
|
|
3491
|
+
}
|
|
3492
|
+
)
|
|
3493
|
+
] }),
|
|
3494
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { fontSize: 12, color: "var(--crm-text-muted)", marginTop: 4 }, children: [
|
|
3495
|
+
"Пусто = все языки. Фильтр по ",
|
|
3496
|
+
/* @__PURE__ */ jsxRuntime.jsx("code", { children: "languageCode" }),
|
|
3497
|
+
" Telegram-аккаунта."
|
|
3498
|
+
] })
|
|
3499
|
+
] }),
|
|
3500
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { children: [
|
|
3501
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: "userIds (опц.)" }),
|
|
3502
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3503
|
+
designSystem.Textarea,
|
|
3504
|
+
{
|
|
3505
|
+
value: userIdsRaw,
|
|
3506
|
+
onChange: (e) => setUserIdsRaw(e.target.value),
|
|
3507
|
+
placeholder: "42, 101, 202 (или с переносами)"
|
|
3508
|
+
}
|
|
3509
|
+
),
|
|
3510
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 12, color: "var(--crm-text-muted)", marginTop: 4 }, children: "Прямая адресация — если заполнено, все остальные фильтры (кроме языков) игнорируются." })
|
|
3511
|
+
] }),
|
|
3512
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3513
|
+
designSystem.Checkbox,
|
|
3514
|
+
{
|
|
3515
|
+
checked: excludeOptedOut,
|
|
3516
|
+
onCheckedChange: (v) => setExcludeOptedOut(Boolean(v)),
|
|
3517
|
+
children: "Исключить отписавшихся (opted_out)"
|
|
3518
|
+
}
|
|
3519
|
+
)
|
|
3520
|
+
] }),
|
|
3521
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { children: [
|
|
3522
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: "dispatchedBy (опц.)" }),
|
|
3523
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3524
|
+
designSystem.TextInput,
|
|
3525
|
+
{
|
|
3526
|
+
value: dispatchedBy,
|
|
3527
|
+
onChange: (e) => setDispatchedBy(e.target.value),
|
|
3528
|
+
placeholder: "ваш email или ник"
|
|
3529
|
+
}
|
|
3530
|
+
)
|
|
3531
|
+
] }),
|
|
3532
|
+
!isTest && dryRunResult !== null && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 3, background: "primary100", hasRadius: true, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "omega", children: [
|
|
3533
|
+
"Подойдёт пользователей: ",
|
|
3534
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: dryRunResult })
|
|
3535
|
+
] }) }),
|
|
3536
|
+
confirmed && !isTest && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 3, background: "warning100", hasRadius: true, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { textColor: "warning700", children: [
|
|
3537
|
+
"Подтвердите: будет отправлено ",
|
|
3538
|
+
dryRunResult ?? "?",
|
|
3539
|
+
" сообщений. Повторное нажатие «Отправить» запустит рассылку."
|
|
3540
|
+
] }) }),
|
|
3541
|
+
err && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 3, background: "danger100", hasRadius: true, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "danger700", children: err.message }) })
|
|
3542
|
+
] }) }),
|
|
3543
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Modal.Footer, { children: [
|
|
3544
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Close, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "tertiary", onClick: close, children: "Отмена" }) }),
|
|
3545
|
+
!isTest && /* @__PURE__ */ jsxRuntime.jsx(
|
|
3546
|
+
designSystem.Button,
|
|
3547
|
+
{
|
|
3548
|
+
variant: "secondary",
|
|
3549
|
+
onClick: onDryRun,
|
|
3550
|
+
loading: dispatchMutation.isPending && dryRunResult === null,
|
|
3551
|
+
disabled: dispatchMutation.isPending,
|
|
3552
|
+
children: "Оценить (dry run)"
|
|
3553
|
+
}
|
|
3554
|
+
),
|
|
3555
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3556
|
+
designSystem.Button,
|
|
3557
|
+
{
|
|
3558
|
+
variant: confirmed && !isTest ? "danger-light" : "default",
|
|
3559
|
+
loading: mutation.isPending,
|
|
3560
|
+
onClick: onConfirm,
|
|
3561
|
+
disabled: mutation.isPending,
|
|
3562
|
+
children: isTest ? "Отправить тест" : confirmed ? "Подтвердить отправку" : "Отправить"
|
|
3563
|
+
}
|
|
3564
|
+
)
|
|
3565
|
+
] })
|
|
3566
|
+
] }) });
|
|
3567
|
+
};
|
|
3568
|
+
const homePageStyles = ".dashboard-container {\n --crm-bg-primary: #ffffff;\n --crm-bg-secondary: #f6f6f9;\n --crm-bg-tertiary: #eaeaef;\n --crm-text-primary: #32324d;\n --crm-text-secondary: #666687;\n --crm-text-muted: #8e8ea9;\n --crm-border: #dcdce4;\n --crm-border-light: #eaeaef;\n --crm-shadow: rgba(0, 0, 0, 0.08);\n --crm-shadow-strong: rgba(0, 0, 0, 0.12);\n --crm-accent: #4945ff;\n --crm-accent-light: #d9d8ff;\n --crm-success: #059669;\n --crm-success-light: #d1fae5;\n --crm-warning: #d97706;\n --crm-warning-light: #fef3c7;\n --crm-danger: #dc2626;\n --crm-danger-light: #fee2e2;\n --crm-header-gradient: linear-gradient(135deg, #4945ff 0%, #7b79ff 100%);\n --crm-chart-grid: #e0e0e0;\n --crm-chart-text: #666687;\n --crm-table-header-bg: #f6f6f9;\n --crm-table-row-hover: #f0f0ff;\n --crm-table-row-stripe: #fafafc;\n --tab-icon-stats-gradient: linear-gradient(135deg, #e0e7ff 0%, #c7d2fe 100%);\n --tab-icon-logs-gradient: linear-gradient(135deg, #d1fae5 0%, #a7f3d0 100%);\n --tab-icon-antispam-gradient: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);\n --tab-icon-manual-pushes-gradient: linear-gradient(135deg, #e0e7ff 0%, #b4b1ff 100%);\n\n min-height: 100vh;\n background: var(--crm-bg-secondary);\n}\n\n.dashboard-container.theme-dark {\n --crm-bg-primary: #212134;\n --crm-bg-secondary: #181826;\n --crm-bg-tertiary: #2a2a42;\n --crm-text-primary: #ffffff;\n --crm-text-secondary: #a5a5ba;\n --crm-text-muted: #7b7b98;\n --crm-border: #3d3d5c;\n --crm-border-light: #2a2a42;\n --crm-shadow: rgba(0, 0, 0, 0.3);\n --crm-shadow-strong: rgba(0, 0, 0, 0.4);\n --crm-accent: #a5a3ff;\n --crm-accent-light: rgba(123, 121, 255, 0.2);\n --crm-success: #34d399;\n --crm-success-light: rgba(16, 185, 129, 0.2);\n --crm-warning: #fbbf24;\n --crm-warning-light: rgba(245, 158, 11, 0.2);\n --crm-danger: #f87171;\n --crm-danger-light: rgba(239, 68, 68, 0.2);\n --crm-header-gradient: linear-gradient(135deg, #2a2a5c 0%, #3d3d80 100%);\n --crm-chart-grid: #3d3d5c;\n --crm-chart-text: #a5a5ba;\n --crm-table-header-bg: #2a2a42;\n --crm-table-row-hover: #2a2a50;\n --crm-table-row-stripe: #1e1e32;\n --tab-icon-stats-gradient: linear-gradient(135deg, rgba(123, 121, 255, 0.2) 0%, rgba(99, 102, 241, 0.3) 100%);\n --tab-icon-logs-gradient: linear-gradient(135deg, rgba(16, 185, 129, 0.2) 0%, rgba(52, 211, 153, 0.3) 100%);\n --tab-icon-antispam-gradient: linear-gradient(135deg, rgba(245, 158, 11, 0.2) 0%, rgba(251, 191, 36, 0.3) 100%);\n --tab-icon-manual-pushes-gradient: linear-gradient(135deg, rgba(123, 121, 255, 0.2) 0%, rgba(165, 163, 255, 0.3) 100%);\n}\n\n.dashboard-header {\n background: var(--crm-header-gradient);\n padding: 24px 32px;\n border-radius: 0 0 16px 16px;\n margin-bottom: 24px;\n box-shadow: 0 4px 24px var(--crm-shadow-strong);\n}\n\n.dashboard-header h1 {\n color: white;\n font-size: 28px;\n font-weight: 700;\n margin: 0 0 4px 0;\n}\n\n.dashboard-header p {\n color: rgba(255, 255, 255, 0.85);\n font-size: 14px;\n margin: 0;\n}\n\n.tab-navigation {\n display: flex;\n gap: 12px;\n padding: 0 24px;\n margin-bottom: 16px;\n}\n\n.tab-card {\n flex: 1;\n display: flex;\n align-items: center;\n gap: 14px;\n padding: 18px 20px;\n background: var(--crm-bg-primary);\n border: 2px solid transparent;\n border-radius: 12px;\n cursor: pointer;\n transition: all 0.2s ease;\n box-shadow: 0 2px 8px var(--crm-shadow);\n position: relative;\n overflow: hidden;\n}\n\n.tab-card::before {\n content: '';\n position: absolute;\n left: 0;\n top: 0;\n bottom: 0;\n width: 4px;\n background: transparent;\n transition: background 0.2s ease;\n}\n\n.tab-card:hover {\n transform: translateY(-2px);\n box-shadow: 0 8px 24px var(--crm-shadow-strong);\n}\n\n.tab-card.active {\n border-color: var(--tab-color, var(--crm-accent));\n box-shadow: 0 4px 16px var(--crm-shadow-strong);\n}\n\n.tab-card.active::before {\n background: var(--tab-color, var(--crm-accent));\n}\n\n.tab-icon {\n width: 44px;\n height: 44px;\n border-radius: 10px;\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n transition: transform 0.2s ease;\n}\n\n.tab-card:hover .tab-icon {\n transform: scale(1.05);\n}\n\n.tab-icon.stats {\n background: var(--tab-icon-stats-gradient);\n color: var(--crm-accent);\n}\n\n.tab-icon.logs {\n background: var(--tab-icon-logs-gradient);\n color: var(--crm-success);\n}\n\n.tab-icon.antispam {\n background: var(--tab-icon-antispam-gradient);\n color: var(--crm-warning);\n}\n\n.tab-icon.manual-pushes {\n background: var(--tab-icon-manual-pushes-gradient);\n color: var(--crm-accent);\n}\n\n.tab-content-wrapper {\n display: flex;\n flex-direction: column;\n gap: 2px;\n flex: 1;\n min-width: 0;\n}\n\n.tab-label {\n font-size: 15px;\n font-weight: 600;\n color: var(--crm-text-primary);\n margin: 0;\n}\n\n.tab-description {\n font-size: 12px;\n color: var(--crm-text-muted);\n margin: 0;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.tab-badge {\n display: flex;\n align-items: center;\n justify-content: center;\n min-width: 28px;\n height: 28px;\n padding: 0 10px;\n border-radius: 14px;\n font-size: 13px;\n font-weight: 600;\n flex-shrink: 0;\n transition: all 0.2s ease;\n}\n\n.tab-badge.stats {\n background: var(--crm-accent-light);\n color: var(--crm-accent);\n}\n\n.tab-badge.logs {\n background: var(--crm-success-light);\n color: var(--crm-success);\n}\n\n.tab-badge.antispam {\n background: var(--crm-warning-light);\n color: var(--crm-warning);\n}\n\n.tab-badge.manual-pushes {\n background: var(--crm-accent-light);\n color: var(--crm-accent);\n}\n\n.tab-card.active .tab-badge.stats {\n background: var(--crm-accent);\n color: white;\n}\n\n.tab-card.active .tab-badge.logs {\n background: var(--crm-success);\n color: white;\n}\n\n.tab-card.active .tab-badge.antispam {\n background: var(--crm-warning);\n color: white;\n}\n\n.tab-card.active .tab-badge.manual-pushes {\n background: var(--crm-accent);\n color: white;\n}\n\n.tab-content {\n margin: 0 24px;\n padding: 24px;\n background: var(--crm-bg-primary);\n border-radius: 16px;\n box-shadow: 0 4px 16px var(--crm-shadow);\n}\n\n.loading-container {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 64px;\n gap: 16px;\n color: var(--crm-text-muted);\n}\n\n.loading-spinner {\n width: 40px;\n height: 40px;\n border: 3px solid var(--crm-border);\n border-top-color: var(--crm-accent);\n border-radius: 50%;\n animation: spin 0.8s linear infinite;\n}\n\n@keyframes spin {\n to {\n transform: rotate(360deg);\n }\n}\n\n/* Stats Cards */\n.stats-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));\n gap: 16px;\n margin-bottom: 24px;\n}\n\n.stat-card {\n background: var(--crm-bg-primary);\n border-radius: 12px;\n padding: 20px;\n border: 1px solid var(--crm-border-light);\n box-shadow: 0 2px 8px var(--crm-shadow);\n transition: all 0.2s ease;\n}\n\n.stat-card:hover {\n transform: translateY(-2px);\n box-shadow: 0 6px 16px var(--crm-shadow-strong);\n}\n\n.stat-card-header {\n display: flex;\n align-items: center;\n gap: 12px;\n margin-bottom: 12px;\n}\n\n.stat-card-icon {\n width: 40px;\n height: 40px;\n border-radius: 10px;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.stat-card-icon.primary {\n background: var(--crm-accent-light);\n color: var(--crm-accent);\n}\n\n.stat-card-icon.success {\n background: var(--crm-success-light);\n color: var(--crm-success);\n}\n\n.stat-card-icon.warning {\n background: var(--crm-warning-light);\n color: var(--crm-warning);\n}\n\n.stat-card-icon.danger {\n background: var(--crm-danger-light);\n color: var(--crm-danger);\n}\n\n.stat-card-value {\n font-size: 28px;\n font-weight: 700;\n color: var(--crm-text-primary);\n line-height: 1.2;\n}\n\n.stat-card-label {\n font-size: 13px;\n color: var(--crm-text-muted);\n margin-top: 4px;\n}\n\n.stat-card-trend {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n font-size: 12px;\n font-weight: 600;\n padding: 2px 8px;\n border-radius: 12px;\n margin-top: 8px;\n}\n\n.stat-card-trend.up {\n background: var(--crm-success-light);\n color: var(--crm-success);\n}\n\n.stat-card-trend.down {\n background: var(--crm-danger-light);\n color: var(--crm-danger);\n}\n\n.stat-card-trend.neutral {\n background: var(--crm-bg-tertiary);\n color: var(--crm-text-muted);\n}\n\n.export-dropdown {\n position: relative;\n display: inline-block;\n}\n\n.export-dropdown-menu {\n position: absolute;\n top: 100%;\n right: 0;\n margin-top: 4px;\n background: var(--crm-bg-primary);\n border: 1px solid var(--crm-border);\n border-radius: 8px;\n box-shadow: 0 4px 16px var(--crm-shadow-strong);\n min-width: 140px;\n z-index: 100;\n overflow: hidden;\n}\n\n.export-dropdown-item {\n display: flex;\n align-items: center;\n gap: 8px;\n width: 100%;\n padding: 10px 14px;\n border: none;\n background: none;\n font-size: 13px;\n color: var(--crm-text-primary);\n cursor: pointer;\n transition: background 0.15s ease;\n}\n\n.export-dropdown-item:hover {\n background: var(--crm-bg-tertiary);\n}\n\n.export-dropdown-item svg {\n width: 16px;\n height: 16px;\n color: var(--crm-text-muted);\n}\n\n.granularity-select {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.granularity-select label {\n font-size: 12px;\n color: var(--crm-text-muted);\n white-space: nowrap;\n}\n\n.granularity-pills {\n display: flex;\n gap: 2px;\n background: var(--crm-bg-tertiary);\n border-radius: 6px;\n padding: 2px;\n}\n\n.granularity-pill {\n padding: 4px 10px;\n font-size: 11px;\n font-weight: 600;\n border: none;\n border-radius: 4px;\n cursor: pointer;\n transition: all 0.15s ease;\n background: transparent;\n color: var(--crm-text-muted);\n}\n\n.granularity-pill.active {\n background: var(--crm-accent);\n color: white;\n}\n\n.granularity-pill:hover:not(.active) {\n background: var(--crm-bg-secondary);\n color: var(--crm-text-primary);\n}\n\n.compare-toggle {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 13px;\n color: var(--crm-text-secondary);\n cursor: pointer;\n}\n\n.compare-toggle input {\n width: 16px;\n height: 16px;\n accent-color: var(--crm-accent);\n}\n\n.compare-toggle:hover {\n color: var(--crm-text-primary);\n}\n\n.metrics-toggle {\n display: flex;\n gap: 12px;\n margin-top: 12px;\n padding-top: 12px;\n border-top: 1px solid var(--crm-border-light);\n}\n\n.metrics-toggle label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 12px;\n color: var(--crm-text-secondary);\n cursor: pointer;\n}\n\n.metrics-toggle input {\n width: 14px;\n height: 14px;\n}\n\n.metrics-toggle label:hover {\n color: var(--crm-text-primary);\n}\n\n/* Chart Container */\n.chart-container {\n background: var(--crm-bg-primary);\n border-radius: 12px;\n padding: 24px;\n border: 1px solid var(--crm-border-light);\n box-shadow: 0 2px 8px var(--crm-shadow);\n}\n\n.chart-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 20px;\n}\n\n.chart-title {\n font-size: 16px;\n font-weight: 600;\n color: var(--crm-text-primary);\n}\n\n.chart-subtitle {\n font-size: 13px;\n color: var(--crm-text-muted);\n margin-top: 2px;\n}\n\n/* Table Styles */\n.crm-table-wrapper {\n background: var(--crm-bg-primary);\n border-radius: 12px;\n border: 1px solid var(--crm-border-light);\n box-shadow: 0 2px 8px var(--crm-shadow);\n overflow: hidden;\n}\n\n.crm-table {\n width: 100%;\n border-collapse: collapse;\n}\n\n.crm-table thead {\n background: var(--crm-table-header-bg);\n}\n\n.crm-table th {\n padding: 14px 16px;\n text-align: left;\n font-size: 11px;\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n color: var(--crm-text-secondary);\n border-bottom: 1px solid var(--crm-border);\n}\n\n.crm-table td {\n padding: 14px 16px;\n font-size: 14px;\n color: var(--crm-text-primary);\n border-bottom: 1px solid var(--crm-border-light);\n}\n\n.crm-table tbody tr {\n transition: background-color 0.15s ease;\n}\n\n.crm-table tbody tr:hover {\n background: var(--crm-table-row-hover);\n}\n\n.crm-table tbody tr:nth-child(even) {\n background: var(--crm-table-row-stripe);\n}\n\n.crm-table tbody tr:nth-child(even):hover {\n background: var(--crm-table-row-hover);\n}\n\n.crm-table tbody tr:last-child td {\n border-bottom: none;\n}\n\n/* Status Badge */\n.status-badge {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 4px 10px;\n border-radius: 6px;\n font-size: 12px;\n font-weight: 600;\n}\n\n.status-badge::before {\n content: '';\n width: 6px;\n height: 6px;\n border-radius: 50%;\n}\n\n.status-badge.sent {\n background: var(--crm-success-light);\n color: var(--crm-success);\n}\n\n.status-badge.sent::before {\n background: var(--crm-success);\n}\n\n.status-badge.planned {\n background: var(--crm-warning-light);\n color: var(--crm-warning);\n}\n\n.status-badge.planned::before {\n background: var(--crm-warning);\n}\n\n.status-badge.failed {\n background: var(--crm-danger-light);\n color: var(--crm-danger);\n}\n\n.status-badge.failed::before {\n background: var(--crm-danger);\n}\n\n.status-badge.cancelled {\n background: var(--crm-bg-tertiary);\n color: var(--crm-text-muted);\n}\n\n.status-badge.cancelled::before {\n background: var(--crm-text-muted);\n}\n\n.status-badge.warning {\n background: var(--crm-warning-light);\n color: var(--crm-warning);\n}\n\n.status-badge.warning::before {\n background: var(--crm-warning);\n}\n\n/* Pagination */\n.pagination {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 16px 0;\n}\n\n.pagination-info {\n font-size: 13px;\n color: var(--crm-text-muted);\n}\n\n.pagination-controls {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.pagination-btn {\n padding: 8px 16px;\n border-radius: 8px;\n font-size: 13px;\n font-weight: 500;\n background: var(--crm-bg-primary);\n border: 1px solid var(--crm-border);\n color: var(--crm-text-primary);\n cursor: pointer;\n transition: all 0.15s ease;\n}\n\n.pagination-btn:hover:not(:disabled) {\n background: var(--crm-accent-light);\n border-color: var(--crm-accent);\n color: var(--crm-accent);\n}\n\n.pagination-btn:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.pagination-current {\n padding: 8px 12px;\n font-size: 13px;\n font-weight: 600;\n color: var(--crm-text-primary);\n}\n\n/* Empty State */\n.empty-state {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 48px 24px;\n text-align: center;\n}\n\n.empty-state-icon {\n width: 64px;\n height: 64px;\n border-radius: 16px;\n background: var(--crm-bg-tertiary);\n display: flex;\n align-items: center;\n justify-content: center;\n margin-bottom: 16px;\n color: var(--crm-text-muted);\n}\n\n.empty-state-title {\n font-size: 16px;\n font-weight: 600;\n color: var(--crm-text-primary);\n margin-bottom: 4px;\n}\n\n.empty-state-description {\n font-size: 14px;\n color: var(--crm-text-muted);\n}\n\n/* Section Header in table wrapper */\n.crm-table-wrapper .section-header,\n.crm-table-wrapper > div:first-child {\n padding: 16px 20px;\n}\n\n/* Funnel grid responsive */\n.funnel-grid {\n display: grid;\n grid-template-columns: repeat(5, 1fr);\n gap: 12px;\n}\n\n@media (max-width: 1200px) {\n .stats-grid {\n grid-template-columns: repeat(3, 1fr) !important;\n }\n\n .funnel-grid {\n grid-template-columns: repeat(3, 1fr);\n }\n}\n\n@media (max-width: 1024px) {\n .tab-navigation {\n flex-direction: column;\n gap: 8px;\n padding: 0 16px;\n }\n\n .tab-card {\n padding: 14px 16px;\n }\n\n .tab-description {\n display: none;\n }\n\n .tab-content {\n margin: 0 16px;\n border-radius: 12px;\n }\n\n .stats-grid {\n grid-template-columns: repeat(2, 1fr) !important;\n }\n\n .funnel-grid {\n grid-template-columns: repeat(2, 1fr);\n }\n\n .chart-container {\n padding: 16px;\n }\n}\n\n@media (max-width: 768px) {\n .stats-grid {\n grid-template-columns: repeat(2, 1fr) !important;\n }\n\n .crm-table th,\n .crm-table td {\n padding: 10px 12px;\n font-size: 13px;\n }\n}\n\n@media (max-width: 640px) {\n .dashboard-header {\n padding: 20px;\n border-radius: 0 0 12px 12px;\n }\n\n .dashboard-header h1 {\n font-size: 22px;\n }\n\n .tab-icon {\n width: 36px;\n height: 36px;\n }\n\n .tab-label {\n font-size: 14px;\n }\n\n .stats-grid {\n grid-template-columns: 1fr !important;\n }\n\n .funnel-grid {\n grid-template-columns: 1fr;\n }\n\n .stat-card-value {\n font-size: 24px;\n }\n\n .chart-container {\n padding: 12px;\n }\n}\n\n/* Today Widget */\n.today-widget {\n background: var(--crm-bg-primary);\n border-radius: 12px;\n padding: 20px;\n border: 1px solid var(--crm-border-light);\n box-shadow: 0 4px 16px var(--crm-shadow);\n}\n\n.today-widget.loading {\n min-height: 160px;\n display: flex;\n flex-direction: column;\n}\n\n.today-widget-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 16px;\n}\n\n.today-widget-title {\n font-size: 13px;\n font-weight: 700;\n letter-spacing: 0.5px;\n text-transform: uppercase;\n color: var(--crm-text-primary);\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.live-dot {\n width: 8px;\n height: 8px;\n background: var(--crm-success);\n border-radius: 50%;\n animation: pulse 2s ease-in-out infinite;\n box-shadow: 0 0 8px var(--crm-success);\n}\n\n@keyframes pulse {\n 0%, 100% { opacity: 1; transform: scale(1); }\n 50% { opacity: 0.7; transform: scale(1.2); }\n}\n\n.today-widget-comparison {\n font-size: 11px;\n color: var(--crm-text-muted);\n background: var(--crm-bg-tertiary);\n padding: 4px 10px;\n border-radius: 12px;\n}\n\n.today-widget-metrics {\n display: grid;\n grid-template-columns: repeat(2, 1fr);\n gap: 10px;\n margin-bottom: 16px;\n}\n\n.today-metric {\n text-align: center;\n padding: 12px 8px;\n background: var(--crm-bg-secondary);\n border-radius: 10px;\n border: 1px solid var(--crm-border-light);\n transition: border-color 0.2s ease, background-color 0.2s ease;\n}\n\n.today-metric:hover {\n border-color: var(--crm-border);\n background: var(--crm-bg-tertiary);\n}\n\n.today-metric-value {\n font-size: 22px;\n font-weight: 700;\n line-height: 1.2;\n color: var(--crm-text-primary);\n}\n\n.today-metric-label {\n font-size: 11px;\n color: var(--crm-text-muted);\n margin-top: 4px;\n font-weight: 500;\n}\n\n.trend-indicator {\n display: inline-flex;\n align-items: center;\n font-size: 11px;\n font-weight: 600;\n margin-top: 6px;\n padding: 3px 8px;\n border-radius: 10px;\n}\n\n.trend-indicator.up {\n background: var(--crm-success-light);\n color: var(--crm-success);\n}\n\n.trend-indicator.down {\n background: var(--crm-danger-light);\n color: var(--crm-danger);\n}\n\n.trend-neutral {\n display: inline-flex;\n font-size: 11px;\n font-weight: 600;\n margin-top: 6px;\n padding: 3px 8px;\n border-radius: 10px;\n background: var(--crm-bg-tertiary);\n color: var(--crm-text-muted);\n}\n\n.today-widget-footer {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding-top: 12px;\n border-top: 1px solid var(--crm-border-light);\n font-size: 12px;\n}\n\n.today-rates {\n display: flex;\n gap: 16px;\n}\n\n.today-rates span {\n color: var(--crm-text-muted);\n font-weight: 500;\n}\n\n.today-rates span strong {\n color: var(--crm-text-primary);\n font-weight: 700;\n}\n\n.today-updated {\n color: var(--crm-text-muted);\n font-size: 11px;\n}\n\n.today-widget-loading {\n flex: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 14px;\n color: var(--crm-text-muted);\n}\n\n/* Cohort Heat Map */\n.cohort-heatmap {\n background: var(--crm-bg-primary);\n border-radius: 12px;\n padding: 24px;\n border: 1px solid var(--crm-border-light);\n}\n\n.cohort-table-wrapper {\n overflow-x: auto;\n margin: 16px 0;\n}\n\n.cohort-table {\n width: 100%;\n border-collapse: collapse;\n font-size: 12px;\n}\n\n.cohort-header-cell {\n padding: 10px 8px;\n text-align: center;\n font-weight: 600;\n color: var(--crm-text-secondary);\n background: var(--crm-bg-secondary);\n border-bottom: 1px solid var(--crm-border);\n}\n\n.cohort-period-header {\n min-width: 60px;\n}\n\n.cohort-date-cell {\n padding: 10px 12px;\n font-weight: 600;\n color: var(--crm-text-primary);\n background: var(--crm-bg-secondary);\n white-space: nowrap;\n}\n\n.cohort-users-cell {\n padding: 10px 12px;\n text-align: right;\n color: var(--crm-text-secondary);\n background: var(--crm-bg-secondary);\n}\n\n.cohort-cell {\n padding: 10px 8px;\n text-align: center;\n font-weight: 600;\n font-size: 11px;\n transition: all 0.15s ease;\n cursor: default;\n}\n\n.cohort-cell:hover {\n filter: brightness(1.1);\n z-index: 1;\n position: relative;\n}\n\n.cohort-legend {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 12px;\n font-size: 11px;\n color: var(--crm-text-muted);\n margin-top: 12px;\n}\n\n.cohort-legend-gradient {\n width: 120px;\n height: 8px;\n border-radius: 4px;\n background: linear-gradient(90deg, rgba(239, 68, 68, 0.6) 0%, rgba(245, 158, 11, 0.6) 50%, rgba(16, 185, 129, 0.8) 100%);\n}\n\n.cohort-loading {\n text-align: center;\n padding: 32px;\n color: var(--crm-text-muted);\n}\n\n/* A/B Tests */\n.ab-tests-section {\n background: var(--crm-bg-primary);\n border-radius: 12px;\n padding: 24px;\n border: 1px solid var(--crm-border-light);\n}\n\n.ab-tests-list {\n display: flex;\n flex-direction: column;\n gap: 16px;\n}\n\n.ab-test-card {\n background: var(--crm-bg-secondary);\n border-radius: 10px;\n padding: 20px;\n border: 1px solid var(--crm-border-light);\n}\n\n.ab-campaign-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 16px;\n}\n\n.ab-test-name {\n font-size: 15px;\n font-weight: 600;\n color: var(--crm-text-primary);\n}\n\n.ab-block-count {\n font-size: 12px;\n font-weight: 500;\n color: var(--crm-text-muted);\n background: var(--crm-bg-tertiary);\n padding: 2px 10px;\n border-radius: 10px;\n}\n\n.ab-blocks-list {\n display: flex;\n flex-direction: column;\n gap: 12px;\n}\n\n.ab-message-block {\n background: var(--crm-bg-primary);\n border-radius: 8px;\n padding: 16px;\n border: 1px solid var(--crm-border-light);\n}\n\n.ab-block-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 12px;\n}\n\n.ab-block-name {\n font-size: 13px;\n font-weight: 600;\n color: var(--crm-text-secondary);\n}\n\n.ab-test-status {\n font-size: 12px;\n font-weight: 600;\n display: flex;\n align-items: center;\n gap: 6px;\n}\n\n.ab-test-variants {\n display: grid;\n grid-template-columns: 1fr auto 1fr;\n gap: 16px;\n align-items: center;\n}\n\n.ab-variant {\n background: var(--crm-bg-secondary);\n border-radius: 8px;\n padding: 16px;\n border: 2px solid transparent;\n transition: all 0.2s ease;\n}\n\n.ab-variant.winner {\n border-color: var(--crm-success);\n box-shadow: 0 0 0 4px rgba(16, 185, 129, 0.1);\n}\n\n.ab-variant-header {\n display: flex;\n align-items: center;\n gap: 8px;\n margin-bottom: 12px;\n}\n\n.ab-variant-name {\n font-weight: 600;\n color: var(--crm-text-primary);\n}\n\n.ab-variant-tag {\n font-size: 10px;\n font-weight: 600;\n padding: 2px 8px;\n border-radius: 10px;\n text-transform: uppercase;\n}\n\n.ab-variant-tag.control {\n background: var(--crm-bg-tertiary);\n color: var(--crm-text-muted);\n}\n\n.ab-variant-tag.treatment {\n background: var(--crm-accent-light);\n color: var(--crm-accent);\n}\n\n.ab-variant-stats {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 12px;\n}\n\n.ab-stat {\n text-align: center;\n}\n\n.ab-stat-value {\n font-size: 18px;\n font-weight: 700;\n color: var(--crm-text-primary);\n}\n\n.ab-stat-label {\n font-size: 10px;\n color: var(--crm-text-muted);\n margin-top: 2px;\n}\n\n.ab-vs {\n font-size: 12px;\n font-weight: 700;\n color: var(--crm-text-muted);\n text-align: center;\n}\n\n.ab-test-analysis {\n display: flex;\n gap: 24px;\n margin-top: 16px;\n padding-top: 16px;\n border-top: 1px solid var(--crm-border-light);\n}\n\n.ab-analysis-item {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 13px;\n}\n\n.ab-analysis-label {\n color: var(--crm-text-muted);\n}\n\n.ab-analysis-value {\n font-weight: 600;\n color: var(--crm-text-primary);\n}\n\n.ab-analysis-value.positive {\n color: var(--crm-success);\n}\n\n.ab-analysis-value.negative {\n color: var(--crm-danger);\n}\n\n.ab-analysis-value.confidence-high {\n color: var(--crm-success);\n}\n\n.ab-analysis-value.confidence-medium {\n color: var(--crm-success);\n}\n\n.ab-analysis-value.confidence-low {\n color: var(--crm-warning);\n}\n\n.ab-tests-loading {\n text-align: center;\n padding: 32px;\n color: var(--crm-text-muted);\n}\n\n/* Multi-variant A/B Tests */\n.ab-test-variants-multi {\n display: flex;\n align-items: stretch;\n gap: 8px;\n flex-wrap: wrap;\n}\n\n.ab-vs-small {\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 10px;\n font-weight: 700;\n color: var(--crm-text-muted);\n padding: 0 4px;\n}\n\n.ab-variant-compact {\n flex: 1;\n min-width: 140px;\n max-width: 200px;\n background: var(--crm-bg-secondary);\n border-radius: 8px;\n padding: 12px;\n border: 2px solid transparent;\n transition: all 0.2s ease;\n}\n\n.ab-variant-compact.winner {\n border-color: var(--crm-success);\n box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.1);\n}\n\n.ab-variant-compact .ab-variant-header {\n margin-bottom: 8px;\n flex-wrap: wrap;\n gap: 4px;\n}\n\n.ab-variant-compact .ab-variant-name {\n font-size: 13px;\n}\n\n.ab-variant-stats-compact {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.ab-stat-row {\n display: flex;\n justify-content: space-between;\n align-items: center;\n font-size: 12px;\n}\n\n.ab-stat-row .ab-stat-label {\n color: var(--crm-text-muted);\n font-size: 11px;\n}\n\n.ab-stat-row .ab-stat-value {\n font-weight: 600;\n color: var(--crm-text-primary);\n font-size: 12px;\n}\n\n.ab-variant-lift {\n margin-top: 8px;\n padding-top: 8px;\n border-top: 1px solid var(--crm-border-light);\n text-align: center;\n font-size: 12px;\n font-weight: 600;\n}\n\n.ab-variant-lift .positive {\n color: var(--crm-success);\n}\n\n.ab-variant-lift .negative {\n color: var(--crm-danger);\n}\n\n.ab-test-analysis-compact {\n display: flex;\n align-items: center;\n gap: 8px;\n margin-top: 12px;\n padding-top: 12px;\n border-top: 1px solid var(--crm-border-light);\n font-size: 12px;\n}\n\n.ab-test-analysis-compact .ab-analysis-label {\n color: var(--crm-text-muted);\n}\n\n.ab-test-analysis-compact .ab-analysis-value {\n font-weight: 600;\n}\n\n@media (max-width: 640px) {\n .ab-test-variants-multi {\n flex-direction: column;\n }\n\n .ab-variant-compact {\n max-width: none;\n }\n\n .ab-vs-small {\n padding: 4px 0;\n }\n}\n\n/* Send Time Heat Map */\n.sendtime-heatmap {\n background: var(--crm-bg-primary);\n border-radius: 12px;\n padding: 24px;\n border: 1px solid var(--crm-border-light);\n}\n\n.sendtime-best {\n text-align: right;\n}\n\n.sendtime-best-label {\n font-size: 11px;\n color: var(--crm-text-muted);\n}\n\n.sendtime-best-value {\n font-size: 16px;\n font-weight: 700;\n color: var(--crm-success);\n}\n\n.sendtime-best-rate {\n font-size: 12px;\n color: var(--crm-success);\n}\n\n.sendtime-grid-wrapper {\n overflow-x: auto;\n margin: 16px 0;\n}\n\n.sendtime-grid {\n display: grid;\n grid-template-columns: 60px repeat(7, 1fr);\n gap: 2px;\n min-width: 400px;\n}\n\n.sendtime-corner {\n background: transparent;\n}\n\n.sendtime-day-header {\n text-align: center;\n font-size: 11px;\n font-weight: 600;\n color: var(--crm-text-secondary);\n padding: 8px 4px;\n}\n\n.sendtime-hour-label {\n font-size: 10px;\n color: var(--crm-text-muted);\n display: flex;\n align-items: center;\n justify-content: flex-end;\n padding-right: 8px;\n}\n\n.sendtime-cell {\n aspect-ratio: 1;\n border-radius: 4px;\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: default;\n transition: all 0.15s ease;\n min-height: 32px;\n}\n\n.sendtime-cell:hover {\n filter: brightness(1.15);\n z-index: 1;\n}\n\n.sendtime-cell.best {\n box-shadow: 0 0 0 2px var(--crm-success);\n}\n\n.sendtime-cell-value {\n font-size: 9px;\n font-weight: 600;\n color: inherit;\n}\n\n.sendtime-legend {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 12px;\n font-size: 11px;\n color: var(--crm-text-muted);\n margin-top: 12px;\n}\n\n.sendtime-legend-gradient {\n width: 120px;\n height: 8px;\n border-radius: 4px;\n background: linear-gradient(90deg, rgba(239, 68, 68, 0.6) 0%, rgba(245, 158, 11, 0.6) 50%, rgba(16, 185, 129, 0.8) 100%);\n}\n\n.sendtime-loading {\n text-align: center;\n padding: 32px;\n color: var(--crm-text-muted);\n}\n\n/* Segment Table */\n.segment-table-section {\n background: var(--crm-bg-primary);\n border-radius: 12px;\n padding: 24px;\n border: 1px solid var(--crm-border-light);\n}\n\n.segment-cards {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));\n gap: 16px;\n}\n\n.segment-card {\n background: var(--crm-bg-secondary);\n border-radius: 10px;\n padding: 16px;\n border-left: 4px solid var(--crm-text-muted);\n transition: all 0.2s ease;\n}\n\n.segment-card:hover {\n transform: translateY(-2px);\n box-shadow: 0 4px 12px var(--crm-shadow);\n}\n\n.segment-card-header {\n display: flex;\n align-items: center;\n gap: 8px;\n margin-bottom: 12px;\n}\n\n.segment-icon {\n font-size: 18px;\n}\n\n.segment-name {\n font-size: 14px;\n font-weight: 600;\n color: var(--crm-text-primary);\n flex: 1;\n}\n\n.segment-users {\n font-size: 12px;\n font-weight: 600;\n padding: 2px 8px;\n border-radius: 10px;\n}\n\n.segment-metrics {\n display: grid;\n grid-template-columns: repeat(4, 1fr);\n gap: 8px;\n margin-bottom: 12px;\n}\n\n.segment-metric {\n text-align: center;\n}\n\n.segment-metric-value {\n font-size: 16px;\n font-weight: 700;\n color: var(--crm-text-primary);\n}\n\n.segment-metric-label {\n font-size: 10px;\n color: var(--crm-text-muted);\n}\n\n.segment-bar-wrapper {\n height: 4px;\n background: var(--crm-bg-tertiary);\n border-radius: 2px;\n overflow: hidden;\n}\n\n.segment-bar {\n height: 100%;\n border-radius: 2px;\n transition: width 0.3s ease;\n}\n\n.segment-loading {\n text-align: center;\n padding: 32px;\n color: var(--crm-text-muted);\n}\n\n/* Goal Tracker */\n.goal-tracker {\n background: var(--crm-bg-primary);\n border-radius: 12px;\n padding: 20px;\n border: 1px solid var(--crm-border-light);\n}\n\n.goal-tracker-header {\n display: flex;\n justify-content: space-between;\n align-items: flex-start;\n margin-bottom: 16px;\n}\n\n.goal-edit-btn {\n background: transparent;\n border: none;\n font-size: 16px;\n cursor: pointer;\n padding: 4px 8px;\n border-radius: 6px;\n transition: background 0.15s ease;\n}\n\n.goal-edit-btn:hover {\n background: var(--crm-bg-tertiary);\n}\n\n.goal-items {\n display: flex;\n flex-direction: column;\n gap: 16px;\n}\n\n.goal-item {\n padding: 12px;\n background: var(--crm-bg-secondary);\n border-radius: 8px;\n}\n\n.goal-item-header {\n display: flex;\n align-items: center;\n gap: 8px;\n margin-bottom: 8px;\n}\n\n.goal-icon {\n font-size: 16px;\n}\n\n.goal-name {\n font-size: 13px;\n font-weight: 600;\n color: var(--crm-text-primary);\n flex: 1;\n}\n\n.goal-progress-badge {\n font-size: 11px;\n font-weight: 700;\n padding: 2px 8px;\n border-radius: 10px;\n}\n\n.goal-bar-wrapper {\n height: 6px;\n background: var(--crm-bg-tertiary);\n border-radius: 3px;\n overflow: hidden;\n margin-bottom: 8px;\n}\n\n.goal-bar {\n height: 100%;\n border-radius: 3px;\n transition: width 0.3s ease;\n}\n\n.goal-values {\n display: flex;\n align-items: center;\n gap: 4px;\n font-size: 12px;\n}\n\n.goal-current {\n font-weight: 600;\n color: var(--crm-text-primary);\n}\n\n.goal-separator {\n color: var(--crm-text-muted);\n}\n\n.goal-target {\n color: var(--crm-text-secondary);\n}\n\n.goal-target-input {\n width: 80px;\n padding: 2px 6px;\n border: 1px solid var(--crm-border);\n border-radius: 4px;\n font-size: 12px;\n background: var(--crm-bg-primary);\n color: var(--crm-text-primary);\n}\n\n.goal-target-input:focus {\n outline: none;\n border-color: var(--crm-accent);\n}\n\n/* Enhanced Stats Layout */\n.stats-header-row {\n display: grid;\n grid-template-columns: 320px 1fr;\n gap: 16px;\n margin-bottom: 24px;\n}\n\n@media (max-width: 1024px) {\n .stats-header-row {\n grid-template-columns: 1fr;\n }\n\n .ab-test-variants {\n grid-template-columns: 1fr;\n }\n\n .ab-vs {\n padding: 8px 0;\n }\n\n .ab-test-analysis {\n flex-wrap: wrap;\n gap: 12px;\n }\n}\n\n@media (max-width: 768px) {\n .segment-metrics {\n grid-template-columns: repeat(2, 1fr);\n }\n}\n";
|
|
3105
3569
|
const filterBarStyles = "/* Filter Bar Container */\n.filter-bar {\n position: sticky;\n top: 0;\n z-index: 10;\n transition: box-shadow 0.2s ease, transform 0.2s ease;\n border-radius: 12px;\n}\n\n.filter-bar.scrolled {\n box-shadow: 0 4px 20px var(--crm-shadow-strong, rgba(0, 0, 0, 0.12));\n}\n\n/* Date Presets Button Group */\n.date-presets-group {\n display: inline-flex;\n gap: 0;\n background: var(--crm-bg-tertiary, #eaeaef);\n border-radius: 8px;\n padding: 3px;\n}\n\n.date-preset-btn {\n padding: 6px 10px;\n font-size: 12px;\n font-weight: 500;\n border-radius: 6px;\n background: transparent;\n border: none;\n cursor: pointer;\n transition: all 0.15s ease;\n color: var(--crm-text-secondary, #666687);\n white-space: nowrap;\n}\n\n.date-preset-btn:hover {\n background: var(--crm-bg-primary, #ffffff);\n color: var(--crm-text-primary, #32324d);\n}\n\n.date-preset-btn.active {\n background: var(--crm-accent, #4945ff);\n color: white;\n box-shadow: 0 2px 4px rgba(73, 69, 255, 0.25);\n}\n\n/* Active Filters */\n.active-filters {\n display: flex;\n align-items: center;\n gap: 12px;\n flex-wrap: wrap;\n}\n\n.active-filters-label {\n font-size: 13px;\n font-weight: 500;\n color: var(--crm-text-muted, #8e8ea9);\n}\n\n.active-filters-chips {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-wrap: wrap;\n}\n\n/* Filter Chips */\n.filter-chip {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 6px 10px;\n background: var(--crm-accent-light, #d9d8ff);\n border: 1px solid transparent;\n border-radius: 20px;\n cursor: pointer;\n transition: all 0.15s ease;\n font-family: inherit;\n}\n\n.filter-chip:hover {\n background: var(--crm-accent, #4945ff);\n transform: translateY(-1px);\n}\n\n.filter-chip:hover .filter-chip-label,\n.filter-chip:hover .filter-chip-value {\n color: white;\n}\n\n.filter-chip:hover .filter-chip-remove {\n opacity: 1;\n color: white;\n}\n\n.filter-chip-label {\n font-size: 11px;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.3px;\n color: var(--crm-accent, #4945ff);\n transition: color 0.15s ease;\n}\n\n.filter-chip-value {\n font-size: 13px;\n font-weight: 500;\n color: var(--crm-text-primary, #32324d);\n max-width: 150px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n transition: color 0.15s ease;\n}\n\n.filter-chip-remove {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 16px;\n height: 16px;\n border-radius: 50%;\n background: rgba(0, 0, 0, 0.1);\n opacity: 0.6;\n transition: all 0.15s ease;\n color: var(--crm-text-primary, #32324d);\n}\n\n.filter-chip:hover .filter-chip-remove {\n background: rgba(255, 255, 255, 0.2);\n}\n\n/* Clear All Button */\n.filter-chip.filter-chip-clear {\n background: var(--crm-bg-tertiary, #eaeaef);\n color: var(--crm-text-secondary, #666687);\n font-size: 12px;\n font-weight: 500;\n padding: 6px 12px;\n}\n\n.filter-chip.filter-chip-clear:hover {\n background: var(--crm-danger, #dc2626);\n color: white;\n}\n\n/* Filter Section Styling */\n.filter-section {\n display: flex;\n align-items: flex-end;\n gap: 12px;\n flex-wrap: wrap;\n}\n\n.filter-section-divider {\n width: 1px;\n height: 32px;\n background: var(--crm-border-light, #eaeaef);\n margin: 0 4px;\n}\n\n/* Filter Actions */\n.filter-actions {\n display: flex;\n align-items: center;\n gap: 8px;\n margin-left: auto;\n}\n\n/* Responsive */\n@media (max-width: 768px) {\n .date-presets-group {\n width: 100%;\n justify-content: center;\n }\n\n .date-preset-btn {\n flex: 1;\n text-align: center;\n padding: 8px 6px;\n }\n\n .active-filters {\n flex-direction: column;\n align-items: flex-start;\n }\n\n .active-filters-chips {\n width: 100%;\n }\n\n .filter-chip {\n flex: 1;\n justify-content: center;\n min-width: 0;\n }\n\n .filter-chip-value {\n max-width: 100px;\n }\n}\n\n/* Animation for chip removal */\n@keyframes chipFadeIn {\n from {\n opacity: 0;\n transform: scale(0.9) translateY(-4px);\n }\n to {\n opacity: 1;\n transform: scale(1) translateY(0);\n }\n}\n\n.filter-chip {\n animation: chipFadeIn 0.2s ease;\n}\n\n/* Advanced Filters Toggle */\n.advanced-toggle {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 6px 12px;\n font-size: 13px;\n font-weight: 500;\n color: var(--crm-text-secondary, #666687);\n background: transparent;\n border: 1px dashed var(--crm-border, #dcdce4);\n border-radius: 8px;\n cursor: pointer;\n transition: all 0.15s ease;\n}\n\n.advanced-toggle:hover {\n border-color: var(--crm-accent, #4945ff);\n color: var(--crm-accent, #4945ff);\n background: var(--crm-accent-light, #d9d8ff);\n}\n\n.advanced-toggle.active {\n border-style: solid;\n border-color: var(--crm-accent, #4945ff);\n color: var(--crm-accent, #4945ff);\n background: var(--crm-accent-light, #d9d8ff);\n}\n\n.advanced-toggle-badge {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 18px;\n height: 18px;\n padding: 0 5px;\n font-size: 11px;\n font-weight: 600;\n background: var(--crm-accent, #4945ff);\n color: white;\n border-radius: 9px;\n}\n\n/* Advanced Section */\n.advanced-section {\n margin-top: 16px;\n padding-top: 16px;\n border-top: 1px solid var(--crm-border-light, #eaeaef);\n animation: slideDown 0.2s ease;\n}\n\n@keyframes slideDown {\n from {\n opacity: 0;\n transform: translateY(-8px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n\n/* Apply Button Indicator */\n.apply-indicator {\n display: inline-block;\n width: 8px;\n height: 8px;\n background: var(--crm-warning, #d97706);\n border-radius: 50%;\n margin-left: 6px;\n animation: pulse 1.5s ease-in-out infinite;\n}\n\n@keyframes pulse {\n 0%, 100% {\n opacity: 1;\n transform: scale(1);\n }\n 50% {\n opacity: 0.6;\n transform: scale(0.85);\n }\n}\n";
|
|
3106
3570
|
const useIsDarkTheme = () => {
|
|
3107
3571
|
const theme = styledComponents.useTheme();
|
|
@@ -3128,6 +3592,13 @@ const tabs = [
|
|
|
3128
3592
|
description: "Заблокированные сообщения",
|
|
3129
3593
|
icon: /* @__PURE__ */ jsxRuntime.jsx(icons.Cross, { width: 20, height: 20 }),
|
|
3130
3594
|
color: "#d97706"
|
|
3595
|
+
},
|
|
3596
|
+
{
|
|
3597
|
+
id: "manual-pushes",
|
|
3598
|
+
label: "Ручные рассылки",
|
|
3599
|
+
description: "Шаблоны Manual Push и ручная отправка",
|
|
3600
|
+
icon: /* @__PURE__ */ jsxRuntime.jsx(icons.Message, { width: 20, height: 20 }),
|
|
3601
|
+
color: "#7b79ff"
|
|
3131
3602
|
}
|
|
3132
3603
|
];
|
|
3133
3604
|
const formatNumber = (num) => {
|
|
@@ -3149,11 +3620,12 @@ const DashboardContent = () => {
|
|
|
3149
3620
|
);
|
|
3150
3621
|
const { data: stats, isLoading } = useDashboardStats(defaultFilters);
|
|
3151
3622
|
const badgeCounts = React.useMemo(() => {
|
|
3152
|
-
if (!stats) return { stats: 0, logs: 0, antispam: 0 };
|
|
3623
|
+
if (!stats) return { stats: 0, logs: 0, antispam: 0, "manual-pushes": 0 };
|
|
3153
3624
|
return {
|
|
3154
3625
|
stats: stats.totals?.sent ?? 0,
|
|
3155
3626
|
logs: stats.funnel?.sent ?? 0,
|
|
3156
|
-
antispam: stats.funnel?.blocked_total ?? 0
|
|
3627
|
+
antispam: stats.funnel?.blocked_total ?? 0,
|
|
3628
|
+
"manual-pushes": 0
|
|
3157
3629
|
};
|
|
3158
3630
|
}, [stats]);
|
|
3159
3631
|
const renderTabContent = () => {
|
|
@@ -3164,6 +3636,8 @@ const DashboardContent = () => {
|
|
|
3164
3636
|
return /* @__PURE__ */ jsxRuntime.jsx(LogsTable, {});
|
|
3165
3637
|
case "antispam":
|
|
3166
3638
|
return /* @__PURE__ */ jsxRuntime.jsx(AntiSpamLogsTable$1, {});
|
|
3639
|
+
case "manual-pushes":
|
|
3640
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ManualPushesView, {});
|
|
3167
3641
|
default:
|
|
3168
3642
|
return null;
|
|
3169
3643
|
}
|