Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
A
AppUms_Student
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
tungnq
AppUms_Student
Commits
9b3fee3a
Commit
9b3fee3a
authored
Sep 26, 2025
by
tungnq
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
TODO: Đã tối ưu và bổ sung thêm chức năng cho ngày
parent
19c4050a
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
388 additions
and
335 deletions
+388
-335
constants.js
src/config/constants.js
+10
-0
useFilterDay.js
src/hooks/useFilterDay.js
+103
-0
drawerView.js
src/routers/Drawer/drawerView.js
+2
-2
index.js
src/screens/class_schedule/filter_date/index.js
+52
-147
style.js
src/screens/class_schedule/filter_date/style.js
+148
-0
view.js
src/screens/class_schedule/filter_date/view.js
+73
-186
No files found.
src/config/constants.js
View file @
9b3fee3a
...
...
@@ -183,3 +183,13 @@ export const DEVICE_EVENT_KEY = {
RELOAD_BALANCE_WALLET
:
"reloadBalanceWallet"
,
LOGOUT_EVENT
:
"logoutEvent"
,
};
// Calendar constants
export
const
HOUR_HEIGHT
=
80
;
export
const
DAYS_SHORT
=
[
'CN'
,
'T2'
,
'T3'
,
'T4'
,
'T5'
,
'T6'
,
'T7'
];
export
const
MONTHS_VI
=
[
'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'
];
src/hooks/useFilterDay.js
0 → 100644
View file @
9b3fee3a
// hooks/useFilterDay.js
import
{
useState
,
useRef
,
useEffect
,
useCallback
}
from
'react'
;
import
{
DeviceEventEmitter
,
PanResponder
}
from
'react-native'
;
import
{
useFocusEffect
}
from
'@react-navigation/native'
;
import
{
HOUR_HEIGHT
,
DAYS_SHORT
,
MONTHS_VI
}
from
'../config/constants'
;
import
{
formatDateToString
,
layoutDayEvents
}
from
'../config/Functions'
;
export
const
useFilterDay
=
(
initialEvents
)
=>
{
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
]);
useFocusEffect
(
useCallback
(()
=>
{
const
today
=
new
Date
();
setCurrentDate
(
today
);
setSelectedDate
(
today
);
DeviceEventEmitter
.
emit
(
'onDateChange'
,
today
);
DeviceEventEmitter
.
emit
(
'updateHeaderMonth'
,
today
.
getMonth
());
},
[])
);
const
getEventsForDate
=
useCallback
((
date
)
=>
{
const
dateStr
=
formatDateToString
(
date
);
return
initialEvents
.
filter
(
e
=>
e
.
date
===
dateStr
);
},
[
initialEvents
]);
const
getDayName
=
(
date
)
=>
DAYS_SHORT
[
date
.
getDay
()];
const
getMonthName
=
(
i
)
=>
MONTHS_VI
[
i
];
const
handleMonthSelect
=
(
monthIndex
)
=>
{
const
newDate
=
new
Date
(
currentDate
);
newDate
.
setMonth
(
monthIndex
);
setCurrentDate
(
newDate
);
setSelectedDate
(
newDate
);
setShowMonthPicker
(
false
);
DeviceEventEmitter
.
emit
(
'onDateChange'
,
newDate
);
DeviceEventEmitter
.
emit
(
'updateHeaderMonth'
,
monthIndex
);
};
const
setDay
=
(
d
)
=>
{
setSelectedDate
(
d
);
setCurrentDate
(
d
);
DeviceEventEmitter
.
emit
(
'onDateChange'
,
d
);
DeviceEventEmitter
.
emit
(
'updateHeaderMonth'
,
d
.
getMonth
());
};
const
swipeToNextDay
=
()
=>
{
const
next
=
new
Date
(
selectedDate
);
next
.
setDate
(
selectedDate
.
getDate
()
+
1
);
setDay
(
next
);
};
const
swipeToPrevDay
=
()
=>
{
const
prev
=
new
Date
(
selectedDate
);
prev
.
setDate
(
selectedDate
.
getDate
()
-
1
);
setDay
(
prev
);
};
const
toggleMonthPicker
=
()
=>
setShowMonthPicker
(
v
=>
!
v
);
const
panResponder
=
PanResponder
.
create
({
onMoveShouldSetPanResponder
:
(
_
,
g
)
=>
Math
.
abs
(
g
.
dx
)
>
30
&&
Math
.
abs
(
g
.
dy
)
<
100
,
onPanResponderRelease
:
(
_
,
g
)
=>
{
if
(
g
.
dx
>
50
)
swipeToPrevDay
();
else
if
(
g
.
dx
<
-
50
)
swipeToNextDay
();
},
});
const
calculateEventPosition
=
(
start
,
end
)
=>
{
const
[
sh
,
sm
]
=
start
.
split
(
':'
).
map
(
Number
);
const
[
eh
,
em
]
=
end
.
split
(
':'
).
map
(
Number
);
const
startMin
=
sh
*
60
+
sm
;
const
endMin
=
eh
*
60
+
em
;
const
topPosition
=
(
startMin
/
60
)
*
HOUR_HEIGHT
;
const
height
=
((
endMin
-
startMin
)
/
60
)
*
HOUR_HEIGHT
;
return
{
topPosition
,
height
};
};
const
calculateEventLayout
=
useCallback
((
events
)
=>
{
return
layoutDayEvents
(
events
,
HOUR_HEIGHT
);
},
[]);
return
{
// state
currentDate
,
selectedDate
,
showMonthPicker
,
scrollViewRef
,
// getters
getEventsForDate
,
getDayName
,
getMonthName
,
// actions
handleMonthSelect
,
toggleMonthPicker
,
swipeToNextDay
,
swipeToPrevDay
,
// gestures
panResponder
,
// layout
calculateEventPosition
,
calculateEventLayout
,
};
};
src/routers/Drawer/drawerView.js
View file @
9b3fee3a
...
...
@@ -15,7 +15,7 @@ import {
}
from
"@react-navigation/drawer"
;
import
{
connect
}
from
"react-redux"
;
import
ClassSchedule
from
"../../screens/class_schedule"
;
import
FilterDate
View
from
'../../screens/class_schedule/filter_date'
;
import
FilterDate
from
'../../screens/class_schedule/filter_date'
;
import
*
as
ScreenName
from
"../ScreenNames"
;
import
CustomDrawerContent
from
"./itemDrawer"
;
import
R
from
"../../assets/R"
;
...
...
@@ -201,7 +201,7 @@ const DrawerNavigatorView = (props) => {
/
>
<
Drawer
.
Screen
name
=
{
ScreenName
.
FILTERDATE
}
component
=
{
FilterDate
View
}
component
=
{
FilterDate
}
options
=
{{
drawerItemStyle
:
{
height
:
0
},
}}
...
...
src/screens/class_schedule/filter_date/index.js
View file @
9b3fee3a
import
React
,
{
useState
,
useRef
,
useEffect
}
from
'react'
;
import
{
DeviceEventEmitter
,
PanResponder
}
from
'react-native'
;
import
FilterDateView
from
'./view'
;
const
FilterDate
=
({
navigation
})
=>
{
const
[
currentDate
,
setCurrentDate
]
=
useState
(
new
Date
(
2025
,
6
,
24
));
const
[
selectedDate
,
setSelectedDate
]
=
useState
(
new
Date
(
2025
,
6
,
24
));
const
[
showMonthPicker
,
setShowMonthPicker
]
=
useState
(
false
);
const
scrollViewRef
=
useRef
(
null
);
useEffect
(()
=>
{
DeviceEventEmitter
.
emit
(
'onDateChange'
,
selectedDate
);
},
[
selectedDate
]);
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-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-24'
,
type
:
'meeting'
,
},
{
id
:
'3'
,
title
:
'Training React Native'
,
subtitle
:
'Online Zoom'
,
time
:
'14:00'
,
endTime
:
'16:00'
,
date
:
'2025-07-25'
,
type
:
'training'
,
},
];
};
const
mockEvents
=
createMockEvents
();
import
React
from
'react'
;
import
FilterDayView
from
'./view'
;
import
{
useFilterDay
}
from
'../../../hooks/useFilterDay'
;
// HOISTED: function declaration (an toàn gọi ở mọi nơi bên dưới)
function
createMockEvents
()
{
const
today
=
new
Date
();
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
handleMonthSelect
=
(
monthIndex
)
=>
{
const
newDate
=
new
Date
(
currentDate
);
newDate
.
setMonth
(
monthIndex
);
setCurrentDate
(
newDate
);
setSelectedDate
(
newDate
);
setShowMonthPicker
(
false
);
DeviceEventEmitter
.
emit
(
'onDateChange'
,
newDate
);
};
const
swipeToNextDay
=
()
=>
{
const
nextDay
=
new
Date
(
selectedDate
);
nextDay
.
setDate
(
selectedDate
.
getDate
()
+
1
);
setSelectedDate
(
nextDay
);
setCurrentDate
(
nextDay
);
DeviceEventEmitter
.
emit
(
'onDateChange'
,
nextDay
);
};
const
swipeToPrevDay
=
()
=>
{
const
prevDay
=
new
Date
(
selectedDate
);
prevDay
.
setDate
(
selectedDate
.
getDate
()
-
1
);
setSelectedDate
(
prevDay
);
setCurrentDate
(
prevDay
);
DeviceEventEmitter
.
emit
(
'onDateChange'
,
prevDay
);
};
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
)
{
swipeToPrevDay
();
}
else
if
(
gestureState
.
dx
<
-
50
)
{
swipeToNextDay
();
}
},
});
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
HOUR_HEIGHT
=
80
;
const
topPosition
=
(
startTotalMinutes
/
60
)
*
HOUR_HEIGHT
;
const
height
=
(
durationMinutes
/
60
)
*
HOUR_HEIGHT
;
return
{
topPosition
,
height
};
};
return
(
<
FilterDateView
navigation
=
{
navigation
}
currentDate
=
{
currentDate
}
selectedDate
=
{
selectedDate
}
showMonthPicker
=
{
showMonthPicker
}
scrollViewRef
=
{
scrollViewRef
}
panResponder
=
{
panResponder
}
getEventsForDate
=
{
getEventsForDate
}
getDayName
=
{
getDayName
}
getMonthName
=
{
getMonthName
}
handleMonthSelect
=
{
handleMonthSelect
}
toggleMonthPicker
=
{
toggleMonthPicker
}
calculateEventPosition
=
{
calculateEventPosition
}
/
>
);
const
y
=
date
.
getFullYear
();
const
m
=
String
(
date
.
getMonth
()
+
1
).
padStart
(
2
,
'0'
);
const
d
=
String
(
date
.
getDate
()).
padStart
(
2
,
'0'
);
return
`
${
y
}
-
${
m
}
-
${
d
}
`
;
};
const
todayStr
=
formatDateToString
(
today
);
const
tomorrow
=
new
Date
(
today
);
tomorrow
.
setDate
(
today
.
getDate
()
+
1
);
const
tomorrowStr
=
formatDateToString
(
tomorrow
);
const
yesterday
=
new
Date
(
today
);
yesterday
.
setDate
(
today
.
getDate
()
-
1
);
const
yesterdayStr
=
formatDateToString
(
yesterday
);
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
:
todayStr
,
type
:
'class'
},
{
id
:
'2'
,
title
:
'Meeting team development'
,
subtitle
:
'Phòng họp A1'
,
time
:
'10:00'
,
endTime
:
'11:30'
,
date
:
todayStr
,
type
:
'meeting'
},
{
id
:
'3'
,
title
:
'Nghỉ giải lao'
,
subtitle
:
'Thư giãn'
,
time
:
'11:30'
,
endTime
:
'12:30'
,
date
:
todayStr
,
type
:
'break'
},
{
id
:
'3a'
,
title
:
'Đang ngủ'
,
subtitle
:
'Thư giãn'
,
time
:
'12:30'
,
endTime
:
'13:30'
,
date
:
todayStr
,
type
:
'break'
},
{
id
:
'4'
,
title
:
'Ăn trưa'
,
subtitle
:
'Căng tin trường'
,
time
:
'12:00'
,
endTime
:
'13:00'
,
date
:
todayStr
,
type
:
'meal'
},
{
id
:
'4a'
,
title
:
'Họp với phụ huynh'
,
subtitle
:
'Phòng 101'
,
time
:
'12:15'
,
endTime
:
'13:15'
,
date
:
todayStr
,
type
:
'meeting'
},
{
id
:
'4b'
,
title
:
'Chấm bài kiểm tra'
,
subtitle
:
'Văn phòng'
,
time
:
'12:30'
,
endTime
:
'14:00'
,
date
:
todayStr
,
type
:
'grading'
},
{
id
:
'5'
,
title
:
'Training React Native'
,
subtitle
:
'Online Zoom'
,
time
:
'14:00'
,
endTime
:
'16:00'
,
date
:
todayStr
,
type
:
'training'
},
{
id
:
'6'
,
title
:
'Code Review Session'
,
subtitle
:
'Dev Team'
,
time
:
'16:30'
,
endTime
:
'17:30'
,
date
:
todayStr
,
type
:
'review'
},
{
id
:
'7'
,
title
:
'Code Review Session'
,
subtitle
:
'Dev Team'
,
time
:
'16:30'
,
endTime
:
'17:30'
,
date
:
todayStr
,
type
:
'review'
},
{
id
:
'6a'
,
title
:
'Cuộc gọi với khách hàng'
,
subtitle
:
'Online'
,
time
:
'16:45'
,
endTime
:
'17:15'
,
date
:
todayStr
,
type
:
'call'
},
{
id
:
'6b'
,
title
:
'Chuẩn bị tài liệu'
,
subtitle
:
'Văn phòng'
,
time
:
'17:00'
,
endTime
:
'18:00'
,
date
:
todayStr
,
type
:
'preparation'
},
{
id
:
'7'
,
title
:
'Họp nội bộ khoa CNTT'
,
subtitle
:
'Phòng họp B2'
,
time
:
'08:00'
,
endTime
:
'10:00'
,
date
:
tomorrowStr
,
type
:
'meeting'
},
{
id
:
'8'
,
title
:
'Lịch học lớp EWC45.364.L1'
,
subtitle
:
'Tiếng Anh chuyên ngành'
,
time
:
'10:30'
,
endTime
:
'12:00'
,
date
:
tomorrowStr
,
type
:
'class'
},
{
id
:
'9'
,
title
:
'Workshop AI trong giáo dục'
,
subtitle
:
'Hội trường lớn'
,
time
:
'13:30'
,
endTime
:
'17:00'
,
date
:
tomorrowStr
,
type
:
'workshop'
},
{
id
:
'10'
,
title
:
'Seminar Công nghệ mới'
,
subtitle
:
'Phòng 301'
,
time
:
'09:00'
,
endTime
:
'11:00'
,
date
:
yesterdayStr
,
type
:
'seminar'
},
{
id
:
'11'
,
title
:
'Chấm bài thi cuối kỳ'
,
subtitle
:
'Văn phòng giảng viên'
,
time
:
'14:00'
,
endTime
:
'17:00'
,
date
:
yesterdayStr
,
type
:
'grading'
},
{
id
:
'12'
,
title
:
'Lịch vào trực lớp IT47.8F7'
,
subtitle
:
'Môn học chuyên ngành'
,
time
:
'07:30'
,
endTime
:
'09:00'
,
date
:
'2025-01-15'
,
type
:
'class'
},
{
id
:
'13'
,
title
:
'Hội nghị khoa học sinh viên'
,
subtitle
:
'Hội trường A'
,
time
:
'08:30'
,
endTime
:
'14:00'
,
date
:
'2025-01-20'
,
type
:
'conference'
},
{
id
:
'14'
,
title
:
'Bảo vệ đồ án tốt nghiệp'
,
subtitle
:
'Phòng 205'
,
time
:
'13:00'
,
endTime
:
'17:30'
,
date
:
'2025-01-25'
,
type
:
'defense'
},
{
id
:
'15'
,
title
:
'Tổng kết học kỳ'
,
subtitle
:
'Phòng họp khoa'
,
time
:
'15:00'
,
endTime
:
'18:00'
,
date
:
'2025-01-30'
,
type
:
'summary'
},
];
}
const
FilterDay
=
()
=>
{
// Tạo dữ liệu 1 lần khi mount, không tái tạo ở mỗi render
const
mockEvents
=
React
.
useMemo
(()
=>
createMockEvents
(),
[]);
// GỌI HOOK SAU KHI mockEvents sẵn sàng
const
vm
=
useFilterDay
(
mockEvents
);
return
<
FilterDayView
{...
vm
}
/>
;
};
export
default
FilterDate
;
\ No newline at end of file
export
default
FilterDay
;
src/screens/class_schedule/filter_date/style.js
0 → 100644
View file @
9b3fee3a
import
{
StyleSheet
,
Dimensions
}
from
'react-native'
;
import
R
from
'../../../assets/R'
;
const
{
width
:
screenWidth
,
height
:
screenHeight
}
=
Dimensions
.
get
(
'window'
);
const
HOUR_HEIGHT
=
80
;
const
styles
=
StyleSheet
.
create
({
// ==================== CONTAINER CHÍNH ====================
// Tương ứng với View chính trong FilterDayView dòng 117
container
:
{
flex
:
1
,
backgroundColor
:
R
.
colors
.
white
,
},
// ==================== MONTH PICKER ====================
// Tương ứng với renderMonthPicker dòng 25-27
monthPickerContainer
:
{
backgroundColor
:
R
.
colors
.
white
,
borderBottomWidth
:
1
,
borderBottomColor
:
R
.
colors
.
gray220
,
paddingVertical
:
10
,
},
monthPickerContent
:
{
paddingHorizontal
:
15
,
},
monthItem
:
{
paddingHorizontal
:
20
,
paddingVertical
:
8
,
marginRight
:
10
,
borderRadius
:
20
,
backgroundColor
:
R
.
colors
.
gray220
,
},
// ==================== DATE INFO CONTAINER ====================
// Tương ứng với renderDateInfo dòng 29-43
dateInfoContainer
:
{
paddingHorizontal
:
15
,
paddingVertical
:
12
,
alignItems
:
'center'
,
justifyContent
:
'center'
,
maxWidth
:
70
,
borderRightWidth
:
1
,
borderRightColor
:
R
.
colors
.
gray220
,
},
dayName
:
{
fontSize
:
R
.
fontsize
.
fontsSize12
,
fontFamily
:
R
.
fonts
.
InterMedium
,
color
:
R
.
colors
.
black
,
fontWeight
:
'600'
,
marginBottom
:
2
,
},
dayNumber
:
{
fontSize
:
R
.
fontsize
.
fontsSize12
,
fontFamily
:
R
.
fonts
.
InterRegular
,
color
:
R
.
colors
.
black
,
fontWeight
:
'400'
,
},
dayNumberToday
:
{
fontSize
:
R
.
fontsize
.
fontsSize12
,
fontFamily
:
R
.
fonts
.
InterRegular
,
color
:
R
.
colors
.
white
,
fontWeight
:
'400'
,
backgroundColor
:
R
.
colors
.
main
,
borderRadius
:
15
,
paddingHorizontal
:
8
,
paddingVertical
:
4
,
},
// ==================== TIME SLOTS CONTAINER ====================
// Tương ứng với renderTimeSlots dòng 45-115
timeSlotsContainer
:
{
flex
:
1
,
backgroundColor
:
R
.
colors
.
white
,
},
scrollContent
:
{
paddingBottom
:
50
,
},
timelineContainer
:
{
flexDirection
:
'row'
,
position
:
'relative'
,
},
// ==================== TIME LABELS COLUMN ====================
// Tương ứng với timeLabelsColumn dòng 56-65
timeLabelsColumn
:
{
minWidth
:
70
,
borderRightWidth
:
1
,
borderRightColor
:
R
.
colors
.
gray220
,
},
timeSlot
:
{
height
:
HOUR_HEIGHT
,
alignItems
:
'center'
,
justifyContent
:
'center'
,
borderBottomWidth
:
1
,
borderBottomColor
:
R
.
colors
.
gray220
,
},
timeText
:
{
fontSize
:
R
.
fontsize
.
fontsSize12
,
fontFamily
:
R
.
fonts
.
InterRegular
,
color
:
R
.
colors
.
black
,
fontWeight
:
'400'
,
},
// ==================== EVENTS COLUMN ====================
// Tương ứng với eventsColumn dòng 67-110
eventsColumn
:
{
flex
:
1
,
position
:
'relative'
,
height
:
24
*
HOUR_HEIGHT
,
},
gridLine
:
{
height
:
HOUR_HEIGHT
,
borderBottomWidth
:
1
,
borderBottomColor
:
R
.
colors
.
gray220
,
width
:
'100%'
,
},
// ==================== EVENT CARDS ====================
// Tương ứng với event rendering dòng 72-109
eventCard
:
{
borderRadius
:
15
,
paddingHorizontal
:
8
,
paddingVertical
:
4
,
justifyContent
:
'flex-start'
,
},
eventTitle
:
{
fontSize
:
R
.
fontsize
.
fontsSize12
,
fontFamily
:
R
.
fonts
.
InterRegular
,
color
:
R
.
colors
.
white
,
fontWeight
:
'400'
,
marginBottom
:
5
,
},
eventSubtitle
:
{
fontSize
:
R
.
fontsize
.
fontsSize12
,
fontFamily
:
R
.
fonts
.
InterRegular
,
fontWeight
:
'400'
,
color
:
R
.
colors
.
white
,
marginBottom
:
5
,
},
eventTime
:
{
fontSize
:
R
.
fontsize
.
fontsSize12
,
fontFamily
:
R
.
fonts
.
InterRegular
,
color
:
R
.
colors
.
white
,
fontWeight
:
'400'
,
},
});
export
default
styles
;
src/screens/class_schedule/filter_date/view.js
View file @
9b3fee3a
import
React
from
'react'
;
import
{
View
,
Text
,
TouchableOpacity
,
ScrollView
,
StyleSheet
,
Dimensions
,
SafeAreaView
,
}
from
'react-native'
;
import
{
View
,
Text
,
TouchableOpacity
,
ScrollView
}
from
'react-native'
;
import
R
from
'../../../assets/R'
;
import
styles
from
'./style'
;
const
{
width
:
screenWidth
,
height
:
screenHeight
}
=
Dimensions
.
get
(
'window'
);
const
HOUR_HEIGHT
=
80
;
const
FilterDateView
=
({
navigation
,
currentDate
,
selectedDate
,
showMonthPicker
,
scrollViewRef
,
panResponder
,
getEventsForDate
,
getDayName
,
getMonthName
,
handleMonthSelect
,
toggleMonthPicker
,
calculateEventPosition
,
})
=>
{
const
renderMonthPicker
=
()
=>
{
if
(
!
showMonthPicker
)
return
null
;
const
FilterDayView
=
props
=>
{
const
{
currentDate
=
new
Date
(),
selectedDate
=
new
Date
(),
showMonthPicker
=
false
,
scrollViewRef
=
{
current
:
null
},
panResponder
=
{
panHandlers
:
{}
},
getEventsForDate
=
()
=>
[],
getDayName
=
()
=>
''
,
getMonthName
=
()
=>
''
,
handleMonthSelect
=
()
=>
{},
toggleMonthPicker
=
()
=>
{},
calculateEventPosition
=
()
=>
({
topPosition
:
0
,
height
:
60
}),
calculateEventLayout
=
()
=>
[],
}
=
props
||
{};
const
isToday
=
date
=>
{
const
today
=
new
Date
();
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
>
date
.
getDate
()
===
today
.
getDate
()
&&
date
.
getMonth
()
===
today
.
getMonth
()
&&
date
.
getFullYear
()
===
today
.
getFullYear
()
);
};
const
renderMonthPicker
=
()
=>
{
if
(
!
showMonthPicker
)
return
null
;
};
const
renderDateInfo
=
()
=>
{
return
(
<
View
style
=
{{
backgroundColor
:
R
.
colors
.
grey_200
}}
>
<
View
style
=
{
styles
.
dateInfoContainer
}
>
<
Text
style
=
{
styles
.
dayName
}
>
{
getDayName
(
selectedDate
)}
<
/Text
>
<
Text
style
=
{
styles
.
dayNumber
}
>
{
selectedDate
.
getDate
()}
<
/Text
>
<
View
style
=
{{
backgroundColor
:
R
.
colors
.
gray220
}}
>
<
View
style
=
{
styles
.
dateInfoContainer
}
>
<
Text
style
=
{
styles
.
dayName
}
>
{
getDayName
(
selectedDate
)}
<
/Text
>
<
Text
style
=
{
isToday
(
selectedDate
)
?
styles
.
dayNumberToday
:
styles
.
dayNumber
}
>
{
selectedDate
.
getDate
()}
<
/Text
>
<
/View
>
<
/View
>
<
/View
>
);
};
...
...
@@ -80,10 +57,9 @@ const FilterDateView= ({
ref
=
{
scrollViewRef
}
showsVerticalScrollIndicator
=
{
false
}
contentContainerStyle
=
{
styles
.
scrollContent
}
>
<
View
style
=
{
styles
.
timelineContainer
}
>
<
View
style
=
{
styles
.
timeLabelsColumn
}
>
{
hours
.
map
(
(
hour
)
=>
{
{
hours
.
map
(
hour
=>
{
const
timeStr
=
hour
.
toString
().
padStart
(
2
,
'0'
)
+
':00'
;
return
(
<
View
key
=
{
hour
}
style
=
{
styles
.
timeSlot
}
>
...
...
@@ -94,13 +70,11 @@ const FilterDateView= ({
<
/View
>
<
View
style
=
{
styles
.
eventsColumn
}
>
{
hours
.
map
(
(
hour
)
=>
(
{
hours
.
map
(
hour
=>
(
<
View
key
=
{
hour
}
style
=
{
styles
.
gridLine
}
/
>
))}
{
selectedEvents
.
map
((
event
)
=>
{
const
{
topPosition
,
height
}
=
calculateEventPosition
(
event
.
time
,
event
.
endTime
);
{
calculateEventLayout
(
selectedEvents
).
map
(
event
=>
{
return
(
<
TouchableOpacity
key
=
{
event
.
id
}
...
...
@@ -108,24 +82,46 @@ const FilterDateView= ({
styles
.
eventCard
,
{
position
:
'absolute'
,
top
:
topPosition
,
height
:
Math
.
max
(
height
,
40
),
left
:
5
,
right
:
15
,
zIndex
:
10
,
top
:
event
.
topPosition
,
height
:
event
.
height
,
left
:
event
.
leftOffset
,
right
:
event
.
rightOffset
,
zIndex
:
event
.
zIndex
,
backgroundColor
:
R
.
colors
.
main
,
}
// Add minimum width to prevent events from being too narrow
minWidth
:
event
.
numColumns
>
3
?
60
:
undefined
,
},
]}
activeOpacity
=
{
0.7
}
>
<
Text
style
=
{
styles
.
eventTitle
}
numberOfLines
=
{
height
>
60
?
2
:
1
}
>
<
Text
style
=
{[
styles
.
eventTitle
,
{
fontSize
:
event
.
numColumns
>
2
?
10
:
event
.
numColumns
>
1
?
12
:
14
,
}
]}
numberOfLines
=
{
event
.
height
>
60
?
2
:
1
}
>
{
event
.
title
}
<
/Text
>
{
height
>
40
&&
(
<
Text
style
=
{
styles
.
eventSubtitle
}
numberOfLines
=
{
1
}
>
{
event
.
height
>
40
&&
(
<
Text
style
=
{[
styles
.
eventSubtitle
,
{
fontSize
:
event
.
numColumns
>
2
?
9
:
event
.
numColumns
>
1
?
11
:
13
,
}
]}
numberOfLines
=
{
1
}
>
{
event
.
subtitle
}
<
/Text
>
)}
<
Text
style
=
{
styles
.
eventTime
}
>
<
Text
style
=
{[
styles
.
eventTime
,
{
fontSize
:
event
.
numColumns
>
2
?
8
:
event
.
numColumns
>
1
?
10
:
12
,
}
]}
>
{
event
.
time
}
-
{
event
.
endTime
}
<
/Text
>
<
/TouchableOpacity
>
...
...
@@ -137,121 +133,13 @@ const FilterDateView= ({
<
/View
>
);
};
return
(
<
SafeArea
View
style
=
{
styles
.
container
}
>
<
View
style
=
{
styles
.
container
}
>
{
renderMonthPicker
()}
{
renderDateInfo
()}
{
renderTimeSlots
()}
<
/
SafeArea
View
>
<
/View
>
);
};
const
styles
=
StyleSheet
.
create
({
container
:
{
flex
:
1
,
backgroundColor
:
R
.
colors
.
white
,
},
monthPickerContainer
:
{
backgroundColor
:
R
.
colors
.
white
,
borderBottomWidth
:
1
,
borderBottomColor
:
R
.
colors
.
grey_200
,
paddingVertical
:
10
,
},
monthPickerContent
:
{
paddingHorizontal
:
15
,
},
monthItem
:
{
paddingHorizontal
:
20
,
paddingVertical
:
8
,
marginRight
:
10
,
borderRadius
:
20
,
backgroundColor
:
R
.
colors
.
grey_50
,
},
dateInfoContainer
:
{
paddingHorizontal
:
15
,
paddingVertical
:
12
,
alignItems
:
'center'
,
justifyContent
:
'center'
,
maxWidth
:
70
,
borderRightWidth
:
1
,
borderRightColor
:
R
.
colors
.
grey_200
,
},
dayName
:
{
fontSize
:
R
.
fontsize
.
fontsSize12
,
fontFamily
:
R
.
fonts
.
InterRegular
,
color
:
R
.
colors
.
black
,
marginBottom
:
2
,
},
dayNumber
:
{
fontSize
:
R
.
fontsize
.
fontsSize14
,
fontFamily
:
R
.
fonts
.
InterSemiBold
,
color
:
R
.
colors
.
blue500
,
},
timeSlotsContainer
:
{
flex
:
1
,
backgroundColor
:
R
.
colors
.
white
,
},
scrollContent
:
{
paddingBottom
:
50
,
},
timelineContainer
:
{
flexDirection
:
'row'
,
position
:
'relative'
,
},
timeLabelsColumn
:
{
minWidth
:
70
,
borderRightWidth
:
1
,
borderRightColor
:
R
.
colors
.
grey_200
,
},
eventsColumn
:
{
flex
:
1
,
position
:
'relative'
,
minHeight
:
24
*
HOUR_HEIGHT
,
},
timeSlot
:
{
height
:
HOUR_HEIGHT
,
alignItems
:
'center'
,
justifyContent
:
'center'
,
borderBottomWidth
:
1
,
borderBottomColor
:
R
.
colors
.
grey_100
,
},
gridLine
:
{
height
:
HOUR_HEIGHT
,
borderBottomWidth
:
1
,
borderBottomColor
:
R
.
colors
.
grey_100
,
width
:
'100%'
,
},
timeText
:
{
fontSize
:
R
.
fontsize
.
fontsSize12
,
fontFamily
:
R
.
fonts
.
InterRegular
,
color
:
R
.
colors
.
black
,
},
eventCard
:
{
borderRadius
:
15
,
paddingLeft
:
15
,
paddingTop
:
10
,
},
eventTitle
:
{
fontSize
:
R
.
fontsize
.
fontsSize12
,
fontFamily
:
R
.
fonts
.
InterRegular
,
color
:
R
.
colors
.
white
,
fontWeight
:
'400'
,
marginBottom
:
5
,
},
eventSubtitle
:
{
fontSize
:
R
.
fontsize
.
fontsSize10
,
fontFamily
:
R
.
fonts
.
InterRegular
,
fontWeight
:
'400'
,
color
:
R
.
colors
.
white
,
marginBottom
:
5
,
},
eventTime
:
{
fontSize
:
R
.
fontsize
.
fontsSize10
,
fontFamily
:
R
.
fonts
.
InterRegular
,
color
:
R
.
colors
.
white
,
fontWeight
:
'400'
,
},
});
export
default
FilterDateView
\ No newline at end of file
export
default
FilterDayView
;
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment