feat: redesign routed ZarinPal merchant dashboard
This commit is contained in:
115
src/lib/components/PaymentHistoryChart.svelte
Normal file
115
src/lib/components/PaymentHistoryChart.svelte
Normal file
@@ -0,0 +1,115 @@
|
||||
<script lang="ts">
|
||||
import type { components } from '$lib/api/schema';
|
||||
import { formatMoney, formatValue } from '$lib/format';
|
||||
|
||||
type Point = components['schemas']['PaymentHistoryPoint'];
|
||||
let { data }: { data: Point[] } = $props();
|
||||
let active = $state<number | null>(null);
|
||||
|
||||
const width = 760;
|
||||
const height = 250;
|
||||
const pad = { x: 34, y: 28 };
|
||||
const max = $derived(Math.max(1, ...data.map((point) => point.bankPaidSessions)));
|
||||
const positions = $derived(
|
||||
data.map((point, index) => ({
|
||||
...point,
|
||||
x: pad.x + (index * (width - pad.x * 2)) / Math.max(1, data.length - 1),
|
||||
y: height - pad.y - (point.bankPaidSessions / max) * (height - pad.y * 2),
|
||||
})),
|
||||
);
|
||||
const path = $derived(
|
||||
positions.map((point, index) => `${index ? 'L' : 'M'} ${point.x} ${point.y}`).join(' '),
|
||||
);
|
||||
|
||||
function persianDate(value: string) {
|
||||
return new Intl.DateTimeFormat('fa-IR-u-ca-persian', { month: 'short', day: 'numeric' }).format(
|
||||
new Date(`${value}T12:00:00Z`),
|
||||
);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="relative min-h-[260px] 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-[250px] 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}
|
||||
<line
|
||||
x1={pad.x}
|
||||
x2={width - pad.x}
|
||||
y1={pad.y + (row * (height - pad.y * 2)) / 4}
|
||||
y2={pad.y + (row * (height - pad.y * 2)) / 4}
|
||||
stroke="var(--border)"
|
||||
stroke-dasharray="4 6"
|
||||
/>
|
||||
{/each}
|
||||
<path
|
||||
d={`${path} L ${positions.at(-1)?.x} ${height - pad.y} L ${positions[0]?.x} ${height - pad.y} 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)"
|
||||
stroke="var(--primary)"
|
||||
stroke-width="3"
|
||||
tabindex="0"
|
||||
role="button"
|
||||
aria-label={`${persianDate(point.localDate)}، ${formatValue(point.bankPaidSessions)} پرداخت موفق`}
|
||||
onmouseenter={() => (active = index)}
|
||||
onmouseleave={() => (active = null)}
|
||||
onfocus={() => (active = index)}
|
||||
onblur={() => (active = null)}
|
||||
/>
|
||||
{/each}
|
||||
</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)}%`}
|
||||
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>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.chart-line {
|
||||
stroke-dasharray: 1800;
|
||||
stroke-dashoffset: 1800;
|
||||
animation: draw-line 900ms cubic-bezier(0.22, 1, 0.36, 1) forwards;
|
||||
}
|
||||
@keyframes draw-line {
|
||||
to {
|
||||
stroke-dashoffset: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user