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
7cf52c5d
Commit
7cf52c5d
authored
Sep 12, 2025
by
tungnq
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
TODO: thêm layout sự kiện lịch và tiện ích định dạng thời gian
parent
df7585d6
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
295 additions
and
510 deletions
+295
-510
Functions.js
src/config/Functions.js
+106
-0
constants.js
src/config/constants.js
+10
-170
useFilterDay.js
src/hooks/useFilterDay.js
+104
-0
index.js
src/screens/class_schedule/filterday/index.js
+52
-337
view.js
src/screens/class_schedule/filterday/view.js
+23
-3
No files found.
src/config/Functions.js
View file @
7cf52c5d
...
@@ -605,3 +605,108 @@ export const getMimeType = fileExt => {
...
@@ -605,3 +605,108 @@ 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
);
};
\ No newline at end of file
src/config/constants.js
View file @
7cf52c5d
import
i18n
from
"../helper/i18/i18n"
;
import
i18n
from
"../helper/i18/i18n"
;
import
R
from
"../assets/R"
;
import
R
from
"../assets/R"
;
export
const
HISTORY_STATUS
=
{
ALL
:
{
code
:
"ALL"
,
color
:
R
.
colors
.
secondary
,
icon
:
R
.
images
.
icCompleted
,
name
:
i18n
.
t
(
"All"
),
status
:
"-1"
,
},
COMPLETED
:
{
code
:
"COMPLETED"
,
color
:
R
.
colors
.
secondary
,
icon
:
R
.
images
.
icCompleted
,
name
:
i18n
.
t
(
"Completed"
),
status
:
"3"
,
},
IN_PROCESSING
:
{
code
:
"IN_PROCESSING"
,
color
:
R
.
colors
.
lightBlue
,
icon
:
R
.
images
.
icInfo
,
name
:
i18n
.
t
(
"InProcessing"
),
status
:
"4"
,
},
FAILED
:
{
code
:
"FAILED"
,
color
:
R
.
colors
.
red1
,
icon
:
R
.
images
.
icFailed
,
name
:
i18n
.
t
(
"Failed"
),
status
:
"7"
,
},
WATTING
:
{
code
:
"TRANS_WAIT_APPROVED"
,
color
:
R
.
colors
.
orange
,
icon
:
R
.
images
.
icInfo
,
name
:
i18n
.
t
(
"InProcessing"
),
status
:
"4"
,
},
};
export
const
SEX
=
{
MALE
:
0
,
FEMALE
:
1
,
};
export
const
TRANSACTION_TYPE
=
{
ALL
:
{
code
:
"ALL"
,
color
:
R
.
colors
.
secondary
,
backgroundButton
:
R
.
images
.
bgDepositButton
,
name
:
i18n
.
t
(
"All"
),
transferType
:
-
1
,
},
DEPOSIT
:
{
code
:
"PUSH"
,
color
:
R
.
colors
.
secondary
,
backgroundButton
:
R
.
images
.
bgDepositButton
,
name
:
i18n
.
t
(
"Deposit"
),
transferType
:
2
,
transferTypeTxt
:
"PUSH"
,
},
WITHDRAW
:
{
code
:
"PULL"
,
color
:
R
.
colors
.
red1
,
backgroundButton
:
R
.
images
.
bgWithdrawButton
,
name
:
i18n
.
t
(
"Withdraw"
),
transferType
:
1
,
transferTypeTxt
:
"PULL"
,
},
BORROW_REQUEST
:
{
code
:
"BORROW_REQUEST"
,
color
:
R
.
colors
.
gray1
,
backgroundButton
:
R
.
images
.
bgBorrowRequest
,
name
:
i18n
.
t
(
"BorrowRequest"
),
transferType
:
3
,
transferTypeTxt
:
"3"
,
},
};
export
const
ACCOUNT_BANK_TYPE
=
{
BANK
:
"Bank"
,
CREDIT
:
"Credit"
,
};
export
const
CELL_COUNT
=
4
;
export
const
SHARE_TYPE
=
{
ALL
:
1
,
FACEBOOK
:
2
,
TWITTER
:
3
,
};
export
const
RATINGS_TYPE
=
{
REFER_FRIEND
:
1
,
BENEFIT
:
2
,
};
export
const
LANGUAGE_LIST
=
[
{
id
:
56
,
name
:
i18n
.
t
(
"Vietnamese"
),
value
:
"vi"
,
code
:
"vi"
,
},
{
id
:
57
,
name
:
i18n
.
t
(
"English"
),
value
:
"en"
,
code
:
"en"
,
},
];
export
const
ASYNC_STORE_KEY
=
{
export
const
ASYNC_STORE_KEY
=
{
TOKEN
:
"@TOKEN"
,
TOKEN
:
"@TOKEN"
,
...
@@ -112,73 +9,6 @@ export const ASYNC_STORE_KEY = {
...
@@ -112,73 +9,6 @@ export const ASYNC_STORE_KEY = {
LANGUAGE
:
"@LANGUAGE"
,
LANGUAGE
:
"@LANGUAGE"
,
};
};
export
const
OTP_TYPE
=
{
CHECK_PHONE_NUMBER
:
0
,
FORGOT_PASSWORD
:
1
,
};
export
const
PROVINCE_LIST
=
[
{
id
:
1
,
code
:
"AG"
,
name
:
"An Giang"
,
},
{
id
:
2
,
code
:
"BR_VT"
,
name
:
"Bà Rịa - Vũng Tàu"
,
},
{
id
:
3
,
code
:
"BL"
,
name
:
"Bạc Liêu"
,
},
{
id
:
4
,
code
:
"BK"
,
name
:
"Bắc Kạn"
,
},
{
id
:
5
,
code
:
"BC"
,
name
:
"Bắc Giang"
,
},
{
id
:
6
,
code
:
"BN"
,
name
:
"Bắc Ninh"
,
},
{
id
:
7
,
code
:
"BT"
,
name
:
"Bến Tre"
,
},
{
id
:
8
,
code
:
"BD"
,
name
:
"Bình Dương"
,
},
{
id
:
9
,
code
:
"BD"
,
name
:
"Bình Định"
,
},
{
id
:
10
,
code
:
"BP"
,
name
:
"Bình Phước"
,
},
{
id
:
11
,
code
:
"HN"
,
name
:
"Hà Nội"
,
},
{
id
:
12
,
code
:
"HCM"
,
name
:
"Hồ Chí Minh"
,
},
];
export
const
DEVICE_EVENT_KEY
=
{
export
const
DEVICE_EVENT_KEY
=
{
RELOAD_BALANCE_WALLET
:
"reloadBalanceWallet"
,
RELOAD_BALANCE_WALLET
:
"reloadBalanceWallet"
,
LOGOUT_EVENT
:
"logoutEvent"
,
LOGOUT_EVENT
:
"logoutEvent"
,
...
@@ -186,3 +16,13 @@ export const DEVICE_EVENT_KEY = {
...
@@ -186,3 +16,13 @@ export const DEVICE_EVENT_KEY = {
export
const
BACKSPACE
=
'Backspace'
;
export
const
BACKSPACE
=
'Backspace'
;
export
const
DELIMITERS
=
[
','
,
';'
,
' '
];
export
const
DELIMITERS
=
[
','
,
';'
,
' '
];
//Calendar
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 @
7cf52c5d
// 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
();
},
});
// Adapter cho view cũ
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/screens/class_schedule/filterday/index.js
View file @
7cf52c5d
import
React
,
{
useState
,
useRef
,
useEffect
}
from
'react'
;
import
React
from
'react'
;
import
{
useNavigation
,
useFocusEffect
}
from
'@react-navigation/native'
;
import
{
DeviceEventEmitter
,
PanResponder
}
from
'react-native'
;
import
FilterDayView
from
'./view'
;
import
FilterDayView
from
'./view'
;
import
{
useFilterDay
}
from
'../../../hooks/useFilterDay'
;
const
FilterDay
=
({
navigation
})
=>
{
const
[
currentDate
,
setCurrentDate
]
=
useState
(
new
Date
());
// HOISTED: function declaration (an toàn gọi ở mọi nơi bên dưới)
const
[
selectedDate
,
setSelectedDate
]
=
useState
(
new
Date
());
function
createMockEvents
()
{
const
[
showMonthPicker
,
setShowMonthPicker
]
=
useState
(
false
);
const
today
=
new
Date
();
const
scrollViewRef
=
useRef
(
null
);
const
formatDateToString
=
(
date
)
=>
{
const
y
=
date
.
getFullYear
();
useEffect
(()
=>
{
const
m
=
String
(
date
.
getMonth
()
+
1
).
padStart
(
2
,
'0'
);
DeviceEventEmitter
.
emit
(
'onDateChange'
,
selectedDate
);
const
d
=
String
(
date
.
getDate
()).
padStart
(
2
,
'0'
);
},
[
selectedDate
]);
return
`
${
y
}
-
${
m
}
-
${
d
}
`
;
};
// Reset về ngày hiện tại khi chuyển màn hình
useFocusEffect
(
const
todayStr
=
formatDateToString
(
today
);
React
.
useCallback
(()
=>
{
const
tomorrow
=
new
Date
(
today
);
tomorrow
.
setDate
(
today
.
getDate
()
+
1
);
const
today
=
new
Date
();
const
tomorrowStr
=
formatDateToString
(
tomorrow
);
setCurrentDate
(
today
);
const
yesterday
=
new
Date
(
today
);
yesterday
.
setDate
(
today
.
getDate
()
-
1
);
setSelectedDate
(
today
);
const
yesterdayStr
=
formatDateToString
(
yesterday
);
DeviceEventEmitter
.
emit
(
'onDateChange'
,
today
);
// Cập nhật header drawer với tháng hiện tại
return
[
DeviceEventEmitter
.
emit
(
'updateHeaderMonth'
,
today
.
getMonth
());
{
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'
},
const
createMockEvents
=
()
=>
{
{
id
:
'4'
,
title
:
'Ăn trưa'
,
subtitle
:
'Căng tin trường'
,
time
:
'12:00'
,
endTime
:
'13:00'
,
date
:
todayStr
,
type
:
'meal'
},
const
today
=
new
Date
();
{
id
:
'4a'
,
title
:
'Họp với phụ huynh'
,
subtitle
:
'Phòng 101'
,
time
:
'12:15'
,
endTime
:
'13:15'
,
date
:
todayStr
,
type
:
'meeting'
},
const
formatDateToString
=
date
=>
{
{
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'
},
const
year
=
date
.
getFullYear
();
{
id
:
'5'
,
title
:
'Training React Native'
,
subtitle
:
'Online Zoom'
,
time
:
'14:00'
,
endTime
:
'16:00'
,
date
:
todayStr
,
type
:
'training'
},
const
month
=
(
date
.
getMonth
()
+
1
).
toString
().
padStart
(
2
,
'0'
);
{
id
:
'6'
,
title
:
'Code Review Session'
,
subtitle
:
'Dev Team'
,
time
:
'16:30'
,
endTime
:
'17:30'
,
date
:
todayStr
,
type
:
'review'
},
const
day
=
date
.
getDate
().
toString
().
padStart
(
2
,
'0'
);
{
id
:
'7'
,
title
:
'Code Review Session'
,
subtitle
:
'Dev Team'
,
time
:
'16:30'
,
endTime
:
'17:30'
,
date
:
todayStr
,
type
:
'review'
},
return
`
${
year
}
-
${
month
}
-
${
day
}
`
;
{
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'
},
const
todayStr
=
formatDateToString
(
today
);
{
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'
},
const
tomorrow
=
new
Date
(
today
);
{
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'
},
tomorrow
.
setDate
(
today
.
getDate
()
+
1
);
{
id
:
'10'
,
title
:
'Seminar Công nghệ mới'
,
subtitle
:
'Phòng 301'
,
time
:
'09:00'
,
endTime
:
'11:00'
,
date
:
yesterdayStr
,
type
:
'seminar'
},
const
tomorrowStr
=
formatDateToString
(
tomorrow
);
{
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'
},
const
yesterday
=
new
Date
(
today
);
{
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'
},
yesterday
.
setDate
(
today
.
getDate
()
-
1
);
{
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'
},
const
yesterdayStr
=
formatDateToString
(
yesterday
);
{
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'
},
];
return
[
}
// Sự kiện hôm nay
{
const
FilterDay
=
()
=>
{
id
:
'1'
,
// Tạo dữ liệu 1 lần khi mount, không tái tạo ở mỗi render
title
:
'Lịch vào trực lớp TTCĐT 445.T1'
,
const
mockEvents
=
React
.
useMemo
(()
=>
createMockEvents
(),
[]);
subtitle
:
'CS Địa lý 4D'
,
time
:
'07:00'
,
// GỌI HOOK SAU KHI mockEvents sẵn sàng
endTime
:
'09:00'
,
const
vm
=
useFilterDay
(
mockEvents
);
date
:
todayStr
,
type
:
'class'
,
return
<
FilterDayView
{...
vm
}
/>
;
},
{
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
:
'3'
,
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
:
'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'
,
},
// Sự kiện ngày mai
{
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'
,
},
// Sự kiện hôm qua
{
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'
,
},
// Sự kiện ngày cố định
{
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
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
handleMonthSelect
=
monthIndex
=>
{
const
newDate
=
new
Date
(
currentDate
);
newDate
.
setMonth
(
monthIndex
);
setCurrentDate
(
newDate
);
setSelectedDate
(
newDate
);
setShowMonthPicker
(
false
);
DeviceEventEmitter
.
emit
(
'onDateChange'
,
newDate
);
// Cập nhật header drawer với tháng mới
DeviceEventEmitter
.
emit
(
'updateHeaderMonth'
,
monthIndex
);
};
const
swipeToNextDay
=
()
=>
{
const
nextDay
=
new
Date
(
selectedDate
);
nextDay
.
setDate
(
selectedDate
.
getDate
()
+
1
);
setSelectedDate
(
nextDay
);
setCurrentDate
(
nextDay
);
DeviceEventEmitter
.
emit
(
'onDateChange'
,
nextDay
);
// Cập nhật header drawer nếu tháng thay đổi
DeviceEventEmitter
.
emit
(
'updateHeaderMonth'
,
nextDay
.
getMonth
());
};
const
swipeToPrevDay
=
()
=>
{
const
prevDay
=
new
Date
(
selectedDate
);
prevDay
.
setDate
(
selectedDate
.
getDate
()
-
1
);
setSelectedDate
(
prevDay
);
setCurrentDate
(
prevDay
);
DeviceEventEmitter
.
emit
(
'onDateChange'
,
prevDay
);
// Cập nhật header drawer nếu tháng thay đổi
DeviceEventEmitter
.
emit
(
'updateHeaderMonth'
,
prevDay
.
getMonth
());
};
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
};
};
const
calculateEventLayout
=
(
events
)
=>
{
return
events
.
map
((
event
,
index
)
=>
{
const
{
topPosition
,
height
}
=
calculateEventPosition
(
event
.
time
,
event
.
endTime
);
// Simple stacking - each subsequent event gets slightly offset
const
stackOffset
=
index
*
8
;
// 8px offset for each event
const
leftOffset
=
5
+
stackOffset
;
const
rightOffset
=
15
+
stackOffset
;
return
{
...
event
,
topPosition
,
height
,
leftOffset
,
rightOffset
,
zIndex
:
10
+
index
// Higher z-index for later events
};
});
};
return
(
<
FilterDayView
navigation
=
{
navigation
}
currentDate
=
{
currentDate
}
selectedDate
=
{
selectedDate
}
showMonthPicker
=
{
showMonthPicker
}
scrollViewRef
=
{
scrollViewRef
}
panResponder
=
{
panResponder
}
getEventsForDate
=
{
getEventsForDate
}
getDayName
=
{
getDayName
}
getMonthName
=
{
getMonthName
}
handleMonthSelect
=
{
handleMonthSelect
}
toggleMonthPicker
=
{
toggleMonthPicker
}
calculateEventPosition
=
{
calculateEventPosition
}
calculateEventLayout
=
{
calculateEventLayout
}
/
>
);
};
};
export
default
FilterDay
;
export
default
FilterDay
;
src/screens/class_schedule/filterday/view.js
View file @
7cf52c5d
...
@@ -88,20 +88,40 @@ const FilterDayView = props => {
...
@@ -88,20 +88,40 @@ const FilterDayView = props => {
right
:
event
.
rightOffset
,
right
:
event
.
rightOffset
,
zIndex
:
event
.
zIndex
,
zIndex
:
event
.
zIndex
,
backgroundColor
:
R
.
colors
.
blue
,
backgroundColor
:
R
.
colors
.
blue
,
// Add minimum width to prevent events from being too narrow
minWidth
:
event
.
numColumns
>
3
?
60
:
undefined
,
},
},
]}
]}
activeOpacity
=
{
0.7
}
>
activeOpacity
=
{
0.7
}
>
<
Text
<
Text
style
=
{
styles
.
eventTitle
}
style
=
{[
styles
.
eventTitle
,
{
fontSize
:
event
.
numColumns
>
2
?
10
:
event
.
numColumns
>
1
?
12
:
14
,
}
]}
numberOfLines
=
{
event
.
height
>
60
?
2
:
1
}
>
numberOfLines
=
{
event
.
height
>
60
?
2
:
1
}
>
{
event
.
title
}
{
event
.
title
}
<
/Text
>
<
/Text
>
{
event
.
height
>
40
&&
(
{
event
.
height
>
40
&&
(
<
Text
style
=
{
styles
.
eventSubtitle
}
numberOfLines
=
{
1
}
>
<
Text
style
=
{[
styles
.
eventSubtitle
,
{
fontSize
:
event
.
numColumns
>
2
?
9
:
event
.
numColumns
>
1
?
11
:
13
,
}
]}
numberOfLines
=
{
1
}
>
{
event
.
subtitle
}
{
event
.
subtitle
}
<
/Text
>
<
/Text
>
)}
)}
<
Text
style
=
{
styles
.
eventTime
}
>
<
Text
style
=
{[
styles
.
eventTime
,
{
fontSize
:
event
.
numColumns
>
2
?
8
:
event
.
numColumns
>
1
?
10
:
12
,
}
]}
>
{
event
.
time
}
-
{
event
.
endTime
}
{
event
.
time
}
-
{
event
.
endTime
}
<
/Text
>
<
/Text
>
<
/TouchableOpacity
>
<
/TouchableOpacity
>
...
...
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