db server and front end comms work card render basic works

This commit is contained in:
2025-11-29 13:31:53 -06:00
parent 80d02737c0
commit e39a3f4239
11 changed files with 167 additions and 181 deletions
+18 -33
View File
@@ -1,39 +1,24 @@
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { APITester } from "./APITester";
import "./index.css";
import { useEffect, useState } from "react";
import StepCard from "./components/ui/StepCard";
import type { FlowStep } from "./types"; // types only
import logo from "./logo.svg";
import reactLogo from "./react.svg";
export default function App() {
const [steps, setSteps] = useState<FlowStep[]>([]);
useEffect(() => {
fetch("http://localhost:3001/steps")
.then(res => res.json())
.then(data => setSteps(data))
.catch(err => console.error("Failed to fetch steps:", err));
}, []);
export function App() {
return (
<div className="container mx-auto p-8 text-center relative z-10">
<div className="flex justify-center items-center gap-8 mb-8">
<img
src={logo}
alt="Bun Logo"
className="h-36 p-6 transition-all duration-300 hover:drop-shadow-[0_0_2em_#646cffaa] scale-120"
/>
<img
src={reactLogo}
alt="React Logo"
className="h-36 p-6 transition-all duration-300 hover:drop-shadow-[0_0_2em_#61dafbaa] [animation:spin_20s_linear_infinite]"
/>
</div>
<Card>
<CardHeader className="gap-4">
<CardTitle className="text-3xl font-bold">Bun + React</CardTitle>
<CardDescription>
Edit <code className="rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono">src/App.tsx</code> and save to
test HMR
</CardDescription>
</CardHeader>
<CardContent>
<APITester />
</CardContent>
</Card>
<div className="p-4 space-y-4">
{steps.length === 0 ? (
<p>Loading steps...</p>
) : (
steps.map(step => <StepCard key={step.id} step={step} />)
)}
</div>
);
}
export default App;
+61
View File
@@ -0,0 +1,61 @@
import { useState } from "react";
// --- Types ---
export type FieldType = {
id: string;
label: string;
type: "textarea" | "array";
};
export type Item = {
id?: number;
text?: string;
followUp?: string;
comment?: string;
flagged?: boolean;
};
// --- Props ---
type DynamicArrayFieldProps = {
field: FieldType;
stepId: string; // keep for future use
items: Item[];
onAdd: (item: Item) => void;
};
export default function DynamicArrayField({ field, stepId: _stepId, items, onAdd }: DynamicArrayFieldProps) {
const [input, setInput] = useState("");
const handleAdd = () => {
if (!input) return;
onAdd({ text: input });
setInput("");
};
return (
<div className="space-y-2">
<label className="block font-medium">{field.label}</label>
{items.map((item, index) => (
<div key={index} className="p-1 border rounded">
{item.text || item.comment}
</div>
))}
<div className="flex space-x-2">
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder={`Add ${field.label}`}
className="border rounded px-2 py-1 flex-1"
/>
<button
type="button"
onClick={handleAdd}
className="bg-blue-500 text-white px-3 py-1 rounded"
>
Add
</button>
</div>
</div>
);
}
+16
View File
@@ -0,0 +1,16 @@
import type{ FlowStep } from "../../types";
type StepCardProps = {
step: FlowStep;
};
export default function StepCard({ step }: StepCardProps) {
return (
<div className="border rounded-lg p-4 bg-white shadow-sm">
<h2 className="font-bold text-lg">{step.stepNumber}: {step.description}</h2>
<p className="text-sm opacity-70">
Stage: {step.stage} | Role: {step.role} | Handoff: {step.handoffTo} via {step.handoffChannel}
</p>
</div>
);
}
-1
View File
@@ -176,4 +176,3 @@ export function getConcern(stepId: string): Concern | null {
};
}
//export default db;
+4 -5
View File
@@ -1,13 +1,12 @@
<!doctype html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" type="image/svg+xml" href="./logo.svg" />
<title>Bun + React</title>
<script type="module" src="./frontend.tsx" async></script>
<title>StepCard Test</title>
<script type="module" src="/src/main.tsx"></script>
</head>
<body>
<body class="bg-gray-100">
<div id="root"></div>
</body>
</html>
+16
View File
@@ -0,0 +1,16 @@
import React from "react";
import { createRoot } from "react-dom/client";
import App from "./App";
const container = document.getElementById("root");
if (!container) throw new Error("Root container not found");
// Only create root if it doesn't already exist (HMR safe)
const root = (window as any)._root || createRoot(container);
(window as any)._root = root;
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
+19
View File
@@ -0,0 +1,19 @@
export const intakeStepSchema = [
{
id: "extendedDesc",
label: "Extended Description",
type: "textarea"
},
{
id: "questions",
label: "Questions",
type: "array",
itemLabel: "Question"
},
{
id: "concerns",
label: "Concerns",
type: "array",
itemLabel: "Concern"
}
] as const;
+5 -61
View File
@@ -1,4 +1,3 @@
// src/server.ts
import { serve } from "bun";
import {
createStep,
@@ -10,7 +9,7 @@ import {
getQuestions,
flagConcern,
getConcern
} from "./db.ts";
} from "./db.ts"; // server-only
const corsHeaders = {
"Access-Control-Allow-Origin": "*",
@@ -27,79 +26,24 @@ async function getJSON(req: Request) {
}
serve({
port: 3000,
port: 3001,
fetch: async (req: Request) => {
const { method } = req;
const path = new URL(req.url).pathname;
if (method === "OPTIONS") {
return new Response(null, { headers: corsHeaders });
}
if (method === "OPTIONS") return new Response(null, { headers: corsHeaders });
// --- Steps ---
if (path === "/steps" && method === "GET") {
if (path === "/steps" && method === "GET")
return new Response(JSON.stringify(getAllSteps()), { headers: { ...corsHeaders, "Content-Type": "application/json" } });
}
if (path.startsWith("/steps/") && method === "GET") {
const stepId = String(path.split("/")[2]);
return new Response(JSON.stringify(getStep(stepId)), { headers: { ...corsHeaders, "Content-Type": "application/json" } });
}
if (path === "/steps" && method === "POST") {
const data = await getJSON(req);
createStep({
id: String(data.id),
stepNumber: String(data.stepNumber),
caseCode: String(data.caseCode),
subStep: String(data.subStep),
stage: String(data.stage),
role: String(data.role),
handoffTo: String(data.handoffTo),
handoffChannel: String(data.handoffChannel),
description: String(data.description),
});
return new Response(JSON.stringify({ success: true }), { headers: { ...corsHeaders, "Content-Type": "application/json" } });
}
// --- Extended Descriptions ---
if (path === "/descriptions" && method === "POST") {
const { stepId, content } = await getJSON(req);
saveExtendedDescription(String(stepId), String(content));
return new Response(JSON.stringify({ success: true }), { headers: { ...corsHeaders, "Content-Type": "application/json" } });
}
if (path.startsWith("/descriptions/") && method === "GET") {
const stepId = String(path.split("/")[2]);
return new Response(JSON.stringify({ stepId, content: getExtendedDescription(stepId) }), { headers: { ...corsHeaders, "Content-Type": "application/json" } });
}
// --- Questions ---
if (path === "/questions" && method === "POST") {
const { stepId, question, followUp } = await getJSON(req);
addQuestion(String(stepId), String(question), followUp ? String(followUp) : null);
return new Response(JSON.stringify({ success: true }), { headers: { ...corsHeaders, "Content-Type": "application/json" } });
}
if (path.startsWith("/questions/") && method === "GET") {
const stepId = String(path.split("/")[2]);
return new Response(JSON.stringify(getQuestions(stepId)), { headers: { ...corsHeaders, "Content-Type": "application/json" } });
}
// --- Concerns ---
if (path === "/concerns" && method === "POST") {
const { stepId, flagged, comment } = await getJSON(req);
flagConcern(String(stepId), Boolean(flagged), String(comment));
return new Response(JSON.stringify({ success: true }), { headers: { ...corsHeaders, "Content-Type": "application/json" } });
}
if (path.startsWith("/concerns/") && method === "GET") {
const stepId = String(path.split("/")[2]);
return new Response(JSON.stringify(getConcern(stepId)), { headers: { ...corsHeaders, "Content-Type": "application/json" } });
}
return new Response("Not Found", { status: 404 });
}
});
console.log("Server running at http://localhost:3000");
console.log("Server running at http://localhost:3001");
View File
+28
View File
@@ -0,0 +1,28 @@
// src/types.ts
export type FlowStep = {
id: string;
stepNumber: string;
caseCode: string;
subStep: string;
stage: string;
role: string;
handoffTo: string;
handoffChannel: string;
description: string;
createdAt: string;
};
export type Question = {
id: number;
stepId: string;
question: string;
followUp: string | null;
createdAt: string;
};
export type Concern = {
stepId: string;
flagged: boolean;
comment: string;
createdAt: string;
};
-81
View File
@@ -1,81 +0,0 @@
// test-server.ts
async function test() {
const base = "http://localhost:3000";
// --- Create a step ---
const stepData = {
id: "step1",
stepNumber: "0.1",
caseCode: "caseA",
subStep: "a",
stage: "Pre-Intake",
role: "Estimator",
handoffTo: "Office Manager",
handoffChannel: "Phone",
description: "Initial ITB received",
};
console.log("Creating step...");
let res = await fetch(`${base}/steps`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(stepData),
});
console.log(await res.json());
// --- Get the step ---
console.log("Getting step...");
res = await fetch(`${base}/steps/${stepData.id}`);
console.log(await res.json());
// --- Add extended description ---
console.log("Adding extended description...");
res = await fetch(`${base}/descriptions`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ stepId: stepData.id, content: "Extended info about step1" }),
});
console.log(await res.json());
// --- Get extended description ---
console.log("Getting extended description...");
res = await fetch(`${base}/descriptions/${stepData.id}`);
console.log(await res.json());
// --- Add a question ---
console.log("Adding question...");
res = await fetch(`${base}/questions`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ stepId: stepData.id, question: "Follow up question?", followUp: "Yes, clarify details" }),
});
console.log(await res.json());
// --- Get questions ---
console.log("Getting questions...");
res = await fetch(`${base}/questions/${stepData.id}`);
console.log(await res.json());
// --- Add a concern ---
console.log("Adding concern...");
res = await fetch(`${base}/concerns`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ stepId: stepData.id, flagged: true, comment: "Check this step for errors" }),
});
console.log(await res.json());
// --- Get concern ---
console.log("Getting concern...");
res = await fetch(`${base}/concerns/${stepData.id}`);
console.log(await res.json());
// --- Get all steps ---
console.log("Getting all steps...");
res = await fetch(`${base}/steps`);
console.log(await res.json());
console.log("Test complete.");
}
test();