Commit a11a4b61 by tungnq

TODO: Refactor code lọc theo tuần

parent 329dacbc
import React from 'react'; import React, {useState, useRef, useEffect} from 'react';
import {Text, View, StyleSheet} from 'react-native'; import {
Dimensions,
DeviceEventEmitter,
PanResponder,
} from 'react-native';
import FilterWeekView from './view'; import FilterWeekView from './view';
const FilterWeek = (props) => { // ==================== HẰNG SỐ ====================
const {width: screenWidth, height: screenHeight} = Dimensions.get('window');
const HOUR_HEIGHT = 80;
const DAY_COLUMN_WIDTH = (screenWidth - 70) / 7;
const FilterWeek = ({navigation}) => {
// ==================== QUẢN LÝ STATE ====================
// B1: State ngày tháng và lịch
const [currentDate, setCurrentDate] = useState(new Date());
const [selectedDate, setSelectedDate] = useState(new Date());
// B2: State month picker
const [showMonthPicker, setShowMonthPicker] = useState(false);
// B3: Tham chiếu scroll
const scrollViewRef = useRef(null);
// ==================== EFFECTS ====================
useEffect(() => {
DeviceEventEmitter.emit('onDateChange', selectedDate);
}, [selectedDate]);
// ==================== HÀM TIỆN ÍCH ====================
// T1: Định dạng ngày thành chuỗi
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}`;
};
// T2: Kiểm tra ngày hôm nay
const isToday = (someDate) => {
const today = new Date();
return (
someDate.getDate() === today.getDate() &&
someDate.getMonth() === today.getMonth() &&
someDate.getFullYear() === today.getFullYear()
);
};
// T3: Lấy tên ngày trong tuần
const getDayName = (date) => {
const days = ['CN', 'T2', 'T3', 'T4', 'T5', 'T6', 'T7'];
return days[date.getDay()];
};
// T4: Lấy tên tháng
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];
};
// T5: Lấy các ngày trong tuần
const getWeekDates = (date) => {
const startOfWeek = new Date(date);
startOfWeek.setDate(date.getDate() - date.getDay());
const weekDates = [];
for (let i = 0; i < 7; i++) {
const weekDate = new Date(startOfWeek);
weekDate.setDate(startOfWeek.getDate() + i);
weekDates.push(weekDate);
}
return weekDates;
};
// T6: Tính toán vị trí sự kiện
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};
};
// ==================== QUẢN LÝ DỮ LIỆU ====================
// D1: Tạo dữ liệu sự kiện mẫu
const createMockEvents = () => {
return [
{
id: '1',
title: 'Lịch vào trực lớp TTCĐT 445.T1',
subtitle: 'CS Địa lý 4D',
time: '07:00',
endTime: '09:00',
date: '2025-08-26',
type: 'class',
},
{
id: '2',
title: 'Meeting team development',
subtitle: 'Phòng họp A1',
time: '10:00',
endTime: '11:30',
date: '2025-07-22',
type: 'meeting',
},
{
id: '3',
title: 'Training React Native',
subtitle: 'Online Zoom',
time: '14:00',
endTime: '16:00',
date: '2025-07-23',
type: 'training',
},
{
id: '4',
title: 'Code Review Session',
subtitle: 'Dev Team',
time: '09:00',
endTime: '10:30',
date: '2025-07-24',
type: 'review',
},
{
id: '5',
title: 'Sprint Planning',
subtitle: 'Scrum Team',
time: '15:00',
endTime: '17:00',
date: '2025-07-25',
type: 'meeting',
},
{
id: '6',
title: 'Tech Talk',
subtitle: 'Conference Room',
time: '11:00',
endTime: '12:00',
date: '2025-07-26',
type: 'training',
},
];
};
// D2: Xử lý dữ liệu sự kiện
const mockEvents = createMockEvents();
// D3: Hàm truy vấn sự kiện
const getEventsForDate = (date) => {
const dateStr = formatDateToString(date);
return mockEvents.filter(event => event.date === dateStr);
};
// ==================== HỆ THỐNG ANIMATION ====================
// A1: Thiết lập PanResponder
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) {
swipeToPrevWeek();
} else if (gestureState.dx < -50) {
swipeToNextWeek();
}
},
});
// ==================== XỬ LÝ SỰ KIỆN ====================
// X1: Xử lý chọn tháng
const handleMonthSelect = (monthIndex) => {
const newDate = new Date(currentDate);
newDate.setMonth(monthIndex);
setCurrentDate(newDate);
setSelectedDate(newDate);
setShowMonthPicker(false);
DeviceEventEmitter.emit('onDateChange', newDate);
};
// X2: Xử lý chuyển tuần
const swipeToNextWeek = () => {
const nextWeek = new Date(currentDate);
nextWeek.setDate(currentDate.getDate() + 7);
setCurrentDate(nextWeek);
setSelectedDate(nextWeek);
DeviceEventEmitter.emit('onDateChange', nextWeek);
};
const swipeToPrevWeek = () => {
const prevWeek = new Date(currentDate);
prevWeek.setDate(currentDate.getDate() - 7);
setCurrentDate(prevWeek);
setSelectedDate(prevWeek);
DeviceEventEmitter.emit('onDateChange', prevWeek);
};
// X3: Xử lý toggle month picker
const toggleMonthPicker = () => {
setShowMonthPicker(!showMonthPicker);
};
return ( return (
<FilterWeekView /> <FilterWeekView
navigation={navigation}
currentDate={currentDate}
selectedDate={selectedDate}
showMonthPicker={showMonthPicker}
scrollViewRef={scrollViewRef}
panResponder={panResponder}
isToday={isToday}
getDayName={getDayName}
getMonthName={getMonthName}
getWeekDates={getWeekDates}
getEventsForDate={getEventsForDate}
calculateEventPosition={calculateEventPosition}
handleMonthSelect={handleMonthSelect}
toggleMonthPicker={toggleMonthPicker}
HOUR_HEIGHT={HOUR_HEIGHT}
DAY_COLUMN_WIDTH={DAY_COLUMN_WIDTH}
/>
); );
}; };
......
import {StyleSheet, Dimensions} from 'react-native';
import R from '../../../assets/R';
const {width} = Dimensions.get('window');
const DAY_COLUMN_WIDTH = (width - 70) / 7;
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: R.colors.white,
},
monthItem: {
paddingHorizontal: 20,
paddingVertical: 8,
marginRight: 10,
borderRadius: 20,
backgroundColor: R.colors.grayBorderInputTextHeader,
},
monthItemSelected: {
backgroundColor: R.colors.blue,
},
monthItemText: {
fontSize: R.fontsize.fontSizeContent,
fontFamily: R.fonts.InterRegular,
color: R.colors.black,
},
monthItemTextSelected: {
color: R.colors.white,
fontFamily: R.fonts.fontMedium,
},
weekHeaderContainer: {
flexDirection: 'row',
backgroundColor: R.colors.grayBorderInputTextHeader,
borderBottomWidth: 1,
borderBottomColor: R.colors.gray,
},
timeColumnHeader: {
width: 70,
},
dayHeaderCell: {
width: DAY_COLUMN_WIDTH,
alignItems: 'center',
justifyContent: 'center',
borderRightWidth: 1,
borderRightColor: R.colors.gray,
},
dayHeaderText: {
fontSize: R.fontsize.fontSizeLabel,
fontFamily: R.fonts.fontRegular,
fontWeight: '400',
color: R.colors.black,
},
dayHeaderNumber: {
fontSize: R.fontsize.fontSizeContent,
fontFamily: R.fonts.fontMedium,
color: R.colors.black,
},
dayHeaderNumberContainerToday: {
borderRadius: 15,
justifyContent: 'center',
alignItems: 'center',
},
dayHeaderNumberToday: {
color: R.colors.blue,
fontFamily: R.fonts.fontMedium,
fontSize: R.fontsize.fontSizeContent,
},
timeSlotsContainer: {
flex: 1,
backgroundColor: R.colors.white,
},
scrollContent: {
paddingBottom: 50,
},
timelineContainer: {
flexDirection: 'row',
position: 'relative',
},
timeLabelsColumn: {
width: 70,
borderRightWidth: 1,
borderRightColor: R.colors.gray,
},
weekGridContainer: {
flex: 1,
flexDirection: 'row',
},
dayColumn: {
width: DAY_COLUMN_WIDTH,
position: 'relative',
borderRightWidth: 1,
borderRightColor: R.colors.gray,
},
timeSlot: {
height: 80,
alignItems: 'center',
justifyContent: 'center',
borderBottomWidth: 1,
borderBottomColor: R.colors.gray,
},
gridCell: {
height: 80,
borderBottomWidth: 1,
borderBottomColor: R.colors.gray,
width: '100%',
},
monthPickerContainer: {
backgroundColor: R.colors.white,
borderBottomWidth: 1,
borderBottomColor: R.colors.gray,
paddingVertical: 10,
},
monthPickerContent: {
paddingHorizontal: 15,
},
timeText: {
fontSize: R.fontsize.fontSizeContent,
fontFamily: R.fonts.fontRegular,
fontWeight: '400',
color: R.colors.black,
},
eventCard: {
borderRadius: 10,
paddingHorizontal: 4,
paddingVertical: 2,
justifyContent: 'center',
},
eventTitle: {
fontSize: R.fontsize.fontSizeBtn,
fontFamily: R.fonts.fontMedium,
color: R.colors.white,
marginBottom: 1,
},
eventSubtitle: {
fontSize: R.fontsize.fontSizeContent,
fontFamily: R.fonts.fontRegular,
color: R.colors.white,
},
eventTime: {
fontSize: R.fontsize.fontSizeContent,
fontFamily: R.fonts.fontRegular,
color: R.colors.white,
},
});
export default styles;
\ No newline at end of file
import React, { useState, useRef, useEffect } from 'react'; import React from 'react';
import { import {
View, View,
Text, Text,
TouchableOpacity, TouchableOpacity,
ScrollView, ScrollView,
StyleSheet, FlatList,
Dimensions,
SafeAreaView,
DeviceEventEmitter,
PanResponder,
} from 'react-native'; } from 'react-native';
import R from '../../../assets/R'; import R from '../../../assets/R';
import styles from './style';
const { width: screenWidth, height: screenHeight } = Dimensions.get('window');
const HOUR_HEIGHT = 80; const FilterWeekView = ({
const DAY_COLUMN_WIDTH = (screenWidth - 70) / 7; navigation,
currentDate,
const FilterWeekView = ({ navigation }) => { selectedDate,
const [currentDate, setCurrentDate] = useState(new Date()); showMonthPicker,
const [selectedDate, setSelectedDate] = useState(new Date()); scrollViewRef,
const [showMonthPicker, setShowMonthPicker] = useState(false); panResponder,
const scrollViewRef = useRef(null); isToday,
getDayName,
useEffect(() => { getMonthName,
DeviceEventEmitter.emit('onDateChange', selectedDate); getWeekDates,
}, [selectedDate]); getEventsForDate,
calculateEventPosition,
const createMockEvents = () => { handleMonthSelect,
return [ toggleMonthPicker,
{ HOUR_HEIGHT,
id: '1', DAY_COLUMN_WIDTH,
title: 'Lịch vào trực lớp TTCĐT 445.T1', }) => {
subtitle: 'CS Địa lý 4D',
time: '07:00',
endTime: '09:00',
date: '2025-08-26',
type: 'class',
},
{
id: '2',
title: 'Meeting team development',
subtitle: 'Phòng họp A1',
time: '10:00',
endTime: '11:30',
date: '2025-07-22',
type: 'meeting',
},
{
id: '3',
title: 'Training React Native',
subtitle: 'Online Zoom',
time: '14:00',
endTime: '16:00',
date: '2025-07-23',
type: 'training',
},
{
id: '4',
title: 'Code Review Session',
subtitle: 'Dev Team',
time: '09:00',
endTime: '10:30',
date: '2025-07-24',
type: 'review',
},
{
id: '5',
title: 'Sprint Planning',
subtitle: 'Scrum Team',
time: '15:00',
endTime: '17:00',
date: '2025-07-25',
type: 'meeting',
},
{
id: '6',
title: 'Tech Talk',
subtitle: 'Conference Room',
time: '11:00',
endTime: '12:00',
date: '2025-07-26',
type: 'training',
},
];
};
const isToday = (someDate) => {
const today = new Date();
return (
someDate.getDate() === today.getDate() &&
someDate.getMonth() === today.getMonth() &&
someDate.getFullYear() === today.getFullYear()
);
};
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 = ['CN', 'T2', 'T3', 'T4', 'T5', 'T6', 'T7'];
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 getWeekDates = (date) => {
const startOfWeek = new Date(date);
startOfWeek.setDate(date.getDate() - date.getDay());
const weekDates = [];
for (let i = 0; i < 7; i++) {
const weekDate = new Date(startOfWeek);
weekDate.setDate(startOfWeek.getDate() + i);
weekDates.push(weekDate);
}
return weekDates;
};
const handleMonthSelect = (monthIndex) => {
const newDate = new Date(currentDate);
newDate.setMonth(monthIndex);
setCurrentDate(newDate);
setSelectedDate(newDate);
setShowMonthPicker(false);
DeviceEventEmitter.emit('onDateChange', newDate);
};
const swipeToNextWeek = () => {
const nextWeek = new Date(currentDate);
nextWeek.setDate(currentDate.getDate() + 7);
setCurrentDate(nextWeek);
setSelectedDate(nextWeek);
DeviceEventEmitter.emit('onDateChange', nextWeek);
};
const swipeToPrevWeek = () => {
const prevWeek = new Date(currentDate);
prevWeek.setDate(currentDate.getDate() - 7);
setCurrentDate(prevWeek);
setSelectedDate(prevWeek);
DeviceEventEmitter.emit('onDateChange', prevWeek);
};
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) {
swipeToPrevWeek();
} else if (gestureState.dx < -50) {
swipeToNextWeek();
}
},
});
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 = () => { const renderMonthPicker = () => {
if (!showMonthPicker) return null; if (!showMonthPicker) return null;
...@@ -326,140 +165,13 @@ const FilterWeekView = ({ navigation }) => { ...@@ -326,140 +165,13 @@ const FilterWeekView = ({ navigation }) => {
}; };
return ( return (
<SafeAreaView style={styles.container}> <View style={styles.container}>
{renderMonthPicker()} {renderMonthPicker()}
{renderWeekHeader()} {renderWeekHeader()}
{renderTimeSlots()} {renderTimeSlots()}
</SafeAreaView> </View>
); );
}; };
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: R.colors.white,
},
monthItem: {
paddingHorizontal: 20,
paddingVertical: 8,
marginRight: 10,
borderRadius: 20,
backgroundColor: R.colors.grayBorderInputTextHeader,
},
monthItemSelected: {
backgroundColor: R.colors.blue,
},
monthItemText: {
fontSize: R.fontsize.fontSizeContent,
fontFamily: R.fonts.InterRegular,
color: R.colors.black,
},
monthItemTextSelected: {
color: R.colors.white,
fontFamily: R.fonts.fontMedium,
},
weekHeaderContainer: {
flexDirection: 'row',
backgroundColor: R.colors.grayBorderInputTextHeader,
borderBottomWidth: 1,
borderBottomColor: R.colors.gray,
},
timeColumnHeader: {
width: 70,
},
dayHeaderCell: {
width: DAY_COLUMN_WIDTH,
alignItems: 'center',
justifyContent: 'center',
borderRightWidth: 1,
borderRightColor: R.colors.gray,
},
dayHeaderText: {
fontSize: R.fontsize.fontSizeLabel,
fontFamily: R.fonts.fontRegular,
fontWeight: '400',
color: R.colors.black,
},
dayHeaderNumber: {
fontSize: R.fontsize.fontSizeContent,
fontFamily: R.fonts.fontMedium,
color: R.colors.black,
},
dayHeaderNumberContainerToday: {
borderRadius: 15,
justifyContent: 'center',
alignItems: 'center',
},
dayHeaderNumberToday: {
color: R.colors.blue,
fontFamily: R.fonts.fontMedium,
fontSize: R.fontsize.fontSizeContent,
},
timeSlotsContainer: {
flex: 1,
backgroundColor: R.colors.white,
},
timelineContainer: {
flexDirection: 'row',
position: 'relative',
},
timeLabelsColumn: {
width: 70,
borderRightWidth: 1,
borderRightColor: R.colors.gray,
},
weekGridContainer: {
flex: 1,
flexDirection: 'row',
},
dayColumn: {
width: DAY_COLUMN_WIDTH,
position: 'relative',
borderRightWidth: 1,
borderRightColor: R.colors.gray,
},
timeSlot: {
height: HOUR_HEIGHT,
alignItems: 'center',
justifyContent: 'center',
borderBottomWidth: 1,
borderBottomColor: R.colors.gray,
},
gridCell: {
height: HOUR_HEIGHT,
borderBottomWidth: 1,
borderBottomColor: R.colors.gray,
width: '100%',
},
timeText: {
fontSize: R.fontsize.fontSizeContent,
fontFamily: R.fonts.fontRegular,
fontWeight: '400',
color: R.colors.black,
},
eventCard: {
borderRadius: 10,
paddingHorizontal: 4,
paddingVertical: 2,
justifyContent: 'center',
},
eventTitle: {
fontSize: R.fontsize.fontSizeBtn,
fontFamily: R.fonts.fontMedium,
color: R.colors.white,
marginBottom: 1,
},
eventSubtitle: {
fontSize: R.fontsize.fontSizeContent,
fontFamily: R.fonts.fontRegular,
color: R.colors.white,
},
eventTime: {
fontSize: R.fontsize.fontSizeContent,
fontFamily: R.fonts.fontRegular,
color: R.colors.white,
},
});
export default FilterWeekView; export default FilterWeekView;
\ No newline at end of file
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