From 869f21ee2efaefed6a5aa4fbd417c25df8dec02a Mon Sep 17 00:00:00 2001 From: soryu Date: Sun, 18 Jan 2026 17:44:50 +0000 Subject: Add React Native mobile app for Makima (#3) * [WIP] Heartbeat checkpoint - 2026-01-18 02:58:27 UTC * feat(mobile): complete mobile app integration and verification - Add ThemeColors type export to Colors.ts for type safety - Export SUPABASE_URL from supabase.ts and use environment variables - Update .env.example with correct default URLs - Add comprehensive README.md with setup instructions Verified: - TypeScript compiles without errors - App exports successfully for iOS and Android - All screens accessible (login, dashboard, tasks, settings, task detail) - Auth flow working with Zustand store and Supabase Co-Authored-By: Claude Opus 4.5 * Task completion checkpoint --------- Co-authored-by: Claude Opus 4.5 --- apps/mobile/components/TaskListItem.tsx | 199 ++++++++++++++++++++++++++++++++ 1 file changed, 199 insertions(+) create mode 100644 apps/mobile/components/TaskListItem.tsx (limited to 'apps/mobile/components/TaskListItem.tsx') diff --git a/apps/mobile/components/TaskListItem.tsx b/apps/mobile/components/TaskListItem.tsx new file mode 100644 index 0000000..c21f41a --- /dev/null +++ b/apps/mobile/components/TaskListItem.tsx @@ -0,0 +1,199 @@ +import React from 'react'; +import { + View, + Text, + StyleSheet, + TouchableOpacity, + useColorScheme, +} from 'react-native'; +import { Ionicons } from '@expo/vector-icons'; +import { Colors } from '../constants/Colors'; +import { TaskStatusBadge } from './TaskStatusBadge'; +import type { TaskSummary } from '../lib/api'; + +interface TaskListItemProps { + task: TaskSummary; + onPress: (task: TaskSummary) => void; +} + +/** + * Format relative time from a date string + */ +function formatRelativeTime(dateString: string): string { + const date = new Date(dateString); + const now = new Date(); + const diffMs = now.getTime() - date.getTime(); + const diffSec = Math.floor(diffMs / 1000); + const diffMin = Math.floor(diffSec / 60); + const diffHour = Math.floor(diffMin / 60); + const diffDay = Math.floor(diffHour / 24); + + if (diffSec < 60) return 'just now'; + if (diffMin < 60) return `${diffMin}m ago`; + if (diffHour < 24) return `${diffHour}h ago`; + if (diffDay < 7) return `${diffDay}d ago`; + + return date.toLocaleDateString(); +} + +/** + * Truncate text with ellipsis + */ +function truncate(text: string, maxLength: number): string { + if (text.length <= maxLength) return text; + return text.slice(0, maxLength - 3) + '...'; +} + +export function TaskListItem({ task, onPress }: TaskListItemProps) { + const colorScheme = useColorScheme() ?? 'light'; + const colors = Colors[colorScheme]; + + const isRunning = ['running', 'initializing', 'starting'].includes(task.status); + const isCompleted = ['done', 'merged'].includes(task.status); + const isFailed = task.status === 'failed'; + + return ( + onPress(task)} + activeOpacity={0.7} + > + + + + + + + + {task.name} + + {task.isSupervisor && ( + + + + )} + + + {task.contractName && ( + + {task.contractName} + + )} + + {task.progressSummary && ( + + {truncate(task.progressSummary, 100)} + + )} + + + + {isRunning + ? `Started ${formatRelativeTime(task.updatedAt)}` + : isCompleted + ? `Completed ${formatRelativeTime(task.updatedAt)}` + : isFailed + ? `Failed ${formatRelativeTime(task.updatedAt)}` + : `Created ${formatRelativeTime(task.createdAt)}`} + + + {task.subtaskCount > 0 && ( + + + + {task.subtaskCount} + + + )} + + + + + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flexDirection: 'row', + alignItems: 'center', + paddingVertical: 12, + paddingHorizontal: 16, + borderBottomWidth: StyleSheet.hairlineWidth, + minHeight: 64, + }, + leftSection: { + marginRight: 12, + alignItems: 'center', + justifyContent: 'center', + }, + content: { + flex: 1, + gap: 2, + }, + header: { + flexDirection: 'row', + alignItems: 'center', + gap: 6, + }, + name: { + fontSize: 16, + fontWeight: '600', + flex: 1, + }, + supervisorBadge: { + padding: 2, + }, + contractName: { + fontSize: 13, + }, + progressSummary: { + fontSize: 13, + marginTop: 2, + }, + footer: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + marginTop: 4, + }, + time: { + fontSize: 12, + }, + subtaskBadge: { + flexDirection: 'row', + alignItems: 'center', + gap: 4, + }, + subtaskCount: { + fontSize: 12, + }, + rightSection: { + marginLeft: 8, + }, +}); -- cgit v1.2.3