feat: briefing auto-generate, pages overhaul (dashboard/screener/report/routines), delete routine
This commit is contained in:
+62
-37
@@ -1,6 +1,9 @@
|
||||
import { createResource, createSignal, For, Show } from "solid-js";
|
||||
import { api } from "../lib/api";
|
||||
import { Citations } from "../components/Citations";
|
||||
import { EmptyState } from "../components/ui";
|
||||
|
||||
const TYPES = ["morning-briefing", "accumulation-radar", "foreign-reversal", "insider-tape", "earnings-countdown", "dividend-calendar", "weekend-review"];
|
||||
|
||||
export default function Routines() {
|
||||
const [data, { refetch }] = createResource(() => api.routines());
|
||||
@@ -8,55 +11,77 @@ export default function Routines() {
|
||||
const [type_, setType] = createSignal("morning-briefing");
|
||||
const [schedule, setSchedule] = createSignal("");
|
||||
const [channels, setChannels] = createSignal("");
|
||||
const [busy, setBusy] = createSignal(false);
|
||||
const [runNowId, setRunNowId] = createSignal<number | null>(null);
|
||||
const [briefing] = createResource(() => api.briefing().catch(() => null));
|
||||
async function subscribe() {
|
||||
await api.createRoutine({
|
||||
type: type_(),
|
||||
schedule_cron: schedule().trim() || undefined,
|
||||
channels: channels().split(",").map((c) => c.trim()).filter(Boolean),
|
||||
});
|
||||
refetch();
|
||||
setBusy(true);
|
||||
try {
|
||||
await api.createRoutine({
|
||||
type: type_(),
|
||||
schedule_cron: schedule().trim() || undefined,
|
||||
channels: channels().split(",").map((c) => c.trim()).filter(Boolean),
|
||||
});
|
||||
refetch();
|
||||
} finally { setBusy(false); }
|
||||
}
|
||||
async function toggle(id: number, enabled: boolean) {
|
||||
await api.updateRoutine(id, { enabled: !enabled });
|
||||
refetch();
|
||||
}
|
||||
async function del(id: number) {
|
||||
await fetch(`/api/routines/${id}`, { method: "DELETE", headers: { "X-User-Key": localStorage.getItem("fs-key") || "demo" } });
|
||||
refetch();
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<h1>Routine Manager</h1>
|
||||
<div class="fs-page-head"><h1>Routine Manager</h1><p class="fs-muted">Scheduled deliveries with citations.</p></div>
|
||||
<div class="fs-card">
|
||||
<h3>Subscribe</h3>
|
||||
<select class="fs-input" value={type_()} onChange={(e) => setType(e.currentTarget.value)}>
|
||||
<For each={["morning-briefing","accumulation-radar","foreign-reversal","insider-tape","earnings-countdown","dividend-calendar","weekend-review"]}>
|
||||
{(t) => <option value={t}>{t}</option>}
|
||||
<div class="fs-row">
|
||||
<select class="fs-input" value={type_()} onChange={(e) => setType(e.currentTarget.value)}>
|
||||
<For each={TYPES}>{(t) => <option value={t}>{t}</option>}</For>
|
||||
</select>
|
||||
<input class="fs-input" placeholder="schedule cron (blank = default)" value={schedule()} onInput={(e) => setSchedule(e.currentTarget.value)} style={{ width: "240px" }} />
|
||||
<input class="fs-input" placeholder="channels, comma-separated" value={channels()} onInput={(e) => setChannels(e.currentTarget.value)} style={{ width: "200px" }} />
|
||||
<button class="fs-btn" onClick={subscribe} disabled={busy()}>Subscribe</button>
|
||||
</div>
|
||||
</div>
|
||||
<Show when={(data()?.routines || []).length} fallback={
|
||||
<div style={{ "margin-top": "14px" }}><EmptyState icon="🗓️" title="No routines yet" hint="Subscribe above — the morning briefing runs 07:30 WIB daily." /></div>
|
||||
}>
|
||||
<div class="fs-grid" style={{ "margin-top": "14px" }}>
|
||||
<For each={data()?.routines || []}>
|
||||
{(r) => (
|
||||
<div class="fs-card">
|
||||
<div class="fs-row" style={{ "justify-content": "space-between" }}>
|
||||
<strong>{r.type}</strong>
|
||||
<span class={`fs-badge ${r.enabled ? "buy" : "info"}`}>{r.enabled ? "on" : "off"}</span>
|
||||
</div>
|
||||
<p class="fs-muted">{r.schedule_cron}</p>
|
||||
<div class="fs-row">
|
||||
<button class="fs-btn ghost" onClick={() => toggle(r.id, r.enabled)}>{r.enabled ? "Disable" : "Enable"}</button>
|
||||
<button class="fs-btn ghost" disabled={runNowId() === r.id}>Run</button>
|
||||
<button class="fs-btn danger" onClick={() => del(r.id)}>Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
</select>
|
||||
<input class="fs-input" placeholder="schedule cron (blank = default)" value={schedule()} onInput={(e) => setSchedule(e.currentTarget.value)} style={{ width: "260px" }} />
|
||||
<input class="fs-input" placeholder="channels, comma-separated" value={channels()} onInput={(e) => setChannels(e.currentTarget.value)} style={{ width: "220px" }} />
|
||||
<button class="fs-btn" onClick={subscribe}>Subscribe</button>
|
||||
</div>
|
||||
<div class="fs-grid">
|
||||
<For each={data()?.routines || []}>
|
||||
{(r) => (
|
||||
<div class="fs-card">
|
||||
<strong>{r.type}</strong> <span class="fs-muted">{r.schedule_cron} · {r.enabled ? "on" : "off"}</span>
|
||||
<div><button class="fs-btn ghost" onClick={() => toggle(r.id, r.enabled)}>{r.enabled ? "Disable" : "Enable"}</button></div>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
<Show when={!data()?.routines?.length}><p class="fs-muted">No routines yet.</p></Show>
|
||||
<div class="fs-card">
|
||||
<h3>Latest briefing</h3>
|
||||
<Show when={briefing()} fallback={<p class="fs-muted">No briefing yet.</p>}>
|
||||
<pre class="fs-muted">{(briefing() as { payload: string })?.payload?.slice(0, 800)}</pre>
|
||||
<Citations items={(briefing() as { citations: string })?.citations} />
|
||||
</Show>
|
||||
</div>
|
||||
<div class="fs-card">
|
||||
<h3>Run history</h3>
|
||||
<button class="fs-btn ghost" onClick={() => refetchRuns()}>Refresh</button>
|
||||
<ul><For each={runs() || []}>{(r) => <li class="fs-muted">{String(r.started_at)} — {String(r.status)}</li>}</For></ul>
|
||||
</div>
|
||||
</Show>
|
||||
<div class="fs-grid" style={{ "margin-top": "14px" }}>
|
||||
<div class="fs-card">
|
||||
<h3>Latest briefing</h3>
|
||||
<Show when={briefing()} fallback={<p class="fs-muted">No briefing yet — it generates after the first morning-briefing run.</p>}>
|
||||
<pre class="fs-muted" style={{ "white-space": "pre-wrap" }}>{(briefing() as { payload: string })?.payload?.slice(0, 800)}</pre>
|
||||
<Citations items={(briefing() as { citations: string })?.citations} />
|
||||
</Show>
|
||||
</div>
|
||||
<div class="fs-card">
|
||||
<h3>Run history</h3>
|
||||
<button class="fs-btn ghost" onClick={() => refetchRuns()}>Refresh</button>
|
||||
<ul><For each={runs() || []}>{(r) => <li class="fs-muted">{String(r.started_at)} — {String(r.status)}</li>}</For></ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user