fix: render completed growth reports reliably
This commit is contained in:
@@ -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"
|
||||||
|
|||||||
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();
|
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();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user