From d8c7e7263c273e49e7ae75af10f1dccbaf7e79fb Mon Sep 17 00:00:00 2001 From: Amirhossein Khalili Date: Thu, 20 Aug 2026 19:47:33 +0330 Subject: [PATCH] feat: deliver Persian merchant analytics dashboard --- README.md | 23 ++ src/lib/components/Dashboard.svelte | 460 +++++++++++++++++++++++++ src/lib/components/DetailsPanel.svelte | 120 +++++++ src/lib/components/Login.svelte | 98 ++++++ src/lib/components/MetricCard.svelte | 20 ++ src/lib/components/SectionFrame.svelte | 51 +++ src/lib/components/ThemeToggle.svelte | 18 + src/routes/+page.svelte | 35 ++ 8 files changed, 825 insertions(+) create mode 100644 src/lib/components/Dashboard.svelte create mode 100644 src/lib/components/DetailsPanel.svelte create mode 100644 src/lib/components/Login.svelte create mode 100644 src/lib/components/MetricCard.svelte create mode 100644 src/lib/components/SectionFrame.svelte create mode 100644 src/lib/components/ThemeToggle.svelte create mode 100644 src/routes/+page.svelte diff --git a/README.md b/README.md index df99f68..be9fcf8 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,29 @@ Persian RTL merchant analytics panel built with SvelteKit 2, Svelte 5, strict Ty Current state: design, specifications, implementation plan, and task list are approved. Phase 4 frontend implementation is authorized. +## Local development + +The dashboard is a client-rendered static SvelteKit application. During development, Vite serves it at `http://127.0.0.1:5173` and proxies same-origin `/api` requests to the Go backend at `http://127.0.0.1:8080`. This preserves the backend-owned secure session model without storing credentials in browser storage. + +```powershell +Copy-Item .env.example .env +npm ci +npm run dev -- --host 127.0.0.1 +``` + +Open `http://127.0.0.1:5173`. The demo merchant credentials are created by the backend importer/bootstrap process; they are intentionally not stored in this repository. + +Quality commands: + +```powershell +npm run generate:api +npm run check +npm test +npm run build +``` + +`PUBLIC_API_BASE_URL` is public routing configuration, not a secret. Keep its default `/api/v1` for the local Vite proxy and the production same-origin Nginx proxy. + - [`DESIGN.md`](DESIGN.md): visual language, interaction rules, responsive behavior, and current design-system state. - [`docs/DASHBOARD-SPEC.md`](docs/DASHBOARD-SPEC.md): approved product requirements and acceptance criteria for implementation planning. - [`docs/DASHBOARD-UX-SPEC.md`](docs/DASHBOARD-UX-SPEC.md): detailed screen map, RTL layouts, async states, data presentation, accessibility, and authorized export workflows. diff --git a/src/lib/components/Dashboard.svelte b/src/lib/components/Dashboard.svelte new file mode 100644 index 0000000..eba5b49 --- /dev/null +++ b/src/lib/components/Dashboard.svelte @@ -0,0 +1,460 @@ + + +پیشخوان | تحلیل عملکرد زرین‌پال + +
+ {#if menuOpen}{/if} + + +
+
+ +
+

سلام، {session.merchant.displayName}

+ +
+
+ +
+ +
+
+
+
+

نمای کلی کسب‌وکار

+

عملکرد پرداخت‌های شما

+

شاخص‌های کلیدی، دقیق و قابل پیگیری در یک نگاه

+
+
+ +
+
+ + {#if error}{/if} + + {#if !run} +
+ {#each Array(4) as _}
{/each} +
+ {:else} + {@const section = run.sections.overview} + retry('overview')} + > + {#if section.state === 'READY'} +
+ + + + +
+ {/if} +
+ {/if} +
+ + {#if run} + {@const payment = run.sections.paymentPerformance} +
+ retry('payment_performance')} + > + {#if payment.state === 'READY'} +
+ + + + +
+
+
+

زمان آغاز پرداخت

+
+ میانه{formatMilliseconds(payment.data.initTiming.p50Ms)} +
+
+ صدک ۹۵{formatMilliseconds(payment.data.initTiming.p95Ms)} +
+
+
+

زمان تأیید

+
+ میانه{formatMilliseconds(payment.data.verifyTiming.p50Ms)} +
+
+ صدک ۹۵{formatMilliseconds(payment.data.verifyTiming.p95Ms)} +
+
+
+ {/if} +
+
+ + {@const fees = run.sections.fees} +
+ retry('fees')} + > + {#if fees.state === 'READY'}
+ +
{/if} +
+
+ + {@const peers = run.sections.peerComparison} +
+ retry('peer_comparison')} + > + {#if peers.state === 'SUPPRESSED'}
+

برای مقایسه امن، داده کافی نیست

+

+ تا رسیدن گروه به حداقل حریم خصوصی، هیچ مقدار یا هویت هم‌گروهی نمایش داده نمی‌شود. +

+
+ {:else if peers.state === 'READY'}
+
+ شاخصمیانه گروهصدک عملکرد شما +
+ {#each peers.data.metrics as metric}
+ {metric.metricId}{formatPercent(metric.cohortMedian)}{formatPercent(metric.merchantPerformancePercentile)} +
{/each} +

+ صدک بالاتر همیشه به‌معنای عملکرد بهتر است؛ شاخص‌های زمان و کارمزد جهت معکوس دارند. +

+
{/if} +
+
+ +
+

کیفیت داده و تعریف شاخص‌ها

+
+
+

مبنای گزارش

+

+ تصویر ثابت داده واردشده با شناسه {run.datasetSnapshotId} +

+
+
+

نسخه تعریف

+

+ {run.metricDefinitionVersion} · محاسبه در سطح نشست پرداخت +

+
+
+

آخرین محاسبه

+

{formatDate(run.updatedAt)}

+
+
+
+ {/if} +
+
+ +{#if details && run} (details = null)} + />{/if} diff --git a/src/lib/components/DetailsPanel.svelte b/src/lib/components/DetailsPanel.svelte new file mode 100644 index 0000000..ff4b025 --- /dev/null +++ b/src/lib/components/DetailsPanel.svelte @@ -0,0 +1,120 @@ + + + diff --git a/src/lib/components/Login.svelte b/src/lib/components/Login.svelte new file mode 100644 index 0000000..43c2d8f --- /dev/null +++ b/src/lib/components/Login.svelte @@ -0,0 +1,98 @@ + + +ورود | تحلیل عملکرد زرین‌پال + +
+ + +
+
+
+ ز + تحلیل عملکرد زرین‌پال +
+

خوش آمدید

+

ورود به پنل پذیرنده

+

برای مشاهده گزارش‌های کسب‌وکار، وارد حساب آزمایشی خود شوید.

+ +
+
+ + +
+
+ + +
+ {#if errorMessage} + + {/if} + +
+

این نسخه برای ارزیابی داده‌های آزمایشی ساخته شده است.

+
+
+
diff --git a/src/lib/components/MetricCard.svelte b/src/lib/components/MetricCard.svelte new file mode 100644 index 0000000..00a81ee --- /dev/null +++ b/src/lib/components/MetricCard.svelte @@ -0,0 +1,20 @@ + + +
+
+

{label}

+ +
+

{value}

+ {#if note}

{note}

{/if} +
diff --git a/src/lib/components/SectionFrame.svelte b/src/lib/components/SectionFrame.svelte new file mode 100644 index 0000000..2a91db6 --- /dev/null +++ b/src/lib/components/SectionFrame.svelte @@ -0,0 +1,51 @@ + + +
+
+
+

{title}

+

{description}

+
+ {#if state === 'READY'} + ✓ آماده + {:else if state === 'SUPPRESSED'} + اطلاعات ناکافی + {/if} +
+ + {#if state === 'FAILED'} + + {:else if state === 'QUEUED' || state === 'CALCULATING'} +
+ {#each Array(4) as _}
{/each} +
+ {:else} + {@render children()} + {/if} +
diff --git a/src/lib/components/ThemeToggle.svelte b/src/lib/components/ThemeToggle.svelte new file mode 100644 index 0000000..498fb86 --- /dev/null +++ b/src/lib/components/ThemeToggle.svelte @@ -0,0 +1,18 @@ + + + diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte new file mode 100644 index 0000000..5853ad7 --- /dev/null +++ b/src/routes/+page.svelte @@ -0,0 +1,35 @@ + + +{#if checking} +
+
+ +

در حال آماده‌سازی پنل…

+
+
+{:else if session} + (session = null)} /> +{:else} + (session = value)} /> +{/if}