135 lines
3.2 KiB
TypeScript
135 lines
3.2 KiB
TypeScript
import {
|
|
SectionList,
|
|
StyleSheet,
|
|
Text,
|
|
View,
|
|
} from "react-native";
|
|
import { BirthdayEntry, useBirthdays } from "@/context/birthdays-context";
|
|
import { BirthdayItem } from "./birthdate-item";
|
|
|
|
interface SectionData {
|
|
title: string;
|
|
data: BirthdayEntry[];
|
|
}
|
|
|
|
export function BirthdayList() {
|
|
const { birthdays, deleteBirthday } = useBirthdays();
|
|
const groupedData = groupBirthdaysByDate(birthdays);
|
|
|
|
return (
|
|
<View style={styles.container}>
|
|
<SectionList
|
|
sections={groupedData}
|
|
keyExtractor={(item) => item.id}
|
|
renderItem={({ item }) => (
|
|
<BirthdayItem
|
|
id={item.id}
|
|
name={item.name}
|
|
date={item.date}
|
|
onDelete={deleteBirthday}
|
|
/>
|
|
)}
|
|
style={styles.list}
|
|
contentContainerStyle={styles.listContent}
|
|
showsVerticalScrollIndicator={false}
|
|
ListEmptyComponent={
|
|
<View style={styles.emptyState}>
|
|
<Text style={styles.emptyTitle}>No birthdays yet</Text>
|
|
<Text style={styles.emptyText}>
|
|
Add one from the form and it will appear here.
|
|
</Text>
|
|
</View>
|
|
}
|
|
renderSectionHeader={({ section: { title } }) => (
|
|
<View style={styles.headerContainer}>
|
|
<Text style={styles.sectionHeader}>{formatDisplayDate(title)}</Text>
|
|
</View>
|
|
)}
|
|
/>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
// Helper function to group and sort
|
|
function groupBirthdaysByDate(data: BirthdayEntry[]): SectionData[] {
|
|
// Sort by date
|
|
const today = new Date();
|
|
const currentYear = today.getFullYear();
|
|
|
|
const daysUntilNext = (month: number, day: number) => {
|
|
let target = new Date(currentYear, month, day);
|
|
if(target < today.setHours(0, 0, 0, 0)){
|
|
target = new Date(currentYear + 1, month, day);
|
|
}
|
|
const msPerDay = 1000 * 60 * 60 * 24;
|
|
const diff = target - new Date(today.setHours(0,0,0,0));
|
|
return Math.ceil(diff / msPerDay);
|
|
};
|
|
|
|
const sorted = [...data].sort((a, b) => {
|
|
const dateA = parseBirthdayDate(a);
|
|
const dateB = parseBirthdayDate(b);
|
|
return daysUntilNext(dateA.getMonth(), dateA.getDate()) - daysUntilNext(dateB.getMonth(), dateB.getDate());
|
|
});
|
|
|
|
// Group by date
|
|
const grouped = sorted.reduce((acc, item) => {
|
|
const existing = acc.find((section) => section.title === item.date);
|
|
|
|
if (existing) {
|
|
existing.data.push(item);
|
|
} else {
|
|
acc.push({ title: item.date, data: [item] });
|
|
}
|
|
|
|
return acc;
|
|
}, [] as SectionData[]);
|
|
|
|
return grouped;
|
|
}
|
|
|
|
function parseBirthdayDate(date: string) {
|
|
return new Date(`${date}T00:00:00`);
|
|
}
|
|
|
|
function formatDisplayDate(date: string) {
|
|
return parseBirthdayDate(date).toLocaleDateString(undefined, {
|
|
month: "long",
|
|
day: "numeric",
|
|
year: "numeric",
|
|
});
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
flex: 1,
|
|
},
|
|
list: {
|
|
flex: 1,
|
|
},
|
|
listContent: {
|
|
paddingBottom: 20,
|
|
},
|
|
headerContainer: {
|
|
paddingVertical: 10,
|
|
paddingHorizontal: 5,
|
|
},
|
|
sectionHeader: {
|
|
fontSize: 18,
|
|
fontWeight: "bold",
|
|
},
|
|
emptyState: {
|
|
paddingHorizontal: 5,
|
|
paddingTop: 24,
|
|
gap: 6,
|
|
},
|
|
emptyTitle: {
|
|
fontSize: 18,
|
|
fontWeight: "bold",
|
|
},
|
|
emptyText: {
|
|
color: "#666",
|
|
fontSize: 15,
|
|
},
|
|
});
|