CityTrack / User /src /components /issues /IssueCard.tsx
0xarchit's picture
User app beta v1 complete
71638d4
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',
},
});