186 lines
5.6 KiB
TypeScript

import React, { useState } from 'react';
import {
View,
Text,
ActivityIndicator,
ScrollView,
TouchableOpacity,
} from 'react-native';
import Icon from 'react-native-vector-icons/Ionicons';
import { useTheme } from '@theme';
import { DashboardProjectActivityItem } from '@interfaces';
import { getStyles } from './projectActivityFeed.styles';
import { ProjectActivityFeedProps } from './projectActivityFeed.props';
import { colors as globalColors } from '@theme';
const AVATAR_COLORS = [
'#5B4CF5', // indigo
'#8B5CF6', // violet
'#06B6D4', // cyan
'#10B981', // emerald
'#F59E0B', // amber
'#EF4444', // red
'#EC4899', // pink
];
const getInitials = (name: string): string => {
const parts = name.trim().split(' ');
if (parts.length >= 2) {
return `${parts[0][0]}${parts[1][0]}`.toUpperCase();
}
return name.slice(0, 2).toUpperCase();
};
const ActivityRow = ({
item,
index,
isLast,
styles,
textMutedColor,
}: {
item: DashboardProjectActivityItem;
index: number;
isLast: boolean;
styles: ReturnType<typeof getStyles>;
textMutedColor: string;
}) => {
const avatarColor = AVATAR_COLORS[index % AVATAR_COLORS.length];
const initials = getInitials(item.fullname_formatted || item.fullname);
return (
<View style={styles.row}>
{/* ── Left: avatar + timeline connector ── */}
<View style={styles.leftCol}>
<View style={[styles.avatar, { backgroundColor: avatarColor }]}>
<Text style={styles.avatarText}>{initials}</Text>
</View>
{!isLast && <View style={styles.connector} />}
</View>
{/* ── Right: bubble content ── */}
<View style={styles.rightCol}>
<View style={styles.bubble}>
{/* Name + time */}
<View style={styles.bubbleHeader}>
<Text style={styles.staffName} numberOfLines={1}>
{item.fullname_formatted || item.fullname}
</Text>
<Text style={styles.timeAgo}>{item.date_time_ago}</Text>
</View>
{/* Action description */}
<View style={styles.descriptionRow}>
<View style={styles.actionDot} />
<Text style={styles.description} numberOfLines={2}>
{item.description}
</Text>
</View>
{/* Project chip */}
<View style={styles.projectChip}>
<Icon name="folder-outline" size={10} color={globalColors.accentAlt} />
<Text style={styles.projectChipText} numberOfLines={1}>
{item.project_name}
</Text>
</View>
{/* Additional data tag */}
{!!item.additional_data && (
<View style={styles.additionalTag}>
<Icon name="document-text-outline" size={11} color={textMutedColor} />
<Text style={styles.additionalText} numberOfLines={1}>
{item.additional_data}
</Text>
</View>
)}
</View>
</View>
</View>
);
};
export const ProjectActivityFeed = ({
data = [],
loading = false,
}: ProjectActivityFeedProps) => {
const { theme: colors, isDark } = useTheme();
const styles = getStyles(colors, isDark);
const [expanded, setExpanded] = useState(false);
const displayData = expanded ? data : data.slice(0, 5);
const hasMore = data.length > 5;
return (
<View style={styles.card}>
{/* ── Header ── */}
<View style={styles.header}>
<View style={styles.titleIconBadge}>
<Icon name="pulse" size={16} color="#FFFFFF" />
</View>
<View style={styles.titleGroup}>
<Text style={styles.title}>Latest Project Activity</Text>
<Text style={styles.subtitle}>recent actions across projects</Text>
</View>
{!loading && data.length > 0 && (
<View style={styles.countBadge}>
<Text style={styles.countBadgeText}>{data.length}</Text>
</View>
)}
</View>
{/* ── Divider ── */}
<View style={styles.divider} />
{/* ── Body ── */}
{loading ? (
<View style={styles.stateContainer}>
<ActivityIndicator size="large" color={globalColors.accent} />
</View>
) : data.length === 0 ? (
<View style={styles.stateContainer}>
<Icon name="calendar-outline" size={32} color={colors.textMuted} />
<Text style={styles.emptyText}>No recent activity</Text>
</View>
) : (
<>
<ScrollView
style={styles.listContent}
scrollEnabled={false}
showsVerticalScrollIndicator={false}
>
{displayData.map((item, index) => (
<ActivityRow
key={item.id}
item={item}
index={index}
isLast={index === displayData.length - 1}
styles={styles}
textMutedColor={colors.textMuted}
/>
))}
</ScrollView>
{hasMore && (
<View style={styles.footer}>
<TouchableOpacity
style={styles.footerButton}
activeOpacity={0.7}
onPress={() => setExpanded(!expanded)}
>
<Text style={styles.footerButtonText}>
{expanded ? 'Show Less' : `Show More (${data.length - 5} more)`}
</Text>
<Icon
name={expanded ? 'chevron-up' : 'chevron-down'}
size={14}
color={globalColors.primary}
/>
</TouchableOpacity>
</View>
)}
</>
)}
</View>
);
};