diff --git a/src/app/admin/families/page.tsx b/src/app/admin/families/page.tsx index c14e71a..16a035b 100644 --- a/src/app/admin/families/page.tsx +++ b/src/app/admin/families/page.tsx @@ -3,6 +3,14 @@ import { useEffect, useState } from "react"; import { useRouter } from "next/navigation"; +interface Member { + id: string; + userId: string; + email: string; + role: string; + displayName: string; +} + interface Family { id: string; name: string; @@ -12,6 +20,7 @@ interface Family { createdAt: string; userCount: number; childCount: number; + members: Member[]; } export default function AdminFamilies() { @@ -20,6 +29,8 @@ export default function AdminFamilies() { const [loading, setLoading] = useState(true); const [search, setSearch] = useState(""); const [tierFilter, setTierFilter] = useState("all"); + const [showMembers, setShowMembers] = useState(null); + const [addMember, setAddMember] = useState<{familyId: string; email: string; role: string; name: string} | null>(null); useEffect(() => { const token = localStorage.getItem("admin_token"); @@ -43,6 +54,39 @@ export default function AdminFamilies() { setLoading(false); }; + const handleAddMember = async () => { + if (!addMember?.email || !addMember?.familyId) return; + try { + const res = await fetch("/api/admin/families", { + method: "POST", + headers: { + Authorization: `Bearer ${localStorage.getItem("admin_token")}`, + "Content-Type": "application/json", + }, + body: JSON.stringify(addMember), + }); + if (res.ok) { + fetchFamilies(); + setAddMember(null); + } + } catch (err) { + console.error("Failed to add member:", err); + } + }; + + const handleRemoveMember = async (memberId: string) => { + if (!confirm("Remove this member?")) return; + try { + const res = await fetch(`/api/admin/families?memberId=${memberId}`, { + method: "DELETE", + headers: { Authorization: `Bearer ${localStorage.getItem("admin_token")}` }, + }); + if (res.ok) fetchFamilies(); + } catch (err) { + console.error("Failed to remove:", err); + } + }; + const filteredFamilies = families.filter((f) => { const matchesSearch = f.name.toLowerCase().includes(search.toLowerCase()); const matchesTier = tierFilter === "all" || f.tier === tierFilter; @@ -125,27 +169,66 @@ export default function AdminFamilies() { {filteredFamilies.map((family) => ( - - -
{family.name}
-
{family.id}
- - - - {family.tier} - - - {family.userCount} - {family.childCount} - - {family.maxChildren} kids, {family.maxMembers} members - - - {family.createdAt?.slice(0, 10)} - - + <> + + +
{family.name}
+
{family.id}
+ + + + {family.tier} + + + + + + {family.childCount} + + {family.maxChildren} kids, {family.maxMembers} members + + + {family.createdAt?.slice(0, 10)} + + + {showMembers === family.id && ( + + +
+
+ setAddMember({familyId: family.id, email: e.target.value, role: "caregiver", name: ""})} + className="flex-1 bg-gray-700 border border-gray-600 rounded px-3 py-2 text-white" + /> + + +
+ {(family.members || []).map((m) => ( +
+ {m.email} ({m.role}) + +
+ ))} +
+ + + )} + ))} diff --git a/src/app/api/admin/families/route.ts b/src/app/api/admin/families/route.ts index e15518c..df662a2 100644 --- a/src/app/api/admin/families/route.ts +++ b/src/app/api/admin/families/route.ts @@ -1,6 +1,7 @@ import { NextResponse } from "next/server"; import { sql } from "@/db"; +// GET all families with members export async function GET(request: Request) { try { const authHeader = request.headers.get("authorization"); @@ -8,7 +9,7 @@ export async function GET(request: Request) { return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } - // Get all families with user and child counts using family_members table + // Get all families with user and child counts const families = await sql` SELECT f.id, @@ -26,6 +27,30 @@ export async function GET(request: Request) { ORDER BY f.created_at DESC `; + // Get members for each family + const familyIds = families.map((f: any) => f.id); + let members: any[] = []; + if (familyIds.length > 0) { + members = await sql` + SELECT fm.id, fm.family_id, fm.user_id, fm.role, fm.display_name, u.email + FROM family_members fm + JOIN users u ON u.id = fm.user_id + WHERE fm.family_id = ANY(${familyIds}) + `; + } + + const memberMap = new Map(); + (members || []).forEach((m: any) => { + if (!memberMap.has(m.family_id)) memberMap.set(m.family_id, []); + memberMap.get(m.family_id).push({ + id: m.id, + userId: m.user_id, + email: m.email, + role: m.role, + displayName: m.display_name, + }); + }); + return NextResponse.json({ families: families.map((f: any) => ({ id: f.id, @@ -36,6 +61,7 @@ export async function GET(request: Request) { createdAt: f.created_at ? new Date(f.created_at).toISOString() : null, userCount: Number(f.user_count) || 0, childCount: Number(f.child_count) || 0, + members: memberMap.get(f.id) || [], })), }); } catch (error) { @@ -69,6 +95,69 @@ export async function PATCH(request: Request) { return NextResponse.json({ success: true }); } catch (error) { + console.error("Admin families error:", error); + return NextResponse.json({ error: String(error) }, { status: 500 }); + } +} + +// Add member to family +export async function POST(request: Request) { + try { + const authHeader = request.headers.get("authorization"); + if (!authHeader || !authHeader.startsWith("Bearer ")) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + + const body = await request.json(); + const { familyId, email, role, displayName } = body; + + if (!familyId || !email) { + return NextResponse.json({ error: "familyId and email required" }, { status: 400 }); + } + + // Find or create user + let users = await sql`SELECT id FROM users WHERE email = ${email}`; + let userId = users?.[0]?.id; + + if (!userId) { + userId = crypto.randomUUID(); + await sql`INSERT INTO users (id, email, created_at, updated_at) VALUES (${userId}, ${email}, NOW(), NOW())`; + } + + // Add to family_members + await sql` + INSERT INTO family_members (id, family_id, user_id, role, display_name, created_at) + VALUES (${crypto.randomUUID()}, ${familyId}, ${userId}, ${role || 'caregiver'}, ${displayName || email}, NOW()) + ON CONFLICT DO NOTHING + `; + + return NextResponse.json({ success: true }); + } catch (error) { + console.error("Admin add member error:", error); + return NextResponse.json({ error: String(error) }, { status: 500 }); + } +} + +// Remove member from family +export async function DELETE(request: Request) { + try { + const authHeader = request.headers.get("authorization"); + if (!authHeader || !authHeader.startsWith("Bearer ")) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + + const { searchParams } = new URL(request.url); + const memberId = searchParams.get("memberId"); + + if (!memberId) { + return NextResponse.json({ error: "memberId required" }, { status: 400 }); + } + + await sql`DELETE FROM family_members WHERE id = ${memberId}`; + + return NextResponse.json({ success: true }); + } catch (error) { + console.error("Admin remove member error:", error); return NextResponse.json({ error: String(error) }, { status: 500 }); } } \ No newline at end of file