fix: render completed growth reports reliably
This commit is contained in:
@@ -25,14 +25,19 @@
|
||||
peer,
|
||||
terminalScoped = false,
|
||||
}: {
|
||||
wins?: Win[];
|
||||
opportunities?: Opportunity[];
|
||||
risks?: Risk[];
|
||||
briefs?: Brief[];
|
||||
wins?: Win[] | null;
|
||||
opportunities?: Opportunity[] | null;
|
||||
risks?: Risk[] | null;
|
||||
briefs?: Brief[] | null;
|
||||
peer?: Peer | null;
|
||||
terminalScoped?: boolean;
|
||||
} = $props();
|
||||
|
||||
const safeWins = $derived(wins ?? []);
|
||||
const safeOpportunities = $derived(opportunities ?? []);
|
||||
const safeRisks = $derived(risks ?? []);
|
||||
const safeBriefs = $derived(briefs ?? []);
|
||||
|
||||
function dateLabel(value: string) {
|
||||
return new Intl.DateTimeFormat('fa-IR-u-ca-persian', {
|
||||
dateStyle: 'medium',
|
||||
@@ -54,7 +59,7 @@
|
||||
></CardHeader
|
||||
>
|
||||
<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">
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div>
|
||||
@@ -79,13 +84,13 @@
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{#if briefs.length}
|
||||
{#if safeBriefs.length}
|
||||
<section aria-labelledby="campaign-briefs-heading">
|
||||
<h2 id="campaign-briefs-heading" class="mb-3 flex items-center gap-2 text-lg font-bold">
|
||||
<SparklesIcon class="text-primary size-5" /> پیشنهادهای آمادهسازی
|
||||
</h2>
|
||||
<div class="grid gap-3 md:grid-cols-2">
|
||||
{#each briefs as brief}
|
||||
{#each safeBriefs as brief}
|
||||
<Card class="rounded-lg shadow-xs">
|
||||
<CardContent class="p-5">
|
||||
<h3 class="font-bold">{brief.headline}</h3>
|
||||
@@ -130,7 +135,7 @@
|
||||
></CardHeader
|
||||
>
|
||||
<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">
|
||||
<h3 class="font-semibold">{win.eventTitleFa}</h3>
|
||||
<p class="text-muted-foreground mt-1 text-xs">{dateLabel(win.startDate)}</p>
|
||||
@@ -155,7 +160,7 @@
|
||||
></CardHeader
|
||||
>
|
||||
<CardContent class="space-y-3">
|
||||
{#each risks.slice(0, 3) as risk}
|
||||
{#each safeRisks.slice(0, 3) as risk}
|
||||
<article
|
||||
id={`evidence-${risk.evidenceId}`}
|
||||
class="rounded-md border bg-amber-50/50 p-4 dark:bg-amber-950/15"
|
||||
|
||||
20
src/lib/components/GrowthInsights.test.ts
Normal file
20
src/lib/components/GrowthInsights.test.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
@@ -147,6 +147,7 @@
|
||||
source.close();
|
||||
schedulePoll();
|
||||
};
|
||||
schedulePoll();
|
||||
}
|
||||
|
||||
function isGrowthReport(value: unknown): value is GrowthReport {
|
||||
@@ -165,9 +166,13 @@
|
||||
if (polling) clearTimeout(polling);
|
||||
polling = setTimeout(async () => {
|
||||
if (!report) return;
|
||||
const reportId = report.reportId;
|
||||
try {
|
||||
report = await api.growthReport(report.reportId);
|
||||
if (['QUEUED', 'ANALYZING', 'GENERATING'].includes(report.status)) schedulePoll();
|
||||
const next = await api.growthReport(reportId);
|
||||
if (report?.reportId !== reportId) return;
|
||||
report = next;
|
||||
if (['QUEUED', 'ANALYZING', 'GENERATING'].includes(next.status)) schedulePoll();
|
||||
else closeRealtime();
|
||||
} catch {
|
||||
schedulePoll();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user