import React, { useState, useEffect, useCallback } from 'react';
import {
StyleSheet, View, Text, FlatList, TextInput, TouchableOpacity,
ActivityIndicator, RefreshControl, StatusBar
} from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { useNavigation, useFocusEffect } from '@react-navigation/native';
import { supabase } from '../../lib/supabase';
import { COLORS, SHADOWS } from '../../styles/theme';
import { Search, ChevronRight, AlertCircle, Clock, CheckCircle2, SlidersHorizontal, X } from 'lucide-react-native';
import * as Haptics from 'expo-haptics';
const FILTER_TABS = [
{ id: 'all', label: 'All Queue' },
{ id: 'pending', label: 'Open / Pending' },
{ id: 'in_progress', label: 'In Progress' },
{ id: 'resolved', label: 'Resolved' },
{ id: 'closed', label: 'Closed' }
];
const AdminTicketsScreen = () => {
const navigation = useNavigation();
const [tickets, setTickets] = useState([]);
const [loading, setLoading] = useState(true);
const [refreshing, setRefreshing] = useState(false);
const [searchQuery, setSearchQuery] = useState('');
const [activeFilter, setActiveFilter] = useState('all');
const fetchTickets = useCallback(async () => {
try {
const { data: { user } } = await supabase.auth.getUser();
if (!user) return;
const { data: profile } = await supabase
.from('profiles')
.select('company')
.eq('id', user.id)
.single();
let query = supabase
.from('tickets')
.select(`
*,
creator:profiles!tickets_user_id_fkey(full_name, email)
`)
.order('created_at', { ascending: false });
if (profile?.company) {
query = query.eq('company', profile.company);
}
const { data, error } = await query;
if (error) {
// Safe fallback without profiles relation if needed
const { data: fallbackData } = await supabase
.from('tickets')
.select('*')
.eq('company', profile?.company)
.order('created_at', { ascending: false });
setTickets(fallbackData || []);
} else {
setTickets(data || []);
}
} catch (e) {
console.error('Fetch admin tickets queue error:', e);
} finally {
setLoading(false);
setRefreshing(false);
}
}, []);
// Fetch when screen focuses
useFocusEffect(
useCallback(() => {
fetchTickets();
}, [fetchTickets])
);
const onRefresh = () => {
setRefreshing(true);
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
fetchTickets();
};
const getFilteredTickets = () => {
let filtered = tickets;
// Filter by tab with self-healing DB status mapping
if (activeFilter !== 'all') {
filtered = filtered.filter(t => {
const rawStatus = String(t.status || '').toLowerCase().trim();
if (activeFilter === 'pending') {
return rawStatus === 'pending' || rawStatus === 'open' || rawStatus === 'pending_human';
}
if (activeFilter === 'in_progress') {
return rawStatus === 'in progress' || rawStatus === 'in_progress';
}
if (activeFilter === 'resolved') {
return rawStatus === 'resolved' || rawStatus === 'auto_resolved' || rawStatus.includes('resolv');
}
if (activeFilter === 'closed') {
return rawStatus === 'closed';
}
return rawStatus === activeFilter.toLowerCase();
});
}
// Filter by search query
if (searchQuery.trim()) {
const q = searchQuery.toLowerCase().trim();
filtered = filtered.filter(t =>
t.subject?.toLowerCase().includes(q) ||
t.description?.toLowerCase().includes(q) ||
t.category?.toLowerCase().includes(q) ||
t.priority?.toLowerCase().includes(q) ||
t.creator?.full_name?.toLowerCase().includes(q) ||
String(t.id).includes(q)
);
}
return filtered;
};
const getStatusColor = (status) => {
const rawStatus = String(status || '').toLowerCase().trim();
if (rawStatus.includes('resolv') || rawStatus === 'closed') {
return COLORS.success;
}
if (rawStatus === 'in progress' || rawStatus === 'in_progress') {
return '#3b82f6'; // Blue
}
return '#fbbf24'; // Yellow
};
const formatTime = (dateStr) => {
const d = new Date(dateStr);
return d.toLocaleDateString(undefined, { month: 'short', day: 'numeric' }) + ' ' + d.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' });
};
const renderTicketItem = ({ item }) => {
const statusColor = getStatusColor(item.status);
const creatorName = item.creator?.full_name || 'System User';
return (
{
Haptics.selectionAsync();
navigation.navigate('AdminTicketDetail', { ticketId: item.id });
}}
activeOpacity={0.8}
>
{creatorName}
{formatTime(item.created_at)}
{(item.priority || 'Low').toUpperCase()}
{item.subject}
{item.description}
{(item.category || 'General').toUpperCase()}
{item.auto_resolve && (
AI AUTO
)}
{String(item.status || 'PENDING').replace('_', ' ').toUpperCase()}
);
};
return (
{/* Header */}
Incident Queue
LIVE FEED
{/* Search Section */}
{searchQuery.length > 0 && (
setSearchQuery('')} style={styles.clearSearchBtn}>
)}
{/* Filter Tabs scrollbar */}
item.id}
contentContainerStyle={styles.tabScroll}
renderItem={({ item }) => (
{
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
setActiveFilter(item.id);
}}
>
{item.label}
)}
/>
{/* Queue list */}
{loading ? (
) : (
item.id}
renderItem={renderTicketItem}
contentContainerStyle={styles.list}
showsVerticalScrollIndicator={false}
RefreshControl={}
ListEmptyComponent={
No tickets match your filters
}
/>
)}
);
};
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: COLORS.background },
header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: 24, paddingTop: 16, paddingBottom: 16 },
title: { fontSize: 28, fontWeight: '900', color: COLORS.text, letterSpacing: -0.8 },
activePill: {
flexDirection: 'row',
alignItems: 'center',
gap: 6,
backgroundColor: '#f0fdf4',
borderWidth: 1,
borderColor: '#bbf7d0',
borderRadius: 100,
paddingHorizontal: 12,
paddingVertical: 5
},
pulseDot: { width: 6, height: 6, borderRadius: 3, backgroundColor: '#22c55e' },
activePillText: { fontSize: 9, fontWeight: '800', color: '#15803d', letterSpacing: 0.5 },
// 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 scroll
tabWrapper: { marginBottom: 12 },
tabScroll: { paddingHorizontal: 20, gap: 8 },
tabItem: { paddingHorizontal: 16, paddingVertical: 10, borderRadius: 100, backgroundColor: '#fff', borderWidth: 1, borderColor: 'rgba(0,0,0,0.02)', ...SHADOWS.soft },
activeTabItem: { backgroundColor: COLORS.primary, borderColor: COLORS.primary },
tabLabel: { fontSize: 13, fontWeight: '700', color: COLORS.textLight },
activeTabLabel: { color: '#fff' },
// List Cards
list: { paddingHorizontal: 20, paddingBottom: 120 },
ticketCard: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: '#fff',
borderRadius: 24,
marginBottom: 12,
overflow: 'hidden',
borderWidth: 1,
borderColor: 'rgba(0,0,0,0.03)',
...SHADOWS.soft
},
statusStripe: { width: 6, alignSelf: 'stretch' },
cardContent: { flex: 1, padding: 18 },
cardHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8 },
creatorInfo: { gap: 1 },
creatorName: { fontSize: 13, fontWeight: '800', color: COLORS.text },
ticketDate: { fontSize: 11, color: COLORS.textMuted, fontWeight: '600' },
priorityBadge: { paddingHorizontal: 8, paddingVertical: 4, borderRadius: 100 },
priorityText: { fontSize: 9, fontWeight: '800' },
subjectText: { fontSize: 15, fontWeight: '800', color: COLORS.text, marginBottom: 4 },
descSnippet: { fontSize: 13, color: COLORS.textLight, lineHeight: 18, marginBottom: 12 },
cardFooter: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' },
categoryBadge: { backgroundColor: '#f3f4f6', paddingHorizontal: 10, paddingVertical: 5, borderRadius: 100 },
categoryText: { fontSize: 10, fontWeight: '800', color: COLORS.textLight },
footerRight: { flexDirection: 'row', alignItems: 'center', gap: 6 },
aiBadge: { backgroundColor: COLORS.primaryLight, paddingHorizontal: 8, paddingVertical: 4, borderRadius: 100 },
aiBadgeText: { fontSize: 9, fontWeight: '900', color: COLORS.primary },
statusBadge: { flexDirection: 'row', alignItems: 'center', gap: 5, borderRadius: 100, paddingHorizontal: 10, paddingVertical: 5 },
statusDot: { width: 5, height: 5, borderRadius: 2.5 },
statusText: { fontSize: 9, fontWeight: '800' },
centered: { flex: 1, justifyContent: 'center', alignItems: 'center', marginTop: 100 },
emptyContainer: { alignItems: 'center', marginTop: 100, gap: 12 },
emptyText: { fontSize: 15, color: COLORS.textMuted, fontWeight: '600' }
});
export default AdminTicketsScreen;