fix: render completed growth reports reliably

This commit is contained in:
2026-08-21 07:49:04 +08:00
parent 71ea59248c
commit ef4e87f5b8
3 changed files with 41 additions and 11 deletions

View File

@@ -25,14 +25,19 @@
peer, peer,
terminalScoped = false, terminalScoped = false,
}: { }: {
wins?: Win[]; wins?: Win[] | null;
opportunities?: Opportunity[]; opportunities?: Opportunity[] | null;
risks?: Risk[]; risks?: Risk[] | null;
briefs?: Brief[]; briefs?: Brief[] | null;
peer?: Peer | null; peer?: Peer | null;
terminalScoped?: boolean; terminalScoped?: boolean;
} = $props(); } = $props();
const safeWins = $derived(wins ?? []);
const safeOpportunities = $derived(opportunities ?? []);
const safeRisks = $derived(risks ?? []);
const safeBriefs = $derived(briefs ?? []);
function dateLabel(value: string) { function dateLabel(value: string) {
return new Intl.DateTimeFormat('fa-IR-u-ca-persian', { return new Intl.DateTimeFormat('fa-IR-u-ca-persian', {
dateStyle: 'medium', dateStyle: 'medium',
@@ -54,7 +59,7 @@
></CardHeader ></CardHeader
> >
<CardContent class="grid gap-3 md:grid-cols-2"> <CardContent class="grid gap-3 md:grid-cols-2">
{#each opportunities.slice(0, 5) as opportunity, index} {#each safeOpportunities.slice(0, 5) as opportunity, index}
<article id={`evidence-${opportunity.evidenceId}`} class="bg-muted/55 rounded-md border p-4"> <article id={`evidence-${opportunity.evidenceId}`} class="bg-muted/55 rounded-md border p-4">
<div class="flex items-start justify-between gap-3"> <div class="flex items-start justify-between gap-3">
<div> <div>
@@ -79,13 +84,13 @@
</CardContent> </CardContent>
</Card> </Card>
{#if briefs.length} {#if safeBriefs.length}
<section aria-labelledby="campaign-briefs-heading"> <section aria-labelledby="campaign-briefs-heading">
<h2 id="campaign-briefs-heading" class="mb-3 flex items-center gap-2 text-lg font-bold"> <h2 id="campaign-briefs-heading" class="mb-3 flex items-center gap-2 text-lg font-bold">
<SparklesIcon class="text-primary size-5" /> پیشنهادهای آماده‌سازی <SparklesIcon class="text-primary size-5" /> پیشنهادهای آماده‌سازی
</h2> </h2>
<div class="grid gap-3 md:grid-cols-2"> <div class="grid gap-3 md:grid-cols-2">
{#each briefs as brief} {#each safeBriefs as brief}
<Card class="rounded-lg shadow-xs"> <Card class="rounded-lg shadow-xs">
<CardContent class="p-5"> <CardContent class="p-5">
<h3 class="font-bold">{brief.headline}</h3> <h3 class="font-bold">{brief.headline}</h3>
@@ -130,7 +135,7 @@
></CardHeader ></CardHeader
> >
<CardContent class="space-y-3"> <CardContent class="space-y-3">
{#each wins as win} {#each safeWins as win}
<article id={`evidence-${win.evidenceId}`} class="border-b pb-3 last:border-0 last:pb-0"> <article id={`evidence-${win.evidenceId}`} class="border-b pb-3 last:border-0 last:pb-0">
<h3 class="font-semibold">{win.eventTitleFa}</h3> <h3 class="font-semibold">{win.eventTitleFa}</h3>
<p class="text-muted-foreground mt-1 text-xs">{dateLabel(win.startDate)}</p> <p class="text-muted-foreground mt-1 text-xs">{dateLabel(win.startDate)}</p>
@@ -155,7 +160,7 @@
></CardHeader ></CardHeader
> >
<CardContent class="space-y-3"> <CardContent class="space-y-3">
{#each risks.slice(0, 3) as risk} {#each safeRisks.slice(0, 3) as risk}
<article <article
id={`evidence-${risk.evidenceId}`} id={`evidence-${risk.evidenceId}`}
class="rounded-md border bg-amber-50/50 p-4 dark:bg-amber-950/15" class="rounded-md border bg-amber-50/50 p-4 dark:bg-amber-950/15"

View File

@@ -0,0 +1,20 @@
import { render, screen } from '@testing-library/svelte';
import { describe, expect, it } from 'vitest';
import GrowthInsights from './GrowthInsights.svelte';
describe('GrowthInsights', () => {
it('renders empty insight states when persisted API arrays are null', () => {
render(GrowthInsights, {
wins: null,
opportunities: null,
risks: null,
briefs: null,
peer: null,
});
expect(screen.getByText('در افق فعلی، فرصت رویدادی معناداری پیدا نشد.')).not.toBeNull();
expect(screen.getByText('هنوز رویداد موفقی با شواهد کافی ثبت نشده است.')).not.toBeNull();
expect(screen.getByText('افت معناداری برای هشدار پیدا نشد.')).not.toBeNull();
});
});

View File

@@ -147,6 +147,7 @@
source.close(); source.close();
schedulePoll(); schedulePoll();
}; };
schedulePoll();
} }
function isGrowthReport(value: unknown): value is GrowthReport { function isGrowthReport(value: unknown): value is GrowthReport {
@@ -165,9 +166,13 @@
if (polling) clearTimeout(polling); if (polling) clearTimeout(polling);
polling = setTimeout(async () => { polling = setTimeout(async () => {
if (!report) return; if (!report) return;
const reportId = report.reportId;
try { try {
report = await api.growthReport(report.reportId); const next = await api.growthReport(reportId);
if (['QUEUED', 'ANALYZING', 'GENERATING'].includes(report.status)) schedulePoll(); if (report?.reportId !== reportId) return;
report = next;
if (['QUEUED', 'ANALYZING', 'GENERATING'].includes(next.status)) schedulePoll();
else closeRealtime();
} catch { } catch {
schedulePoll(); schedulePoll();
} }