Files
birthday/apk/components/birthdate-list.tsx
Aran Roig 96a9816e42
All checks were successful
Build APK / build (push) Successful in 12m34s
CI/CD Works
2026-04-24 17:17:18 +02:00

154 lines
3.8 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[] {
const today = new Date();
const currentYear = today.getFullYear();
const todayAtMidnight = new Date(today);
todayAtMidnight.setHours(0, 0, 0, 0);
const daysUntilNext = (month: number, day: number) => {
let target = new Date(currentYear, month, day);
if (target < todayAtMidnight) {
target = new Date(currentYear + 1, month, day);
}
const msPerDay = 1000 * 60 * 60 * 24;
const diff = target.getTime() - todayAtMidnight.getTime();
return Math.ceil(diff / msPerDay);
};
const sorted = [...data].sort((a, b) => {
const dateA = parseBirthdayDate(a.date);
const dateB = parseBirthdayDate(b.date);
const nextBirthdayDiff =
daysUntilNext(dateA.getMonth(), dateA.getDate()) -
daysUntilNext(dateB.getMonth(), dateB.getDate());
if (nextBirthdayDiff !== 0) {
return nextBirthdayDiff;
}
return dateA.getFullYear() - dateB.getFullYear();
});
const grouped = sorted.reduce((acc, item) => {
const sectionKey = getMonthDayKey(item.date);
const existing = acc.find((section) => section.title === sectionKey);
if (existing) {
existing.data.push(item);
} else {
acc.push({ title: sectionKey, data: [item] });
}
return acc;
}, [] as SectionData[]);
return grouped;
}
function parseBirthdayDate(date: string) {
return new Date(`${date}T00:00:00`);
}
function getMonthDayKey(date: string) {
const parsedDate = parseBirthdayDate(date);
const month = `${parsedDate.getMonth() + 1}`.padStart(2, "0");
const day = `${parsedDate.getDate()}`.padStart(2, "0");
return `${month}-${day}`;
}
function formatDisplayDate(date: string) {
const [month, day] = date.split("-").map(Number);
return new Date(2000, month - 1, day).toLocaleDateString(undefined, {
month: "long",
day: "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,
},
});