feat: redesign routed ZarinPal merchant dashboard

This commit is contained in:
2026-08-20 21:41:18 +03:30
parent d8eeff57cd
commit 9ce2e6ec83
183 changed files with 7991 additions and 1569 deletions

View File

@@ -56,12 +56,12 @@ If an implementation-facing API description later conflicts with an approved sou
## 4. Actors and session model
| Actor | Entry | Visible merchant scope | Switching behavior |
|---|---|---|---|
| Demo merchant user | Assigned username and password | Exactly the merchant bound to the authenticated server session | Log out, then sign in with another seeded merchant account |
| Merchant user | Same MVP login adapter | Exactly one merchant | Same as demo user until ZarinPal SSO replaces the adapter |
| Judge/operator | Uses separately distributed seeded credentials | No hidden operator or cross-tenant browser privilege | Uses explicit logout/login between demonstrations |
| Finance/legal recipient | Receives an authorized CSV outside the dashboard workflow | Only fields in the merchant-owned exported detail view | No dashboard access is implied by receiving a file |
| Actor | Entry | Visible merchant scope | Switching behavior |
| ----------------------- | --------------------------------------------------------- | -------------------------------------------------------------- | ---------------------------------------------------------- |
| Demo merchant user | Assigned username and password | Exactly the merchant bound to the authenticated server session | Log out, then sign in with another seeded merchant account |
| Merchant user | Same MVP login adapter | Exactly one merchant | Same as demo user until ZarinPal SSO replaces the adapter |
| Judge/operator | Uses separately distributed seeded credentials | No hidden operator or cross-tenant browser privilege | Uses explicit logout/login between demonstrations |
| Finance/legal recipient | Receives an authorized CSV outside the dashboard workflow | Only fields in the merchant-owned exported detail view | No dashboard access is implied by receiving a file |
The active merchant appears as identity context, not as a selector. The UI must not present a merchant dropdown that suggests the current session can change tenant scope.
@@ -69,18 +69,18 @@ The active merchant appears as identity context, not as a selector. The UI must
Technical route names are proposed for planning only; final route contracts remain subject to the approved interface and static-host fallback.
| Screen | Persian label | Primary task | Entry and exits |
|---|---|---|---|
| Login | ورود | Establish the demo merchant session | Entry from unauthenticated routes/session expiry; exit to overview |
| Overview | پیشخوان | Understand current outcomes and the most important qualifying changes | Default authenticated destination; links to focused analysis/detail |
| Payment performance | عملکرد پرداخت | Diagnose success, verification, failure, retry, and timing | From navigation/KPIs/insights; links to three approved detail views |
| Unverified-paid detail | پرداخت‌های تأییدنشده | Reconcile bank-paid sessions awaiting merchant verification | From verification card; CSV eligible |
| Failed-session detail | پرداخت‌های ناموفق | Review observed failure stages without invented causes | From failure card; CSV eligible |
| Retried-session detail | پرداخت‌های تکرارشده | Review attempt history and eventual recovery | From retry card; CSV eligible |
| Fees | کارمزد و درآمد | Understand real merchant fee amount and paid fee rate in Rial | From navigation/overview; no attempt-grain fee multiplication |
| Peer comparison | مقایسه با هم‌گروه‌ها | Compare approved metrics with eligible same-category merchants | Aggregate-only; never links to peer rows or peer export |
| Metric quality | کیفیت داده و شاخص‌ها | Understand definitions, periods, freshness, coverage, suppression, and limitations | From navigation and definition/provenance links |
| Account/session | حساب کاربری | Confirm current merchant identity, switch theme, or log out | No roles/profile editing in MVP |
| Screen | Persian label | Primary task | Entry and exits |
| ---------------------- | -------------------- | ---------------------------------------------------------------------------------- | ------------------------------------------------------------------- |
| Login | ورود | Establish the demo merchant session | Entry from unauthenticated routes/session expiry; exit to overview |
| Overview | پیشخوان | Understand current outcomes and the most important qualifying changes | Default authenticated destination; links to focused analysis/detail |
| Payment performance | عملکرد پرداخت | Diagnose success, verification, failure, retry, and timing | From navigation/KPIs/insights; links to three approved detail views |
| Unverified-paid detail | پرداخت‌های تأییدنشده | Reconcile bank-paid sessions awaiting merchant verification | From verification card; CSV eligible |
| Failed-session detail | پرداخت‌های ناموفق | Review observed failure stages without invented causes | From failure card; CSV eligible |
| Retried-session detail | پرداخت‌های تکرارشده | Review attempt history and eventual recovery | From retry card; CSV eligible |
| Fees | کارمزد و درآمد | Understand real merchant fee amount and paid fee rate in Rial | From navigation/overview; no attempt-grain fee multiplication |
| Peer comparison | مقایسه با هم‌گروه‌ها | Compare approved metrics with eligible same-category merchants | Aggregate-only; never links to peer rows or peer export |
| Metric quality | کیفیت داده و شاخص‌ها | Understand definitions, periods, freshness, coverage, suppression, and limitations | From navigation and definition/provenance links |
| Account/session | حساب کاربری | Confirm current merchant identity, switch theme, or log out | No roles/profile editing in MVP |
### 5.1 Navigation hierarchy
@@ -304,12 +304,12 @@ Example qualifying pattern: «نرخ تکمیل تأیید ۴٫۲ واحد در
#### Recommended visual forms
| Question | Primary form | Exact alternative |
|---|---|---|
| Question | Primary form | Exact alternative |
| ------------------------------------------- | ------------------------------------------------------------------------------------------------------- | ---------------------------------------------- |
| How are final session outcomes distributed? | Direct-labeled stacked bar; `Paid` and `Verified` visually distinct but grouped under bank-paid meaning | Semantic table of count, share, and definition |
| Is retry recovery changing? | Period trend line/columns with exact current rate and denominator | Period/value table |
| What are p50 and p95 times? | Paired dot/range plot per timing metric; no dual-axis chart | Table with p50, p95, sample, coverage, unit |
| Where did failed sessions observably reach? | Ordered horizontal bars for zero-attempt, reached-bank, and other observed stages | Stage/count table with caveat |
| Is retry recovery changing? | Period trend line/columns with exact current rate and denominator | Period/value table |
| What are p50 and p95 times? | Paired dot/range plot per timing metric; no dual-axis chart | Table with p50, p95, sample, coverage, unit |
| Where did failed sessions observably reach? | Ordered horizontal bars for zero-attempt, reached-bank, and other observed stages | Stage/count table with caveat |
`NoAttempt` means zero normalized attempts, not a failed attempt. `InBank` means an observed gateway stage, not bank rejection. Timing nulls are excluded, never displayed as zero. Reversed is outside the success numerator and appears separately when non-zero.
@@ -430,24 +430,24 @@ Overall progress is written as a contextual message such as «۲ از ۴ بخش
### 10.2 Section state matrix
| Contract/UI state | Visible content | Available actions | Accessibility behavior |
|---|---|---|---|
| Not requested | Section title and neutral placeholder only when the screen has not initiated that section | Apply valid filters/start access | No live announcement |
| Starting | Stable section frame; «در حال آماده‌سازی تحلیل…» | Controls that would duplicate start are busy/disabled | One overall polite status; `aria-busy` on affected region |
| Queued | Section title, period/unit context, layout-matched skeleton, «در صف محاسبه» | None | Status text remains available; skeleton is hidden from assistive tech |
| Calculating | Same stable frame, «در حال محاسبه…»; automatic backend retries remain in this state | None; no manual retry yet | Overall progress announces meaningful transitions, not animation frames |
| Ready | Exact KPI/takeaway/chart/table/trace metadata | Definition, approved drill-down/export | Focus does not move; one concise completion announcement when useful |
| Ready with no rows | Purpose-specific empty copy and period | Change period; no retry unless backend says failure | Announced as successful empty result, not error |
| Ready with unavailable comparison | Current exact metric plus «مبنای مقایسه در دسترس نیست» | Definition/change period | No trend icon, relative change, or insight claim |
| Ready with undefined rate | Numerator/denominator evidence and «نرخ قابل محاسبه نیست» | Definition/change period | Never spoken/displayed as zero |
| Suppressed | Peer-specific privacy/insufficient-data explanation; no peer values | Definition/change period | Status text explains that merchant data is unaffected |
| Failed after exhaustion | Section-local safe error; error/reference id only if approved and non-sensitive | «تلاش دوباره برای این بخش» | Error is associated with section heading; retry receives focus only if user initiated it |
| Manual retry active | Ready siblings unchanged; failed section returns to calculating with retry label | Retry disabled until terminal response | Polite status; no repeated countdown announcements |
| Rate-limited | Ready content retained; affected action explains wait and honors `Retry-After` | Retry becomes available only when permitted; no loop | Remaining time is not announced every second; announce initial limit and availability |
| Connection recovering | Ready content retained; unfinished sections show «در حال بازیابی اتصال…» | Optional explicit reconnect only after automatic strategy stops | One overall polite status; heartbeat events are silent |
| Replay expired | Ready content retained; «در حال همگام‌سازی آخرین وضعیت…» while current run snapshot loads | None during snapshot; safe retry on failure | Announce snapshot recovery outcome once |
| Unauthorized/session ended | Merchant data removed from active view and navigation returns to login | «ورود دوباره» | Focus lands on login heading; generic session-ended message |
| Forbidden/not found | No other-merchant existence detail; safe boundary message | Return to authorized overview | Page title and error are announced; no leaked identifier |
| Contract/UI state | Visible content | Available actions | Accessibility behavior |
| --------------------------------- | ----------------------------------------------------------------------------------------- | --------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| Not requested | Section title and neutral placeholder only when the screen has not initiated that section | Apply valid filters/start access | No live announcement |
| Starting | Stable section frame; «در حال آماده‌سازی تحلیل…» | Controls that would duplicate start are busy/disabled | One overall polite status; `aria-busy` on affected region |
| Queued | Section title, period/unit context, layout-matched skeleton, «در صف محاسبه» | None | Status text remains available; skeleton is hidden from assistive tech |
| Calculating | Same stable frame, «در حال محاسبه…»; automatic backend retries remain in this state | None; no manual retry yet | Overall progress announces meaningful transitions, not animation frames |
| Ready | Exact KPI/takeaway/chart/table/trace metadata | Definition, approved drill-down/export | Focus does not move; one concise completion announcement when useful |
| Ready with no rows | Purpose-specific empty copy and period | Change period; no retry unless backend says failure | Announced as successful empty result, not error |
| Ready with unavailable comparison | Current exact metric plus «مبنای مقایسه در دسترس نیست» | Definition/change period | No trend icon, relative change, or insight claim |
| Ready with undefined rate | Numerator/denominator evidence and «نرخ قابل محاسبه نیست» | Definition/change period | Never spoken/displayed as zero |
| Suppressed | Peer-specific privacy/insufficient-data explanation; no peer values | Definition/change period | Status text explains that merchant data is unaffected |
| Failed after exhaustion | Section-local safe error; error/reference id only if approved and non-sensitive | «تلاش دوباره برای این بخش» | Error is associated with section heading; retry receives focus only if user initiated it |
| Manual retry active | Ready siblings unchanged; failed section returns to calculating with retry label | Retry disabled until terminal response | Polite status; no repeated countdown announcements |
| Rate-limited | Ready content retained; affected action explains wait and honors `Retry-After` | Retry becomes available only when permitted; no loop | Remaining time is not announced every second; announce initial limit and availability |
| Connection recovering | Ready content retained; unfinished sections show «در حال بازیابی اتصال…» | Optional explicit reconnect only after automatic strategy stops | One overall polite status; heartbeat events are silent |
| Replay expired | Ready content retained; «در حال همگام‌سازی آخرین وضعیت…» while current run snapshot loads | None during snapshot; safe retry on failure | Announce snapshot recovery outcome once |
| Unauthorized/session ended | Merchant data removed from active view and navigation returns to login | «ورود دوباره» | Focus lands on login heading; generic session-ended message |
| Forbidden/not found | No other-merchant existence detail; safe boundary message | Return to authorized overview | Page title and error are announced; no leaked identifier |
### 10.3 Stale/freshness language
@@ -499,17 +499,17 @@ Chart rules:
### 11.3 Status vocabulary
| Data meaning | Persian display | Prohibited interpretation |
|---|---|---|
| `Verified` | تأییدشده | — |
| `Paid` | پرداخت‌شده، در انتظار تأیید پذیرنده | ناموفق |
| `Failed` | ناموفق | A specific cause unless the source proves it |
| `Reversed` | برگشت‌خورده | Successful/finalized |
| `NoAttempt` | بدون تلاش پرداخت | A failed attempt |
| `InBank` | رسیده به درگاه بانکی | ردشده توسط بانک |
| Undefined rate | نرخ قابل محاسبه نیست | `۰٪` |
| No rows | داده‌ای در این بازه وجود ندارد | System failure |
| Peer suppression | داده هم‌گروه کافی نیست | Merchant underperformance |
| Data meaning | Persian display | Prohibited interpretation |
| ---------------- | ----------------------------------- | -------------------------------------------- |
| `Verified` | تأییدشده | — |
| `Paid` | پرداخت‌شده، در انتظار تأیید پذیرنده | ناموفق |
| `Failed` | ناموفق | A specific cause unless the source proves it |
| `Reversed` | برگشت‌خورده | Successful/finalized |
| `NoAttempt` | بدون تلاش پرداخت | A failed attempt |
| `InBank` | رسیده به درگاه بانکی | ردشده توسط بانک |
| Undefined rate | نرخ قابل محاسبه نیست | `۰٪` |
| No rows | داده‌ای در این بازه وجود ندارد | System failure |
| Peer suppression | داده هم‌گروه کافی نیست | Merchant underperformance |
## 12. Persian RTL content and formatting
@@ -587,18 +587,18 @@ This is a scope confirmation, not a destructive confirmation. Focus returns to t
### 14.3 Generation and download states
| State | UX behavior |
|---|---|
| Ready to request | Show scope summary; generation action enabled |
| Requesting | «در حال ثبت درخواست…»; prevent duplicate request |
| Generating | Keep detail table usable; show «فایل در حال آماده‌سازی است…» with period/unit; do not imply browser-side row assembly |
| Ready | Show «فایل آماده است» plus explicit «دانلود CSV»; download is tied to the authorized artifact/response |
| Empty | «برای فیلترهای انتخاب‌شده ردیفی برای خروجی وجود ندارد.»; no blank misleading file unless interface explicitly returns one |
| Rate-limited | Preserve table and generation context; honor `Retry-After`; no retry loop |
| Failed/recoverable | Safe reason category and «تلاش دوباره» for the same approved scope |
| Session ended | Cancel access to artifact, remove ready download action, return to login; re-login does not silently resume old download |
| Forbidden/not found | Do not confirm another merchant/artifact exists; return to authorized detail context |
| Filters changed | Invalidate the prior preflight scope; require a new explicit generation request for current filters |
| State | UX behavior |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| Ready to request | Show scope summary; generation action enabled |
| Requesting | «در حال ثبت درخواست…»; prevent duplicate request |
| Generating | Keep detail table usable; show «فایل در حال آماده‌سازی است…» with period/unit; do not imply browser-side row assembly |
| Ready | Show «فایل آماده است» plus explicit «دانلود CSV»; download is tied to the authorized artifact/response |
| Empty | «برای فیلترهای انتخاب‌شده ردیفی برای خروجی وجود ندارد.»; no blank misleading file unless interface explicitly returns one |
| Rate-limited | Preserve table and generation context; honor `Retry-After`; no retry loop |
| Failed/recoverable | Safe reason category and «تلاش دوباره» for the same approved scope |
| Session ended | Cancel access to artifact, remove ready download action, return to login; re-login does not silently resume old download |
| Forbidden/not found | Do not confirm another merchant/artifact exists; return to authorized detail context |
| Filters changed | Invalidate the prior preflight scope; require a new explicit generation request for current filters |
### 14.4 Export content boundary
@@ -668,18 +668,18 @@ Normative basis: [WCAG 2.2](https://www.w3.org/TR/WCAG22/). The focus-obscuring
## 16. Responsive behavior matrix
| Concern | Mobile <768 px | Tablet 768–1023 px | Desktop ≥1024 px |
|---|---|---|---|
| Navigation | Right drawer; compact header | Right drawer | Fixed right sidebar |
| Main gutter | 16 px | 20–24 px | 24–32 px |
| KPI layout | One column | One or two by content fit | Four headline cards in one row when space permits |
| Filter bar | Disclosure/full-width controls; explicit Apply | Two rows | One quiet row where content fits |
| Charts | ≥240 px; exact value first; table toggle | Full width or two-column by fit | 8/4 or full 12-column |
| Detail table | Priority row summary + disclosure; optional labeled horizontal table | Priority columns; controlled horizontal scroll | Full semantic table |
| Export preflight | Full-screen dialog/sheet respecting safe areas | Modal/inline panel | Modal/inline panel near action |
| Insights | Stack after relevant KPI | Stack or companion column | Companion 4-column panel where useful |
| Merchant identity | Two-line compact context | Compact context | Full sidebar block |
| Theme control | Accessible icon+name in drawer/header | Header/drawer | Utility area |
| Concern | Mobile <768 px | Tablet 768–1023 px | Desktop ≥1024 px |
| ----------------- | -------------------------------------------------------------------- | ---------------------------------------------- | ------------------------------------------------- |
| Navigation | Right drawer; compact header | Right drawer | Fixed right sidebar |
| Main gutter | 16 px | 20–24 px | 24–32 px |
| KPI layout | One column | One or two by content fit | Four headline cards in one row when space permits |
| Filter bar | Disclosure/full-width controls; explicit Apply | Two rows | One quiet row where content fits |
| Charts | ≥240 px; exact value first; table toggle | Full width or two-column by fit | 8/4 or full 12-column |
| Detail table | Priority row summary + disclosure; optional labeled horizontal table | Priority columns; controlled horizontal scroll | Full semantic table |
| Export preflight | Full-screen dialog/sheet respecting safe areas | Modal/inline panel | Modal/inline panel near action |
| Insights | Stack after relevant KPI | Stack or companion column | Companion 4-column panel where useful |
| Merchant identity | Two-line compact context | Compact context | Full sidebar block |
| Theme control | Accessible icon+name in drawer/header | Header/drawer | Utility area |
Acceptance widths: 360 px, 768 px, 1024 px, and 1440 px; include 320 CSS px reflow/zoom verification.
@@ -687,21 +687,21 @@ Acceptance widths: 360 px, 768 px, 1024 px, and 1440 px; include 320 CSS px refl
All strings are complete messages; variable insertion must preserve Persian grammar and bidi isolation.
| Situation | Approved pattern |
|---|---|
| Overall progress | «{آماده} از ۴ بخش تحلیل آماده است.» |
| Calculating | «این بخش در حال محاسبه است…» |
| Recovering SSE | «ارتباط با تحلیل قطع شد؛ در حال بازیابی اتصال…» |
| Snapshot fallback | «در حال همگام‌سازی آخرین وضعیت تحلیل…» |
| Section failure | «محاسبه این بخش انجام نشد. می‌توانید فقط همین بخش را دوباره اجرا کنید.» |
| Rate limited | «درخواست‌های زیادی ثبت شده است. پس از {زمان} دوباره تلاش کنید.» |
| Baseline unavailable | «مبنای مقایسه برای این بازه در دسترس نیست.» |
| Undefined rate | «به‌دلیل نبود مخرج معتبر، این نرخ قابل محاسبه نیست.» |
| Empty period | «در بازه انتخاب‌شده داده‌ای برای این شاخص وجود ندارد.» |
| Session ended | «نشست شما پایان یافته است. برای ادامه دوباره وارد شوید.» |
| Safe forbidden | «به این اطلاعات دسترسی ندارید. به پیشخوان حساب خود برگردید.» |
| Export ready | «فایل CSV برای بازه انتخاب‌شده آماده است.» |
| Export failed | «ساخت فایل انجام نشد. می‌توانید همین درخواست را دوباره ثبت کنید.» |
| Situation | Approved pattern |
| -------------------- | ----------------------------------------------------------------------- |
| Overall progress | «{آماده} از ۴ بخش تحلیل آماده است.» |
| Calculating | «این بخش در حال محاسبه است…» |
| Recovering SSE | «ارتباط با تحلیل قطع شد؛ در حال بازیابی اتصال…» |
| Snapshot fallback | «در حال همگام‌سازی آخرین وضعیت تحلیل…» |
| Section failure | «محاسبه این بخش انجام نشد. می‌توانید فقط همین بخش را دوباره اجرا کنید.» |
| Rate limited | «درخواست‌های زیادی ثبت شده است. پس از {زمان} دوباره تلاش کنید.» |
| Baseline unavailable | «مبنای مقایسه برای این بازه در دسترس نیست.» |
| Undefined rate | «به‌دلیل نبود مخرج معتبر، این نرخ قابل محاسبه نیست.» |
| Empty period | «در بازه انتخاب‌شده داده‌ای برای این شاخص وجود ندارد.» |
| Session ended | «نشست شما پایان یافته است. برای ادامه دوباره وارد شوید.» |
| Safe forbidden | «به این اطلاعات دسترسی ندارید. به پیشخوان حساب خود برگردید.» |
| Export ready | «فایل CSV برای بازه انتخاب‌شده آماده است.» |
| Export failed | «ساخت فایل انجام نشد. می‌توانید همین درخواست را دوباره ثبت کنید.» |
Persian digits are applied to rendered variable values. Technical identifiers inside an error/support reference remain Latin and isolated LTR.