Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
A
AppUms_Lecturer
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_Lecturer
Commits
a10f7f83
Commit
a10f7f83
authored
Aug 25, 2025
by
tungnq
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
TODO: Đã hoàn thiện giao diện màn 3 ngày
parent
366d8116
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
463 additions
and
20 deletions
+463
-20
index.js
src/screens/class_schedule/filter3day/index.js
+1
-1
view..js
src/screens/class_schedule/filter3day/view..js
+0
-12
view.js
src/screens/class_schedule/filter3day/view.js
+462
-7
No files found.
src/screens/class_schedule/filter3day/index.js
View file @
a10f7f83
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
(
...
...
src/screens/class_schedule/filter3day/view..js
deleted
100644 → 0
View file @
366d8116
import
{
StyleSheet
,
Text
,
View
}
from
'react-native'
import
React
from
'react'
const
Filter3DayView
=
()
=>
{
return
(
<
View
>
<
Text
>
Filter3DayView
<
/Text
>
<
/View
>
)
}
export
default
Filter3DayView
src/screens/class_schedule/filter3day/view.js
View file @
a10f7f83
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
}
>
Th
ờ
i
gian
h
ọ
c
:
{
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
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