2026-09-15 16:39:27 +07:00
import { createSignal , For , Show , onMount } from "solid-js" ;
2026-09-15 12:36:48 +07:00
import { useParams } from "@solidjs/router" ;
import { api , type ReportPayload } from "../lib/api" ;
import { Citations } from "../components/Citations" ;
2026-09-15 17:40:39 +07:00
import { PageHead , RecBadge } from "../components/ui" ;
import { Badge } from "../components/ui/badge" ;
import { Button } from "../components/ui/button" ;
import { Card , CardContent , CardHeader , CardTitle } from "../components/ui/card" ;
import { Skeleton } from "../components/ui/skeleton" ;
import { TextField , TextFieldInput } from "../components/ui/text-field" ;
2026-09-15 12:36:48 +07:00
export default function Report() {
const params = useParams ();
const [ rep , setRep ] = createSignal < ReportPayload | null >( null );
const [ md , setMd ] = createSignal ( "" );
2026-09-15 16:39:27 +07:00
const [ question , setQuestion ] = createSignal ( "" );
2026-09-15 12:36:48 +07:00
const [ answer , setAnswer ] = createSignal ( "" );
2026-09-15 16:39:27 +07:00
const [ busy , setBusy ] = createSignal ( true );
const [ asking , setAsking ] = createSignal ( false );
2026-09-15 12:36:48 +07:00
const [ err , setErr ] = createSignal ( "" );
2026-09-15 16:39:27 +07:00
const authHeaders = () => ({ "X-User-Key" : localStorage . getItem ( "fs-key" ) || "demo" });
2026-09-15 12:36:48 +07:00
async function load() {
2026-09-15 16:39:27 +07:00
setErr ( "" ); setBusy ( true );
2026-09-15 12:36:48 +07:00
try { setRep ( await api . report ( params . ticker )); }
catch ( e ) { setErr ( String ( e )); }
2026-09-15 16:39:27 +07:00
finally { setBusy ( false ); }
2026-09-15 12:36:48 +07:00
}
2026-09-15 16:39:27 +07:00
onMount ( load );
2026-09-15 12:36:48 +07:00
async function exportMd() { setMd ( await api . reportMd ( params . ticker )); }
async function ask() {
2026-09-15 16:39:27 +07:00
if ( ! question (). trim ()) return ;
setAsking ( true );
try {
const r = await api . interrogate ( params . ticker , question ());
setAnswer ( r . answer );
} finally { setAsking ( false ); }
}
function dl ( url : string , name : string ) {
const a = document . createElement ( "a" );
a . href = url ; a . download = name ; a . click ();
2026-09-15 12:36:48 +07:00
}
return (
2026-09-15 17:40:39 +07:00
< div class = "space-y-4" >
< div class = "flex items-center justify-between" >
< PageHead title = { `Report: ${ params . ticker } ` } />
< Button variant = "outline" onClick = { load }> ↻ Regenerate </ Button >
2026-09-15 16:39:27 +07:00
</ div >
2026-09-15 17:40:39 +07:00
< Show when = { err ()}>< p class = "text-sm text-destructive" >{ err ()}</ p ></ Show >
< Show when = { busy ()}>< div class = "space-y-2" >< Skeleton class = "h-32 w-full" />< Skeleton class = "h-24 w-full" /></ div ></ Show >
2026-09-15 12:36:48 +07:00
< Show when = { rep ()}>
2026-09-15 19:50:48 +07:00
< Show when = { rep () ! . narasi_awam }>
< Card class = "border-primary/30 bg-gradient-to-br from-primary/10 to-transparent" >
< CardHeader >< CardTitle class = "text-xl" > Kesimpulan buat kamu 🤝 </ CardTitle ></ CardHeader >
< CardContent >< p class = "whitespace-pre-wrap text-base leading-relaxed" >{ rep () ! . narasi_awam }</ p ></ CardContent >
</ Card >
</ Show >
2026-09-15 17:40:39 +07:00
< Card class = "border-l-4 border-l-primary" >
< CardContent class = "flex flex-wrap items-center gap-2 pt-6" >
2026-09-15 16:39:27 +07:00
< RecBadge rec = { rep () ! . synthesis . recommendation } />
< strong > conviction { rep () ! . synthesis . conviction } / 5 </ strong >
2026-09-15 17:40:39 +07:00
< Show when = { rep () ! . synthesis . conflict }>< Badge variant = "secondary" > conflict flagged </ Badge ></ Show >
< span class = "text-sm text-muted-foreground" > position { rep () ! . synthesis . position_pct } % </ span >
</ CardContent >
< CardContent >
< p class = "text-sm" >{ rep () ! . synthesis . thesis }</ p >
< div class = "mt-2" >< Citations items = { rep () ! . citations } /></ div >
</ CardContent >
</ Card >
< Card >
< CardHeader >< CardTitle > Export </ CardTitle ></ CardHeader >
< CardContent class = "flex flex-wrap gap-2" >
< Button variant = "outline" onClick = { exportMd }> Markdown </ Button >
< Button variant = "outline" onClick = { async () => {
2026-09-15 16:39:27 +07:00
const r = await fetch ( `/api/report/ ${ params . ticker } ?format=html` , { method : "POST" , headers : authHeaders () });
dl ( URL . createObjectURL ( new Blob ([ await r . text ()], { type : "text/html" })), ` ${ params . ticker } -report.html` );
2026-09-15 17:40:39 +07:00
}}> HTML </ Button >
< Button variant = "outline" onClick = {() => {
2026-09-15 16:39:27 +07:00
dl ( URL . createObjectURL ( new Blob ([ JSON . stringify ( rep (), null , 1 )], { type : "application/json" })), ` ${ params . ticker } -report.json` );
2026-09-15 17:40:39 +07:00
}}> JSON </ Button >
< Button variant = "outline" onClick = { async () => {
2026-09-15 16:39:27 +07:00
const r = await fetch ( `/api/report/ ${ params . ticker } ?format=pdf` , { method : "POST" , headers : authHeaders () });
dl ( URL . createObjectURL ( await r . blob ()), ` ${ params . ticker } -report.pdf` );
2026-09-15 17:40:39 +07:00
}}> PDF </ Button >
</ CardContent >
</ Card >
2026-09-15 12:36:48 +07:00
< For each = { rep () ! . sections }>
2026-09-15 17:40:39 +07:00
{( s ) => < Card >< CardHeader >< CardTitle >{ s . name }</ CardTitle ></ CardHeader >< CardContent >< p class = "whitespace-pre-wrap text-sm" >{ s . body }</ p >< div class = "mt-2" >< Citations items = { s . citations } /></ div ></ CardContent ></ Card >}
2026-09-15 12:36:48 +07:00
</ For >
2026-09-15 17:40:39 +07:00
< Card >
< CardHeader >< CardTitle > Interrogate this report </ CardTitle ></ CardHeader >
< CardContent class = "space-y-3" >
< div class = "flex gap-2" >
< TextField class = "flex-1" >< TextFieldInput placeholder = "Ask about this report…" value = { question ()} onInput = {( e ) => setQuestion ( e . currentTarget . value )} /></ TextField >
< Button onClick = { ask } disabled = { asking ()}>{ asking () ? "…" : "Ask" }</ Button >
</ div >
< Show when = { answer ()}>< p class = "text-sm" >{ answer ()}</ p ></ Show >
</ CardContent >
</ Card >
2026-09-15 12:36:48 +07:00
</ Show >
2026-09-15 17:40:39 +07:00
< Show when = { md ()}>< Card >< CardContent class = "pt-6" >< pre class = "whitespace-pre-wrap text-sm text-muted-foreground" >{ md (). slice ( 0 , 2000 )}</ pre ></ CardContent ></ Card ></ Show >
2026-09-15 12:36:48 +07:00
</ div >
);
}