Commit a10f7f83 by tungnq

TODO: Đã hoàn thiện giao diện màn 3 ngày

parent 366d8116
import React from 'react'; import React from 'react';
import {Text, View, StyleSheet} from 'react-native'; import {Text, View, StyleSheet} from 'react-native';
import Filter3DayView from './view'; import Filter3DayView from './view'
const Filter3Day = (props) => { const Filter3Day = (props) => {
return ( return (
......
import { StyleSheet, Text, View } from 'react-native'
import React from 'react'
const Filter3DayView = () => {
return (
<View>
<Text>Filter3DayView</Text>
</View>
)
}
export default Filter3DayView
import { StyleSheet, Text, View } from 'react-native' import React, {useState, useRef, useEffect} from 'react';
import React from 'react' import {
View,
Text,
TouchableOpacity,
ScrollView,
StyleSheet,
Dimensions,
SafeAreaView,
DeviceEventEmitter,
PanResponder,
} from 'react-native';
import R from '../../../assets/R';
const {width: screenWidth, height: screenHeight} = Dimensions.get('window');
const HOUR_HEIGHT = 80;
const DAY_COLUMN_WIDTH = (screenWidth - 70) / 3;
const Filter3DayView = () => { const Filter3DayView = ({navigation}) => {
const [currentDate, setCurrentDate] = useState(new Date());
const [selectedDate, setSelectedDate] = useState(new Date());
const [showMonthPicker, setShowMonthPicker] = useState(false);
const scrollViewRef = useRef(null);
useEffect(() => {
DeviceEventEmitter.emit('onDateChange', selectedDate);
}, [selectedDate]);
useEffect(() => {
DeviceEventEmitter.emit('onDateChange', currentDate);
}, [currentDate]);
const createMockEvents = () => {
return [
{
id: '1',
title: 'Lịch vào trực lớp TTCĐT 445.T1',
subtitle: 'CS lớp 4D',
time: '07:00',
endTime: '09:00',
date: '2025-07-24',
type: 'class',
},
{
id: '2',
title: 'Meeting team development',
subtitle: 'Phòng họp A1',
time: '10:00',
endTime: '11:30',
date: '2025-07-25',
type: 'meeting',
},
{
id: '3',
title: 'Training React Native',
subtitle: 'Online Zoom',
time: '14:00',
endTime: '16:00',
date: '2025-07-26',
type: 'training',
},
{
id: '4',
title: 'Code Review Session',
subtitle: 'Dev Team',
time: '09:30',
endTime: '11:30',
date: '2025-08-25',
type: 'review',
},
];
};
const mockEvents = createMockEvents();
const formatDateToString = (date) => {
const year = date.getFullYear();
const month = (date.getMonth() + 1).toString().padStart(2, '0');
const day = date.getDate().toString().padStart(2, '0');
return `${year}-${month}-${day}`;
};
const getEventsForDate = (date) => {
const dateStr = formatDateToString(date);
return mockEvents.filter(event => event.date === dateStr);
};
const getDayName = (date) => {
const days = ['Chủ nhật', 'Thứ 2', 'Thứ 3', 'Thứ 4', 'Thứ 5', 'Thứ 6', 'Thứ 7'];
return days[date.getDay()];
};
const getMonthName = (monthIndex) => {
const months = [
'Tháng 1', 'Tháng 2', 'Tháng 3', 'Tháng 4', 'Tháng 5', 'Tháng 6',
'Tháng 7', 'Tháng 8', 'Tháng 9', 'Tháng 10', 'Tháng 11', 'Tháng 12',
];
return months[monthIndex];
};
const get3DaysDates = (date) => {
const dates = [];
for (let i = 0; i < 3; i++) {
const dayDate = new Date(date);
dayDate.setDate(date.getDate() + i);
dates.push(dayDate);
}
return dates;
};
const isToday = (date) => {
const today = new Date();
return (
date.getDate() === today.getDate() &&
date.getMonth() === today.getMonth() &&
date.getFullYear() === today.getFullYear()
);
};
const handleMonthSelect = (monthIndex) => {
const newDate = new Date(currentDate);
newDate.setMonth(monthIndex);
setCurrentDate(newDate);
setSelectedDate(newDate);
setShowMonthPicker(false);
DeviceEventEmitter.emit('onDateChange', newDate);
};
const swipeToNext3Days = () => {
const nextDate = new Date(currentDate);
nextDate.setDate(currentDate.getDate() + 3);
setCurrentDate(nextDate);
setSelectedDate(nextDate);
DeviceEventEmitter.emit('onDateChange', nextDate);
};
const swipeToPrev3Days = () => {
const prevDate = new Date(currentDate);
prevDate.setDate(currentDate.getDate() - 3);
setCurrentDate(prevDate);
setSelectedDate(prevDate);
DeviceEventEmitter.emit('onDateChange', prevDate);
};
const toggleMonthPicker = () => {
setShowMonthPicker(!showMonthPicker);
};
const panResponder = PanResponder.create({
onMoveShouldSetPanResponder: (evt, gestureState) => {
return Math.abs(gestureState.dx) > 30 && Math.abs(gestureState.dy) < 100;
},
onPanResponderMove: (evt, gestureState) => {},
onPanResponderRelease: (evt, gestureState) => {
if (gestureState.dx > 50) {
swipeToPrev3Days();
} else if (gestureState.dx < -50) {
swipeToNext3Days();
}
},
});
const calculateEventPosition = (startTime, endTime) => {
const [startHour, startMinute] = startTime.split(':').map(Number);
const [endHour, endMinute] = endTime.split(':').map(Number);
const startTotalMinutes = startHour * 60 + startMinute;
const endTotalMinutes = endHour * 60 + endMinute;
const durationMinutes = endTotalMinutes - startTotalMinutes;
const topPosition = (startTotalMinutes / 60) * HOUR_HEIGHT;
const height = (durationMinutes / 60) * HOUR_HEIGHT;
return { topPosition, height };
};
const renderMonthPicker = () => {
if (!showMonthPicker) return null;
return (
<View style={styles.monthPickerContainer}>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={styles.monthPickerContent}>
{Array.from({length: 12}, (_, index) => (
<TouchableOpacity
key={index}
style={[
styles.monthItem,
currentDate.getMonth() === index && styles.monthItemSelected,
]}
onPress={() => handleMonthSelect(index)}>
<Text
style={[
styles.monthItemText,
currentDate.getMonth() === index && styles.monthItemTextSelected,
]}>
{getMonthName(index)}
</Text>
</TouchableOpacity>
))}
</ScrollView>
</View>
);
};
const render3DaysHeader = () => {
const threeDaysDates = get3DaysDates(currentDate);
return (
<View style={styles.daysHeaderContainer}>
<View style={styles.timeColumnHeader} />
{threeDaysDates.map((date, index) => {
const isTodayDate = isToday(date);
return (
<View key={index} style={styles.dayHeaderCell}>
<Text style={[
styles.dayHeaderText,
isTodayDate && styles.todayHeaderText
]}>
{getDayName(date)}
</Text>
<View style={[
styles.dayNumberContainer,
isTodayDate && styles.todayNumberContainer
]}>
<Text style={[
styles.dayHeaderNumber,
isTodayDate && styles.todayHeaderNumber
]}>
{date.getDate()}
</Text>
</View>
</View>
);
})}
</View>
);
};
const renderTimeSlots = () => {
const hours = Array.from({length: 24}, (_, i) => i);
const threeDaysDates = get3DaysDates(currentDate);
return (
<View style={styles.timeSlotsContainer} {...panResponder.panHandlers}>
<ScrollView
ref={scrollViewRef}
showsVerticalScrollIndicator={false}
contentContainerStyle={styles.scrollContent}>
<View style={styles.timelineContainer}>
<View style={styles.timeLabelsColumn}>
{hours.map((hour) => {
const timeStr = hour.toString().padStart(2, '0') + ':00';
return ( return (
<View> <View key={hour} style={styles.timeSlot}>
<Text>Filter3DayView</Text> <Text style={styles.timeText}>{timeStr}</Text>
</View> </View>
) );
} })}
</View>
<View style={styles.daysGridContainer}>
{threeDaysDates.map((date, dayIndex) => (
<View key={dayIndex} style={styles.dayColumn}>
{hours.map((hour) => (
<View key={hour} style={styles.gridCell} />
))}
{getEventsForDate(date).map((event) => {
const { topPosition, height } = calculateEventPosition(event.time, event.endTime);
return (
<TouchableOpacity
key={event.id}
style={[
styles.eventCard,
{
position: 'absolute',
top: topPosition,
height: Math.max(height, 40),
left: 4,
right: 4,
zIndex: 10,
backgroundColor: R.colors.blue,
}
]}
activeOpacity={0.7}>
<Text style={styles.eventTitle} numberOfLines={height > 80 ? 3 : 2}>
{event.title}
</Text>
{height > 50 && (
<Text style={styles.eventSubtitle} numberOfLines={1}>
{event.subtitle}
</Text>
)}
<Text style={styles.eventTime}>
Thi gian hc: {event.time} - {event.endTime}, {getDayName(date)} {date.getDate()}/{date.getMonth() + 1}/{date.getFullYear()}
</Text>
</TouchableOpacity>
);
})}
</View>
))}
</View>
</View>
</ScrollView>
</View>
);
};
return (
<SafeAreaView style={styles.container}>
{renderMonthPicker()}
{render3DaysHeader()}
{renderTimeSlots()}
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: R.colors.white,
},
monthPickerContainer: {
backgroundColor: R.colors.white,
borderBottomWidth: 1,
borderBottomColor: R.colors.gray,
paddingVertical: 10,
},
monthPickerContent: {
paddingHorizontal: 15,
},
monthItem: {
paddingHorizontal: 20,
paddingVertical: 10,
marginRight: 10,
borderRadius: 20,
backgroundColor: R.colors.gray,
},
monthItemSelected: {
backgroundColor: R.colors.black,
},
monthItemText: {
fontSize: R.fontsize.fontSizeLabel,
fontFamily: R.fonts.fontRegular,
color: R.colors.black,
},
monthItemTextSelected: {
color: R.colors.white,
fontFamily: R.fonts.fontMedium,
},
daysHeaderContainer: {
flexDirection: 'row',
backgroundColor: R.colors.gray,
borderBottomWidth: 1,
borderBottomColor: R.colors.gray2,
},
timeColumnHeader: {
width: 70,
},
dayHeaderCell: {
width: DAY_COLUMN_WIDTH,
alignItems: 'center',
justifyContent: 'center',
borderRightWidth: 1,
borderRightColor: R.colors.gray2,
},
dayHeaderText: {
fontSize: R.fontsize.fontSizeLabel,
fontFamily: R.fonts.fontRegular,
color: R.colors.black,
},
todayHeaderText: {
color: R.colors.black,
fontFamily: R.fonts.fontRegular,
},
dayNumberContainer: {
minWidth: 28,
minHeight: 28,
borderRadius: 15,
alignItems: 'center',
justifyContent: 'center',
marginVertical:5
},
todayNumberContainer: {
backgroundColor: R.colors.blue,
},
dayHeaderNumber: {
fontSize: R.fontsize.fontSizeLabel,
fontFamily: R.fonts.fontRegular,
color: R.colors.black,
},
todayHeaderNumber: {
color: R.colors.white,
fontFamily: R.fonts.fontMedium,
},
timeSlotsContainer: {
flex: 1,
backgroundColor: R.colors.white,
},
scrollContent: {
},
timelineContainer: {
flexDirection: 'row',
position: 'relative',
},
timeLabelsColumn: {
width: 70,
borderRightWidth: 1,
borderRightColor: R.colors.gray2,
},
daysGridContainer: {
flex: 1,
flexDirection: 'row',
},
dayColumn: {
width: DAY_COLUMN_WIDTH,
position: 'relative',
borderRightWidth: 1,
borderRightColor: R.colors.gray2,
},
timeSlot: {
height: HOUR_HEIGHT,
alignItems: 'center',
justifyContent: 'center',
borderBottomWidth: 1,
borderBottomColor: R.colors.gray2,
},
gridCell: {
height: HOUR_HEIGHT,
borderBottomWidth: 1,
borderBottomColor: R.colors.gray2,
width: '100%',
},
timeText: {
fontSize: R.fontsize.fontSizeLabel,
fontFamily: R.fonts.fontRegular,
color: R.colors.black,
},
eventCard: {
borderRadius: 8,
paddingHorizontal: 6,
paddingVertical: 4,
},
eventTitle: {
fontSize: R.fontsize.fontSizeLabel,
fontFamily: R.fonts.fontMedium,
color: R.colors.white,
marginBottom: 2,
},
eventSubtitle: {
fontSize: R.fontsize.fontSizeLabel,
fontFamily: R.fonts.fontRegular,
color: R.colors.white,
},
eventTime: {
fontSize: R.fontsize.fontSizeLabel,
fontFamily: R.fonts.fontRegular,
color: R.colors.white,
},
});
export default Filter3DayView export default Filter3DayView
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment