import { View, Text, ScrollView, RefreshControl, ActivityIndicator, TouchableOpacity } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import { useQueries } from '@tanstack/react-query'; import { router } from 'expo-router'; import { api } from '../../services/api'; import { useAuthStore } from '../../stores/authStore'; // ─── Constants ──────────────────────────────────────────────────────────────── const PRIORITY_COLOR: Record = { HIGH: '#DC2626', NORMAL: '#0891B2' }; const PRIORITY_BG: Record = { HIGH: '#FEE2E2', NORMAL: '#ECFEFF' }; const STATUS_COLOR: Record = { OPEN: '#0891B2', IN_PROGRESS: '#D97706', RESOLVED: '#16A34A', CLOSED: '#6B7280' }; const TYPE_COLOR: Record = { INSTALLATION: '#0891B2', SUPPORT: '#7C3AED', BILLING: '#D97706' }; // ─── KPI Card ──────────────────────────────────────────────────────────────── function KpiCard({ label, value, color, bg }: { label: string; value: string | number; color: string; bg: string }) { return ( {label} {value} ); } // ─── Ticket Row ─────────────────────────────────────────────────────────────── function TaskRow({ task, onPress }: { task: any; onPress: () => void }) { const typeColor = TYPE_COLOR[task.type] ?? '#6B7280'; const isHigh = task.priority === 'HIGH'; return ( {task.type} {isHigh && ( HIGH )} {task.status?.replace('_', ' ')} {task.subject} {task.client?.firstName} {task.client?.lastName} {task.assignedTo ? ` · ${task.assignedTo.firstName} ${task.assignedTo.lastName}` : ' · Unassigned'} ); } // ─── Main Screen ────────────────────────────────────────────────────────────── export default function DashboardScreen() { const { user } = useAuthStore(); const hour = new Date().getHours(); const greeting = hour < 12 ? 'Good morning' : hour < 18 ? 'Good afternoon' : 'Good evening'; const [summaryQ, tasksQ] = useQueries({ queries: [ { queryKey: ['dashboard'], queryFn: () => api.get('/api/v1/dashboard/summary').then(r => r.data), }, { queryKey: ['dashboard-tasks'], queryFn: () => api.get('/api/v1/tickets?status=OPEN&status=IN_PROGRESS&limit=20') .then(r => r.data?.data ?? r.data ?? []), }, ], }); const isLoading = summaryQ.isLoading || tasksQ.isLoading; const isRefetching = summaryQ.isRefetching || tasksQ.isRefetching; const summary = summaryQ.data; // Real dashboard API shape: // { subscribers: { total, active, pending, suspended }, // billing: { unpaidInvoices, overdueInvoices }, // support: { openTickets, inProgressTickets }, // tasks: { pending }, // revenue: { thisMonth, lastMonth, growth } } const totalClients = summary?.subscribers?.total ?? '—'; const activeSubscribers = summary?.subscribers?.active ?? '—'; const unpaidInvoices = summary?.billing?.unpaidInvoices ?? '—'; const openTickets = summary?.support?.openTickets ?? '—'; const thisMonthRevenue = summary?.revenue?.thisMonth ?? null; const allTasks: any[] = tasksQ.data ?? []; const unassigned = allTasks.filter((t: any) => !t.assignedToId); const assigned = allTasks.filter((t: any) => !!t.assignedToId); const prioOrder: Record = { HIGH: 0, NORMAL: 1 }; const byPrio = (a: any, b: any) => (prioOrder[a.priority] ?? 2) - (prioOrder[b.priority] ?? 2); const refetchAll = () => { summaryQ.refetch(); tasksQ.refetch(); }; return ( } > {/* Header */} {greeting}, {user?.firstName ?? 'Field Staff'} {isLoading ? ( ) : ( {/* KPI Row 1 */} {/* KPI Row 2 */} {/* Revenue card */} {thisMonthRevenue !== null && ( This Month's Revenue ₱{Number(thisMonthRevenue).toLocaleString()} {summary?.revenue?.growth !== undefined && ( +{summary.revenue.growth}% )} )} {/* Unassigned Tasks */} Unassigned {unassigned.length > 0 && ( {unassigned.length} )} router.push('/(app)/tasks')} hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}> View all {unassigned.length === 0 ? ( No unassigned tasks ) : ( {[...unassigned].sort(byPrio).slice(0, 5).map((t: any) => ( router.push(`/(app)/tasks/${t.id}`)} /> ))} )} {/* Assigned Tasks */} {assigned.length > 0 && ( <> Assigned Tasks {[...assigned].sort(byPrio).slice(0, 5).map((t: any) => ( router.push(`/(app)/tasks/${t.id}`)} /> ))} )} )} ); }