diff --git a/backend/src/routes/coaching.js b/backend/src/routes/coaching.js index 51e5b64..d2c6dcb 100644 --- a/backend/src/routes/coaching.js +++ b/backend/src/routes/coaching.js @@ -11,13 +11,21 @@ function isClientUser(req) { function portalClientIncludes() { return [ - { model: db.sessions, as: "sessions", order: [["session_at", "DESC"]] }, + { model: db.sessions, as: "sessions", where: { status: "shared" }, required: false, order: [["session_at", "DESC"]] }, { model: db.action_items, as: "action_items", order: [["due_at", "ASC"]] }, { model: db.resources, as: "resources", where: { is_shared: true }, required: false }, { model: db.prep_briefs, as: "prep_briefs", order: [["updatedAt", "DESC"]] }, ]; } +function splitActionItems(value) { + return String(value || "") + .split(/\n|;/) + .map((item) => item.replace(/^[-*]\s*/, "").trim()) + .filter(Boolean) + .slice(0, 8); +} + router.get( "/summary", wrapAsync(async (req, res) => { @@ -137,6 +145,107 @@ router.post( }), ); +router.post( + "/session-memory/save", + wrapAsync(async (req, res) => { + const data = req.body.data || req.body; + + if (!data.clientId) { + res.status(400).send({ error: "client_id_required" }); + return; + } + + const client = await db.clients.findByPk(data.clientId); + + if (!client) { + res.status(404).send({ error: "client_not_found" }); + return; + } + + const status = data.shareWithClient ? "shared" : "draft_review"; + const session = await db.sessions.create({ + clientId: data.clientId, + title: data.title, + session_at: data.session_at || new Date(), + status, + transcript_notes: data.transcript_notes, + ai_summary: data.ai_summary, + key_topics: data.key_topics, + goals_discussed: data.goals_discussed, + blockers: data.blockers, + commitments: data.commitments, + homework: data.homework, + emotional_themes: data.emotional_themes, + important_quotes: data.important_quotes, + follow_up_email: data.follow_up_email, + next_session_prep: data.next_session_prep, + private_coach_notes: data.private_coach_notes, + shared_client_notes: data.shared_client_notes, + createdById: req.currentUser.id, + updatedById: req.currentUser.id, + }); + + const actionTitles = splitActionItems(data.commitments || data.homework); + + for (const title of actionTitles) { + await db.action_items.create({ + clientId: data.clientId, + sessionId: session.id, + title, + status: "not_started", + createdById: req.currentUser.id, + updatedById: req.currentUser.id, + }); + } + + await db.prep_briefs.create({ + clientId: data.clientId, + sessionId: session.id, + next_session_at: client.next_session_at, + previous_summary: data.ai_summary, + open_commitments: actionTitles.join("; "), + suggested_questions: data.next_session_prep, + sensitive_topics: data.blockers, + status: "ready", + createdById: req.currentUser.id, + updatedById: req.currentUser.id, + }); + + const savedSession = await db.sessions.findByPk(session.id, { + include: [ + { model: db.clients, as: "client" }, + { model: db.action_items, as: "action_items", order: [["createdAt", "ASC"]] }, + ], + }); + + res.status(200).send(savedSession); + }), +); + +router.patch( + "/sessions/:id/share", + wrapAsync(async (req, res) => { + const session = await db.sessions.findByPk(req.params.id); + + if (!session) { + res.status(404).send({ error: "session_not_found" }); + return; + } + + await session.update({ + shared_client_notes: req.body.shared_client_notes || session.shared_client_notes, + status: "shared", + updatedById: req.currentUser.id, + }); + + const savedSession = await db.sessions.findByPk(session.id, { + include: [{ model: db.clients, as: "client" }], + }); + + res.status(200).send(savedSession); + }), +); + router.post( "/session-memory/generate", wrapAsync(async (req, res) => { diff --git a/frontend/src/pages/session-memory.tsx b/frontend/src/pages/session-memory.tsx index 0afcc15..ed6f944 100644 --- a/frontend/src/pages/session-memory.tsx +++ b/frontend/src/pages/session-memory.tsx @@ -20,13 +20,25 @@ type Client = { name: string; }; -type Session = { - id: string; +type MemoryDraft = { title?: string; ai_summary?: string; key_topics?: string; + goals_discussed?: string; + blockers?: string; + commitments?: string; homework?: string; + emotional_themes?: string; + important_quotes?: string; follow_up_email?: string; + next_session_prep?: string; + private_coach_notes?: string; + shared_client_notes?: string; +}; + +type Session = MemoryDraft & { + id: string; + status?: string; client?: Client; }; @@ -46,32 +58,73 @@ function Panel({ ); } -function OutputBlock({ - title, - children, +function TextField({ + label, + value, + onChange, + rows = 3, }: { - title: string; - children: React.ReactNode; + label: string; + value: string; + onChange: (value: string) => void; + rows?: number; }) { return ( -
-

- {title} -

-
{children}
-
+