summaryrefslogblamecommitdiff
path: root/apps/mobile/app/(tabs)/index.tsx
blob: 96b1a8fb3cca07e28eff1e7d210fe7aa2ebc498e (plain) (tree)












































































































































































































































































































































































                                                                                      
import React from 'react';
import {
  View,
  Text,
  StyleSheet,
  ScrollView,
  TouchableOpacity,
  useColorScheme,
  RefreshControl,
} from 'react-native';
import { useRouter } from 'expo-router';
import { Ionicons } from '@expo/vector-icons';
import { Colors, TaskStatusColors } from '../../constants/Colors';
import { useTasks, getTaskCounts } from '../../hooks/useTasks';
import { usePendingQuestions } from '../../hooks/useQuestions';
import { TaskStatusBadge } from '../../components/TaskStatusBadge';
import type { TaskSummary } from '../../lib/api';

interface StatCardProps {
  title: string;
  value: number;
  icon: keyof typeof Ionicons.glyphMap;
  color: string;
  onPress?: () => void;
}

function StatCard({ title, value, icon, color, onPress }: StatCardProps) {
  const colorScheme = useColorScheme() ?? 'light';
  const colors = Colors[colorScheme];

  return (
    <TouchableOpacity
      style={[styles.statCard, { backgroundColor: colors.card }]}
      onPress={onPress}
      activeOpacity={onPress ? 0.7 : 1}
      disabled={!onPress}
    >
      <View style={[styles.iconContainer, { backgroundColor: color + '20' }]}>
        <Ionicons name={icon} size={24} color={color} />
      </View>
      <Text style={[styles.statValue, { color: colors.text }]}>{value}</Text>
      <Text style={[styles.statTitle, { color: colors.secondaryText }]}>
        {title}
      </Text>
    </TouchableOpacity>
  );
}

interface QuickTaskItemProps {
  task: TaskSummary;
  onPress: () => void;
}

function QuickTaskItem({ task, onPress }: QuickTaskItemProps) {
  const colorScheme = useColorScheme() ?? 'light';
  const colors = Colors[colorScheme];

  return (
    <TouchableOpacity
      style={[styles.quickTaskItem, { backgroundColor: colors.card }]}
      onPress={onPress}
      activeOpacity={0.7}
    >
      <TaskStatusBadge status={task.status} size="small" />
      <View style={styles.quickTaskContent}>
        <Text
          style={[styles.quickTaskName, { color: colors.text }]}
          numberOfLines={1}
        >
          {task.name}
        </Text>
        {task.progressSummary && (
          <Text
            style={[styles.quickTaskSummary, { color: colors.secondaryText }]}
            numberOfLines={1}
          >
            {task.progressSummary}
          </Text>
        )}
      </View>
      <Ionicons name="chevron-forward" size={16} color={colors.secondaryText} />
    </TouchableOpacity>
  );
}

export default function DashboardScreen() {
  const colorScheme = useColorScheme() ?? 'light';
  const colors = Colors[colorScheme];
  const router = useRouter();

  const {
    data: tasks,
    isLoading: isLoadingTasks,
    refetch: refetchTasks,
    isRefetching: isRefetchingTasks,
  } = useTasks();

  const {
    data: questions,
    isLoading: isLoadingQuestions,
    refetch: refetchQuestions,
    isRefetching: isRefetchingQuestions,
  } = usePendingQuestions();

  const isRefreshing = isRefetchingTasks || isRefetchingQuestions;

  const handleRefresh = () => {
    refetchTasks();
    refetchQuestions();
  };

  // Calculate counts
  const counts = tasks ? getTaskCounts(tasks) : null;
  const questionCount = questions?.length ?? 0;

  // Get running tasks for quick access
  const runningTasks = tasks?.filter((t) =>
    ['running', 'initializing', 'starting'].includes(t.status)
  ) ?? [];

  // Get tasks needing attention (blocked, paused, or with questions)
  const attentionTasks = tasks?.filter((t) =>
    ['blocked', 'paused'].includes(t.status)
  ) ?? [];

  return (
    <ScrollView
      style={[styles.container, { backgroundColor: colors.background }]}
      contentContainerStyle={styles.content}
      refreshControl={
        <RefreshControl
          refreshing={isRefreshing}
          onRefresh={handleRefresh}
          tintColor={colors.tint}
        />
      }
    >
      {/* Stats Grid */}
      <View style={styles.statsGrid}>
        <StatCard
          title="Running"
          value={counts?.running ?? 0}
          icon="play-circle"
          color={TaskStatusColors.running.dot}
          onPress={() => router.push('/(tabs)/tasks')}
        />
        <StatCard
          title="Questions"
          value={questionCount}
          icon="help-circle"
          color="#f59e0b"
          onPress={questionCount > 0 ? () => router.push('/(tabs)/tasks') : undefined}
        />
        <StatCard
          title="Completed"
          value={counts?.completed ?? 0}
          icon="checkmark-circle"
          color={TaskStatusColors.done.dot}
          onPress={() => router.push('/(tabs)/tasks')}
        />
        <StatCard
          title="Failed"
          value={counts?.failed ?? 0}
          icon="alert-circle"
          color={TaskStatusColors.failed.dot}
          onPress={counts?.failed ? () => router.push('/(tabs)/tasks') : undefined}
        />
      </View>

      {/* Pending Questions Alert */}
      {questionCount > 0 && (
        <TouchableOpacity
          style={[styles.alertBanner, { backgroundColor: '#fef3c7' }]}
          onPress={() => router.push('/(tabs)/tasks')}
          activeOpacity={0.7}
        >
          <Ionicons name="help-circle" size={24} color="#92400e" />
          <View style={styles.alertContent}>
            <Text style={[styles.alertTitle, { color: '#92400e' }]}>
              {questionCount} Question{questionCount !== 1 ? 's' : ''} Waiting
            </Text>
            <Text style={[styles.alertSubtitle, { color: '#b45309' }]}>
              Tap to review and respond
            </Text>
          </View>
          <Ionicons name="chevron-forward" size={20} color="#92400e" />
        </TouchableOpacity>
      )}

      {/* Running Tasks */}
      {runningTasks.length > 0 && (
        <View style={styles.section}>
          <View style={styles.sectionHeader}>
            <Text style={[styles.sectionTitle, { color: colors.text }]}>
              Running Tasks
            </Text>
            <TouchableOpacity onPress={() => router.push('/(tabs)/tasks')}>
              <Text style={[styles.seeAllButton, { color: colors.tint }]}>
                See All
              </Text>
            </TouchableOpacity>
          </View>
          <View style={styles.taskList}>
            {runningTasks.slice(0, 3).map((task) => (
              <QuickTaskItem
                key={task.id}
                task={task}
                onPress={() => router.push(`/task/${task.id}`)}
              />
            ))}
          </View>
        </View>
      )}

      {/* Needs Attention */}
      {attentionTasks.length > 0 && (
        <View style={styles.section}>
          <View style={styles.sectionHeader}>
            <Text style={[styles.sectionTitle, { color: colors.text }]}>
              Needs Attention
            </Text>
            <TouchableOpacity onPress={() => router.push('/(tabs)/tasks')}>
              <Text style={[styles.seeAllButton, { color: colors.tint }]}>
                See All
              </Text>
            </TouchableOpacity>
          </View>
          <View style={styles.taskList}>
            {attentionTasks.slice(0, 3).map((task) => (
              <QuickTaskItem
                key={task.id}
                task={task}
                onPress={() => router.push(`/task/${task.id}`)}
              />
            ))}
          </View>
        </View>
      )}

      {/* Empty State */}
      {!isLoadingTasks && (!tasks || tasks.length === 0) && (
        <View style={styles.emptyState}>
          <Ionicons
            name="cube-outline"
            size={64}
            color={colors.secondaryText}
          />
          <Text style={[styles.emptyTitle, { color: colors.text }]}>
            No tasks yet
          </Text>
          <Text style={[styles.emptyMessage, { color: colors.secondaryText }]}>
            Tasks created from contracts will appear here
          </Text>
        </View>
      )}
    </ScrollView>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  content: {
    padding: 16,
    gap: 20,
  },
  statsGrid: {
    flexDirection: 'row',
    flexWrap: 'wrap',
    gap: 12,
  },
  statCard: {
    flex: 1,
    minWidth: '45%',
    padding: 16,
    borderRadius: 12,
    alignItems: 'center',
    gap: 8,
  },
  iconContainer: {
    width: 48,
    height: 48,
    borderRadius: 24,
    alignItems: 'center',
    justifyContent: 'center',
  },
  statValue: {
    fontSize: 28,
    fontWeight: '700',
  },
  statTitle: {
    fontSize: 14,
    fontWeight: '500',
  },
  alertBanner: {
    flexDirection: 'row',
    alignItems: 'center',
    padding: 16,
    borderRadius: 12,
    gap: 12,
  },
  alertContent: {
    flex: 1,
  },
  alertTitle: {
    fontSize: 16,
    fontWeight: '600',
  },
  alertSubtitle: {
    fontSize: 13,
    marginTop: 2,
  },
  section: {
    gap: 12,
  },
  sectionHeader: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
  },
  sectionTitle: {
    fontSize: 18,
    fontWeight: '600',
  },
  seeAllButton: {
    fontSize: 14,
    fontWeight: '500',
  },
  taskList: {
    gap: 8,
  },
  quickTaskItem: {
    flexDirection: 'row',
    alignItems: 'center',
    padding: 12,
    borderRadius: 10,
    gap: 12,
  },
  quickTaskContent: {
    flex: 1,
    gap: 2,
  },
  quickTaskName: {
    fontSize: 15,
    fontWeight: '500',
  },
  quickTaskSummary: {
    fontSize: 13,
  },
  emptyState: {
    alignItems: 'center',
    justifyContent: 'center',
    paddingVertical: 48,
    gap: 12,
  },
  emptyTitle: {
    fontSize: 18,
    fontWeight: '600',
  },
  emptyMessage: {
    fontSize: 14,
    textAlign: 'center',
  },
});