fix: polish dashboard loading and interactions

This commit is contained in:
2026-08-20 22:59:07 +03:30
parent b599cdfba4
commit 05e4dcd123
7 changed files with 48 additions and 26 deletions

View File

@@ -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>

View File

@@ -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,13 +82,15 @@
onselect={applyPeriod}
/>
{:else}
<PeriodPicker
periods={dashboard.periods}
range={pickerRange}
periodId={pickerRange === filters.range ? filters.period : ''}
bind:open={desktopPickerOpen}
onselect={applyPeriod}
/>
<div class="border-border border-s ps-5">
<PeriodPicker
periods={dashboard.periods}
range={pickerRange}
periodId={pickerRange === filters.range ? filters.period : ''}
bind:open={desktopPickerOpen}
onselect={applyPeriod}
/>
</div>
{/if}
</div>
{/snippet}

View File

@@ -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,13 +99,20 @@
</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"
>
<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">{formatMoney(positions[active].bankPaidAmountRial)}</span>
<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">{formatMoney(positions[active].bankPaidAmountRial)}</span>
</div>
</div>
{/if}
{/if}

View File

@@ -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'
? 'انتخاب ماه'
: 'انتخاب فصل'}

View File

@@ -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>