From 9d0015808ca1687f7a7ea9462400a4fd7b334a74 Mon Sep 17 00:00:00 2001 From: aramesh-site deploy Date: Sat, 26 Sep 2026 21:43:59 +0000 Subject: [PATCH] =?UTF-8?q?feat(frontend):=20=D8=AA=D8=AD=D9=84=DB=8C?= =?UTF-8?q?=D9=84=20=D8=B1=D9=81=D8=AA=D8=A7=D8=B1=20=D8=A8=D8=A7=20OpenPa?= =?UTF-8?q?nel=20=D8=A8=D9=87=E2=80=8C=D8=AC=D8=A7=DB=8C=20=D8=A7=D8=AC?= =?UTF-8?q?=D8=B1=D8=A7=DB=8C=20=D9=86=D9=85=D9=88=D9=86=D9=87=D9=94=20?= =?UTF-8?q?=D8=AF=D9=88=D9=85=20Umami?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit نمونهٔ OpenPanel موجود (stats.*) با یک پروژهٔ دوم برای این سایت استفاده می‌شود؛ VITE_OPENPANEL_URL/VITE_OPENPANEL_CLIENT_ID در زمان بیلد تزریق می‌شوند و در نبودشان همه‌چیز بی‌اثر است. API عمومی track() تغییر نکرده و مسیر Umami هم برای عقب‌نشینی دست‌نخورده مانده. --- frontend/src/analytics.ts | 58 +++++++++++++++++++++++++++++++++++---- frontend/src/index.tsx | 3 ++ 2 files changed, 56 insertions(+), 5 deletions(-) diff --git a/frontend/src/analytics.ts b/frontend/src/analytics.ts index 2cf504c..fb9f688 100644 --- a/frontend/src/analytics.ts +++ b/frontend/src/analytics.ts @@ -1,17 +1,65 @@ -// لایهٔ نازک تحلیل رفتار (Umami) — اگر اسکریپت ردیاب بارگیری نشده باشد -// (VITE_UMAMI_SRC خالی)، همهٔ فراخوانی‌ها بی‌اثر و ارزان‌اند. +// لایهٔ نازک تحلیل رفتار — OpenPanel. +// +// نمونهٔ OpenPanel از قبل روی این سرور بالا است (stats.bennu-api-bahrestan.ir) و +// پروژهٔ ERP هم از همان استفاده می‌کند؛ به‌جای اجرای یک ابزار تحلیل دوم (Umami)، +// همین یک نمونه با یک «پروژهٔ» دوم برای این سایت به کار می‌رود. +// +// اگر VITE_OPENPANEL_URL و VITE_OPENPANEL_CLIENT_ID در زمان بیلد تنظیم نشده باشند، +// همهٔ فراخوانی‌ها بی‌اثر و ارزان‌اند (مثل حالت فعلی Umami). +// +// مسیر Umami دست‌نخورده مانده تا اگر اسکریپت قدیمی جایی فعال بود از کار نیفتد. declare global { interface Window { + op?: (...args: unknown[]) => void umami?: { track: (event?: string, data?: Record) => void } } } -/** رخداد سفارشی — نام رخداد در داشبورد Umami نمایش داده می‌شود. */ -export function track(event: string, data?: Record) { +const OP_URL = (import.meta.env?.VITE_OPENPANEL_URL as string | undefined)?.trim() +const OP_CLIENT_ID = (import.meta.env?.VITE_OPENPANEL_CLIENT_ID as string | undefined)?.trim() +const OP_SCRIPT_URL = (import.meta.env?.VITE_OPENPANEL_SCRIPT_URL as string | undefined)?.trim() + +// شیم صف OpenPanel — تا رسیدن op1.js، فراخوانی‌ها در صف می‌مانند و بعد تخلیه می‌شوند. +function loadOpenPanelScript(src: string): void { + if (document.querySelector('script[data-openpanel]')) return + const shim = document.createElement('script') + shim.textContent = + 'window.op=window.op||function(){var n=[];return new Proxy(function(){arguments.length&&n.push([].slice.call(arguments))},{get:function(t,r){return"q"===r?n:function(){n.push([r].concat([].slice.call(arguments)))}},has:function(t,r){return"q"===r}}) }();' + document.head.appendChild(shim) + const script = document.createElement('script') + script.src = src + script.async = true + script.defer = true + script.dataset.openpanel = '1' + document.head.appendChild(script) +} + +/** یک‌بار در شروع اپ صدا زده می‌شود؛ بدون تنظیمات، هیچ کاری نمی‌کند. */ +export function initAnalytics(): void { + if (!OP_URL || !OP_CLIENT_ID) return try { - window.umami?.track(event, data) + if (OP_SCRIPT_URL) loadOpenPanelScript(OP_SCRIPT_URL) + window.op?.('init', { + apiUrl: OP_URL, + clientId: OP_CLIENT_ID, + trackScreenViews: true, // بازدید صفحه → کدام صفحات دیده می‌شوند + }) } catch { // تحلیل رفتار هرگز نباید مسیر اصلی را بشکند } } + +/** رخداد سفارشی — در داشبورد OpenPanel نمایش داده می‌شود. */ +export function track(event: string, data?: Record) { + try { + window.op?.('track', event, data ?? {}) + } catch { + /* ignore */ + } + try { + window.umami?.track(event, data) + } catch { + /* ignore */ + } +} diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx index cf28de2..af94cbd 100644 --- a/frontend/src/index.tsx +++ b/frontend/src/index.tsx @@ -2,6 +2,9 @@ import "./index.css"; import React from "react"; import ReactDOM from "react-dom/client"; import { App } from "./App"; +import { initAnalytics } from "./analytics"; + +initAnalytics(); const rootEl = document.getElementById("root"); if (rootEl) {