Add coach next-session prep views
This commit is contained in:
parent
0e65518e41
commit
22bb909af8
@ -48,6 +48,12 @@ router.get(
|
|||||||
order: [["next_session_at", "ASC"]],
|
order: [["next_session_at", "ASC"]],
|
||||||
include: [{ model: db.packages, as: "package" }],
|
include: [{ model: db.packages, as: "package" }],
|
||||||
});
|
});
|
||||||
|
const upcomingPrepBriefs = await db.prep_briefs.findAll({
|
||||||
|
limit: 4,
|
||||||
|
order: [["next_session_at", "ASC"]],
|
||||||
|
where: { status: "ready" },
|
||||||
|
include: [{ model: db.clients, as: "client" }],
|
||||||
|
});
|
||||||
|
|
||||||
res.status(200).send({
|
res.status(200).send({
|
||||||
counts: {
|
counts: {
|
||||||
@ -59,6 +65,7 @@ router.get(
|
|||||||
},
|
},
|
||||||
nextSessions,
|
nextSessions,
|
||||||
activeClients,
|
activeClients,
|
||||||
|
upcomingPrepBriefs,
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -23,6 +23,10 @@ type ActionItem = {
|
|||||||
|
|
||||||
type PrepBrief = {
|
type PrepBrief = {
|
||||||
id: string;
|
id: string;
|
||||||
|
previous_summary?: string;
|
||||||
|
open_commitments?: string;
|
||||||
|
suggested_questions?: string;
|
||||||
|
sensitive_topics?: string;
|
||||||
client_reflection?: string;
|
client_reflection?: string;
|
||||||
client_reflection_at?: string;
|
client_reflection_at?: string;
|
||||||
};
|
};
|
||||||
@ -77,6 +81,26 @@ function EmptyState({ label }: { label: string }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function PrepText({ value }: { value?: string }) {
|
||||||
|
if (!value) {
|
||||||
|
return <EmptyState label='No prep details saved yet.' />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='space-y-2'>
|
||||||
|
{value
|
||||||
|
.split(/\n|;/)
|
||||||
|
.map((item) => item.trim())
|
||||||
|
.filter(Boolean)
|
||||||
|
.map((item) => (
|
||||||
|
<p key={item} className='text-sm leading-6 text-[#72798a]'>
|
||||||
|
{item}
|
||||||
|
</p>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const Clients = () => {
|
const Clients = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [clients, setClients] = React.useState<Client[]>([]);
|
const [clients, setClients] = React.useState<Client[]>([]);
|
||||||
@ -108,6 +132,7 @@ const Clients = () => {
|
|||||||
const latestReflection = selectedClient?.prep_briefs?.find((brief) => {
|
const latestReflection = selectedClient?.prep_briefs?.find((brief) => {
|
||||||
return Boolean(brief.client_reflection);
|
return Boolean(brief.client_reflection);
|
||||||
});
|
});
|
||||||
|
const latestPrepBrief = selectedClient?.prep_briefs?.[0];
|
||||||
|
|
||||||
function selectClient(clientId: string) {
|
function selectClient(clientId: string) {
|
||||||
setSelectedClientId(clientId);
|
setSelectedClientId(clientId);
|
||||||
@ -243,6 +268,74 @@ const Clients = () => {
|
|||||||
</div>
|
</div>
|
||||||
</Panel>
|
</Panel>
|
||||||
|
|
||||||
|
<Panel>
|
||||||
|
<div className='border-b border-[#19192d]/10 p-5'>
|
||||||
|
<div className='flex items-center justify-between gap-4'>
|
||||||
|
<div>
|
||||||
|
<p className='text-xs font-semibold uppercase tracking-[0.22em] text-[#b17a1e]'>
|
||||||
|
Next-session prep
|
||||||
|
</p>
|
||||||
|
<h3 className='mt-2 text-lg font-semibold text-[#19192d]'>
|
||||||
|
Coach prep brief
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<span className='rounded-full bg-[#f3fbf8] px-3 py-1 text-xs font-semibold text-[#35b7a5]'>
|
||||||
|
Ready
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{latestPrepBrief ? (
|
||||||
|
<div className='grid gap-4 p-5 lg:grid-cols-2'>
|
||||||
|
<div className='rounded-lg bg-[#fffdf9] p-4'>
|
||||||
|
<p className='text-xs font-semibold uppercase tracking-[0.18em] text-[#b17a1e]'>
|
||||||
|
Client reflection
|
||||||
|
</p>
|
||||||
|
<div className='mt-3'>
|
||||||
|
<PrepText value={latestPrepBrief.client_reflection} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='rounded-lg bg-[#fffdf9] p-4'>
|
||||||
|
<p className='text-xs font-semibold uppercase tracking-[0.18em] text-[#b17a1e]'>
|
||||||
|
Open commitments
|
||||||
|
</p>
|
||||||
|
<div className='mt-3'>
|
||||||
|
<PrepText value={latestPrepBrief.open_commitments} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='rounded-lg bg-[#fffdf9] p-4'>
|
||||||
|
<p className='text-xs font-semibold uppercase tracking-[0.18em] text-[#b17a1e]'>
|
||||||
|
Suggested questions
|
||||||
|
</p>
|
||||||
|
<div className='mt-3'>
|
||||||
|
<PrepText
|
||||||
|
value={latestPrepBrief.suggested_questions}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='rounded-lg bg-[#fffdf9] p-4'>
|
||||||
|
<p className='text-xs font-semibold uppercase tracking-[0.18em] text-[#b17a1e]'>
|
||||||
|
Watch points
|
||||||
|
</p>
|
||||||
|
<div className='mt-3'>
|
||||||
|
<PrepText value={latestPrepBrief.sensitive_topics} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='rounded-lg bg-[#fffdf9] p-4 lg:col-span-2'>
|
||||||
|
<p className='text-xs font-semibold uppercase tracking-[0.18em] text-[#b17a1e]'>
|
||||||
|
Previous summary
|
||||||
|
</p>
|
||||||
|
<div className='mt-3'>
|
||||||
|
<PrepText value={latestPrepBrief.previous_summary} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className='p-5'>
|
||||||
|
<EmptyState label='No next-session prep brief yet.' />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Panel>
|
||||||
|
|
||||||
<div className='grid gap-4 lg:grid-cols-2'>
|
<div className='grid gap-4 lg:grid-cols-2'>
|
||||||
<Panel>
|
<Panel>
|
||||||
<div className='border-b border-[#19192d]/10 p-5'>
|
<div className='border-b border-[#19192d]/10 p-5'>
|
||||||
|
|||||||
@ -35,6 +35,17 @@ type Session = {
|
|||||||
client?: Client;
|
client?: Client;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type PrepBrief = {
|
||||||
|
id: string;
|
||||||
|
next_session_at?: string;
|
||||||
|
previous_summary?: string;
|
||||||
|
open_commitments?: string;
|
||||||
|
suggested_questions?: string;
|
||||||
|
sensitive_topics?: string;
|
||||||
|
client_reflection?: string;
|
||||||
|
client?: Client;
|
||||||
|
};
|
||||||
|
|
||||||
type Summary = {
|
type Summary = {
|
||||||
counts: {
|
counts: {
|
||||||
clients: number;
|
clients: number;
|
||||||
@ -45,6 +56,7 @@ type Summary = {
|
|||||||
};
|
};
|
||||||
activeClients: Client[];
|
activeClients: Client[];
|
||||||
nextSessions: Session[];
|
nextSessions: Session[];
|
||||||
|
upcomingPrepBriefs: PrepBrief[];
|
||||||
};
|
};
|
||||||
|
|
||||||
const emptySummary: Summary = {
|
const emptySummary: Summary = {
|
||||||
@ -57,6 +69,7 @@ const emptySummary: Summary = {
|
|||||||
},
|
},
|
||||||
activeClients: [],
|
activeClients: [],
|
||||||
nextSessions: [],
|
nextSessions: [],
|
||||||
|
upcomingPrepBriefs: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
function ShellCard({
|
function ShellCard({
|
||||||
@ -89,7 +102,7 @@ const Dashboard = () => {
|
|||||||
loadSummary();
|
loadSummary();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const nextSession = summary.nextSessions[0];
|
const nextPrepBrief = summary.upcomingPrepBriefs[0];
|
||||||
const stats = [
|
const stats = [
|
||||||
{
|
{
|
||||||
href: '/clients',
|
href: '/clients',
|
||||||
@ -160,22 +173,24 @@ const Dashboard = () => {
|
|||||||
<p className='text-xs font-semibold uppercase tracking-[0.22em] text-[#b17a1e]'>
|
<p className='text-xs font-semibold uppercase tracking-[0.22em] text-[#b17a1e]'>
|
||||||
Next session prep
|
Next session prep
|
||||||
</p>
|
</p>
|
||||||
{nextSession ? (
|
{nextPrepBrief ? (
|
||||||
<div>
|
<div>
|
||||||
<h2 className='mt-4 text-lg font-semibold text-[#19192d]'>
|
<h2 className='mt-4 text-lg font-semibold text-[#19192d]'>
|
||||||
{nextSession.client?.name || 'Client session'}
|
{nextPrepBrief.client?.name || 'Client session'}
|
||||||
</h2>
|
</h2>
|
||||||
<p className='mt-1 text-sm text-[#72798a]'>
|
<p className='mt-1 text-sm text-[#72798a]'>
|
||||||
{nextSession.title}
|
{nextPrepBrief.client?.role_title} ·{' '}
|
||||||
|
{nextPrepBrief.client?.company}
|
||||||
</p>
|
</p>
|
||||||
<p className='mt-5 leading-6 text-[#72798a]'>
|
<p className='mt-5 leading-6 text-[#72798a]'>
|
||||||
{nextSession.ai_summary}
|
{nextPrepBrief.suggested_questions ||
|
||||||
|
nextPrepBrief.previous_summary}
|
||||||
</p>
|
</p>
|
||||||
<Link
|
<Link
|
||||||
href='/session-memory'
|
href={`/clients?clientId=${nextPrepBrief.client?.id}`}
|
||||||
className='mt-4 inline-flex items-center gap-2 rounded-full bg-[#35b7a5] px-3 py-1.5 text-sm font-semibold text-white'
|
className='mt-4 inline-flex items-center gap-2 rounded-full bg-[#35b7a5] px-3 py-1.5 text-sm font-semibold text-white'
|
||||||
>
|
>
|
||||||
Review memory
|
Open prep
|
||||||
<BaseIcon path={mdiArrowRight} size={18} />
|
<BaseIcon path={mdiArrowRight} size={18} />
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
@ -292,6 +307,62 @@ const Dashboard = () => {
|
|||||||
</div>
|
</div>
|
||||||
</ShellCard>
|
</ShellCard>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<ShellCard className='mt-4'>
|
||||||
|
<div className='border-b border-[#19192d]/10 p-4'>
|
||||||
|
<div className='flex items-center justify-between gap-4'>
|
||||||
|
<div>
|
||||||
|
<p className='text-xs font-semibold uppercase tracking-[0.22em] text-[#b17a1e]'>
|
||||||
|
Next-session prep
|
||||||
|
</p>
|
||||||
|
<h2 className='mt-2 text-lg font-semibold'>
|
||||||
|
Ready prep briefs
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<Link
|
||||||
|
className='text-sm font-semibold text-[#35b7a5]'
|
||||||
|
href='/clients'
|
||||||
|
>
|
||||||
|
Open clients
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='grid gap-0 divide-y divide-[#19192d]/10 xl:grid-cols-2 xl:divide-x xl:divide-y-0'>
|
||||||
|
{summary.upcomingPrepBriefs.map((brief) => (
|
||||||
|
<Link
|
||||||
|
key={brief.id}
|
||||||
|
href={`/clients?clientId=${brief.client?.id}`}
|
||||||
|
className='block p-5 transition hover:bg-[#fffdf9]'
|
||||||
|
>
|
||||||
|
<div className='flex items-start justify-between gap-4'>
|
||||||
|
<div>
|
||||||
|
<p className='font-semibold text-[#19192d]'>
|
||||||
|
{brief.client?.name}
|
||||||
|
</p>
|
||||||
|
<p className='mt-1 text-sm text-[#72798a]'>
|
||||||
|
{brief.client?.role_title} · {brief.client?.company}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<span className='rounded-full bg-[#f3fbf8] px-3 py-1 text-xs font-semibold text-[#35b7a5]'>
|
||||||
|
Ready
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<p className='mt-4 line-clamp-2 text-sm leading-6 text-[#72798a]'>
|
||||||
|
{brief.client_reflection ||
|
||||||
|
brief.suggested_questions ||
|
||||||
|
brief.previous_summary}
|
||||||
|
</p>
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
{summary.upcomingPrepBriefs.length === 0 && (
|
||||||
|
<p className='p-5 text-sm text-[#72798a]'>
|
||||||
|
{loading
|
||||||
|
? 'Loading prep briefs...'
|
||||||
|
: 'No prep briefs ready yet.'}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</ShellCard>
|
||||||
</div>
|
</div>
|
||||||
</SectionMain>
|
</SectionMain>
|
||||||
</>
|
</>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user