feat: show hourly labels for daily charts
This commit is contained in:
2
src/lib/api/schema.d.ts
vendored
2
src/lib/api/schema.d.ts
vendored
@@ -723,6 +723,8 @@ export interface components {
|
||||
PaymentHistoryPoint: {
|
||||
/** Format: date */
|
||||
localDate: string;
|
||||
/** @description Tehran-local hour bucket, present only for DAY periods. */
|
||||
localHour?: number;
|
||||
/** Format: int64 */
|
||||
totalSessions: number;
|
||||
/** Format: int64 */
|
||||
|
||||
@@ -177,14 +177,18 @@
|
||||
><ChartNoAxesCombinedIcon class="text-primary size-5" /> روند پرداختهای موفق</CardTitle
|
||||
>
|
||||
<p class="text-muted-foreground mt-1 text-xs">
|
||||
برای دیدن مبلغ و تعداد هر روز، روی نقطههای نمودار بروید.
|
||||
برای دیدن مبلغ و تعداد هر {run.period.periodType === 'DAY' ? 'ساعت' : 'روز'}، روی نقطههای
|
||||
نمودار بروید.
|
||||
</p>
|
||||
</div>
|
||||
<Badge variant="secondary" class="rounded-md">{run.period.jalaliLabelFa}</Badge>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent class="px-3 pt-5 sm:px-6"
|
||||
><PaymentHistoryChart data={overview.data.paymentHistory} /></CardContent
|
||||
><PaymentHistoryChart
|
||||
data={overview.data.paymentHistory}
|
||||
periodType={run.period.periodType}
|
||||
/></CardContent
|
||||
>
|
||||
</Card>
|
||||
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
import { formatMoney, formatValue } from '$lib/format';
|
||||
|
||||
type Point = components['schemas']['PaymentHistoryPoint'];
|
||||
let { data }: { data: Point[] } = $props();
|
||||
type PeriodType = components['schemas']['PeriodType'];
|
||||
let { data, periodType }: { data: Point[]; periodType: PeriodType } = $props();
|
||||
let active = $state<number | null>(null);
|
||||
|
||||
const width = 760;
|
||||
@@ -40,6 +41,27 @@
|
||||
);
|
||||
}
|
||||
|
||||
function persianHour(value: number) {
|
||||
const hour = new Intl.NumberFormat('fa-IR', {
|
||||
minimumIntegerDigits: 2,
|
||||
maximumFractionDigits: 0,
|
||||
useGrouping: false,
|
||||
}).format(value);
|
||||
return `${hour}:۰۰`;
|
||||
}
|
||||
|
||||
function axisLabel(point: Point) {
|
||||
return periodType === 'DAY' && point.localHour !== undefined
|
||||
? persianHour(point.localHour)
|
||||
: persianDate(point.localDate);
|
||||
}
|
||||
|
||||
function fullPointLabel(point: Point) {
|
||||
return periodType === 'DAY' && point.localHour !== undefined
|
||||
? `${persianDate(point.localDate)}، ساعت ${persianHour(point.localHour)}`
|
||||
: persianDate(point.localDate);
|
||||
}
|
||||
|
||||
function tooltipPosition(point: (typeof positions)[number]) {
|
||||
const horizontalPercent = (point.x / width) * 100;
|
||||
const verticalPercent = (point.y / height) * 100;
|
||||
@@ -104,7 +126,7 @@
|
||||
fill="var(--muted-foreground)"
|
||||
font-size="9"
|
||||
text-anchor="middle"
|
||||
direction="rtl">{persianDate(tick.localDate)}</text
|
||||
direction="rtl">{axisLabel(tick)}</text
|
||||
>
|
||||
{/each}
|
||||
<text
|
||||
@@ -113,7 +135,7 @@
|
||||
fill="var(--muted-foreground)"
|
||||
font-size="10"
|
||||
text-anchor="middle"
|
||||
direction="rtl">تاریخ</text
|
||||
direction="rtl">{periodType === 'DAY' ? 'ساعت' : 'تاریخ'}</text
|
||||
>
|
||||
<text
|
||||
x={-((pad.top + height - pad.bottom) / 2)}
|
||||
@@ -147,7 +169,7 @@
|
||||
stroke-width="3"
|
||||
tabindex="0"
|
||||
role="button"
|
||||
aria-label={`${persianDate(point.localDate)}، ${formatValue(point.bankPaidSessions)} پرداخت موفق`}
|
||||
aria-label={`${fullPointLabel(point)}، ${formatValue(point.bankPaidSessions)} پرداخت موفق`}
|
||||
onmouseenter={() => (active = index)}
|
||||
onmouseleave={() => (active = null)}
|
||||
onfocus={() => (active = index)}
|
||||
@@ -165,7 +187,7 @@
|
||||
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>
|
||||
<strong class="block">{fullPointLabel(positions[active])}</strong>
|
||||
<span class="block text-white/75"
|
||||
>{formatValue(positions[active].bankPaidSessions)} پرداخت موفق</span
|
||||
>
|
||||
|
||||
32
src/lib/components/PaymentHistoryChart.test.ts
Normal file
32
src/lib/components/PaymentHistoryChart.test.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { render, screen } from '@testing-library/svelte';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import PaymentHistoryChart from './PaymentHistoryChart.svelte';
|
||||
|
||||
describe('PaymentHistoryChart', () => {
|
||||
it('uses Persian hour labels for a one-day period', () => {
|
||||
render(PaymentHistoryChart, {
|
||||
periodType: 'DAY',
|
||||
data: [
|
||||
{
|
||||
localDate: '2026-07-01',
|
||||
localHour: 0,
|
||||
totalSessions: 10,
|
||||
bankPaidSessions: 8,
|
||||
bankPaidAmountRial: 800_000,
|
||||
},
|
||||
{
|
||||
localDate: '2026-07-01',
|
||||
localHour: 12,
|
||||
totalSessions: 14,
|
||||
bankPaidSessions: 11,
|
||||
bankPaidAmountRial: 1_100_000,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(screen.getByText('۰۰:۰۰')).not.toBeNull();
|
||||
expect(screen.getByText('۱۲:۰۰')).not.toBeNull();
|
||||
expect(screen.getByText('ساعت')).not.toBeNull();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user