import { useEffect, useState } from 'react' import { View, Text, ScrollView, StyleSheet, Alert } from 'react-native' import { useAuth } from '../context/AuthContext.jsx' import { householdsApi, usersApi } from '../api/client.js' import { formatDate } from '../utils/searchUtils.js' import { StatusBanner, Panel, SectionHeading, InputField, Btn, BtnRow, EmptyState, EntityRow, EntityMeta, EntityActions, FormNote } from '../components/ui.jsx' import { colors, spacing, fontSize, radius } from '../theme.js' const EMPTY_HOUSEHOLD_FORM = { name: '', description: '' } const EMPTY_USER_FORM = { firstName: '', lastName: '', email: '', password: '', confirmPassword: '' } function formatPersonName(p) { const full = [p.firstName, p.lastName].filter(Boolean).join(' ') return full || p.email || 'Unnamed person' } export default function AdminScreen() { const { isAuthenticated, isSiteAdmin, register } = useAuth() const [loading, setLoading] = useState(false) const [error, setError] = useState('') const [status, setStatus] = useState('') const [households, setHouseholds] = useState([]) const [selectedHouseholdId, setSelectedHouseholdId] = useState('') const [editingHouseholdId, setEditingHouseholdId] = useState('') const [householdForm, setHouseholdForm] = useState(EMPTY_HOUSEHOLD_FORM) const [inviteEmail, setInviteEmail] = useState('') const [userForm, setUserForm] = useState(EMPTY_USER_FORM) const [createdUser, setCreatedUser] = useState(null) const [householdHistory, setHouseholdHistory] = useState([]) const [historyLoading, setHistoryLoading] = useState(false) const [historyError, setHistoryError] = useState('') async function loadHouseholds(preferredId = '') { const r = await householdsApi.getHouseholds() const next = Array.isArray(r) ? r : [] setHouseholds(next) setSelectedHouseholdId(id => { const t = preferredId || id; return next.some(h => h.id === t) ? t : next[0]?.id ?? '' }) setEditingHouseholdId(id => next.some(h => h.id === id) ? id : '') } useEffect(() => { let cancelled = false async function init() { if (!isAuthenticated) { setHouseholds([]); return } setLoading(true); setError('') try { const r = await householdsApi.getHouseholds() if (cancelled) return const next = Array.isArray(r) ? r : [] setHouseholds(next); setSelectedHouseholdId(next[0]?.id ?? '') } catch (e) { if (!cancelled) setError(e.message) } finally { if (!cancelled) setLoading(false) } } init() return () => { cancelled = true } }, [isAuthenticated]) useEffect(() => { let cancelled = false async function loadHistory() { if (!isAuthenticated || !selectedHouseholdId) { setHouseholdHistory([]); return } setHistoryLoading(true); setHistoryError('') try { const r = await householdsApi.getHouseholdHistory(selectedHouseholdId); if (!cancelled) setHouseholdHistory(Array.isArray(r) ? r : []) } catch (e) { if (!cancelled) { setHouseholdHistory([]); setHistoryError(e.message) } } finally { if (!cancelled) setHistoryLoading(false) } } loadHistory() return () => { cancelled = true } }, [isAuthenticated, selectedHouseholdId]) const selectedHousehold = households.find(h => h.id === selectedHouseholdId) ?? null const editingHousehold = households.find(h => h.id === editingHouseholdId) ?? null const canManageSelected = Boolean(selectedHousehold && (isSiteAdmin || selectedHousehold.isCurrentUserHouseholdAdmin)) const canSubmitForm = editingHouseholdId ? Boolean(editingHousehold && (isSiteAdmin || editingHousehold.isCurrentUserHouseholdAdmin)) : isSiteAdmin async function submitHousehold() { const name = householdForm.name.trim() if (!name) { setError('Household name is required.'); return } if (!canSubmitForm) { setError(editingHouseholdId ? 'You can only edit households you administer.' : 'Only site admins can create households.'); return } setLoading(true); setError(''); setStatus('') try { const payload = { name, description: householdForm.description.trim() || null } const result = editingHouseholdId ? await householdsApi.updateHousehold(editingHouseholdId, payload) : await householdsApi.createHousehold(payload) await loadHouseholds(result?.id ?? editingHouseholdId) setEditingHouseholdId(''); setHouseholdForm(EMPTY_HOUSEHOLD_FORM) setStatus(editingHouseholdId ? 'Household updated.' : 'Household created.') } catch (e) { setError(e.message) } finally { setLoading(false) } } async function submitInvite() { const email = inviteEmail.trim() if (!selectedHouseholdId) { setError('Select a household first.'); return } if (!email) { setError('Email is required.'); return } if (!canManageSelected) { setError('You can only invite users to households you administer.'); return } setLoading(true); setError(''); setStatus('') try { await householdsApi.inviteHouseholdMember(selectedHouseholdId, { email }); await loadHouseholds(selectedHouseholdId); setInviteEmail(''); setStatus('Invitation sent.') } catch (e) { setError(e.message) } finally { setLoading(false) } } async function submitCreateUser() { if (!isSiteAdmin) { setError('Only site admins can create users.'); return } const email = userForm.email.trim() if (!email) { setError('Email is required.'); return } if (!userForm.password) { setError('Password is required.'); return } if (userForm.password !== userForm.confirmPassword) { setError('Passwords do not match.'); return } setLoading(true); setError(''); setStatus('') try { const result = await register({ email, password: userForm.password, confirmPassword: userForm.confirmPassword, firstName: userForm.firstName.trim() || null, lastName: userForm.lastName.trim() || null }) try { const users = await usersApi.getUsers() const match = Array.isArray(users) ? users.find(u => u.email?.toLowerCase() === email.toLowerCase()) : null setCreatedUser(match ?? { email, firstName: userForm.firstName.trim(), lastName: userForm.lastName.trim(), roles: [] }) } catch { setCreatedUser({ email, firstName: userForm.firstName.trim(), lastName: userForm.lastName.trim(), roles: [] }) } setUserForm(EMPTY_USER_FORM); setStatus(result?.message || 'User created.') } catch (e) { setError(e.message) } finally { setLoading(false) } } async function handleLeave(id) { Alert.alert('Leave household?', undefined, [ { text: 'Cancel' }, { text: 'Leave', style: 'destructive', onPress: async () => { setLoading(true); setError(''); setStatus('') try { await householdsApi.leaveHousehold(id); await loadHouseholds(selectedHouseholdId === id ? '' : selectedHouseholdId); setStatus('Household left.') } catch (e) { setError(e.message) } finally { setLoading(false) } }}, ]) } if (!isAuthenticated) return ( Sign in to access admin features. ) const totalMembers = households.reduce((n, h) => n + (h.members?.length ?? 0), 0) const managedCount = households.filter(h => h.isCurrentUserHouseholdAdmin || isSiteAdmin).length return ( {error ? {error} : null} {status ? {status} : null} {loading ? Processing... : null} Households{households.length} Managed{managedCount} Members{totalMembers} { setEditingHouseholdId(''); setHouseholdForm(EMPTY_HOUSEHOLD_FORM) }} /> : null} /> {!isSiteAdmin && !editingHouseholdId && Only site admins can create households.} setHouseholdForm(f => ({ ...f, name: v }))} placeholder="Main Household" /> setHouseholdForm(f => ({ ...f, description: v }))} placeholder="Shared pantry access" multiline /> { setEditingHouseholdId(''); setHouseholdForm(EMPTY_HOUSEHOLD_FORM) }} /> {selectedHousehold?.name || 'None selected'}} /> {!selectedHousehold ? Select a household to invite a member. : ( <> {!canManageSelected && You can only invite users to households you administer.} setInviteEmail('')} /> )} {isSiteAdmin ? 'Creates a new account via the register endpoint.' : 'Only site admins can create users.'} setUserForm(f => ({ ...f, firstName: v }))} placeholder="Jordan" editable={isSiteAdmin} /> setUserForm(f => ({ ...f, lastName: v }))} placeholder="Lee" editable={isSiteAdmin} /> setUserForm(f => ({ ...f, email: v }))} keyboardType="email-address" autoCapitalize="none" placeholder="new@example.com" editable={isSiteAdmin} /> setUserForm(f => ({ ...f, password: v }))} secureTextEntry placeholder="Password" editable={isSiteAdmin} /> setUserForm(f => ({ ...f, confirmPassword: v }))} secureTextEntry placeholder="Repeat password" editable={isSiteAdmin} /> setUserForm(EMPTY_USER_FORM)} disabled={!isSiteAdmin} /> {createdUser && ( {formatPersonName(createdUser)} {createdUser.email} {createdUser.id && ID: {createdUser.id}} {(Array.isArray(createdUser.roles) && createdUser.roles.length > 0 ? createdUser.roles : ['No roles assigned'] ).map((r, i) => {r})} )} {selectedHousehold?.name || 'None selected'}} /> {historyLoading ? Loading history... : null} {historyError ? {historyError} : null} {!selectedHousehold ? Select a household to see its history. : householdHistory.length === 0 ? No history returned. : householdHistory.map(entry => ( {entry.action} {formatDate(entry.changedAt) || 'Unknown'} by {entry.changedByEmail || 'Unknown'} {entry.description || 'No description.'} {entry.affectedUserEmail && Affected: {entry.affectedUserEmail}} )) } {households.length} total} /> {households.length === 0 ? No households yet. : households.map(h => { const canManage = isSiteAdmin || h.isCurrentUserHouseholdAdmin return ( {h.name} {h.isCurrentUserHouseholdAdmin && Household admin} {h.description || 'No description.'} Created: {formatDate(h.createdAt) || 'Not available'} {(h.members ?? []).map(member => ( {formatPersonName(member)} {member.email} ))} setSelectedHouseholdId(h.id)} /> {canManage && { setSelectedHouseholdId(h.id); setEditingHouseholdId(h.id); setHouseholdForm({ name: h.name ?? '', description: h.description ?? '' }) }} />} handleLeave(h.id)} /> ) })} ) } const s = StyleSheet.create({ container: { flex: 1, backgroundColor: colors.bg }, content: { padding: spacing.md }, authMsg: { padding: spacing.lg, textAlign: 'center', color: colors.textMuted, fontSize: fontSize.md }, statsGrid: { flexDirection: 'row', gap: spacing.sm }, statCard: { flex: 1, backgroundColor: colors.surfaceMuted, borderRadius: radius.sm, padding: spacing.sm, alignItems: 'center' }, statLabel: { fontSize: fontSize.xs, color: colors.textMuted }, statValue: { fontSize: fontSize.xl, fontWeight: '700', color: colors.text }, strongText: { fontSize: fontSize.md, fontWeight: '600', color: colors.text }, subtleText: { fontSize: fontSize.sm, color: colors.textMuted }, householdHeader: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }, badgeRow: { flexDirection: 'row', gap: spacing.xs }, adminBadge: { backgroundColor: colors.accent + '22', borderRadius: radius.sm, paddingHorizontal: spacing.xs, paddingVertical: 2 }, badgeText: { fontSize: fontSize.xs, color: colors.accent }, memberRow: { paddingVertical: spacing.xs, borderTopWidth: 1, borderColor: colors.border, marginTop: spacing.xs }, memberName: { fontSize: fontSize.sm, fontWeight: '600', color: colors.text }, createdUserCard: { marginTop: spacing.sm, padding: spacing.sm, borderWidth: 1, borderColor: colors.border, borderRadius: radius.sm }, roleList: { flexDirection: 'row', flexWrap: 'wrap', gap: spacing.xs, marginTop: spacing.xs }, roleBadge: { backgroundColor: colors.chipNeutral, borderRadius: radius.sm, paddingHorizontal: spacing.xs, paddingVertical: 2 }, roleBadgeText: { fontSize: fontSize.xs, color: colors.textSoft }, })