Download User/src/components/issues/IssueCard.tsx from 0xarchit/CityTrack: direct link, hf CLI and curl.
- Browser
- Download file 5.37 kB
-
https://huggingface.co/spaces/0xarchit/CityTrack/resolve/main/User/src/components/issues/IssueCard.tsx
- Command line
-
hf download hf://spaces/0xarchit/CityTrack/User/src/components/issues/IssueCard.tsx
-
curl -L -o IssueCard.tsx https://huggingface.co/spaces/0xarchit/CityTrack/resolve/main/User/src/components/issues/IssueCard.tsx
5.37 kB
| import React from 'react'; | |
| import { View, Text, StyleSheet, Image, TouchableOpacity } from 'react-native'; | |
| import { Ionicons } from '@expo/vector-icons'; | |
| import { Card } from '../ui/Card'; | |
| import { colors, spacing, typography, borderRadius } from '../../theme'; | |
| import { Issue } from '../../types'; | |
| interface IssueCardProps { | |
| issue: Issue; | |
| onPress?: () => void; | |
| } | |
| const priorityColors: Record<number, string> = { | |
| 1: colors.priority.critical, | |
| 2: colors.priority.high, | |
| 3: colors.priority.medium, | |
| 4: colors.priority.low, | |
| }; | |
| const priorityLabels: Record<number, string> = { | |
| 1: 'CRITICAL', | |
| 2: 'HIGH', | |
| 3: 'MEDIUM', | |
| 4: 'LOW', | |
| }; | |
| const stateColors: Record<string, string> = { | |
| reported: colors.status.info, | |
| validated: colors.accent.purple, | |
| assigned: colors.accent.cyan, | |
| in_progress: colors.status.warning, | |
| resolved: colors.status.success, | |
| closed: colors.text.tertiary, | |
| rejected: colors.status.error, | |
| }; | |
| export function IssueCard({ issue, onPress }: IssueCardProps) { | |
| const priorityColor = issue.priority ? priorityColors[issue.priority] : colors.text.tertiary; | |
| const priorityLabel = issue.priority ? priorityLabels[issue.priority] : 'N/A'; | |
| const stateColor = stateColors[issue.state] || colors.text.tertiary; | |
| const formatDate = (dateStr: string) => { | |
| const date = new Date(dateStr); | |
| return date.toLocaleDateString('en-US', { | |
| month: 'short', | |
| day: 'numeric', | |
| hour: '2-digit', | |
| minute: '2-digit', | |
| }); | |
| }; | |
| return ( | |
| <TouchableOpacity onPress={onPress} activeOpacity={0.8}> | |
| <Card variant="glass" style={styles.container}> | |
| <View style={styles.header}> | |
| {issue.annotated_urls[0] ? ( | |
| <Image source={{ uri: issue.annotated_urls[0] }} style={styles.thumbnail} /> | |
| ) : issue.image_urls[0] ? ( | |
| <Image source={{ uri: issue.image_urls[0] }} style={styles.thumbnail} /> | |
| ) : ( | |
| <View style={[styles.thumbnail, styles.placeholderImage]}> | |
| <Ionicons name="camera" size={24} color={colors.text.tertiary} /> | |
| </View> | |
| )} | |
| <View style={styles.info}> | |
| <View style={styles.badges}> | |
| <View style={[styles.badge, { backgroundColor: priorityColor }]}> | |
| <Text style={styles.badgeText}>{priorityLabel}</Text> | |
| </View> | |
| <View style={[styles.badge, styles.stateBadge, { borderColor: stateColor }]}> | |
| <Text style={[styles.badgeText, { color: stateColor }]}> | |
| {issue.state.toUpperCase().replace('_', ' ')} | |
| </Text> | |
| </View> | |
| </View> | |
| {issue.category ? ( | |
| <Text style={styles.category}>{issue.category}</Text> | |
| ) : null} | |
| {issue.confidence !== undefined && issue.confidence !== null ? ( | |
| <Text style={styles.confidence}> | |
| Confidence: {(issue.confidence * 100).toFixed(0)}% | |
| </Text> | |
| ) : null} | |
| </View> | |
| </View> | |
| {issue.description ? ( | |
| <Text style={styles.description} numberOfLines={2}> | |
| {issue.description} | |
| </Text> | |
| ) : null} | |
| <View style={styles.footer}> | |
| <Text style={styles.date}>{formatDate(issue.created_at)}</Text> | |
| {issue.is_duplicate ? ( | |
| <View style={styles.duplicateBadge}> | |
| <Text style={styles.duplicateText}>DUPLICATE</Text> | |
| </View> | |
| ) : null} | |
| </View> | |
| </Card> | |
| </TouchableOpacity> | |
| ); | |
| } | |
| const styles = StyleSheet.create({ | |
| container: { | |
| marginBottom: spacing.md, | |
| }, | |
| header: { | |
| flexDirection: 'row', | |
| }, | |
| thumbnail: { | |
| width: 80, | |
| height: 80, | |
| borderRadius: borderRadius.md, | |
| }, | |
| placeholderImage: { | |
| backgroundColor: colors.background.tertiary, | |
| alignItems: 'center', | |
| justifyContent: 'center', | |
| }, | |
| info: { | |
| flex: 1, | |
| marginLeft: spacing.md, | |
| }, | |
| badges: { | |
| flexDirection: 'row', | |
| flexWrap: 'wrap', | |
| gap: spacing.xs, | |
| }, | |
| badge: { | |
| paddingHorizontal: spacing.sm, | |
| paddingVertical: spacing.xs, | |
| borderRadius: borderRadius.sm, | |
| }, | |
| stateBadge: { | |
| backgroundColor: 'transparent', | |
| borderWidth: 1, | |
| }, | |
| badgeText: { | |
| color: colors.text.primary, | |
| fontSize: 10, | |
| fontWeight: '600', | |
| }, | |
| category: { | |
| color: colors.text.primary, | |
| fontSize: typography.body.fontSize, | |
| fontWeight: '600', | |
| marginTop: spacing.sm, | |
| }, | |
| confidence: { | |
| color: colors.text.secondary, | |
| fontSize: typography.caption.fontSize, | |
| marginTop: spacing.xs, | |
| }, | |
| description: { | |
| color: colors.text.secondary, | |
| fontSize: typography.bodySmall.fontSize, | |
| marginTop: spacing.md, | |
| }, | |
| footer: { | |
| flexDirection: 'row', | |
| justifyContent: 'space-between', | |
| alignItems: 'center', | |
| marginTop: spacing.md, | |
| paddingTop: spacing.md, | |
| borderTopWidth: 1, | |
| borderTopColor: colors.border.light, | |
| }, | |
| date: { | |
| color: colors.text.tertiary, | |
| fontSize: typography.caption.fontSize, | |
| }, | |
| duplicateBadge: { | |
| backgroundColor: colors.status.warning, | |
| paddingHorizontal: spacing.sm, | |
| paddingVertical: spacing.xs, | |
| borderRadius: borderRadius.sm, | |
| }, | |
| duplicateText: { | |
| color: colors.text.inverse, | |
| fontSize: 10, | |
| fontWeight: '600', | |
| }, | |
| }); | |