Added main functionality

This commit is contained in:
2026-04-21 23:24:54 +02:00
parent cb7555590f
commit e8e902119b
7 changed files with 545 additions and 39 deletions

View File

@@ -1,18 +1,126 @@
// BirthdayItem.tsx
import { StyleSheet, Text, View } from "react-native";
import { useState } from "react";
import {
Modal,
Pressable,
StyleSheet,
Text,
View,
} from "react-native";
interface BirthdayItemProps {
id: string;
name: string;
date: string;
age?: number;
onDelete: (id: string) => void;
}
export function BirthdayItem({ name, date, age }: BirthdayItemProps) {
export function BirthdayItem({ id, name, date, onDelete }: BirthdayItemProps) {
const [menuOpen, setMenuOpen] = useState(false);
const [menuPosition, setMenuPosition] = useState({ top: 0, left: 0 });
const daysUntilNext = (birthdayDate: string) => {
const today = new Date();
const parsedDate = new Date(birthdayDate);
const month = parsedDate.getMonth();
const day = parsedDate.getDate();
const currentYear = today.getFullYear();
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.getTime() - new Date(today.setHours(0, 0, 0, 0)).getTime();
return Math.ceil(diff / msPerDay);
};
const computeNewAge = (birthdayDate: string) => {
const parsedDate = new Date(birthdayDate);
const today = new Date();
let age = today.getFullYear() - parsedDate.getFullYear();
const monthDiff = today.getMonth() - parsedDate.getMonth();
const dayDiff = today.getDate() - parsedDate.getDate();
if (monthDiff < 0 || (monthDiff === 0 && dayDiff < 0)) {
age--;
}
return age;
};
const days = daysUntilNext(date);
const handleOpenMenu = (event: {
nativeEvent: { pageX: number; pageY: number };
}) => {
setMenuPosition({
top: event.nativeEvent.pageY + 8,
left: event.nativeEvent.pageX - 136,
});
setMenuOpen(true);
};
const handleDelete = () => {
setMenuOpen(false);
onDelete(id);
};
return (
<View style={styles.itemContainer}>
<Text style={styles.name}>{name}</Text>
{age && <Text style={styles.age}>Age: {age}</Text>}
</View>
<>
<View style={styles.itemContainer}>
<View style={styles.contentRow}>
<View style={styles.textContainer}>
<Text style={styles.name}>{name}</Text>
{days === 0 ? (
<Text style={styles.age}>
{name} makes {computeNewAge(date)} years today!
</Text>
) : (
date && <Text style={styles.age}>Days remaining: {days}</Text>
)}
</View>
<Pressable
accessibilityLabel={`Open actions for ${name}`}
hitSlop={8}
onPress={handleOpenMenu}
style={styles.menuTrigger}
>
<Text style={styles.menuTriggerText}></Text>
</Pressable>
</View>
</View>
<Modal
animationType="fade"
onRequestClose={() => setMenuOpen(false)}
transparent
visible={menuOpen}
>
<View style={styles.menuBackdrop}>
<Pressable
onPress={() => setMenuOpen(false)}
style={styles.menuBackdropPressable}
/>
<View
style={[
styles.menu,
{
top: Math.max(menuPosition.top, 12),
left: Math.max(menuPosition.left, 12),
},
]}
>
<Pressable disabled style={[styles.menuItem, styles.menuItemDisabled]}>
<Text style={[styles.menuItemText, styles.menuItemTextDisabled]}>
Edit
</Text>
</Pressable>
<Pressable onPress={handleDelete} style={styles.menuItem}>
<Text style={[styles.menuItemText, styles.deleteText]}>Delete</Text>
</Pressable>
</View>
</View>
</Modal>
</>
);
}
@@ -24,6 +132,27 @@ const styles = StyleSheet.create({
elevation: 2,
marginBottom: 10,
},
contentRow: {
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
gap: 12,
},
textContainer: {
flex: 1,
},
menuTrigger: {
alignItems: "center",
borderRadius: 999,
height: 36,
justifyContent: "center",
width: 36,
},
menuTriggerText: {
color: "#444",
fontSize: 22,
lineHeight: 22,
},
name: {
fontSize: 16,
fontWeight: "bold",
@@ -35,4 +164,40 @@ const styles = StyleSheet.create({
fontSize: 12,
marginTop: 5,
},
menuBackdrop: {
...StyleSheet.absoluteFillObject,
},
menuBackdropPressable: {
...StyleSheet.absoluteFillObject,
},
menu: {
backgroundColor: "#fff",
borderRadius: 8,
elevation: 8,
minWidth: 140,
overflow: "hidden",
position: "absolute",
shadowColor: "#000",
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.15,
shadowRadius: 12,
},
menuItem: {
paddingHorizontal: 14,
paddingVertical: 12,
},
menuItemDisabled: {
opacity: 0.45,
},
menuItemText: {
color: "#222",
fontSize: 14,
fontWeight: "500",
},
menuItemTextDisabled: {
color: "#666",
},
deleteText: {
color: "#c53a3a",
},
});

View File

@@ -13,7 +13,7 @@ interface SectionData {
}
export function BirthdayList() {
const { birthdays } = useBirthdays();
const { birthdays, deleteBirthday } = useBirthdays();
const groupedData = groupBirthdaysByDate(birthdays);
return (
@@ -22,7 +22,12 @@ export function BirthdayList() {
sections={groupedData}
keyExtractor={(item) => item.id}
renderItem={({ item }) => (
<BirthdayItem name={item.name} date={item.date} />
<BirthdayItem
id={item.id}
name={item.name}
date={item.date}
onDelete={deleteBirthday}
/>
)}
style={styles.list}
contentContainerStyle={styles.listContent}
@@ -48,8 +53,23 @@ export function BirthdayList() {
// 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) => {
return parseBirthdayDate(a.date).getTime() - parseBirthdayDate(b.date).getTime();
const dateA = parseBirthdayDate(a);
const dateB = parseBirthdayDate(b);
return daysUntilNext(dateA.getMonth(), dateA.getDate()) - daysUntilNext(dateB.getMonth(), dateB.getDate());
});
// Group by date