feat(settings): add Pediatrician Name field alongside phone

- New families.pediatrician_name column (migration 0008)
- Settings card: Name input above Phone input, single Save
- Emergency page: shows doctor's name above the call button
- AI medical redirects: personalised "Call Dr. X: +91…" message

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Manohar Gupta 2026-05-28 00:49:05 +05:30
parent 260187b0de
commit 90c8d13814
8 changed files with 54 additions and 23 deletions

View file

@ -0,0 +1,2 @@
ALTER TABLE families
ADD COLUMN IF NOT EXISTS pediatrician_name text;

View file

@ -57,6 +57,13 @@
"when": 1748480400000,
"tag": "0007_subscription_status",
"breakpoints": true
},
{
"idx": 8,
"version": "7",
"when": 1748566800000,
"tag": "0008_pediatrician_name",
"breakpoints": true
}
]
}

View file

@ -17,10 +17,12 @@ const RULE_META: Record<string, { icon: string; title: string; color: string }>
export default function EmergencyPage() {
const [phone, setPhone] = useState<string | null>(null);
const [name, setName] = useState<string | null>(null);
useEffect(() => {
fetch("/api/family").then(r => r.json()).then(d => {
setPhone(d.family?.pediatrician_phone || null);
setName(d.family?.pediatrician_name || null);
}).catch(() => {});
}, []);
@ -38,20 +40,25 @@ export default function EmergencyPage() {
</div>
{/* Call button */}
<div className="p-4">
<div className="p-4 space-y-1">
{phone ? (
<a
href={`tel:${phone}`}
className="w-full flex items-center justify-center gap-3 bg-red-500 hover:bg-red-600 text-white rounded-2xl p-4 text-lg font-bold shadow-md shadow-red-200 dark:shadow-red-900/30 transition-colors"
>
📞 Call Pediatrician Now
</a>
<>
{name && (
<p className="text-center text-sm font-medium text-gray-600 dark:text-gray-300 mb-2">{name}</p>
)}
<a
href={`tel:${phone}`}
className="w-full flex items-center justify-center gap-3 bg-red-500 hover:bg-red-600 text-white rounded-2xl p-4 text-lg font-bold shadow-md shadow-red-200 dark:shadow-red-900/30 transition-colors"
>
📞 Call Pediatrician Now
</a>
</>
) : (
<Link
href="/settings"
className="w-full flex items-center justify-center gap-2 bg-gray-200 dark:bg-gray-700 text-gray-600 dark:text-gray-300 rounded-2xl p-4 font-medium"
>
+ Add pediatrician phone in Settings
+ Add pediatrician info in Settings
</Link>
)}
</div>

View file

@ -39,6 +39,7 @@ export default function SettingsPage() {
const [inviteRole, setInviteRole] = useState("caregiver");
const [inviteLoading, setInviteLoading] = useState(false);
const [pedPhone, setPedPhone] = useState("");
const [pedName, setPedName] = useState("");
const [pedSaving, setPedSaving] = useState(false);
// Check if can invite more members (client-side pre-check; server enforces)
@ -60,6 +61,7 @@ export default function SettingsPage() {
fetchInvites();
fetch("/api/family").then(r => r.json()).then(d => {
if (d.family?.pediatrician_phone) setPedPhone(d.family.pediatrician_phone);
if (d.family?.pediatrician_name) setPedName(d.family.pediatrician_name);
}).catch(() => {});
}
}, [familyId]);
@ -92,12 +94,12 @@ export default function SettingsPage() {
setExporting(false);
};
const savePedPhone = async () => {
const savePedInfo = async () => {
setPedSaving(true);
await fetch("/api/family", {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ pediatricianPhone: pedPhone }),
body: JSON.stringify({ pediatricianPhone: pedPhone, pediatricianName: pedName }),
}).catch(() => {});
setPedSaving(false);
};
@ -356,16 +358,22 @@ export default function SettingsPage() {
)}
</div>
{/* Pediatrician Phone */}
<div className="bg-white dark:bg-gray-800 rounded-xl p-4 space-y-2">
{/* Pediatrician */}
<div className="bg-white dark:bg-gray-800 rounded-xl p-4 space-y-3">
<div className="flex items-center gap-2">
<span className="text-xl">🏥</span>
<div className="font-medium dark:text-white">Pediatrician Phone</div>
<div className="font-medium dark:text-white">Pediatrician</div>
</div>
<p className="text-xs text-gray-400 dark:text-gray-500">Shown on the emergency guide and in AI medical redirects.</p>
<Input
type="text"
value={pedName}
onChange={e => setPedName(e.target.value)}
placeholder="Dr. Priya Sharma"
/>
<div className="flex gap-2">
<Input type="tel" value={pedPhone} onChange={e => setPedPhone(e.target.value)} placeholder="+91 98765 43210" className="flex-1" />
<Button size="sm" loading={pedSaving} onClick={savePedPhone}>Save</Button>
<Button size="sm" loading={pedSaving} onClick={savePedInfo}>Save</Button>
</div>
<Link href="/medical/emergency" className="text-xs text-rose-500 dark:text-rose-400">
View Emergency Guide

View file

@ -38,8 +38,10 @@ export async function POST(request: Request) {
// ── 1. HARD GUARDRAIL: keyword-based medical detection (most conservative) ──
const medicalIntent = detectMedicalIntent(lastUserMsg);
if (medicalIntent.isMedical) {
const families = await sql`SELECT pediatrician_phone FROM families WHERE id = ${familyId} LIMIT 1`;
const families = await sql`SELECT pediatrician_phone, pediatrician_name FROM families WHERE id = ${familyId} LIMIT 1`;
const phone = families[0]?.pediatrician_phone;
const pedName = families[0]?.pediatrician_name;
const pedLabel = pedName ? `Dr. ${pedName.replace(/^Dr\.?\s*/i, "")}` : "your pediatrician";
const reply = [
`I can't interpret symptoms — that's a pediatrician's job, not mine.`,
@ -47,8 +49,8 @@ export async function POST(request: Request) {
ESCALATION_RULES[medicalIntent.category],
``,
phone
? `Call your pediatrician now: ${phone}`
: `Add your pediatrician's phone in Settings so I can show it here.`,
? `Call ${pedLabel} now: ${phone}`
: `Add your pediatrician's info in Settings so I can show it here.`,
].join("\n");
await logAudit({
@ -84,13 +86,15 @@ export async function POST(request: Request) {
// Medical_redirect from classifier → also redirect
if (classification.intent === "medical_redirect") {
const families = await sql`SELECT pediatrician_phone FROM families WHERE id = ${familyId} LIMIT 1`;
const families = await sql`SELECT pediatrician_phone, pediatrician_name FROM families WHERE id = ${familyId} LIMIT 1`;
const phone = families[0]?.pediatrician_phone;
const pedName = families[0]?.pediatrician_name;
const pedLabel = pedName ? `Dr. ${pedName.replace(/^Dr\.?\s*/i, "")}` : "your pediatrician";
const reply = [
`That sounds like something your pediatrician should assess.`,
``,
ESCALATION_RULES["default"],
phone ? `Call: ${phone}` : `Add your pediatrician's phone in Settings.`,
phone ? `Call ${pedLabel}: ${phone}` : `Add your pediatrician's info in Settings.`,
].join("\n");
await logUsage({ familyId, userId: session.userId, intent: "medical_redirect", durationMs: Date.now() - start });

View file

@ -35,6 +35,8 @@ export async function POST(req: Request) {
`ALTER TABLE family_invites ADD COLUMN IF NOT EXISTS accepted_at timestamp`,
// subscription_status on families (0007) — payment-provider abstraction
`ALTER TABLE families ADD COLUMN IF NOT EXISTS subscription_status varchar(20) DEFAULT NULL`,
// pediatrician_name on families (0008)
`ALTER TABLE families ADD COLUMN IF NOT EXISTS pediatrician_name text`,
// circles tables (0003)
`CREATE TABLE IF NOT EXISTS circles (id uuid PRIMARY KEY DEFAULT gen_random_uuid(), name text NOT NULL, created_by uuid NOT NULL REFERENCES families(id), created_at timestamptz NOT NULL DEFAULT now())`,
`CREATE TABLE IF NOT EXISTS circle_members (circle_id uuid NOT NULL REFERENCES circles(id) ON DELETE CASCADE, family_id uuid NOT NULL REFERENCES families(id), role text NOT NULL DEFAULT 'member', joined_at timestamptz NOT NULL DEFAULT now(), PRIMARY KEY (circle_id, family_id))`,

View file

@ -9,7 +9,7 @@ export async function GET(request: Request) {
try {
const family = await sql.unsafe(
`SELECT id, name, tier, max_children, max_members FROM families WHERE id = $1`,
`SELECT id, name, tier, max_children, max_members, pediatrician_phone, pediatrician_name FROM families WHERE id = $1`,
[auth.session!.familyId]
);
@ -31,11 +31,11 @@ export async function PATCH(request: Request) {
try {
const body = await request.json();
const { name, pediatricianPhone, tier } = body;
const { name, pediatricianPhone, pediatricianName, tier } = body;
await sql.unsafe(
`UPDATE families SET name = COALESCE($1, name), pediatrician_phone = COALESCE($2, pediatrician_phone), tier = COALESCE($3, tier), updated_at = NOW() WHERE id = $4`,
[name, pediatricianPhone, tier, auth.session!.familyId]
`UPDATE families SET name = COALESCE($1, name), pediatrician_phone = COALESCE($2, pediatrician_phone), pediatrician_name = COALESCE($3, pediatrician_name), tier = COALESCE($4, tier), updated_at = NOW() WHERE id = $5`,
[name, pediatricianPhone, pediatricianName, tier, auth.session!.familyId]
);
return NextResponse.json({ success: true });

View file

@ -49,6 +49,7 @@ export const families = pgTable("families", {
maxChildren: integer("max_children").default(1),
maxMembers: integer("max_members").default(2),
pediatricianPhone: text("pediatrician_phone"),
pediatricianName: text("pediatrician_name"),
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at").defaultNow().notNull(),
});