xtablo-source/xtablo-expo/components/tasks/TaskRow.tsx
Arthur Belleville 66d9ab9ad8
fix(expo): address review findings - cache invalidation, date handling, cleanup
- Add tasks cache invalidation to etape create/update hooks
- Fix timezone-unsafe overdue date comparison in TaskRow
- Add due date picker to EtapeSheet (was missing from spec)
- Use initialized ref to prevent form state overwrite on refetch
- Remove unused deleteTask import from TaskList

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 14:07:42 +02:00

120 lines
3.2 KiB
TypeScript

import React from "react";
import { View, Text, TouchableOpacity, StyleSheet } from "react-native";
import { Task, TASK_STATUSES } from "@/types/tasks.types";
import { useThemeColor } from "@/hooks/useThemeColor";
import { useColorScheme } from "@/hooks/useColorScheme";
import { Calendar } from "lucide-react-native";
type TaskRowProps = {
task: Task;
onPress: (task: Task) => void;
};
export default function TaskRow({ task, onPress }: TaskRowProps) {
const colorScheme = useColorScheme();
const isDark = colorScheme === "dark";
const textColor = useThemeColor({ light: "#1f2937", dark: "#f9fafb" }, "text");
const subtextColor = useThemeColor({ light: "#6b7280", dark: "#9ca3af" }, "text");
const borderColor = isDark ? "#374151" : "#e5e7eb";
const statusColor = TASK_STATUSES.find((s) => s.value === task.status)?.color ?? "#9ca3af";
const isOverdue = task.due_date && task.due_date < new Date().toISOString().slice(0, 10);
return (
<TouchableOpacity
style={[styles.container, { borderBottomColor: borderColor }]}
onPress={() => onPress(task)}
activeOpacity={0.7}
>
<View style={[styles.statusDot, { backgroundColor: statusColor }]} />
<View style={styles.content}>
<Text style={[styles.title, { color: textColor }]} numberOfLines={1}>
{task.title}
</Text>
<View style={styles.meta}>
{task.due_date && (
<View style={styles.dueDateBadge}>
<Calendar size={12} color={isOverdue ? "#ef4444" : subtextColor} />
<Text
style={[
styles.dueDate,
{ color: isOverdue ? "#ef4444" : subtextColor },
]}
>
{new Date(task.due_date).toLocaleDateString("fr-FR", {
day: "numeric",
month: "short",
})}
</Text>
</View>
)}
</View>
</View>
{task.assignee_name ? (
<View style={styles.avatar}>
<Text style={styles.avatarText}>
{task.assignee_name.charAt(0).toUpperCase()}
</Text>
</View>
) : (
<View style={[styles.avatar, styles.avatarEmpty]} />
)}
</TouchableOpacity>
);
}
const styles = StyleSheet.create({
container: {
flexDirection: "row",
alignItems: "center",
paddingVertical: 12,
paddingHorizontal: 16,
borderBottomWidth: StyleSheet.hairlineWidth,
gap: 12,
},
statusDot: {
width: 10,
height: 10,
borderRadius: 5,
},
content: {
flex: 1,
gap: 2,
},
title: {
fontSize: 15,
fontWeight: "500",
},
meta: {
flexDirection: "row",
alignItems: "center",
gap: 8,
},
dueDateBadge: {
flexDirection: "row",
alignItems: "center",
gap: 4,
},
dueDate: {
fontSize: 12,
},
avatar: {
width: 28,
height: 28,
borderRadius: 14,
backgroundColor: "#3b82f6",
alignItems: "center",
justifyContent: "center",
},
avatarEmpty: {
backgroundColor: "transparent",
borderWidth: 1.5,
borderColor: "#d1d5db",
borderStyle: "dashed",
},
avatarText: {
color: "#ffffff",
fontSize: 13,
fontWeight: "600",
},
});