File size: 4,707 Bytes
baf8a7b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
import { useEffect, useState } from "react";
import {
  Buildings, Stack, FileText, ShieldCheck, SealCheck, Check,
} from "@phosphor-icons/react";
import { api, type RunSummary } from "./lib/api";
import { Intake } from "./components/Intake";
import { CinIntake } from "./components/CinIntake";
import { Ledger } from "./components/Ledger";
import { Draft } from "./components/Draft";
import { Board } from "./components/Board";
import { Handoff } from "./components/Handoff";

const STEPS = [
  { id: "intake", label: "Issuer", icon: Buildings },
  { id: "ledger", label: "Disclosure ledger", icon: Stack },
  { id: "draft", label: "Draft", icon: FileText },
  { id: "board", label: "Consistency check", icon: ShieldCheck },
  { id: "handoff", label: "Handoff", icon: SealCheck },
] as const;

type StepId = (typeof STEPS)[number]["id"];

function IntakeTabs({ onStarted }: { onStarted: (r: RunSummary) => void }) {
  const [mode, setMode] = useState<"listed" | "cin">("listed");
  return (
    <>
      <div className="page-head">
        <h1 className="page">Select an issuer</h1>
        <p className="lede">
          A listed company can be found by search and read from its own filing. A company that
          has not listed yet has no exchange history at all, so it is identified by its
          Corporate Identity Number and its records are the ones it supplies.
        </p>
        <div className="filters" style={{ marginTop: 12 }}>
          <button className="filter" aria-pressed={mode === "listed"}
                  onClick={() => setMode("listed")}>listed on BSE</button>
          <button className="filter" aria-pressed={mode === "cin"}
                  onClick={() => setMode("cin")}>pre-IPO, by CIN</button>
        </div>
      </div>
      {mode === "listed" ? <Intake onStarted={onStarted} embedded />
                         : <CinIntake onStarted={onStarted} />}
    </>
  );
}

export function App() {
  const [step, setStep] = useState<StepId>("intake");
  const [run, setRun] = useState<RunSummary | null>(null);
  const [health, setHealth] = useState<any>(null);

  useEffect(() => { api.health().then(setHealth).catch(() => setHealth(null)); }, []);

  const reached = (id: StepId) => {
    if (!run) return id === "intake";
    return STEPS.findIndex((s) => s.id === id) <= STEPS.findIndex((s) => s.id === step);
  };

  return (
    <div className="app">
      <header className="topbar">
        <span className="brand">Primary<b>Draft</b></span>
        <span className="brand-sub">the IPO offer document that checks itself</span>
        <div className="topbar-right">
          {health && (
            <>
              <span className="badge badge-accent">{health.rule_count} deterministic checks</span>
              <span className="badge badge-quiet">{health.companies} real BSE SME issuers</span>
              <span className="badge badge-quiet">
                narrative AI {health.llm?.enabled ? "on" : "off"}
              </span>
            </>
          )}
        </div>
      </header>

      <nav className="steps" aria-label="Workflow">
        {STEPS.map((s, i) => {
          const Icon = s.icon;
          const isCurrent = s.id === step;
          const done = run !== null && STEPS.findIndex((x) => x.id === step) > i;
          return (
            <button
              key={s.id}
              className={`step${done ? " step-done" : ""}`}
              aria-current={isCurrent ? "step" : undefined}
              disabled={!reached(s.id)}
              onClick={() => reached(s.id) && setStep(s.id)}
            >
              <span className="step-i">{done ? <Check size={15} weight="bold" /> : <Icon size={15} />}</span>
              {s.label}
            </button>
          );
        })}
        {run && (
          <span style={{
            marginLeft: "auto", alignSelf: "center", paddingRight: 8,
            fontSize: 12, color: "var(--text-2)",
          }}>
            {run.company_name} <span className="mono" style={{ fontSize: 11 }}>{run.cin}</span>
          </span>
        )}
      </nav>

      <main className="main">
        {step === "intake" && (
          <IntakeTabs onStarted={(r) => { setRun(r); setStep("ledger"); }} />
        )}
        {step === "ledger" && run && (
          <Ledger run={run} onNext={() => setStep("draft")} />
        )}
        {step === "draft" && run && (
          <Draft run={run} onRunUpdate={setRun} onNext={() => setStep("board")} />
        )}
        {step === "board" && run && (
          <Board run={run} onRunUpdate={setRun} onNext={() => setStep("handoff")} />
        )}
        {step === "handoff" && run && <Handoff runId={run.id} />}
      </main>
    </div>
  );
}