fix: polish dashboard loading and interactions
This commit is contained in:
@@ -132,6 +132,7 @@ The approved Persian-first baseline remains authoritative where the shipped arti
|
||||
|
||||
| Version | Date | Decision |
|
||||
| ------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| 0.8.0 | 2026-08-20 | Increased light-theme skeleton contrast, constrained and animated chart tooltips, made default period choices explicitly applicable, hid collapsed-sidebar labels, and strengthened the separation between period type and exact-period controls. |
|
||||
| 0.7.0 | 2026-08-20 | Made period changes confirm-on-selection, moved narrow-screen filters into a bottom sheet, enlarged collapsed navigation targets, simplified focus rings, and promoted the fee-share chart into a dedicated visual column. |
|
||||
| 0.6.0 | 2026-08-20 | Added full-span Jalali period selection, unified transaction status/amount controls, persistent skeleton states, custom RTL dropdowns, semantic peer/retry tones, and fee-share visualization; tightened the narrow navbar and 600px KPI breakpoint. |
|
||||
| 0.5.0 | 2026-08-20 | Reconciled the approved baseline with the shipped routed frontend and desktop, mobile, and 639px review captures; converted the file to canonical DESIGN.md format and added the design sidecar. |
|
||||
@@ -288,7 +289,7 @@ Scope changes immediately replace data-dependent cards, tables, detail panels, a
|
||||
|
||||
### Payment-History Chart
|
||||
|
||||
The signature chart is a blue line with outlined points, a low-opacity blue area, and dashed neutral gridlines. It follows a written title and reporting-period chip, provides focusable points with spoken values, and animates its initial trace once over 900ms while reduced-motion collapses the effect.
|
||||
The signature chart is a blue line with outlined points, a low-opacity blue area, and dashed neutral gridlines. It follows a written title and reporting-period chip, provides focusable points with spoken values, and animates its initial trace once over 900ms while reduced-motion collapses the effect. Point tooltips use a short scale/fade transition, flip above or below according to vertical room, and clamp their center so the surface never leaves the chart wrapper.
|
||||
|
||||
### Transaction Table
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
--text: #19191a;
|
||||
--text-secondary: #3d3d42;
|
||||
--muted: #f3f4f6;
|
||||
--skeleton: #e1e3e8;
|
||||
--border: #e4e4e7;
|
||||
--brand: #0a33ff;
|
||||
--brand-hover: #0828cc;
|
||||
@@ -87,6 +88,7 @@
|
||||
--secondary: #27272a;
|
||||
--secondary-foreground: #fafafa;
|
||||
--muted: #27272a;
|
||||
--skeleton: #323237;
|
||||
--muted-foreground: #a1a1aa;
|
||||
--accent: #202950;
|
||||
--accent-foreground: #a9baff;
|
||||
@@ -244,6 +246,7 @@ select {
|
||||
--color-accent: var(--accent);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
--color-muted: var(--muted);
|
||||
--color-skeleton: var(--skeleton);
|
||||
--color-secondary-foreground: var(--secondary-foreground);
|
||||
--color-secondary: var(--secondary);
|
||||
--color-primary-foreground: var(--primary-foreground);
|
||||
|
||||
@@ -100,7 +100,7 @@
|
||||
<Sidebar.MenuButton
|
||||
isActive={isActive(item.href)}
|
||||
tooltipContent={item.label}
|
||||
class="data-[active=true]:text-primary h-11 justify-start px-3 text-[13px] group-data-[collapsible=icon]:size-11! group-data-[collapsible=icon]:justify-center group-data-[collapsible=icon]:p-0! group-data-[collapsible=icon]:[&_svg]:size-[21px]!"
|
||||
class="data-[active=true]:text-primary h-11 justify-start px-3 text-[13px] group-data-[collapsible=icon]:size-11! group-data-[collapsible=icon]:justify-center group-data-[collapsible=icon]:p-0! group-data-[collapsible=icon]:[&_svg]:size-[21px]! group-data-[collapsible=icon]:[&>span]:hidden"
|
||||
>
|
||||
{#snippet child({ props })}
|
||||
<a
|
||||
@@ -109,7 +109,7 @@
|
||||
aria-current={isActive(item.href) ? 'page' : undefined}
|
||||
>
|
||||
<item.icon class="size-[19px]" strokeWidth={1.8} />
|
||||
<span>{item.label}</span>
|
||||
<span class="group-data-[collapsible=icon]:hidden">{item.label}</span>
|
||||
</a>
|
||||
{/snippet}
|
||||
</Sidebar.MenuButton>
|
||||
@@ -124,11 +124,11 @@
|
||||
<Sidebar.MenuItem>
|
||||
<Sidebar.MenuButton
|
||||
tooltipContent="خروج"
|
||||
class="h-11 px-3 group-data-[collapsible=icon]:size-11! group-data-[collapsible=icon]:justify-center group-data-[collapsible=icon]:p-0! group-data-[collapsible=icon]:[&_svg]:size-5!"
|
||||
class="h-11 px-3 group-data-[collapsible=icon]:size-11! group-data-[collapsible=icon]:justify-center group-data-[collapsible=icon]:p-0! group-data-[collapsible=icon]:[&_svg]:size-5! group-data-[collapsible=icon]:[&>span]:hidden"
|
||||
onclick={logout}
|
||||
>
|
||||
<LogOutIcon class="size-[18px]" />
|
||||
<span>خروج از حساب</span>
|
||||
<span class="group-data-[collapsible=icon]:hidden">خروج از حساب</span>
|
||||
</Sidebar.MenuButton>
|
||||
</Sidebar.MenuItem>
|
||||
</Sidebar.Menu>
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
</script>
|
||||
|
||||
{#snippet periodControls(mobile: boolean)}
|
||||
<div class={mobile ? 'grid gap-4' : 'flex min-w-0 items-center gap-3'}>
|
||||
<div class={mobile ? 'grid gap-4' : 'flex min-w-0 items-center gap-5'}>
|
||||
<div
|
||||
class={mobile ? 'grid grid-cols-2 gap-2' : 'flex max-w-full items-center gap-1 overflow-x-auto'}
|
||||
aria-label="بازه گزارش"
|
||||
@@ -82,6 +82,7 @@
|
||||
onselect={applyPeriod}
|
||||
/>
|
||||
{:else}
|
||||
<div class="border-border border-s ps-5">
|
||||
<PeriodPicker
|
||||
periods={dashboard.periods}
|
||||
range={pickerRange}
|
||||
@@ -89,6 +90,7 @@
|
||||
bind:open={desktopPickerOpen}
|
||||
onselect={applyPeriod}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/snippet}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { scale } from 'svelte/transition';
|
||||
|
||||
import type { components } from '$lib/api/schema';
|
||||
import { formatMoney, formatValue } from '$lib/format';
|
||||
|
||||
@@ -26,6 +28,14 @@
|
||||
new Date(`${value}T12:00:00Z`),
|
||||
);
|
||||
}
|
||||
|
||||
function tooltipPosition(point: (typeof positions)[number]) {
|
||||
const horizontalPercent = (point.x / width) * 100;
|
||||
const verticalPercent = (point.y / height) * 100;
|
||||
const placeBelow = verticalPercent < 34;
|
||||
const top = placeBelow ? verticalPercent + 5 : verticalPercent - 5;
|
||||
return `left:clamp(5.75rem, ${horizontalPercent}%, calc(100% - 5.75rem)); top:${top}%; transform:translate(-50%, ${placeBelow ? '0' : '-100%'});`;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="relative min-h-[260px] w-full overflow-hidden" dir="ltr">
|
||||
@@ -89,14 +99,21 @@
|
||||
</svg>
|
||||
{#if active !== null && positions[active]}
|
||||
<div
|
||||
class="pointer-events-none absolute z-10 w-44 -translate-x-1/2 rounded-md bg-zinc-950 p-3 text-right text-xs leading-6 text-white shadow-xl"
|
||||
style={`left:${(positions[active].x / width) * 100}%; top:${Math.max(0, (positions[active].y / height) * 100 - 8)}%`}
|
||||
class="pointer-events-none absolute z-10 w-44 transition-[left,top,transform] duration-150 ease-out motion-reduce:transition-none"
|
||||
style={tooltipPosition(positions[active])}
|
||||
dir="rtl"
|
||||
>
|
||||
<div
|
||||
class="rounded-md bg-zinc-950 p-3 text-right text-xs leading-6 text-white shadow-xl"
|
||||
transition:scale={{ duration: 150, start: 0.96 }}
|
||||
>
|
||||
<strong class="block">{persianDate(positions[active].localDate)}</strong>
|
||||
<span class="block text-white/75">{formatValue(positions[active].bankPaidSessions)} پرداخت موفق</span>
|
||||
<span class="block text-white/75"
|
||||
>{formatValue(positions[active].bankPaidSessions)} پرداخت موفق</span
|
||||
>
|
||||
<span class="block text-white/75">{formatMoney(positions[active].bankPaidAmountRial)}</span>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -55,9 +55,8 @@
|
||||
.filter((period) => period.periodType === periodTypeForRange(range) && period.isComplete)
|
||||
.sort((left, right) => right.localStartAt.localeCompare(left.localStartAt)),
|
||||
);
|
||||
const selectedPeriod = $derived(
|
||||
matchingPeriods.find((period) => period.periodId === periodId) ?? matchingPeriods[0],
|
||||
);
|
||||
const explicitPeriod = $derived(matchingPeriods.find((period) => period.periodId === periodId));
|
||||
const selectedPeriod = $derived(explicitPeriod ?? matchingPeriods[0]);
|
||||
const calendarGrid = $derived(
|
||||
!selectedYear || (range !== 'day' && range !== 'week')
|
||||
? []
|
||||
@@ -168,8 +167,8 @@
|
||||
<span>{range === 'month' ? 'ماه' : 'فصل'}</span>
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={offered.some((period) => period.periodId === selectedPeriod?.periodId)
|
||||
? selectedPeriod?.periodId
|
||||
value={explicitPeriod && offered.some((period) => period.periodId === explicitPeriod.periodId)
|
||||
? explicitPeriod.periodId
|
||||
: ''}
|
||||
onValueChange={(value) => {
|
||||
if (namedOptions.some((option) => option.period?.periodId === value)) choose(value);
|
||||
@@ -182,8 +181,8 @@
|
||||
>
|
||||
<Select.Trigger class="min-h-11 w-full cursor-pointer rounded-md px-3 text-sm sm:min-h-9">
|
||||
<span data-slot="select-value">
|
||||
{offered.find((period) => period.periodId === selectedPeriod?.periodId)
|
||||
? shortPeriodLabel(selectedPeriod)
|
||||
{explicitPeriod && offered.find((period) => period.periodId === explicitPeriod.periodId)
|
||||
? shortPeriodLabel(explicitPeriod)
|
||||
: range === 'month'
|
||||
? 'انتخاب ماه'
|
||||
: 'انتخاب فصل'}
|
||||
|
||||
@@ -12,6 +12,6 @@
|
||||
<div
|
||||
bind:this={ref}
|
||||
data-slot="skeleton"
|
||||
class={cn('bg-muted animate-pulse rounded-md', className)}
|
||||
class={cn('bg-skeleton ring-border/70 animate-pulse rounded-md ring-1', className)}
|
||||
{...restProps}
|
||||
></div>
|
||||
|
||||
Reference in New Issue
Block a user