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:
parent
260187b0de
commit
90c8d13814
8 changed files with 54 additions and 23 deletions
2
drizzle/0008_pediatrician_name.sql
Normal file
2
drizzle/0008_pediatrician_name.sql
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE families
|
||||
ADD COLUMN IF NOT EXISTS pediatrician_name text;
|
||||
|
|
@ -57,6 +57,13 @@
|
|||
"when": 1748480400000,
|
||||
"tag": "0007_subscription_status",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 8,
|
||||
"version": "7",
|
||||
"when": 1748566800000,
|
||||
"tag": "0008_pediatrician_name",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -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 ? (
|
||||
<>
|
||||
{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>
|
||||
|
|
|
|||
|
|
@ -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 →
|
||||
|
|
|
|||
|
|
@ -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 });
|
||||
|
|
|
|||
|
|
@ -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))`,
|
||||
|
|
|
|||
|
|
@ -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 });
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue