mirror of
https://github.com/temetro/temetro.git
synced 2026-09-03 14:27:58 +00:00
Add command palette, footer clinic menu, patient Sheet, Analysis page, favicon
Several navigation/UX additions: - Command palette (⌘K): components/command-palette.tsx wraps the app shell (mounted in app/(app)/layout.tsx) with a controlled COSS CommandDialog listing the nav pages; a "Quick nav" Kbd button in the sidebar footer also opens it. Installed @coss/kbd. Extracted the nav list into lib/nav.ts so the sidebar and palette share one source of truth. - Clinic switcher moved from the sidebar body into the footer; its menu now opens a read-only "Clinic info" dialog and a "Create clinic" dialog instead of routing to /onboarding. Shared CreateClinicForm (components/clinic/) is reused by onboarding. - Patients: clicking a row opens a right-side Sheet with the full record (components/patients/patient-detail-sheet.tsx) instead of navigating to the chat; PatientResult gained a vertical "column" layout for the Sheet. - New Analysis page (app/(app)/analysis/) — a mock dashboard (revenue/profit, patient volume, appointments, operations) reusing Sparkline + Card + Badge. - Logo: set metadata.icons so the new mark appears as the browser-tab favicon. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
import { AnalysisView } from "@/components/analysis/analysis-view";
|
||||
import { SidebarInset } from "@/components/ui/sidebar";
|
||||
|
||||
export default function AnalysisPage() {
|
||||
return (
|
||||
<SidebarInset className="flex flex-1 flex-col overflow-y-auto">
|
||||
<AnalysisView />
|
||||
</SidebarInset>
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { AppAuthGuard } from "@/components/auth/app-auth-guard";
|
||||
import { CommandPaletteProvider } from "@/components/command-palette";
|
||||
import { DashboardSidebar } from "@/components/sidebar-02/app-sidebar";
|
||||
import { SidebarProvider } from "@/components/ui/sidebar";
|
||||
|
||||
@@ -9,12 +10,14 @@ export default function AppLayout({
|
||||
}) {
|
||||
return (
|
||||
<AppAuthGuard>
|
||||
<SidebarProvider>
|
||||
<div className="relative flex h-dvh w-full">
|
||||
<DashboardSidebar />
|
||||
{children}
|
||||
</div>
|
||||
</SidebarProvider>
|
||||
<CommandPaletteProvider>
|
||||
<SidebarProvider>
|
||||
<div className="relative flex h-dvh w-full">
|
||||
<DashboardSidebar />
|
||||
{children}
|
||||
</div>
|
||||
</SidebarProvider>
|
||||
</CommandPaletteProvider>
|
||||
</AppAuthGuard>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,32 +1,16 @@
|
||||
"use client";
|
||||
|
||||
import { useRouter } from "next/navigation";
|
||||
import { type FormEvent, useEffect, useState } from "react";
|
||||
import { useEffect } from "react";
|
||||
|
||||
import { AuthShell, Field, FormAlert } from "@/components/auth/auth-ui";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { AuthShell } from "@/components/auth/auth-ui";
|
||||
import { CreateClinicForm } from "@/components/clinic/create-clinic-form";
|
||||
import { authClient } from "@/lib/auth-client";
|
||||
import { notify } from "@/lib/toast";
|
||||
|
||||
function slugify(value: string): string {
|
||||
return value
|
||||
.toLowerCase()
|
||||
.trim()
|
||||
.replace(/[^a-z0-9]+/g, "-")
|
||||
.replace(/^-+|-+$/g, "");
|
||||
}
|
||||
|
||||
export default function OnboardingPage() {
|
||||
const router = useRouter();
|
||||
const { data: session, isPending } = authClient.useSession();
|
||||
|
||||
const [name, setName] = useState("");
|
||||
const [slug, setSlug] = useState("");
|
||||
const [slugEdited, setSlugEdited] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
|
||||
// Send unauthenticated users to login. Authenticated users (whether brand
|
||||
// new or creating an additional clinic) stay on this page.
|
||||
useEffect(() => {
|
||||
@@ -34,69 +18,12 @@ export default function OnboardingPage() {
|
||||
if (!session?.user) router.replace("/login");
|
||||
}, [session, isPending, router]);
|
||||
|
||||
const onSubmit = async (event: FormEvent) => {
|
||||
event.preventDefault();
|
||||
if (submitting) return;
|
||||
setSubmitting(true);
|
||||
setError(null);
|
||||
|
||||
const finalSlug = (slugEdited ? slug : slugify(name)) || slugify(name);
|
||||
const { data: org, error: createErr } = await authClient.organization.create(
|
||||
{ name: name.trim(), slug: finalSlug }
|
||||
);
|
||||
|
||||
if (createErr || !org) {
|
||||
const message = createErr?.message ?? "Could not create the clinic.";
|
||||
setError(message);
|
||||
notify.error("Couldn't create clinic", message);
|
||||
setSubmitting(false);
|
||||
return;
|
||||
}
|
||||
|
||||
await authClient.organization.setActive({ organizationId: org.id });
|
||||
notify.success("Clinic created", `${org.name} is ready.`);
|
||||
router.push("/");
|
||||
};
|
||||
|
||||
return (
|
||||
<AuthShell
|
||||
subtitle="Create your clinic to start organizing patient records"
|
||||
title="Set up your clinic"
|
||||
>
|
||||
<form className="flex flex-col gap-4" onSubmit={onSubmit}>
|
||||
{error && <FormAlert>{error}</FormAlert>}
|
||||
<Field htmlFor="name" label="Clinic name">
|
||||
<Input
|
||||
id="name"
|
||||
onChange={(e) => {
|
||||
setName(e.target.value);
|
||||
if (!slugEdited) setSlug(slugify(e.target.value));
|
||||
}}
|
||||
placeholder="North Side Family Practice"
|
||||
required
|
||||
value={name}
|
||||
/>
|
||||
</Field>
|
||||
<Field
|
||||
hint="Used in links and invitations. Lowercase letters, numbers and dashes."
|
||||
htmlFor="slug"
|
||||
label="Clinic URL slug"
|
||||
>
|
||||
<Input
|
||||
id="slug"
|
||||
onChange={(e) => {
|
||||
setSlugEdited(true);
|
||||
setSlug(slugify(e.target.value));
|
||||
}}
|
||||
placeholder="north-side-family-practice"
|
||||
required
|
||||
value={slug}
|
||||
/>
|
||||
</Field>
|
||||
<Button className="mt-1 w-full" disabled={submitting} type="submit">
|
||||
{submitting ? "Creating clinic…" : "Create clinic"}
|
||||
</Button>
|
||||
</form>
|
||||
<CreateClinicForm onCreated={() => router.push("/")} />
|
||||
</AuthShell>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ export const metadata: Metadata = {
|
||||
title: "temetro — AI assistant for clinicians",
|
||||
description:
|
||||
"Retrieve patient information by simply asking. The open-source AI assistant for clinicians.",
|
||||
icons: { icon: "/temetro-logo.png", apple: "/temetro-logo.png" },
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
|
||||
Reference in New Issue
Block a user