141 lines
3.6 KiB
TypeScript
141 lines
3.6 KiB
TypeScript
import { MenstrualTimeline } from '@/utils/menstrualCycle';
|
|
import React, { useMemo } from 'react';
|
|
import { StyleSheet, Text, View } from 'react-native';
|
|
import { DayCell } from './DayCell';
|
|
import { WEEK_LABELS } from './constants';
|
|
|
|
const chunkArray = <T,>(array: T[], size: number): T[][] => {
|
|
const result: T[][] = [];
|
|
for (let i = 0; i < array.length; i += size) {
|
|
result.push(array.slice(i, i + size));
|
|
}
|
|
return result;
|
|
};
|
|
|
|
interface MonthBlockProps {
|
|
month: MenstrualTimeline['months'][number];
|
|
selectedDateKey: string;
|
|
onSelect: (dateKey: string) => void;
|
|
renderTip: (colIndex: number) => React.ReactNode;
|
|
weekLabels?: string[];
|
|
}
|
|
|
|
export const MonthBlock: React.FC<MonthBlockProps> = ({
|
|
month,
|
|
selectedDateKey,
|
|
onSelect,
|
|
renderTip,
|
|
weekLabels,
|
|
}) => {
|
|
const weeks = useMemo(() => chunkArray(month.cells, 7), [month.cells]);
|
|
const labels = weekLabels?.length === 7 ? weekLabels : WEEK_LABELS;
|
|
|
|
return (
|
|
<View style={styles.monthCard}>
|
|
<View style={styles.monthHeader}>
|
|
<Text style={styles.monthTitle}>{month.title}</Text>
|
|
<Text style={styles.monthSubtitle}>{month.subtitle}</Text>
|
|
</View>
|
|
<View style={styles.weekRow}>
|
|
{labels.map((label) => (
|
|
<Text key={label} style={styles.weekLabel}>
|
|
{label}
|
|
</Text>
|
|
))}
|
|
</View>
|
|
<View style={styles.monthGrid}>
|
|
{weeks.map((week, weekIndex) => {
|
|
const selectedIndex = week.findIndex(
|
|
(c) => c.type === 'day' && c.date.format('YYYY-MM-DD') === selectedDateKey
|
|
);
|
|
|
|
return (
|
|
<React.Fragment key={weekIndex}>
|
|
<View style={styles.daysRow}>
|
|
{week.map((cell) => {
|
|
if (cell.type === 'placeholder') {
|
|
return <View key={cell.key} style={styles.dayCell} />;
|
|
}
|
|
const dateKey = cell.date.format('YYYY-MM-DD');
|
|
return (
|
|
<DayCell
|
|
key={cell.key}
|
|
cell={cell}
|
|
isSelected={selectedDateKey === dateKey}
|
|
onPress={() => onSelect(dateKey)}
|
|
/>
|
|
);
|
|
})}
|
|
</View>
|
|
{selectedIndex !== -1 && (
|
|
<View style={styles.inlineTipContainer}>
|
|
{renderTip(selectedIndex)}
|
|
</View>
|
|
)}
|
|
</React.Fragment>
|
|
);
|
|
})}
|
|
</View>
|
|
</View>
|
|
);
|
|
};
|
|
|
|
const styles = StyleSheet.create({
|
|
monthCard: {
|
|
backgroundColor: '#fff',
|
|
borderRadius: 16,
|
|
padding: 14,
|
|
marginBottom: 12,
|
|
shadowColor: '#000',
|
|
shadowOpacity: 0.08,
|
|
shadowRadius: 8,
|
|
shadowOffset: { width: 0, height: 6 },
|
|
elevation: 2,
|
|
},
|
|
monthHeader: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
justifyContent: 'space-between',
|
|
marginBottom: 8,
|
|
},
|
|
monthTitle: {
|
|
fontSize: 17,
|
|
fontWeight: '800',
|
|
color: '#0f172a',
|
|
fontFamily: 'AliBold',
|
|
},
|
|
monthSubtitle: {
|
|
fontSize: 12,
|
|
color: '#6b7280',
|
|
fontFamily: 'AliRegular',
|
|
},
|
|
weekRow: {
|
|
flexDirection: 'row',
|
|
justifyContent: 'space-between',
|
|
marginBottom: 6,
|
|
paddingHorizontal: 4,
|
|
},
|
|
weekLabel: {
|
|
width: '14.28%',
|
|
textAlign: 'center',
|
|
fontSize: 12,
|
|
color: '#94a3b8',
|
|
fontFamily: 'AliRegular',
|
|
},
|
|
monthGrid: {
|
|
flexDirection: 'column',
|
|
},
|
|
daysRow: {
|
|
flexDirection: 'row',
|
|
},
|
|
dayCell: {
|
|
width: '14.28%',
|
|
alignItems: 'center',
|
|
marginVertical: 6,
|
|
},
|
|
inlineTipContainer: {
|
|
paddingBottom: 6,
|
|
marginBottom: 6,
|
|
},
|
|
});
|