fix: preserve chart width on mobile

This commit is contained in:
2026-08-21 16:46:42 +08:00
parent 7b8ee07b91
commit e3ead133f4
2 changed files with 120 additions and 107 deletions

View File

@@ -71,131 +71,140 @@
}
</script>
<div class="relative min-h-[290px] w-full overflow-hidden" dir="ltr">
{#if data.length === 0}
<div class="text-muted-foreground grid h-[260px] place-items-center text-sm">
داده‌ای برای این بازه ثبت نشده است.
</div>
{:else}
<svg
viewBox={`0 0 ${width} ${height}`}
class="h-auto min-h-[280px] w-full overflow-visible"
role="img"
aria-label="روند پرداخت‌های موفق"
>
<defs>
<linearGradient id="payment-area" x1="0" x2="0" y1="0" y2="1">
<stop offset="0" stop-color="var(--primary)" stop-opacity=".22" />
<stop offset="1" stop-color="var(--primary)" stop-opacity="0" />
</linearGradient>
</defs>
{#each [0, 1, 2, 3, 4] as row}
{@const y = pad.top + (row * (height - pad.top - pad.bottom)) / 4}
<!-- svelte-ignore a11y_no_noninteractive_tabindex (the horizontally scrollable chart must be keyboard reachable) -->
<div
class="w-full overflow-x-auto overscroll-x-contain"
dir="ltr"
role="region"
aria-label="نمودار روند پرداخت‌های موفق، قابل پیمایش افقی"
tabindex="0"
>
<div class="relative min-h-[290px] min-w-[760px] md:min-w-0">
{#if data.length === 0}
<div class="text-muted-foreground grid h-[260px] place-items-center text-sm">
داده‌ای برای این بازه ثبت نشده است.
</div>
{:else}
<svg
viewBox={`0 0 ${width} ${height}`}
class="h-auto min-h-[280px] w-full overflow-visible"
role="img"
aria-label="روند پرداخت‌های موفق"
>
<defs>
<linearGradient id="payment-area" x1="0" x2="0" y1="0" y2="1">
<stop offset="0" stop-color="var(--primary)" stop-opacity=".22" />
<stop offset="1" stop-color="var(--primary)" stop-opacity="0" />
</linearGradient>
</defs>
{#each [0, 1, 2, 3, 4] as row}
{@const y = pad.top + (row * (height - pad.top - pad.bottom)) / 4}
<line
x1={pad.left}
x2={width - pad.right}
y1={y}
y2={y}
stroke="var(--border)"
stroke-dasharray="4 6"
/>
<text x={pad.left - 10} y={y + 4} fill="var(--muted-foreground)" font-size="10" text-anchor="end"
>{formatValue(Math.round(max * (1 - row / 4)))}</text
>
{/each}
<line x1={pad.left} x2={pad.left} y1={pad.top} y2={height - pad.bottom} stroke="var(--border)" />
<line
x1={pad.left}
x2={width - pad.right}
y1={y}
y2={y}
stroke="var(--border)"
stroke-dasharray="4 6"
/>
<text x={pad.left - 10} y={y + 4} fill="var(--muted-foreground)" font-size="10" text-anchor="end"
>{formatValue(Math.round(max * (1 - row / 4)))}</text
>
{/each}
<line x1={pad.left} x2={pad.left} y1={pad.top} y2={height - pad.bottom} stroke="var(--border)" />
<line
x1={pad.left}
x2={width - pad.right}
y1={height - pad.bottom}
y2={height - pad.bottom}
stroke="var(--border)"
/>
{#each xTickIndexes as tickIndex}
{@const tick = positions[tickIndex]}
<line
x1={tick.x}
x2={tick.x}
y1={height - pad.bottom}
y2={height - pad.bottom + 5}
y2={height - pad.bottom}
stroke="var(--border)"
/>
{#each xTickIndexes as tickIndex}
{@const tick = positions[tickIndex]}
<line
x1={tick.x}
x2={tick.x}
y1={height - pad.bottom}
y2={height - pad.bottom + 5}
stroke="var(--border)"
/>
<text
x={tick.x}
y={height - pad.bottom + 18}
fill="var(--muted-foreground)"
font-size="9"
text-anchor="middle"
direction="rtl">{axisLabel(tick)}</text
>
{/each}
<text
x={tick.x}
y={height - pad.bottom + 18}
x={(pad.left + width - pad.right) / 2}
y={height - 5}
fill="var(--muted-foreground)"
font-size="9"
font-size="10"
text-anchor="middle"
direction="rtl">{axisLabel(tick)}</text
direction="rtl">{periodType === 'DAY' ? 'ساعت' : 'تاریخ'}</text
>
{/each}
<text
x={(pad.left + width - pad.right) / 2}
y={height - 5}
fill="var(--muted-foreground)"
font-size="10"
text-anchor="middle"
direction="rtl">{periodType === 'DAY' ? 'ساعت' : 'تاریخ'}</text
>
<text
x={-((pad.top + height - pad.bottom) / 2)}
y="13"
fill="var(--muted-foreground)"
font-size="10"
text-anchor="middle"
transform="rotate(-90)"
direction="rtl">تعداد پرداخت موفق</text
>
<path
d={`${path} L ${positions.at(-1)?.x} ${height - pad.bottom} L ${positions[0]?.x} ${height - pad.bottom} Z`}
fill="url(#payment-area)"
/>
<path
d={path}
fill="none"
stroke="var(--primary)"
stroke-width="3"
stroke-linecap="round"
stroke-linejoin="round"
class="chart-line"
/>
{#each positions as point, index}
<circle
cx={point.x}
cy={point.y}
r={active === index ? 6 : 3.5}
fill="var(--card)"
<text
x={-((pad.top + height - pad.bottom) / 2)}
y="13"
fill="var(--muted-foreground)"
font-size="10"
text-anchor="middle"
transform="rotate(-90)"
direction="rtl">تعداد پرداخت موفق</text
>
<path
d={`${path} L ${positions.at(-1)?.x} ${height - pad.bottom} L ${positions[0]?.x} ${height - pad.bottom} Z`}
fill="url(#payment-area)"
/>
<path
d={path}
fill="none"
stroke="var(--primary)"
stroke-width="3"
tabindex="0"
role="button"
aria-label={`${fullPointLabel(point)}، ${formatValue(point.bankPaidSessions)} پرداخت موفق`}
onmouseenter={() => (active = index)}
onmouseleave={() => (active = null)}
onfocus={() => (active = index)}
onblur={() => (active = null)}
stroke-linecap="round"
stroke-linejoin="round"
class="chart-line"
/>
{/each}
</svg>
{#if active !== null && positions[active]}
<div
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"
>
{#each positions as point, index}
<circle
cx={point.x}
cy={point.y}
r={active === index ? 6 : 3.5}
fill="var(--card)"
stroke="var(--primary)"
stroke-width="3"
tabindex="0"
role="button"
aria-label={`${fullPointLabel(point)}، ${formatValue(point.bankPaidSessions)} پرداخت موفق`}
onmouseenter={() => (active = index)}
onmouseleave={() => (active = null)}
onfocus={() => (active = index)}
onblur={() => (active = null)}
/>
{/each}
</svg>
{#if active !== null && positions[active]}
<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 }}
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">{fullPointLabel(positions[active])}</strong>
<span class="block text-white/75"
>{formatValue(positions[active].bankPaidSessions)} پرداخت موفق</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 }}
>
<span class="block text-white/75">{formatMoney(positions[active].bankPaidAmountRial)}</span>
<strong class="block">{fullPointLabel(positions[active])}</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>
</div>
{/if}
{/if}
{/if}
</div>
</div>
<style>

View File

@@ -28,5 +28,9 @@ describe('PaymentHistoryChart', () => {
expect(screen.getByText('۰۰:۰۰')).not.toBeNull();
expect(screen.getByText('۱۲:۰۰')).not.toBeNull();
expect(screen.getByText('ساعت')).not.toBeNull();
const scrollRegion = screen.getByRole('region', { name: /قابل پیمایش افقی/ });
expect(scrollRegion.className).toContain('overflow-x-auto');
expect(scrollRegion.firstElementChild?.className).toContain('min-w-[760px]');
});
});