import React, { useState, useEffect, useCallback, useMemo } from 'react'; import { StyleSheet, View, Text, FlatList, TextInput, TouchableOpacity, ActivityIndicator, RefreshControl, StatusBar, Alert, Modal } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import { supabase } from '../../lib/supabase'; import { COLORS, SHADOWS } from '../../styles/theme'; import { UserCheck, UserX, Users, Mail, Building2, ShieldAlert, Search, X, Eye, Trash2, Shield, Calendar, Hash, Loader2 } from 'lucide-react-native'; import * as Haptics from 'expo-haptics'; const AdminUsersScreen = () => { const [profiles, setProfiles] = useState([]); const [loading, setLoading] = useState(true); const [refreshing, setRefreshing] = useState(false); const [activeTab, setActiveTab] = useState('pending'); // 'pending' | 'active' const [searchQuery, setSearchQuery] = useState(''); const [currentAdmin, setCurrentAdmin] = useState(null); // Modal / Detail States const [selectedUser, setSelectedUser] = useState(null); const [showDetailModal, setShowDetailModal] = useState(false); const [updatingUser, setUpdatingUser] = useState(null); // ID of user currently being modified in DB const fetchUsers = useCallback(async () => { try { const { data: { user } } = await supabase.auth.getUser(); if (!user) return; setCurrentAdmin(user); const { data: adminProfile } = await supabase .from('profiles') .select('*') .eq('id', user.id) .single(); if (!adminProfile?.company) return; // Fetch all profiles belonging to the same company const { data, error } = await supabase .from('profiles') .select('*') .eq('company', adminProfile.company) .order('full_name', { ascending: true }); if (!error && data) { // Exclude the current logged in admin themselves from directory list setProfiles(data.filter(p => p.id !== user.id)); } } catch (e) { console.error('Fetch company users error:', e); } finally { setLoading(false); setRefreshing(false); } }, []); useEffect(() => { fetchUsers(); // Subscribe to profile changes to update real-time const profilesChannel = supabase .channel('company_profiles_admin') .on('postgres_changes', { event: '*', schema: 'public', table: 'profiles' }, () => fetchUsers()) .subscribe(); return () => { supabase.removeChannel(profilesChannel); }; }, [fetchUsers]); const onRefresh = () => { setRefreshing(true); Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); fetchUsers(); }; const handleApprove = async (userItem) => { Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); setUpdatingUser(userItem.id); try { // 1. Update Profile in public.profiles const { error } = await supabase .from('profiles') .update({ status: 'active', company_id: userItem.company_id || null }) .eq('id', userItem.id); if (error) { Alert.alert("Error", "Could not approve user registration."); } else { // 2. Insert system notification to let user know they're approved await supabase.from('notifications').insert({ user_id: userItem.id, title: "Account Approved", message: `Your registration for ${userItem.company} has been approved by the Administrator. Welcome!`, type: "status_change", is_unread: true }); // 3. Try to trigger background edge email function if available (Non-blocking) (async () => { try { await supabase.functions.invoke('send-user-approval-email', { body: { userId: userItem.id, email: userItem.email, name: userItem.full_name, company: userItem.company } }); } catch (_) {} })(); fetchUsers(); } } catch (e) { console.error(e); } finally { setUpdatingUser(null); } }; const handleDecline = (userItem) => { Haptics.notificationAsync(Haptics.NotificationFeedbackType.Warning); Alert.alert( "Decline Registration", `Are you sure you want to reject ${userItem.full_name}'s registration?`, [ { text: "Cancel", style: "cancel" }, { text: "Decline", style: "destructive", onPress: async () => { setUpdatingUser(userItem.id); try { const { error } = await supabase .from('profiles') .update({ status: 'rejected' }) .eq('id', userItem.id); if (error) { Alert.alert("Error", "Could not reject user."); } else { fetchUsers(); } } catch (e) { console.error(e); } finally { setUpdatingUser(null); } } } ] ); }; const handleUpdateRole = async (userItem, newRole) => { Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium); setUpdatingUser(userItem.id); try { const { error } = await supabase .from('profiles') .update({ role: newRole }) .eq('id', userItem.id); if (error) { Alert.alert("Error", "Could not update user security clearance."); } else { if (selectedUser?.id === userItem.id) { setSelectedUser(prev => ({ ...prev, role: newRole })); } fetchUsers(); } } catch (e) { console.error(e); } finally { setUpdatingUser(null); } }; const handlePurgeUser = (userItem) => { Haptics.notificationAsync(Haptics.NotificationFeedbackType.Warning); Alert.alert( "Purge User Record", `CRITICAL: You are about to permanently delete @${userItem.full_name}'s profile from the system active directory. This action is irreversible.`, [ { text: "Abort", style: "cancel" }, { text: "Confirm Purge", style: "destructive", onPress: async () => { setUpdatingUser(userItem.id); try { const { error } = await supabase .from('profiles') .delete() .eq('id', userItem.id); if (error) { Alert.alert("Error", "Could not delete user profile from DB."); } else { setShowDetailModal(false); setSelectedUser(null); fetchUsers(); } } catch (e) { console.error(e); } finally { setUpdatingUser(null); } } } ] ); }; const getFilteredUsers = useMemo(() => { let filtered = profiles; // Tab Filter if (activeTab === 'pending') { filtered = filtered.filter(p => p.status === 'pending_approval'); } else { filtered = filtered.filter(p => p.status === 'active'); } // Search Query Filter if (searchQuery.trim()) { const q = searchQuery.toLowerCase().trim(); filtered = filtered.filter(p => p.full_name?.toLowerCase().includes(q) || p.email?.toLowerCase().includes(q) || p.role?.toLowerCase().includes(q) || String(p.id).includes(q) ); } return filtered; }, [profiles, activeTab, searchQuery]); const openProfileDetail = (userItem) => { Haptics.selectionAsync(); setSelectedUser(userItem); setShowDetailModal(true); }; const renderUserItem = ({ item }) => { const isPending = item.status === 'pending_approval'; const isUserAdmin = item.role === 'admin' || item.role === 'master_admin'; return ( openProfileDetail(item)} activeOpacity={0.7} > {item.full_name?.[0]?.toUpperCase() || 'U'} openProfileDetail(item)} activeOpacity={0.7}> {item.full_name} {item.email} {isUserAdmin ? 'Administrator' : 'Standard Employee'} {updatingUser === item.id ? ( ) : isPending ? ( handleApprove(item)} title="Approve" > handleDecline(item)} title="Decline" > ) : ( openProfileDetail(item)} > )} ); }; return ( {/* Header */} Company Directory {/* Search Input */} {searchQuery.length > 0 && ( setSearchQuery('')} style={styles.clearSearchBtn}> )} {/* Tabs */} { Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); setActiveTab('pending'); }} > Pending Review ({profiles.filter(p => p.status === 'pending_approval').length}) { Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); setActiveTab('active'); }} > Active Members ({profiles.filter(p => p.status === 'active').length}) {/* List */} {loading ? ( ) : ( item.id} renderItem={renderUserItem} contentContainerStyle={styles.list} showsVerticalScrollIndicator={false} RefreshControl={} ListEmptyComponent={ {activeTab === 'pending' ? 'No registrations pending review' : 'No active members match your search'} } /> )} {/* Sliding Profile Detail Modal Sheet */} setShowDetailModal(false)} > {/* Modal Header */} {selectedUser?.full_name?.[0]?.toUpperCase() || 'U'} {selectedUser?.full_name} {selectedUser?.email} setShowDetailModal(false)} style={styles.closeBtn}> {/* Modal Body */} {selectedUser && ( ENTITY METADATA SYSTEM UUID {selectedUser.id} JOINED DATE {new Date(selectedUser.created_at).toLocaleString(undefined, { dateStyle: 'medium', timeStyle: 'short' })} SECURITY PROTOCOLS handleUpdateRole(selectedUser, 'user')} > Standard User handleUpdateRole(selectedUser, 'admin')} > Administrator {/* Loading state indicator for updates */} {updatingUser === selectedUser.id && ( SYNCHRONIZING ROLES... )} {/* Direct Actions */} handlePurgeUser(selectedUser)} disabled={updatingUser === selectedUser.id} > Purge Employee Record )} ); }; const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: COLORS.background }, header: { paddingHorizontal: 24, paddingTop: 16, paddingBottom: 16 }, title: { fontSize: 28, fontWeight: '900', color: COLORS.text, letterSpacing: -0.8 }, // Search searchSection: { paddingHorizontal: 20, marginBottom: 12 }, searchBar: { flexDirection: 'row', alignItems: 'center', backgroundColor: '#fff', borderRadius: 16, paddingHorizontal: 16, height: 52, ...SHADOWS.soft, borderWidth: 1, borderColor: 'rgba(0,0,0,0.03)' }, input: { flex: 1, marginLeft: 10, fontSize: 14, fontWeight: '600', color: COLORS.text }, clearSearchBtn: { padding: 4 }, // Tabs tabContainer: { flexDirection: 'row', marginHorizontal: 20, backgroundColor: 'rgba(0,0,0,0.03)', borderRadius: 16, padding: 4, marginBottom: 16, }, tabButton: { flex: 1, flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: 6, paddingVertical: 12, borderRadius: 12, }, activeTabButton: { backgroundColor: '#ffffff', ...SHADOWS.soft, }, tabText: { fontSize: 12.5, fontWeight: '700', color: COLORS.textMuted, }, activeTabText: { color: COLORS.text, }, // Cards List list: { paddingHorizontal: 20, paddingBottom: 120 }, userCard: { flexDirection: 'row', alignItems: 'center', backgroundColor: '#fff', borderRadius: 24, padding: 16, marginBottom: 12, borderWidth: 1, borderColor: 'rgba(0,0,0,0.03)', ...SHADOWS.soft }, avatar: { width: 46, height: 46, borderRadius: 14, backgroundColor: COLORS.primaryLight, justifyContent: 'center', alignItems: 'center', marginRight: 14 }, avatarText: { fontSize: 16, fontWeight: '800', color: COLORS.primary }, userInfo: { flex: 1, gap: 3 }, userName: { fontSize: 15, fontWeight: '800', color: COLORS.text }, metaRow: { flexDirection: 'row', alignItems: 'center', gap: 6 }, metaText: { fontSize: 12, color: COLORS.textMuted, fontWeight: '600', maxWidth: '90%' }, // Actions actions: { flexDirection: 'row', gap: 8 }, actionBtn: { width: 38, height: 38, borderRadius: 12, justifyContent: 'center', alignItems: 'center', ...SHADOWS.soft }, approveBtn: { backgroundColor: COLORS.primary }, declineBtn: { backgroundColor: '#ef4444' }, viewDetailsBtn: { width: 36, height: 36, borderRadius: 10, backgroundColor: '#f3f4f6', justifyContent: 'center', alignItems: 'center' }, centered: { flex: 1, justifyContent: 'center', alignItems: 'center', marginTop: 100 }, empty: { alignItems: 'center', marginTop: 100, gap: 12 }, emptyText: { fontSize: 14, color: COLORS.textMuted, fontWeight: '600', textAlign: 'center', paddingHorizontal: 40 }, // Sliding sheet modal styles modalOverlay: { flex: 1, backgroundColor: 'rgba(0,0,0,0.45)', justifyContent: 'flex-end' }, modalSheet: { backgroundColor: '#fff', borderTopLeftRadius: 32, borderTopRightRadius: 32, padding: 24, maxHeight: '85%', ...SHADOWS.soft }, modalHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 24 }, modalAvatar: { width: 50, height: 50, borderRadius: 16, backgroundColor: COLORS.primaryLight, justifyContent: 'center', alignItems: 'center' }, modalAvatarText: { fontSize: 18, fontWeight: '900', color: COLORS.primary }, modalTitle: { fontSize: 18, fontWeight: '900', color: COLORS.text, maxWidth: 220 }, modalSubtitle: { fontSize: 12, color: COLORS.textMuted, fontWeight: '600', marginTop: 2, maxWidth: 220 }, closeBtn: { width: 36, height: 36, borderRadius: 18, backgroundColor: '#f3f4f6', justifyContent: 'center', alignItems: 'center' }, modalBody: { gap: 18 }, sectionHeader: { fontSize: 10.5, fontWeight: '800', color: COLORS.textMuted, letterSpacing: 1.2 }, metaCard: { backgroundColor: '#f8faf9', borderRadius: 20, paddingHorizontal: 16, paddingVertical: 8, borderWidth: 1, borderColor: 'rgba(0,0,0,0.03)' }, metaCardRow: { flexDirection: 'row', alignItems: 'center', gap: 12, paddingVertical: 12, borderBottomWidth: 1, borderBottomColor: 'rgba(0,0,0,0.04)' }, metaLabel: { fontSize: 9.5, fontWeight: '800', color: COLORS.textMuted, letterSpacing: 0.5 }, metaValue: { fontSize: 13, fontWeight: '700', color: COLORS.text, marginTop: 2 }, metaValueMono: { fontSize: 11.5, fontFamily: 'monospace', fontWeight: '700', color: COLORS.text, marginTop: 2 }, rolesRow: { flexDirection: 'row', gap: 12 }, roleOptionCard: { flex: 1, paddingVertical: 16, alignItems: 'center', gap: 8, borderRadius: 20, borderWidth: 1.5, borderColor: '#e5e7eb', backgroundColor: '#fff' }, activeRoleCard: { borderColor: COLORS.primary, backgroundColor: COLORS.primaryLight + '12' }, roleOptionText: { fontSize: 12.5, fontWeight: '700', color: COLORS.textMuted }, activeRoleText: { color: COLORS.text, fontWeight: '900' }, modalActions: { marginTop: 8 }, purgeBtn: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: 8, backgroundColor: '#ef4444', height: 52, borderRadius: 18, ...SHADOWS.soft }, purgeBtnText: { fontSize: 13, fontWeight: '800', color: '#fff', textTransform: 'uppercase', letterSpacing: 0.5 } }); export default AdminUsersScreen;