fix: scope page insights to visible periods

This commit is contained in:
2026-08-22 17:25:12 +08:00
parent 7524406e79
commit e7655262ac
4 changed files with 37 additions and 14 deletions

View File

@@ -1076,7 +1076,7 @@ export interface components {
};
AIInsightRefreshRequest: {
terminalId?: string;
/** @description Used only by the transactions module. */
/** @description Server-offered reporting period used to match the visible analytics run. */
periodId?: string;
};
AIInsightJob: {
@@ -2469,7 +2469,7 @@ export interface operations {
parameters: {
query?: {
terminalId?: string;
/** @description Used only by the transactions module to match the selected reporting period. */
/** @description Server-offered reporting period used to match the visible analytics run. */
periodId?: string;
};
header?: {

View File

@@ -1,6 +1,6 @@
import { render, screen, waitFor } from '@testing-library/svelte';
import { cleanup, render, screen, waitFor } from '@testing-library/svelte';
import { tick } from 'svelte';
import { describe, expect, it, vi } from 'vitest';
import { afterEach, describe, expect, it, vi } from 'vitest';
import type { AIInsight } from '$lib/api/client';
import AIInsightPanel from './AIInsightPanel.svelte';
@@ -57,6 +57,8 @@ const insight: AIInsight = {
generatedAt: '2026-08-22T00:00:00Z',
};
afterEach(cleanup);
describe('AIInsightPanel', () => {
it('leaves the skeleton after loading an insight and does not fetch it again', async () => {
latestAIInsight.mockReset();
@@ -64,24 +66,33 @@ describe('AIInsightPanel', () => {
render(AIInsightPanel, { module: 'overview' });
await screen.findByText('فروش در این بازه رشد کرده است.');
await screen.findByText('آخرین روز ثبت‌شده باثبات بوده است.');
await tick();
await waitFor(() => expect(screen.queryByLabelText('در حال دریافت تحلیل هوشمند')).toBeNull());
expect(latestAIInsight).toHaveBeenCalledTimes(1);
expect(screen.getByText('فروش در این بازه رشد کرده است.')).not.toBeNull();
expect(screen.queryByText('روند کوتاه‌مدت باثبات پیش‌بینی می‌شود.')).toBeNull();
expect(screen.queryByText('یافته‌ها')).toBeNull();
});
it('loads a transaction insight with the resolved period scope', async () => {
it('loads every page insight with the resolved period scope', async () => {
latestAIInsight.mockReset();
latestAIInsight.mockResolvedValueOnce({
...insight,
module: 'transactions',
module: 'overview',
scope: { terminalId: 'T83', periodId: 'period-7' },
});
render(AIInsightPanel, { module: 'transactions', terminalId: 'T83', periodId: 'period-7' });
render(AIInsightPanel, {
module: 'overview',
terminalId: 'T83',
periodId: 'period-7',
periodLabel: 'مرداد ۱۴۰۵',
});
await screen.findByText('فروش در این بازه رشد کرده است.');
expect(latestAIInsight).toHaveBeenCalledWith('transactions', 'T83', 'period-7');
await screen.findByText('آخرین روز ثبت‌شده باثبات بوده است.');
expect(screen.getByText('مرداد ۱۴۰۵')).not.toBeNull();
expect(latestAIInsight).toHaveBeenCalledWith('overview', 'T83', 'period-7');
});
});

View File

@@ -120,8 +120,6 @@
<PageHeader title={titles[kind][0]} description={titles[kind][1]} icon={icons[kind]} />
<AIInsightPanel module={kind} terminalId={filters.terminal === 'all' ? undefined : filters.terminal} />
{#if loading}
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-4" role="status" aria-label="در حال محاسبه گزارش">
{#each Array(4) as _}<Skeleton class="h-40 rounded-lg" />{/each}
@@ -140,6 +138,13 @@
{@const fees = run.sections.fees}
{@const peers = run.sections.peerComparison}
<AIInsightPanel
module={kind}
terminalId={filters.terminal === 'all' ? undefined : filters.terminal}
periodId={run.period.periodId}
periodLabel={run.period.jalaliLabelFa}
/>
{#if kind === 'overview' && overview.state === 'READY'}
<div class="kpi-grid grid gap-3 sm:gap-4">
<KpiCard

View File

@@ -16,6 +16,7 @@
import { useDashboardContext } from '$lib/dashboard-context.svelte';
import { parseDashboardFilters } from '$lib/dashboard-filters';
import { formatPercent, formatValue } from '$lib/format';
import AIInsightPanel from './AIInsightPanel.svelte';
import KpiCard from './KpiCard.svelte';
import PageHeader from './PageHeader.svelte';
import TransactionStatusChart from './TransactionStatusChart.svelte';
@@ -79,6 +80,12 @@
</Card>
{:else if run}
{@const transactions = run.sections.transactions}
<AIInsightPanel
module="transactions"
terminalId={filters.terminal === 'all' ? undefined : filters.terminal}
periodId={run.period.periodId}
periodLabel={run.period.jalaliLabelFa}
/>
{#if transactions.state === 'READY'}
<div class="kpi-grid grid gap-3 sm:gap-4">
<KpiCard
@@ -146,8 +153,8 @@
><ChartColumnBigIcon class="text-primary size-5" /> روند ترکیب وضعیت‌ها</CardTitle
>
<p class="text-muted-foreground mt-2 max-w-2xl text-xs leading-6">
هر خط، تغییر تعداد یک وضعیت را در طول زمان نشان می‌دهد؛ جزئیات تعداد و سهم هر نقطه با
تمرکز صفحه‌کلید یا اشاره‌گر در دسترس است.
هر خط، تغییر تعداد یک وضعیت را در طول زمان نشان می‌دهد؛ جزئیات تعداد و سهم هر نقطه با تمرکز
صفحه‌کلید یا اشاره‌گر در دسترس است.
</p>
</div>
<Badge variant="secondary" class="rounded-md">{run.period.jalaliLabelFa}</Badge>