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
e10510ea
Commit
e10510ea
authored
Sep 26, 2025
by
tungnq
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
TODO: Đã chỉnh sửa xong filter tháng
parent
ad6996b4
Show whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
418 additions
and
163 deletions
+418
-163
App.js
App.js
+10
-1
arrow_left.png
src/assets/icon/arrow_left.png
+0
-0
arrow_right.png
src/assets/icon/arrow_right.png
+0
-0
images.js
src/assets/images.js
+2
-0
Functions.js
src/config/Functions.js
+140
-1
TabNavigation.js
src/routers/TabNavigation.js
+3
-2
index.js
src/screens/class_schedule/index.js
+112
-65
style.js
src/screens/class_schedule/style.js
+84
-46
view.js
src/screens/class_schedule/view.js
+62
-42
style.js
src/screens/home/style.js
+5
-6
No files found.
App.js
View file @
e10510ea
...
@@ -7,7 +7,7 @@
...
@@ -7,7 +7,7 @@
*/
*/
import
React
,
{
useEffect
}
from
'react'
;
import
React
,
{
useEffect
}
from
'react'
;
import
{
View
,
Text
}
from
'react-native'
;
import
{
View
,
Text
,
DeviceEventEmitter
}
from
'react-native'
;
import
{
Provider
}
from
'react-redux'
;
import
{
Provider
}
from
'react-redux'
;
import
{
createStore
,
applyMiddleware
}
from
'redux'
;
import
{
createStore
,
applyMiddleware
}
from
'redux'
;
import
rootReducer
from
'./src/reducers/index'
;
import
rootReducer
from
'./src/reducers/index'
;
...
@@ -16,6 +16,15 @@ import createSagaMiddleware from 'redux-saga';
...
@@ -16,6 +16,15 @@ import createSagaMiddleware from 'redux-saga';
import
rootSaga
from
'./src/saga/rootSaga'
;
import
rootSaga
from
'./src/saga/rootSaga'
;
// import FirebaseNotification from "./src/helper/FirebaseNotification";
// import FirebaseNotification from "./src/helper/FirebaseNotification";
import
SplashScreen
from
'react-native-splash-screen'
;
import
SplashScreen
from
'react-native-splash-screen'
;
if
(
!
DeviceEventEmitter
.
removeListener
)
{
DeviceEventEmitter
.
removeListener
=
function
(
eventType
,
listener
)
{
// Use the new API
const
subscription
=
this
.
addListener
(
eventType
,
()
=>
{});
if
(
subscription
&&
subscription
.
remove
)
{
subscription
.
remove
();
}
};
}
const
sagaMiddleware
=
createSagaMiddleware
();
const
sagaMiddleware
=
createSagaMiddleware
();
let
store
=
createStore
(
rootReducer
,
applyMiddleware
(
sagaMiddleware
));
let
store
=
createStore
(
rootReducer
,
applyMiddleware
(
sagaMiddleware
));
...
...
src/assets/icon/arrow_left.png
0 → 100644
View file @
e10510ea
157 Bytes
src/assets/icon/arrow_right.png
0 → 100644
View file @
e10510ea
153 Bytes
src/assets/images.js
View file @
e10510ea
...
@@ -43,6 +43,8 @@ const images = {
...
@@ -43,6 +43,8 @@ const images = {
icProfileUn
:
require
(
'./icon/profile_navigation_unsel.png'
),
icProfileUn
:
require
(
'./icon/profile_navigation_unsel.png'
),
igProfileDemo
:
require
(
'./images/image.png'
),
igProfileDemo
:
require
(
'./images/image.png'
),
icImageDownload
:
require
(
'./images/icImageDownload.png'
),
icImageDownload
:
require
(
'./images/icImageDownload.png'
),
icLeft
:
require
(
'./icon/arrow_left.png'
),
icRight
:
require
(
'./icon/arrow_right.png'
),
ic3Date
:
Date3Drawer
,
ic3Date
:
Date3Drawer
,
icDateDrawer
:
DateDrawer
,
icDateDrawer
:
DateDrawer
,
icMenu
:
Menu
,
icMenu
:
Menu
,
...
...
src/config/Functions.js
View file @
e10510ea
import
React
from
'react'
;
import
React
,
{
useRef
,
useEffect
}
from
'react'
;
import
{
import
{
Dimensions
,
Dimensions
,
Platform
,
Platform
,
...
@@ -234,6 +234,9 @@ export const removeItemFromArr2 = (items, index) => {
...
@@ -234,6 +234,9 @@ export const removeItemFromArr2 = (items, index) => {
return
fill
;
return
fill
;
};
};
export
const
isValidEmail
=
email
=>
email
.
length
>
0
&&
/
(
.+
)
@
(
.+
){2,}\.(
.+
){2,}
/gim
.
test
(
email
);
export
const
removeItemFromArr
=
(
items
,
index
)
=>
{
export
const
removeItemFromArr
=
(
items
,
index
)
=>
{
items
.
splice
(
index
,
1
);
items
.
splice
(
index
,
1
);
return
items
;
return
items
;
...
@@ -601,3 +604,138 @@ export const getMimeType = fileExt => {
...
@@ -601,3 +604,138 @@ export const getMimeType = fileExt => {
return
'*/*'
;
return
'*/*'
;
}
}
};
};
//Calendar
export
const
parseMinutes
=
timeStr
=>
{
if
(
!
timeStr
||
typeof
timeStr
!==
'string'
)
return
0
;
const
parts
=
timeStr
.
split
(
':'
);
if
(
parts
.
length
!==
2
)
return
0
;
const
[
h
,
m
]
=
parts
.
map
(
Number
);
if
(
isNaN
(
h
)
||
isNaN
(
m
))
return
0
;
return
h
*
60
+
m
;
};
export
const
formatDateToString
=
date
=>
{
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
}
`
;
};
export
const
isSameDay
=
(
a
,
b
)
=>
a
.
getFullYear
()
===
b
.
getFullYear
()
&&
a
.
getMonth
()
===
b
.
getMonth
()
&&
a
.
getDate
()
===
b
.
getDate
();
/**
* Trả về mảng events đã có toạ độ:
* { topPosition, height, leftOffset, rightOffset, zIndex, columnIndex, numColumns }
*/
export
const
layoutDayEvents
=
(
events
,
hourHeight
=
80
)
=>
{
if
(
!
events
||
events
.
length
===
0
)
return
[];
// Chuẩn hoá + sort theo start
const
mapped
=
events
.
filter
(
e
=>
e
?.
time
&&
e
?.
endTime
)
.
map
((
e
,
i
)
=>
({
...
e
,
start
:
parseMinutes
(
e
.
time
),
end
:
parseMinutes
(
e
.
endTime
),
_idx
:
i
,
}))
.
filter
(
e
=>
e
.
end
>
e
.
start
)
.
sort
((
a
,
b
)
=>
a
.
start
-
b
.
start
);
// Gom nhóm overlap tuyến tính
const
groups
=
[];
let
cur
=
null
;
for
(
const
ev
of
mapped
)
{
if
(
!
cur
||
ev
.
start
>=
cur
.
maxEnd
)
{
cur
=
{
id
:
groups
.
length
,
items
:
[
ev
],
maxEnd
:
ev
.
end
};
groups
.
push
(
cur
);
}
else
{
cur
.
items
.
push
(
ev
);
if
(
ev
.
end
>
cur
.
maxEnd
)
cur
.
maxEnd
=
ev
.
end
;
}
}
const
out
=
[];
// Gán cột theo greedy interval partitioning
for
(
const
g
of
groups
)
{
const
colEnds
=
[];
// end time cuối mỗi cột
const
colOf
=
{};
// event.id -> columnIndex
const
items
=
[...
g
.
items
].
sort
((
a
,
b
)
=>
a
.
start
-
b
.
start
);
for
(
const
ev
of
items
)
{
let
placed
=
false
;
for
(
let
c
=
0
;
c
<
colEnds
.
length
;
c
++
)
{
if
(
ev
.
start
>=
colEnds
[
c
])
{
colEnds
[
c
]
=
ev
.
end
;
colOf
[
ev
.
id
]
=
c
;
placed
=
true
;
break
;
}
}
if
(
!
placed
)
{
colOf
[
ev
.
id
]
=
colEnds
.
length
;
colEnds
.
push
(
ev
.
end
);
}
}
const
numCols
=
Math
.
max
(
colEnds
.
length
,
1
);
for
(
const
ev
of
items
)
{
const
columnIndex
=
colOf
[
ev
.
id
]
??
0
;
const
topPosition
=
(
ev
.
start
/
60
)
*
hourHeight
;
const
height
=
((
ev
.
end
-
ev
.
start
)
/
60
)
*
hourHeight
;
const
widthPct
=
100
/
numCols
;
const
leftPct
=
columnIndex
*
widthPct
;
out
.
push
({
...
ev
,
topPosition
,
height
,
leftOffset
:
`
${
Math
.
round
(
leftPct
*
100
)
/
100
}
%`
,
rightOffset
:
`
${
Math
.
round
((
100
-
(
leftPct
+
widthPct
))
*
100
)
/
100
}
%`
,
widthPct
,
columnIndex
,
numColumns
:
numCols
,
zIndex
:
10
+
columnIndex
,
});
}
}
// Trả về theo thứ tự ban đầu để render ổn định
return
out
.
sort
((
a
,
b
)
=>
a
.
_idx
-
b
.
_idx
);
/**
* Hook đếm số lần render của một component
*/
};
export
const
useRenderCount
=
(
name
=
'Component'
)
=>
{
const
renderCount
=
useRef
(
0
);
renderCount
.
current
+=
1
;
useEffect
(()
=>
{
console
.
log
(
`
${
name
}
đã render
${
renderCount
.
current
}
lần`
);
});
return
renderCount
.
current
;
};
export
const
monthNames
=
[
'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'
,
]
\ No newline at end of file
src/routers/TabNavigation.js
View file @
e10510ea
...
@@ -124,8 +124,8 @@ export default connect(mapStateToProps, {})(TabNavigator);
...
@@ -124,8 +124,8 @@ export default connect(mapStateToProps, {})(TabNavigator);
const
styles
=
StyleSheet
.
create
({
const
styles
=
StyleSheet
.
create
({
tabBarIcon
:
{
tabBarIcon
:
{
width
:
Platform
.
OS
===
'ios'
?
20
:
'50%'
,
width
:
Platform
.
OS
===
'ios'
?
20
:
25
,
height
:
Platform
.
OS
===
'ios'
?
20
:
'50%'
,
height
:
Platform
.
OS
===
'ios'
?
20
:
25
,
resizeMode
:
'contain'
resizeMode
:
'contain'
}
}
});
});
\ No newline at end of file
src/screens/class_schedule/index.js
View file @
e10510ea
import
React
,
{
useState
,
useMemo
,
useRef
}
from
'react'
;
import
React
,
{
useState
,
useMemo
,
useRef
,
useEffect
}
from
'react'
;
import
{
Animated
,
PanResponder
,
Dimensions
}
from
'react-native'
;
import
{
Animated
,
PanResponder
,
Dimensions
,
DeviceEventEmitter
}
from
'react-native'
;
import
{
useFocusEffect
}
from
'@react-navigation/native'
;
import
ClassScheduleView
from
'./view'
;
import
ClassScheduleView
from
'./view'
;
// ==================== HẰNG SỐ ====================
const
{
height
:
screenHeight
}
=
Dimensions
.
get
(
'window'
);
const
{
height
:
screenHeight
}
=
Dimensions
.
get
(
'window'
);
const
BOTTOM_SHEET_HEIGHT
=
screenHeight
*
0.6
;
const
BOTTOM_SHEET_HEIGHT
=
screenHeight
*
0.6
;
const
ClassSchedule
=
({
events
=
[],
onDateSelect
,
onEventPress
})
=>
{
const
ClassSchedule
=
({
events
=
[],
onDateSelect
,
onEventPress
})
=>
{
const
[
currentDate
,
setCurrentDate
]
=
useState
(
new
Date
(
2025
,
7
,
1
));
// ==================== QUẢN LÝ STATE ====================
// B1: State ngày tháng và lịch
const
[
currentDate
,
setCurrentDate
]
=
useState
(
new
Date
());
const
[
selectedDate
,
setSelectedDate
]
=
useState
(
null
);
const
[
selectedDate
,
setSelectedDate
]
=
useState
(
null
);
// ==================== EFFECTS ====================
// Reset về ngày hiện tại khi chuyển màn hình
useFocusEffect
(
React
.
useCallback
(()
=>
{
const
today
=
new
Date
();
setCurrentDate
(
today
);
setSelectedDate
(
null
);
DeviceEventEmitter
.
emit
(
'onDateChange'
,
today
);
// Cập nhật header drawer với tháng hiện tại
DeviceEventEmitter
.
emit
(
'updateHeaderMonth'
,
today
.
getMonth
());
},
[])
);
// B2: State bottom sheet
const
[
showBottomSheet
,
setShowBottomSheet
]
=
useState
(
false
);
const
[
showBottomSheet
,
setShowBottomSheet
]
=
useState
(
false
);
const
bottomSheetTranslateY
=
useRef
(
new
Animated
.
Value
(
BOTTOM_SHEET_HEIGHT
)).
current
;
// B3: Tham chiếu animation
const
bottomSheetTranslateY
=
useRef
(
new
Animated
.
Value
(
BOTTOM_SHEET_HEIGHT
),
).
current
;
const
formatDateToString
=
(
date
)
=>
{
// ==================== HÀM TIỆN ÍCH ====================
// T1: Định dạng ngày thành chuỗi
const
formatDateToString
=
date
=>
{
const
year
=
date
.
getFullYear
();
const
year
=
date
.
getFullYear
();
const
month
=
(
date
.
getMonth
()
+
1
).
toString
().
padStart
(
2
,
'0'
);
const
month
=
(
date
.
getMonth
()
+
1
).
toString
().
padStart
(
2
,
'0'
);
const
day
=
date
.
getDate
().
toString
().
padStart
(
2
,
'0'
);
const
day
=
date
.
getDate
().
toString
().
padStart
(
2
,
'0'
);
return
`
${
year
}
-
${
month
}
-
${
day
}
`
;
return
`
${
year
}
-
${
month
}
-
${
day
}
`
;
};
};
// T2: Định dạng ngày để hiển thị
const
formatDateToDisplay
=
date
=>
{
const
day
=
date
.
getDate
().
toString
().
padStart
(
2
,
'0'
);
const
month
=
(
date
.
getMonth
()
+
1
).
toString
().
padStart
(
2
,
'0'
);
const
year
=
date
.
getFullYear
();
return
`Lịch ngày
${
day
}
/
${
month
}
/
${
year
}
`
;
};
// T3: Chuyển đổi chuỗi thành ngày
const
parseLocalDate
=
dateString
=>
{
const
[
year
,
month
,
day
]
=
dateString
.
split
(
'-'
).
map
(
Number
);
return
new
Date
(
year
,
month
-
1
,
day
);
};
// ==================== QUẢN LÝ DỮ LIỆU ====================
// D1: Tạo dữ liệu sự kiện mẫu
const
createMockEvents
=
()
=>
{
const
createMockEvents
=
()
=>
{
const
today
=
new
Date
();
const
today
=
new
Date
();
const
todayStr
=
formatDateToString
(
today
);
const
todayStr
=
formatDateToString
(
today
);
...
@@ -147,32 +187,25 @@ const ClassSchedule = ({events = [], onDateSelect, onEventPress}) => {
...
@@ -147,32 +187,25 @@ const ClassSchedule = ({events = [], onDateSelect, onEventPress}) => {
];
];
};
};
// D2: Xử lý dữ liệu sự kiện
const
mockEvents
=
createMockEvents
();
const
mockEvents
=
createMockEvents
();
const
allEvents
=
[...
events
,
...
mockEvents
];
const
allEvents
=
[...
events
,
...
mockEvents
];
const
panResponder
=
useRef
(
// D3: Hàm truy vấn sự kiện
PanResponder
.
create
({
const
getEventsForDate
=
date
=>
{
onMoveShouldSetPanResponder
:
(
evt
,
gestureState
)
=>
{
const
dateStr
=
formatDateToString
(
date
);
return
Math
.
abs
(
gestureState
.
dy
)
>
10
;
return
allEvents
.
filter
(
event
=>
event
.
date
===
dateStr
);
},
};
onPanResponderMove
:
(
evt
,
gestureState
)
=>
{
if
(
gestureState
.
dy
>
0
)
{
bottomSheetTranslateY
.
setValue
(
gestureState
.
dy
);
}
},
onPanResponderRelease
:
(
evt
,
gestureState
)
=>
{
if
(
gestureState
.
dy
>
100
)
{
hideBottomSheetModal
();
}
else
{
Animated
.
spring
(
bottomSheetTranslateY
,
{
toValue
:
0
,
useNativeDriver
:
true
,
}).
start
();
}
},
})
).
current
;
const
getSelectedEvents
=
()
=>
{
if
(
!
selectedDate
)
return
[];
return
allEvents
.
filter
(
event
=>
event
.
date
===
selectedDate
)
.
sort
((
a
,
b
)
=>
a
.
time
.
localeCompare
(
b
.
time
));
};
// ==================== LOGIC LỊCH ====================
// L1: Tạo dữ liệu tháng
const
getMonthData
=
useMemo
(()
=>
{
const
getMonthData
=
useMemo
(()
=>
{
const
year
=
currentDate
.
getFullYear
();
const
year
=
currentDate
.
getFullYear
();
const
month
=
currentDate
.
getMonth
();
const
month
=
currentDate
.
getMonth
();
...
@@ -198,28 +231,12 @@ const ClassSchedule = ({events = [], onDateSelect, onEventPress}) => {
...
@@ -198,28 +231,12 @@ const ClassSchedule = ({events = [], onDateSelect, onEventPress}) => {
};
};
},
[
currentDate
]);
},
[
currentDate
]);
const
getEventsForDate
=
(
date
)
=>
{
// L2: Hàm kiểm tra ngày tháng
const
dateStr
=
formatDateToString
(
date
);
const
isCurrentMonth
=
date
=>
{
return
allEvents
.
filter
(
event
=>
event
.
date
===
dateStr
);
};
const
parseLocalDate
=
(
dateString
)
=>
{
const
[
year
,
month
,
day
]
=
dateString
.
split
(
'-'
).
map
(
Number
);
return
new
Date
(
year
,
month
-
1
,
day
);
};
const
formatDateToDisplay
=
(
date
)
=>
{
const
day
=
date
.
getDate
().
toString
().
padStart
(
2
,
'0'
);
const
month
=
(
date
.
getMonth
()
+
1
).
toString
().
padStart
(
2
,
'0'
);
const
year
=
date
.
getFullYear
();
return
`Lịch ngày
${
day
}
/
${
month
}
/
${
year
}
`
;
};
const
isCurrentMonth
=
(
date
)
=>
{
return
date
.
getMonth
()
===
currentDate
.
getMonth
();
return
date
.
getMonth
()
===
currentDate
.
getMonth
();
};
};
const
isToday
=
(
date
)
=>
{
const
isToday
=
date
=>
{
const
today
=
new
Date
();
const
today
=
new
Date
();
return
(
return
(
date
.
getDate
()
===
today
.
getDate
()
&&
date
.
getDate
()
===
today
.
getDate
()
&&
...
@@ -228,20 +245,32 @@ const ClassSchedule = ({events = [], onDateSelect, onEventPress}) => {
...
@@ -228,20 +245,32 @@ const ClassSchedule = ({events = [], onDateSelect, onEventPress}) => {
);
);
};
};
const
navigateMonth
=
(
direction
)
=>
{
// ==================== HỆ THỐNG ANIMATION ====================
const
newDate
=
new
Date
(
currentDate
);
// A1: Thiết lập PanResponder
if
(
direction
===
'prev'
)
{
const
panResponder
=
useRef
(
newDate
.
setMonth
(
newDate
.
getMonth
()
-
1
);
PanResponder
.
create
({
}
else
{
onMoveShouldSetPanResponder
:
(
evt
,
gestureState
)
=>
{
newDate
.
setMonth
(
newDate
.
getMonth
()
+
1
);
return
Math
.
abs
(
gestureState
.
dy
)
>
10
;
},
onPanResponderMove
:
(
evt
,
gestureState
)
=>
{
if
(
gestureState
.
dy
>
0
)
{
bottomSheetTranslateY
.
setValue
(
gestureState
.
dy
);
}
}
setCurrentDate
(
newDate
);
},
setSelectedDate
(
null
);
onPanResponderRelease
:
(
evt
,
gestureState
)
=>
{
if
(
showBottomSheet
)
{
if
(
gestureState
.
dy
>
100
)
{
hideBottomSheetModal
();
hideBottomSheetModal
();
}
else
{
Animated
.
spring
(
bottomSheetTranslateY
,
{
toValue
:
0
,
useNativeDriver
:
true
,
}).
start
();
}
}
};
},
}),
).
current
;
// A2: Hàm animation Bottom Sheet
const
showBottomSheetModal
=
()
=>
{
const
showBottomSheetModal
=
()
=>
{
setShowBottomSheet
(
true
);
setShowBottomSheet
(
true
);
Animated
.
spring
(
bottomSheetTranslateY
,
{
Animated
.
spring
(
bottomSheetTranslateY
,
{
...
@@ -262,7 +291,30 @@ const ClassSchedule = ({events = [], onDateSelect, onEventPress}) => {
...
@@ -262,7 +291,30 @@ const ClassSchedule = ({events = [], onDateSelect, onEventPress}) => {
});
});
};
};
const
handleDatePress
=
(
date
)
=>
{
// ==================== XỬ LÝ SỰ KIỆN ====================
// X1: Xử lý điều hướng
const
navigateMonth
=
direction
=>
{
const
newDate
=
new
Date
(
currentDate
);
if
(
direction
===
'prev'
)
{
newDate
.
setMonth
(
newDate
.
getMonth
()
-
1
);
}
else
{
newDate
.
setMonth
(
newDate
.
getMonth
()
+
1
);
}
setCurrentDate
(
newDate
);
setSelectedDate
(
null
);
// Phát sự kiện để cập nhật header title
DeviceEventEmitter
.
emit
(
'onDateChange'
,
newDate
.
toISOString
());
// Cập nhật header drawer với tháng mới
DeviceEventEmitter
.
emit
(
'updateHeaderMonth'
,
newDate
.
getMonth
());
if
(
showBottomSheet
)
{
hideBottomSheetModal
();
}
};
// X2: Xử lý chọn ngày
const
handleDatePress
=
date
=>
{
const
dateStr
=
formatDateToString
(
date
);
const
dateStr
=
formatDateToString
(
date
);
const
dayEvents
=
getEventsForDate
(
date
);
const
dayEvents
=
getEventsForDate
(
date
);
...
@@ -274,7 +326,8 @@ const ClassSchedule = ({events = [], onDateSelect, onEventPress}) => {
...
@@ -274,7 +326,8 @@ const ClassSchedule = ({events = [], onDateSelect, onEventPress}) => {
}
}
};
};
const
handleEventPress
=
(
event
)
=>
{
// X3: Xử lý tương tác sự kiện
const
handleEventPress
=
event
=>
{
onEventPress
?.(
event
);
onEventPress
?.(
event
);
};
};
...
@@ -282,13 +335,6 @@ const ClassSchedule = ({events = [], onDateSelect, onEventPress}) => {
...
@@ -282,13 +335,6 @@ const ClassSchedule = ({events = [], onDateSelect, onEventPress}) => {
hideBottomSheetModal
();
hideBottomSheetModal
();
};
};
const
getSelectedEvents
=
()
=>
{
if
(
!
selectedDate
)
return
[];
return
allEvents
.
filter
(
event
=>
event
.
date
===
selectedDate
)
.
sort
((
a
,
b
)
=>
a
.
time
.
localeCompare
(
b
.
time
));
};
return
(
return
(
<
ClassScheduleView
<
ClassScheduleView
currentDate
=
{
currentDate
}
currentDate
=
{
currentDate
}
...
@@ -300,6 +346,7 @@ const ClassSchedule = ({events = [], onDateSelect, onEventPress}) => {
...
@@ -300,6 +346,7 @@ const ClassSchedule = ({events = [], onDateSelect, onEventPress}) => {
getEventsForDate
=
{
getEventsForDate
}
getEventsForDate
=
{
getEventsForDate
}
parseLocalDate
=
{
parseLocalDate
}
parseLocalDate
=
{
parseLocalDate
}
formatDateToDisplay
=
{
formatDateToDisplay
}
formatDateToDisplay
=
{
formatDateToDisplay
}
formatDateToString
=
{
formatDateToString
}
isCurrentMonth
=
{
isCurrentMonth
}
isCurrentMonth
=
{
isCurrentMonth
}
isToday
=
{
isToday
}
isToday
=
{
isToday
}
navigateMonth
=
{
navigateMonth
}
navigateMonth
=
{
navigateMonth
}
...
...
src/screens/class_schedule/style.js
View file @
e10510ea
...
@@ -3,15 +3,18 @@ import R from '../../assets/R';
...
@@ -3,15 +3,18 @@ import R from '../../assets/R';
const
{
width
:
screenWidth
,
height
:
screenHeight
}
=
Dimensions
.
get
(
'window'
);
const
{
width
:
screenWidth
,
height
:
screenHeight
}
=
Dimensions
.
get
(
'window'
);
const
CELL_WIDTH
=
(
screenWidth
-
30
)
/
7
;
const
CELL_WIDTH
=
(
screenWidth
-
30
)
/
7
;
const
CELL_HEIGHT
=
(
screenHeight
-
1
4
0
)
/
6
;
const
CELL_HEIGHT
=
(
screenHeight
-
1
6
0
)
/
6
;
const
BOTTOM_SHEET_HEIGHT
=
screenHeight
*
0.6
;
const
BOTTOM_SHEET_HEIGHT
=
screenHeight
*
0.6
;
const
styles
=
StyleSheet
.
create
({
const
styles
=
StyleSheet
.
create
({
// Container chính của màn hình
container
:
{
container
:
{
flex
:
1
,
flex
:
1
,
backgroundColor
:
R
.
colors
.
white
,
backgroundColor
:
R
.
colors
.
white
,
alignItems
:
'center'
,
alignItems
:
'center'
,
},
},
// Header tháng/năm với nút điều hướng
header
:
{
header
:
{
flexDirection
:
'row'
,
flexDirection
:
'row'
,
alignItems
:
'center'
,
alignItems
:
'center'
,
...
@@ -19,24 +22,20 @@ const styles = StyleSheet.create({
...
@@ -19,24 +22,20 @@ const styles = StyleSheet.create({
paddingVertical
:
15
,
paddingVertical
:
15
,
},
},
header_title
:
{
header_title
:
{
fontSize
:
R
.
fontsize
.
fontsSize16
,
fontSize
:
R
.
fontsize
.
fontsSize14
,
fontFamily
:
R
.
fonts
.
InterMedium
,
fontFamily
:
R
.
fonts
.
InterMedium
,
color
:
R
.
colors
.
black
,
color
:
R
.
colors
.
black
,
fontWeight
:
'600'
,
fontWeight
:
'600'
,
},
},
// Nút điều hướng tháng trước/sau
navButton
:
{
navButton
:
{
width
:
30
,
width
:
30
,
height
:
30
,
height
:
30
,
borderRadius
:
20
,
borderRadius
:
20
,
backgroundColor
:
R
.
colors
.
blue500
,
alignItems
:
'center'
,
alignItems
:
'center'
,
justifyContent
:
'center'
,
justifyContent
:
'center'
,
},
},
navButtonText
:
{
// Tiêu đề các ngày trong tuần
color
:
R
.
colors
.
white
,
fontSize
:
R
.
fontsize
.
fontsSize16
,
fontFamily
:
R
.
fonts
.
InterMedium
,
},
weekDaysContainer
:
{
weekDaysContainer
:
{
flexDirection
:
'row'
,
flexDirection
:
'row'
,
paddingBottom
:
5
,
paddingBottom
:
5
,
...
@@ -47,78 +46,94 @@ const styles = StyleSheet.create({
...
@@ -47,78 +46,94 @@ const styles = StyleSheet.create({
alignItems
:
'center'
,
alignItems
:
'center'
,
},
},
weekDayText
:
{
weekDayText
:
{
fontFamily
:
R
.
fonts
.
InterRegular
,
fontFamily
:
R
.
fonts
.
InterMedium
,
fontSize
:
R
.
fontsize
.
fontsSize10
,
fontSize
:
R
.
fontsize
.
fontsSize12
,
fontWeight
:
'4
00'
,
fontWeight
:
'6
00'
,
color
:
R
.
colors
.
black
,
color
:
R
.
colors
.
black
,
},
},
calendarGrid
:
{
},
// Lưới lịch
calendarGrid
:
{},
weekRow
:
{
weekRow
:
{
flexDirection
:
'row'
,
flexDirection
:
'row'
,
},
},
// Ô ngày trong lịch
dayCell
:
{
dayCell
:
{
width
:
CELL_WIDTH
,
width
:
CELL_WIDTH
,
minHeight
:
CELL_HEIGHT
,
minHeight
:
CELL_HEIGHT
,
borderWidth
:
1
,
borderWidth
:
1
,
borderColor
:
R
.
colors
.
grey_20
0
,
borderColor
:
R
.
colors
.
gray15
0
,
padding
:
4
,
padding
:
6
,
alignItems
:
'center'
,
alignItems
:
'center'
,
},
},
// Ô ngày được chọn
selectedDayCell
:
{
selectedDayCell
:
{
borderColor
:
R
.
colors
.
blue500
,
borderColor
:
R
.
colors
.
main
,
borderWidth
:
1
,
borderWidth
:
1
,
},
},
// Text số ngày
dayText
:
{
dayText
:
{
fontSize
:
R
.
fontsize
.
fontsSize12
,
fontSize
:
R
.
fontsize
.
fontsSize12
,
fontWeight
:
'5
00'
,
fontWeight
:
'4
00'
,
fontFamily
:
R
.
fonts
.
InterMedium
,
fontFamily
:
R
.
fonts
.
InterRegular
,
color
:
R
.
colors
.
black
,
color
:
R
.
colors
.
black
,
marginBottom
:
2
,
marginBottom
:
2
,
},
},
// Text ngày không thuộc tháng hiện tại
dayTextInactive
:
{
dayTextInactive
:
{
color
:
R
.
colors
.
grey_100
,
color
:
R
.
colors
.
black
,
opacity
:
1
,
opacity
:
1
,
},
},
// Text ngày được chọn
selectedDayText
:
{
selectedDayText
:
{
color
:
R
.
colors
.
black
,
color
:
R
.
colors
.
black
,
fontWeight
:
'bold
'
,
fontWeight
:
'500
'
,
fontFamily
:
R
.
fonts
.
InterSemiBold
,
fontFamily
:
R
.
fonts
.
InterSemiBold
,
},
},
// Text ngày hôm nay
todayText
:
{
todayText
:
{
color
:
R
.
colors
.
white
,
color
:
R
.
colors
.
white
,
fontWeight
:
'bold'
,
fontWeight
:
'600'
,
fontFamily
:
R
.
fonts
.
InterSemiBold
,
fontFamily
:
R
.
fonts
.
fontSemiBold
,
backgroundColor
:
R
.
colors
.
blue500
,
backgroundColor
:
R
.
colors
.
main
,
borderRadius
:
10
,
borderRadius
:
15
,
paddingHorizontal
:
5
,
paddingHorizontal
:
6
,
paddingVertical
:
4
,
},
},
// Sự kiện trong ô ngày
eventsContainer
:
{
eventsContainer
:
{
width
:
'100%'
,
width
:
'100%'
,
flex
:
1
,
flex
:
1
,
},
},
// Thanh sự kiện nhỏ trong ô ngày
eventBar
:
{
eventBar
:
{
paddingVertical
:
2
,
paddingVertical
:
2
,
paddingHorizontal
:
5
,
paddingHorizontal
:
5
,
borderRadius
:
5
,
borderRadius
:
10
,
marginBottom
:
2
,
marginBottom
:
2
,
backgroundColor
:
R
.
colors
.
main
,
},
},
eventBarText
:
{
eventBarText
:
{
fontSize
:
R
.
fontsize
.
fontsSize10
,
fontSize
:
R
.
fontsize
.
fontsSize12
,
color
:
R
.
colors
.
white
,
color
:
R
.
colors
.
white
,
fontWeight
:
'5
00'
,
fontWeight
:
'4
00'
,
fontFamily
:
R
.
fonts
.
InterRegular
fontFamily
:
R
.
fonts
.
InterRegular
,
},
},
// Text hiển thị số sự kiện còn lại
moreEventsText
:
{
moreEventsText
:
{
fontSize
:
R
.
fontsize
.
fontsSize10
,
fontSize
:
R
.
fontsize
.
fontsSize12
,
color
:
R
.
colors
.
grey_100
,
color
:
R
.
colors
.
gray150
,
fontWeight
:
'400'
,
fontFamily
:
R
.
fonts
.
InterRegular
,
textAlign
:
'center'
,
textAlign
:
'center'
,
},
},
// Modal bottom sheet
modalBackdrop
:
{
modalBackdrop
:
{
flex
:
1
,
flex
:
1
,
backgroundColor
:
R
.
colors
.
grey_20
0
,
backgroundColor
:
R
.
colors
.
black25
0
,
justifyContent
:
'flex-end'
,
justifyContent
:
'flex-end'
,
},
},
bottomSheet
:
{
bottomSheet
:
{
...
@@ -126,46 +141,63 @@ const styles = StyleSheet.create({
...
@@ -126,46 +141,63 @@ const styles = StyleSheet.create({
backgroundColor
:
R
.
colors
.
white
,
backgroundColor
:
R
.
colors
.
white
,
borderTopLeftRadius
:
20
,
borderTopLeftRadius
:
20
,
borderTopRightRadius
:
20
,
borderTopRightRadius
:
20
,
},
},
// Nội dung bottom sheet
bottomSheetContent
:
{
bottomSheetContent
:
{
paddingHorizontal
:
15
,
height
:
BOTTOM_SHEET_HEIGHT
,
},
},
// Thanh kéo
dragHandle
:
{
dragHandle
:
{
width
:
40
,
width
:
40
,
height
:
4
,
height
:
4
,
backgroundColor
:
R
.
colors
.
grey_20
0
,
backgroundColor
:
R
.
colors
.
gray15
0
,
borderRadius
:
2
,
borderRadius
:
5
,
alignSelf
:
'center'
,
alignSelf
:
'center'
,
marginTop
:
10
,
marginTop
:
10
,
marginBottom
:
15
,
marginBottom
:
15
,
},
},
// Header của bottom sheet
bottomSheetHeader
:
{
bottomSheetHeader
:
{
flexDirection
:
'row'
,
flexDirection
:
'row'
,
alignItems
:
'center'
,
alignItems
:
'center'
,
justifyContent
:
'space-between'
,
justifyContent
:
'space-between'
,
marginBottom
:
20
,
paddingHorizontal
:
15
,
},
},
bottomSheetTitle
:
{
bottomSheetTitle
:
{
fontSize
:
R
.
fontsize
.
fontSizeHeader1
,
fontSize
:
R
.
fontsize
.
fontsSize12
,
fontFamily
:
R
.
fonts
.
InterMedium
,
fontFamily
:
R
.
fonts
.
InterMedium
,
color
:
R
.
colors
.
black
,
color
:
R
.
colors
.
black
,
fontWeight
:
'600'
,
flex
:
1
,
flex
:
1
,
},
},
// Nút đóng bottom sheet
closeButton
:
{
closeButton
:
{
width
:
30
,
width
:
30
,
height
:
30
,
height
:
30
,
borderRadius
:
15
,
borderRadius
:
15
,
backgroundColor
:
R
.
colors
.
grey_20
0
,
backgroundColor
:
R
.
colors
.
gray15
0
,
alignItems
:
'center'
,
alignItems
:
'center'
,
justifyContent
:
'center'
,
justifyContent
:
'center'
,
},
},
closeButtonText
:
{
closeButtonText
:
{
fontSize
:
R
.
fontsize
.
fontsSize12
,
fontSize
:
R
.
fontsize
.
fontsSize12
,
color
:
R
.
colors
.
grey_800
,
color
:
R
.
colors
.
black
,
fontFamily
:
R
.
fonts
.
InterRegular
,
fontFamily
:
R
.
fonts
.
InterRegular
,
fontWeight
:
'400'
,
},
},
// Danh sách sự kiện trong bottom sheet
// ScrollView chứa danh sách sự kiện
eventsScrollView
:
{
paddingTop
:
10
,
},
// Container cho từng sự kiện
containerBottomSheet
:
{
flex
:
1
,
marginBottom
:
10
,
},
// Trạng thái không có sự kiện
noEventsContainer
:
{
noEventsContainer
:
{
flex
:
1
,
flex
:
1
,
alignItems
:
'center'
,
alignItems
:
'center'
,
...
@@ -175,17 +207,20 @@ const styles = StyleSheet.create({
...
@@ -175,17 +207,20 @@ const styles = StyleSheet.create({
noEventsText
:
{
noEventsText
:
{
fontSize
:
R
.
fontsize
.
fontsSize12
,
fontSize
:
R
.
fontsize
.
fontsSize12
,
fontFamily
:
R
.
fonts
.
InterRegular
,
fontFamily
:
R
.
fonts
.
InterRegular
,
color
:
R
.
colors
.
grey_80
0
,
color
:
R
.
colors
.
gray15
0
,
fontWeight
:
'400'
fontWeight
:
'400'
,
},
},
// Card sự kiện chi tiết
eventCard
:
{
eventCard
:
{
flexDirection
:
'row'
,
flexDirection
:
'row'
,
backgroundColor
:
R
.
colors
.
white
,
backgroundColor
:
R
.
colors
.
white
,
borderRadius
:
12
,
borderRadius
:
12
,
padding
:
15
,
padding
:
15
,
marginBottom
:
12
,
marginBottom
:
10
,
marginHorizontal
:
15
,
borderLeftWidth
:
4
,
borderLeftWidth
:
4
,
borderLeftColor
:
R
.
colors
.
blue500
,
borderLeftColor
:
R
.
colors
.
main
,
shadowColor
:
R
.
colors
.
black
,
shadowColor
:
R
.
colors
.
black
,
shadowOffset
:
{
shadowOffset
:
{
width
:
0
,
width
:
0
,
...
@@ -195,6 +230,7 @@ const styles = StyleSheet.create({
...
@@ -195,6 +230,7 @@ const styles = StyleSheet.create({
shadowRadius
:
1
,
shadowRadius
:
1
,
elevation
:
2
,
elevation
:
2
,
},
},
// Container thời gian sự kiện
eventTimeContainer
:
{
eventTimeContainer
:
{
minWidth
:
80
,
minWidth
:
80
,
alignItems
:
'flex-start'
,
alignItems
:
'flex-start'
,
...
@@ -204,9 +240,10 @@ const styles = StyleSheet.create({
...
@@ -204,9 +240,10 @@ const styles = StyleSheet.create({
eventTime
:
{
eventTime
:
{
fontSize
:
R
.
fontsize
.
fontsSize12
,
fontSize
:
R
.
fontsize
.
fontsSize12
,
fontFamily
:
R
.
fonts
.
InterMedium
,
fontFamily
:
R
.
fonts
.
InterMedium
,
color
:
R
.
colors
.
blue500
,
color
:
R
.
colors
.
main
,
fontWeight
:
'600'
,
fontWeight
:
'600'
,
},
},
// Container nội dung sự kiện
eventContent
:
{
eventContent
:
{
flex
:
1
,
flex
:
1
,
},
},
...
@@ -220,7 +257,8 @@ const styles = StyleSheet.create({
...
@@ -220,7 +257,8 @@ const styles = StyleSheet.create({
eventDescription
:
{
eventDescription
:
{
fontSize
:
R
.
fontsize
.
fontsSize12
,
fontSize
:
R
.
fontsize
.
fontsSize12
,
fontFamily
:
R
.
fonts
.
InterRegular
,
fontFamily
:
R
.
fonts
.
InterRegular
,
color
:
R
.
colors
.
grey_800
,
color
:
R
.
colors
.
gray200
,
fontWeight
:
'400'
,
},
},
});
});
...
...
src/screens/class_schedule/view.js
View file @
e10510ea
...
@@ -3,20 +3,23 @@ import {
...
@@ -3,20 +3,23 @@ import {
Text
,
Text
,
View
,
View
,
TouchableOpacity
,
TouchableOpacity
,
StyleSheet
,
ScrollView
,
ScrollView
,
Dimensions
,
Modal
,
Modal
,
Animated
,
Animated
,
SafeAreaView
,
LogBox
,
PanResponder
,
Image
}
from
'react-native'
;
}
from
'react-native'
;
import
R
from
'../../assets/R'
;
import
R
from
'../../assets/R'
;
import
{
styles
,
CELL_WIDTH
,
BOTTOM_SHEET_HEIGHT
}
from
'./style'
;
import
{
styles
}
from
'./style'
;
import
{
useNavigation
}
from
'@react-navigation/native'
;
import
{
useNavigation
}
from
'@react-navigation/native'
;
import
*
as
SCREENNAME
from
'../../routers/ScreenNames'
;
import
*
as
SCREENNAME
from
'../../routers/ScreenNames'
;
import
{
monthNames
}
from
'../../config/Functions'
;
LogBox
.
ignoreLogs
([
'[Reanimated] Reduced motion setting is enabled'
,
'Each child in a list should have a unique "key" prop'
]);
const
ClassScheduleView
=
({
const
ClassScheduleView
=
({
currentDate
,
selectedDate
,
selectedDate
,
showBottomSheet
,
showBottomSheet
,
bottomSheetTranslateY
,
bottomSheetTranslateY
,
...
@@ -25,6 +28,7 @@ const ClassScheduleView = ({
...
@@ -25,6 +28,7 @@ const ClassScheduleView = ({
getEventsForDate
,
getEventsForDate
,
parseLocalDate
,
parseLocalDate
,
formatDateToDisplay
,
formatDateToDisplay
,
formatDateToString
,
isCurrentMonth
,
isCurrentMonth
,
isToday
,
isToday
,
navigateMonth
,
navigateMonth
,
...
@@ -34,18 +38,38 @@ const ClassScheduleView = ({
...
@@ -34,18 +38,38 @@ const ClassScheduleView = ({
getSelectedEvents
,
getSelectedEvents
,
})
=>
{
})
=>
{
const
navigation
=
useNavigation
();
const
navigation
=
useNavigation
();
const
renderHeader
=
()
=>
{
const
monthNames
=
[
'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'
,
];
// Tạo PanResponder cho swipe gesture điều hướng tháng
const
swipePanResponder
=
PanResponder
.
create
({
onMoveShouldSetPanResponder
:
(
evt
,
gestureState
)
=>
{
// Chỉ kích hoạt khi vuốt ngang với khoảng cách đủ lớn
return
Math
.
abs
(
gestureState
.
dx
)
>
Math
.
abs
(
gestureState
.
dy
)
&&
Math
.
abs
(
gestureState
.
dx
)
>
20
;
},
onPanResponderMove
:
(
evt
,
gestureState
)
=>
{
// Có thể thêm animation preview ở đây nếu cần
},
onPanResponderRelease
:
(
evt
,
gestureState
)
=>
{
const
swipeThreshold
=
50
;
// Ngưỡng tối thiểu để kích hoạt swipe
if
(
gestureState
.
dx
>
swipeThreshold
)
{
// Vuốt phải -> tháng trước
navigateMonth
(
'prev'
);
}
else
if
(
gestureState
.
dx
<
-
swipeThreshold
)
{
// Vuốt trái -> tháng sau
navigateMonth
(
'next'
);
}
},
});
const
renderHeader
=
()
=>
{
return
(
return
(
<
View
style
=
{
styles
.
header
}
>
<
View
style
=
{
styles
.
header
}
>
<
TouchableOpacity
<
TouchableOpacity
style
=
{
styles
.
navButton
}
style
=
{
styles
.
navButton
}
onPress
=
{()
=>
navigateMonth
(
'prev'
)}
>
onPress
=
{()
=>
navigateMonth
(
'prev'
)}
>
<
Text
style
=
{
styles
.
navButtonText
}
>
‹
<
/Text
>
<
Image
source
=
{
R
.
images
.
icLeft
}
style
=
{
styles
.
icBack
}
/
>
<
/TouchableOpacity
>
<
/TouchableOpacity
>
<
Text
style
=
{
styles
.
header_title
}
>
<
Text
style
=
{
styles
.
header_title
}
>
...
@@ -55,7 +79,10 @@ const ClassScheduleView = ({
...
@@ -55,7 +79,10 @@ const ClassScheduleView = ({
<
TouchableOpacity
<
TouchableOpacity
style
=
{
styles
.
navButton
}
style
=
{
styles
.
navButton
}
onPress
=
{()
=>
navigateMonth
(
'next'
)}
>
onPress
=
{()
=>
navigateMonth
(
'next'
)}
>
<
Text
style
=
{
styles
.
navButtonText
}
>
›
<
/Text
>
<
Image
source
=
{
R
.
images
.
icRight
}
style
=
{
styles
.
icBack
}
/
>
<
/TouchableOpacity
>
<
/TouchableOpacity
>
<
/View
>
<
/View
>
);
);
...
@@ -75,7 +102,7 @@ const ClassScheduleView = ({
...
@@ -75,7 +102,7 @@ const ClassScheduleView = ({
);
);
};
};
const
renderDayCell
=
(
date
,
index
)
=>
{
const
renderDayCell
=
(
date
,
index
,
formatDateToString
)
=>
{
const
dayEvents
=
getEventsForDate
(
date
);
const
dayEvents
=
getEventsForDate
(
date
);
const
isSelected
=
selectedDate
===
formatDateToString
(
date
);
const
isSelected
=
selectedDate
===
formatDateToString
(
date
);
const
isTodayDate
=
isToday
(
date
);
const
isTodayDate
=
isToday
(
date
);
...
@@ -88,10 +115,11 @@ const ClassScheduleView = ({
...
@@ -88,10 +115,11 @@ const ClassScheduleView = ({
styles
.
dayCell
,
styles
.
dayCell
,
isSelected
&&
styles
.
selectedDayCell
,
isSelected
&&
styles
.
selectedDayCell
,
isTodayDate
&&
styles
.
todayCell
,
isTodayDate
&&
styles
.
todayCell
,
!
isInCurrentMonth
&&
{
backgroundColor
:
R
.
colors
.
gray220
},
]}
]}
onPress
=
{()
=>
handleDatePress
(
date
)}
onPress
=
{()
=>
handleDatePress
(
date
)}
activeOpacity
=
{
0.7
}
>
activeOpacity
=
{
0.7
}
>
<
Text
<
Text
style
=
{[
style
=
{[
styles
.
dayText
,
styles
.
dayText
,
...
@@ -107,10 +135,7 @@ const ClassScheduleView = ({
...
@@ -107,10 +135,7 @@ const ClassScheduleView = ({
{
dayEvents
.
slice
(
0
,
2
).
map
((
event
,
eventIndex
)
=>
(
{
dayEvents
.
slice
(
0
,
2
).
map
((
event
,
eventIndex
)
=>
(
<
TouchableOpacity
<
TouchableOpacity
key
=
{
event
.
id
}
key
=
{
event
.
id
}
style
=
{[
style
=
{[
styles
.
eventBar
]}
styles
.
eventBar
,
{
backgroundColor
:
R
.
colors
.
main
},
]}
onPress
=
{()
=>
handleEventPress
(
event
)}
>
onPress
=
{()
=>
handleEventPress
(
event
)}
>
<
Text
style
=
{
styles
.
eventBarText
}
numberOfLines
=
{
1
}
>
<
Text
style
=
{
styles
.
eventBarText
}
numberOfLines
=
{
1
}
>
{
event
.
title
}
{
event
.
title
}
...
@@ -126,14 +151,6 @@ const ClassScheduleView = ({
...
@@ -126,14 +151,6 @@ const ClassScheduleView = ({
);
);
};
};
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
renderCalendarGrid
=
()
=>
{
const
renderCalendarGrid
=
()
=>
{
const
weeks
=
[];
const
weeks
=
[];
for
(
let
i
=
0
;
i
<
6
;
i
++
)
{
for
(
let
i
=
0
;
i
<
6
;
i
++
)
{
...
@@ -141,7 +158,7 @@ const ClassScheduleView = ({
...
@@ -141,7 +158,7 @@ const ClassScheduleView = ({
weeks
.
push
(
weeks
.
push
(
<
View
key
=
{
i
}
style
=
{
styles
.
weekRow
}
>
<
View
key
=
{
i
}
style
=
{
styles
.
weekRow
}
>
{
week
.
map
((
date
,
dayIndex
)
=>
{
week
.
map
((
date
,
dayIndex
)
=>
renderDayCell
(
date
,
i
*
7
+
dayIndex
),
renderDayCell
(
date
,
i
*
7
+
dayIndex
,
formatDateToString
),
)}
)}
<
/View>
,
<
/View>
,
);
);
...
@@ -157,7 +174,7 @@ const ClassScheduleView = ({
...
@@ -157,7 +174,7 @@ const ClassScheduleView = ({
return
(
return
(
<
View
style
=
{
styles
.
bottomSheetContent
}
>
<
View
style
=
{
styles
.
bottomSheetContent
}
>
<
View
style
=
{
styles
.
dragHandle
}
/
>
<
View
style
=
{
styles
.
dragHandle
}
><
/View
>
<
View
style
=
{
styles
.
bottomSheetHeader
}
>
<
View
style
=
{
styles
.
bottomSheetHeader
}
>
<
Text
style
=
{
styles
.
bottomSheetTitle
}
>
<
Text
style
=
{
styles
.
bottomSheetTitle
}
>
...
@@ -169,7 +186,6 @@ const ClassScheduleView = ({
...
@@ -169,7 +186,6 @@ const ClassScheduleView = ({
<
Text
style
=
{
styles
.
closeButtonText
}
>
✕
<
/Text
>
<
Text
style
=
{
styles
.
closeButtonText
}
>
✕
<
/Text
>
<
/TouchableOpacity
>
<
/TouchableOpacity
>
<
/View
>
<
/View
>
<
ScrollView
<
ScrollView
style
=
{
styles
.
eventsScrollView
}
style
=
{
styles
.
eventsScrollView
}
showsVerticalScrollIndicator
=
{
false
}
>
showsVerticalScrollIndicator
=
{
false
}
>
...
@@ -179,12 +195,14 @@ const ClassScheduleView = ({
...
@@ -179,12 +195,14 @@ const ClassScheduleView = ({
<
/View
>
<
/View
>
)
:
(
)
:
(
selectedEvents
.
map
((
event
,
index
)
=>
(
selectedEvents
.
map
((
event
,
index
)
=>
(
<
View
style
=
{
styles
.
containerBottomSheet
}
>
<
TouchableOpacity
<
TouchableOpacity
key
=
{
event
.
id
}
key
=
{
event
.
id
}
style
=
{
styles
.
eventCard
}
style
=
{
styles
.
eventCard
}
onPress
=
{()
=>
navigation
.
navigate
(
SCREENNAME
.
DETAILCLASSSCHEDULE
,
{
event
})}
onPress
=
{()
=>
navigation
.
navigate
(
SCREENNAME
.
DETAILSCHEDULE
,
{
event
})
}
activeOpacity
=
{
0.7
}
>
activeOpacity
=
{
0.7
}
>
<
View
style
=
{
styles
.
eventTimeContainer
}
>
<
View
style
=
{
styles
.
eventTimeContainer
}
>
<
Text
style
=
{
styles
.
eventTime
}
>
<
Text
style
=
{
styles
.
eventTime
}
>
{
event
.
time
}
{
event
.
time
}
...
@@ -203,6 +221,7 @@ const ClassScheduleView = ({
...
@@ -203,6 +221,7 @@ const ClassScheduleView = ({
)}
)}
<
/View
>
<
/View
>
<
/TouchableOpacity
>
<
/TouchableOpacity
>
<
/View
>
))
))
)}
)}
<
/ScrollView
>
<
/ScrollView
>
...
@@ -210,6 +229,8 @@ const ClassScheduleView = ({
...
@@ -210,6 +229,8 @@ const ClassScheduleView = ({
);
);
};
};
// ===== RENDER BOTTOM SHEET - Modal bottom sheet =====
// Sử dụng styles: modalBackdrop, bottomSheet
const
renderBottomSheet
=
()
=>
{
const
renderBottomSheet
=
()
=>
{
return
(
return
(
<
Modal
<
Modal
...
@@ -217,17 +238,15 @@ const ClassScheduleView = ({
...
@@ -217,17 +238,15 @@ const ClassScheduleView = ({
transparent
=
{
true
}
transparent
=
{
true
}
animationType
=
"none"
animationType
=
"none"
onRequestClose
=
{
handleCloseBottomSheet
}
>
onRequestClose
=
{
handleCloseBottomSheet
}
>
<
TouchableOpacity
<
TouchableOpacity
style
=
{
styles
.
modalBackdrop
}
style
=
{
styles
.
modalBackdrop
}
activeOpacity
=
{
1
}
activeOpacity
=
{
1
}
onPress
=
{
handleCloseBottomSheet
}
>
onPress
=
{
handleCloseBottomSheet
}
>
<
Animated
.
View
<
Animated
.
View
style
=
{[
style
=
{[
styles
.
bottomSheet
,
styles
.
bottomSheet
,
{
{
transform
:
[{
translateY
:
bottomSheetTranslateY
}],
transform
:
[{
translateY
:
bottomSheetTranslateY
}],
},
},
]}
]}
{...
panResponder
.
panHandlers
}
>
{...
panResponder
.
panHandlers
}
>
...
@@ -240,19 +259,20 @@ const ClassScheduleView = ({
...
@@ -240,19 +259,20 @@ const ClassScheduleView = ({
);
);
};
};
// ===== MAIN RENDER - Render chính của component =====
// Sử dụng styles: container
return
(
return
(
<
View
style
=
{
styles
.
container
}
>
<
SafeAreaView
style
=
{
styles
.
container
}
>
<
ScrollView
showsVerticalScrollIndicator
=
{
false
}
>
<
ScrollView
showsVerticalScrollIndicator
=
{
false
}
>
<
View
style
=
{
styles
.
body
}
{...
swipePanResponder
.
panHandlers
}
>
{
renderHeader
()}
{
renderHeader
()}
{
renderWeekDays
()}
{
renderWeekDays
()}
{
renderCalendarGrid
()}
{
renderCalendarGrid
()}
<
/View
>
<
/ScrollView
>
<
/ScrollView
>
{
renderBottomSheet
()}
{
renderBottomSheet
()}
<
/
SafeArea
View
>
<
/View
>
);
);
};
};
export
default
ClassScheduleView
;
export
default
ClassScheduleView
;
src/screens/home/style.js
View file @
e10510ea
...
@@ -89,7 +89,7 @@ const styles = StyleSheet.create({
...
@@ -89,7 +89,7 @@ const styles = StyleSheet.create({
backgroundColor
:
R
.
colors
.
blue500
,
backgroundColor
:
R
.
colors
.
blue500
,
},
},
avatar_text
:
{
avatar_text
:
{
fontSize
:
R
.
fontsize
.
fontsSize1
6
,
fontSize
:
R
.
fontsize
.
fontsSize1
4
,
fontWeight
:
'600'
,
fontWeight
:
'600'
,
color
:
R
.
colors
.
white
,
color
:
R
.
colors
.
white
,
fontFamily
:
R
.
fonts
.
InterSemiBold
,
fontFamily
:
R
.
fonts
.
InterSemiBold
,
...
@@ -100,9 +100,9 @@ const styles = StyleSheet.create({
...
@@ -100,9 +100,9 @@ const styles = StyleSheet.create({
},
},
text_card_info
:
{
text_card_info
:
{
fontSize
:
R
.
fontsize
.
fontsSize12
,
fontSize
:
R
.
fontsize
.
fontsSize12
,
fontWeight
:
'
6
00'
,
fontWeight
:
'
4
00'
,
color
:
R
.
colors
.
black
,
color
:
R
.
colors
.
black
,
fontFamily
:
R
.
fonts
.
Inter
Medium
,
fontFamily
:
R
.
fonts
.
Inter
Regular
,
numberOfLines
:
1
,
numberOfLines
:
1
,
ellipsizeMode
:
'tail'
,
ellipsizeMode
:
'tail'
,
},
},
...
@@ -110,15 +110,14 @@ const styles = StyleSheet.create({
...
@@ -110,15 +110,14 @@ const styles = StyleSheet.create({
paddingVertical
:
5
,
paddingVertical
:
5
,
paddingHorizontal
:
10
,
paddingHorizontal
:
10
,
backgroundColor
:
R
.
colors
.
gray220
,
backgroundColor
:
R
.
colors
.
gray220
,
minHeight
:
21
,
height
:
30
,
maxWidth
:
108
,
borderRadius
:
15
,
borderRadius
:
15
,
flexDirection
:
'row'
,
flexDirection
:
'row'
,
justifyContent
:
'center'
,
justifyContent
:
'center'
,
alignItems
:
'center'
,
alignItems
:
'center'
,
},
},
btn_text
:
{
btn_text
:
{
fontSize
:
R
.
fontsize
.
fontsSize1
0
,
fontSize
:
R
.
fontsize
.
fontsSize1
2
,
fontWeight
:
'400'
,
fontWeight
:
'400'
,
color
:
R
.
colors
.
black
,
color
:
R
.
colors
.
black
,
fontFamily
:
R
.
fonts
.
InterRegular
,
fontFamily
:
R
.
fonts
.
InterRegular
,
...
...
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