mirror of
https://github.com/temetro/temetro.git
synced 2026-08-23 08:19:14 +00:00
feat: clinic-wide AI (analytics/earnings/inventory + invoice-from-file),
real Live card, persistent chat history Analytics & earnings: - Analytics now carries real money computed from invoices (billed/paid/ outstanding + by-month); new Earnings section on the Analysis page drawn with the project's Bklit chart components (shared EarningsChart). AI agent reaches the whole clinic: - new read tools getClinicInfo / getAnalytics / listInventory render clinic, analytics (with a Bklit earnings chart) and inventory cards in chat - proposeInvoice turns an uploaded purchase/medication list into an invoice (new "invoice" action-preview kind → createInvoice); invoices/appointments auto-create/link a patient (ensurePatient) so they hit the Patients page Live card: - plots real data — patients checked in today — via GET /api/analytics/live (polled); value pill clamped and margins widened so nothing spills the card Persistent AI chat history (Claude-style): - ai_chat_threads + ai_chat_messages (migration 0016); per-user, org-scoped thread CRUD under /api/chat/threads - chat panel owns a thread id, loads /?thread=<id>, and auto-saves after each exchange; sidebar lists past chats (open/delete), "New chat" starts fresh Verified with backend typecheck + frontend tsc + next build. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -290,30 +290,38 @@ export function LiveLine({
|
||||
/>
|
||||
</g>
|
||||
|
||||
{/* Badge — use popover vars so text is never white-on-white */}
|
||||
{badge && (
|
||||
<g transform={`translate(${liveDotX + 12},${liveDotY})`}>
|
||||
<rect
|
||||
fill="var(--popover)"
|
||||
height={24}
|
||||
opacity={0.95}
|
||||
rx={6}
|
||||
width={formatValue(liveValue).length * 7.5 + 16}
|
||||
x={0}
|
||||
y={-12}
|
||||
/>
|
||||
<text
|
||||
fill="var(--popover-foreground)"
|
||||
fontFamily="SF Mono, Menlo, Monaco, monospace"
|
||||
fontSize={11}
|
||||
fontWeight={500}
|
||||
x={8}
|
||||
y={4}
|
||||
>
|
||||
{formatValue(liveValue)}
|
||||
</text>
|
||||
</g>
|
||||
)}
|
||||
{/* Badge — use popover vars so text is never white-on-white. Flip it to
|
||||
the left of the dot when it would overflow the right edge, so the
|
||||
value never spills outside the card. */}
|
||||
{badge &&
|
||||
(() => {
|
||||
const badgeWidth = formatValue(liveValue).length * 7.5 + 16;
|
||||
const flip = liveDotX + 12 + badgeWidth > innerWidth;
|
||||
const badgeX = flip ? liveDotX - 12 - badgeWidth : liveDotX + 12;
|
||||
return (
|
||||
<g transform={`translate(${badgeX},${liveDotY})`}>
|
||||
<rect
|
||||
fill="var(--popover)"
|
||||
height={24}
|
||||
opacity={0.95}
|
||||
rx={6}
|
||||
width={badgeWidth}
|
||||
x={0}
|
||||
y={-12}
|
||||
/>
|
||||
<text
|
||||
fill="var(--popover-foreground)"
|
||||
fontFamily="SF Mono, Menlo, Monaco, monospace"
|
||||
fontSize={11}
|
||||
fontWeight={500}
|
||||
x={8}
|
||||
y={4}
|
||||
>
|
||||
{formatValue(liveValue)}
|
||||
</text>
|
||||
</g>
|
||||
);
|
||||
})()}
|
||||
</motion.g>
|
||||
</>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user