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
8a6ebbb7
Commit
8a6ebbb7
authored
Sep 17, 2025
by
tungnq
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
TODO: Bổ sung hàm check render
parent
1f57d4df
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
50 additions
and
36 deletions
+50
-36
Button.js
src/components/Button.js
+0
-2
view.js
src/components/TabView/view.js
+25
-23
Functions.js
src/config/Functions.js
+24
-11
view.js
src/screens/list_work/view.js
+1
-0
No files found.
src/components/Button.js
View file @
8a6ebbb7
...
@@ -7,8 +7,6 @@ import {
...
@@ -7,8 +7,6 @@ import {
Text
,
Text
,
Image
,
Image
,
}
from
'react-native'
;
}
from
'react-native'
;
import
R
from
'../assets/R'
;
const
Button
=
props
=>
{
const
Button
=
props
=>
{
const
{
const
{
title
,
title
,
...
...
src/components/TabView/view.js
View file @
8a6ebbb7
import
React
,
{
useState
,
useEffect
,
useRef
}
from
'react'
;
import
React
,
{
useState
,
useEffect
,
useRef
}
from
'react'
;
import
{
import
{
View
,
View
,
Text
,
Text
,
...
@@ -10,12 +10,12 @@ import {
...
@@ -10,12 +10,12 @@ import {
import
Icon
from
'react-native-vector-icons/MaterialIcons'
;
import
Icon
from
'react-native-vector-icons/MaterialIcons'
;
import
R
from
'../../assets/R'
;
import
R
from
'../../assets/R'
;
const
{
width
:
screenWidth
}
=
Dimensions
.
get
(
'window'
);
const
{
width
:
screenWidth
}
=
Dimensions
.
get
(
'window'
);
/**
/**
* Component TabView có thể tái sử dụng
* Component TabView có thể tái sử dụng
* Hỗ trợ 2 chế độ: filter (lọc dữ liệu) và navigate (điều hướng)
* Hỗ trợ 2 chế độ: filter (lọc dữ liệu) và navigate (điều hướng)
*
*
* Props:
* Props:
* - data: Mảng dữ liệu tab [{key, label, icon?, disabled?}]
* - data: Mảng dữ liệu tab [{key, label, icon?, disabled?}]
* - mode: 'filter' (lọc) hoặc 'navigate' (điều hướng)
* - mode: 'filter' (lọc) hoặc 'navigate' (điều hướng)
...
@@ -60,12 +60,14 @@ const TabViewComponent = ({
...
@@ -60,12 +60,14 @@ const TabViewComponent = ({
if
(
data
.
length
===
0
)
return
;
if
(
data
.
length
===
0
)
return
;
let
initialKey
=
defaultActiveKey
;
let
initialKey
=
defaultActiveKey
;
// Kiểm tra xem defaultActiveKey có tồn tại trong data không
// Kiểm tra xem defaultActiveKey có tồn tại trong data không
if
(
defaultActiveKey
)
{
if
(
defaultActiveKey
)
{
const
keyExists
=
data
.
some
(
item
=>
item
.
key
===
defaultActiveKey
);
const
keyExists
=
data
.
some
(
item
=>
item
.
key
===
defaultActiveKey
);
if
(
!
keyExists
)
{
if
(
!
keyExists
)
{
console
.
warn
(
`TabView: defaultActiveKey "
${
defaultActiveKey
}
" không tìm thấy trong data. Sử dụng tab đầu tiên.`
);
console
.
warn
(
`TabView: defaultActiveKey "
${
defaultActiveKey
}
" không tìm thấy trong data. Sử dụng tab đầu tiên.`
,
);
initialKey
=
data
[
0
].
key
;
initialKey
=
data
[
0
].
key
;
}
}
}
else
{
}
else
{
...
@@ -79,12 +81,14 @@ const TabViewComponent = ({
...
@@ -79,12 +81,14 @@ const TabViewComponent = ({
// Kiểm tra key trùng lặp trong data
// Kiểm tra key trùng lặp trong data
useEffect
(()
=>
{
useEffect
(()
=>
{
if
(
data
.
length
===
0
)
return
;
if
(
data
.
length
===
0
)
return
;
const
keys
=
data
.
map
(
item
=>
item
.
key
);
const
keys
=
data
.
map
(
item
=>
item
.
key
);
const
uniqueKeys
=
[...
new
Set
(
keys
)];
const
uniqueKeys
=
[...
new
Set
(
keys
)];
if
(
keys
.
length
!==
uniqueKeys
.
length
)
{
if
(
keys
.
length
!==
uniqueKeys
.
length
)
{
console
.
warn
(
'TabView: Phát hiện key trùng lặp trong mảng data. Có thể gây lỗi không mong muốn.'
);
console
.
warn
(
'TabView: Phát hiện key trùng lặp trong mảng data. Có thể gây lỗi không mong muốn.'
,
);
}
}
},
[
data
]);
},
[
data
]);
...
@@ -101,19 +105,19 @@ const TabViewComponent = ({
...
@@ -101,19 +105,19 @@ const TabViewComponent = ({
const
tabCenter
=
x
+
width
/
2
;
// Tâm của tab
const
tabCenter
=
x
+
width
/
2
;
// Tâm của tab
const
screenCenter
=
screenWidth
/
2
;
// Tâm màn hình
const
screenCenter
=
screenWidth
/
2
;
// Tâm màn hình
const
scrollToX
=
Math
.
max
(
0
,
tabCenter
-
screenCenter
);
// Vị trí cần cuộn
const
scrollToX
=
Math
.
max
(
0
,
tabCenter
-
screenCenter
);
// Vị trí cần cuộn
scrollViewRef
.
current
.
scrollTo
({
scrollViewRef
.
current
.
scrollTo
({
x
:
scrollToX
,
x
:
scrollToX
,
animated
:
true
,
animated
:
true
,
});
});
},
},
()
=>
{}
// Callback khi đo thất bại
()
=>
{}
,
// Callback khi đo thất bại
);
);
}
}
},
[
activeKey
,
scrollable
]);
},
[
activeKey
,
scrollable
]);
// Xử lý khi người dùng nhấn vào tab
// Xử lý khi người dùng nhấn vào tab
const
handleTabPress
=
(
item
)
=>
{
const
handleTabPress
=
item
=>
{
// Không làm gì nếu tab bị disabled
// Không làm gì nếu tab bị disabled
if
(
item
.
disabled
)
return
;
if
(
item
.
disabled
)
return
;
...
@@ -143,7 +147,7 @@ const TabViewComponent = ({
...
@@ -143,7 +147,7 @@ const TabViewComponent = ({
return
(
return
(
<
TouchableOpacity
<
TouchableOpacity
key
=
{
item
.
key
}
key
=
{
item
.
key
}
ref
=
{
ref
=>
tabRefs
.
current
[
item
.
key
]
=
ref
}
// Lưu ref để dùng cho auto scroll
ref
=
{
ref
=>
(
tabRefs
.
current
[
item
.
key
]
=
ref
)
}
// Lưu ref để dùng cho auto scroll
style
=
{[
style
=
{[
styles
.
tab
,
// Style cơ bản
styles
.
tab
,
// Style cơ bản
tabStyle
,
// Style tùy chỉnh từ props
tabStyle
,
// Style tùy chỉnh từ props
...
@@ -153,8 +157,7 @@ const TabViewComponent = ({
...
@@ -153,8 +157,7 @@ const TabViewComponent = ({
]}
]}
onPress
=
{()
=>
handleTabPress
(
item
)}
onPress
=
{()
=>
handleTabPress
(
item
)}
disabled
=
{
isDisabled
}
disabled
=
{
isDisabled
}
activeOpacity
=
{
0.7
}
activeOpacity
=
{
0.7
}
>
>
<
View
style
=
{
styles
.
tabContent
}
>
<
View
style
=
{
styles
.
tabContent
}
>
{
/* Hiển thị icon nếu có */
}
{
/* Hiển thị icon nếu có */
}
{
item
.
icon
&&
(
{
item
.
icon
&&
(
...
@@ -169,25 +172,24 @@ const TabViewComponent = ({
...
@@ -169,25 +172,24 @@ const TabViewComponent = ({
<
Text
<
Text
style
=
{[
style
=
{[
styles
.
tabText
,
// Style text cơ bản
styles
.
tabText
,
// Style text cơ bản
{
color
:
isActive
?
activeColor
:
inactiveColor
},
// Dùng màu từ props
{
color
:
isActive
?
activeColor
:
inactiveColor
},
// Dùng màu từ props
textStyle
,
// Style text tùy chỉnh
textStyle
,
// Style text tùy chỉnh
isActive
&&
styles
.
activeTabText
,
// Style text khi active
isActive
&&
styles
.
activeTabText
,
// Style text khi active
isActive
&&
activeTextStyle
,
// Style text active tùy chỉnh
isActive
&&
activeTextStyle
,
// Style text active tùy chỉnh
isDisabled
&&
styles
.
disabledTabText
,
// Style text khi disabled
isDisabled
&&
styles
.
disabledTabText
,
// Style text khi disabled
]}
]}
numberOfLines
=
{
1
}
numberOfLines
=
{
1
}
>
>
{
/* Ưu tiên label, sau đó title, name, cuối cùng là 'Tab' */
}
{
/* Ưu tiên label, sau đó title, name, cuối cùng là 'Tab' */
}
{
item
.
label
||
item
.
title
||
item
.
name
||
'Tab'
}
{
item
.
label
||
item
.
title
||
item
.
name
||
'Tab'
}
<
/Text
>
<
/Text
>
<
/View
>
<
/View
>
{
/* Thanh gạch dưới khi tab active */
}
{
/* Thanh gạch dưới khi tab active */
}
{
isActive
&&
showActiveIndicator
&&
(
{
isActive
&&
showActiveIndicator
&&
(
<
View
<
View
style
=
{[
style
=
{[
styles
.
activeIndicator
,
styles
.
activeIndicator
,
{
backgroundColor
:
activeIndicatorColor
||
activeColor
}
// Dùng màu từ props
{
backgroundColor
:
activeIndicatorColor
||
activeColor
},
// Dùng màu từ props
]}
]}
/
>
/
>
)}
)}
<
/TouchableOpacity
>
<
/TouchableOpacity
>
...
@@ -222,7 +224,7 @@ const TabViewComponent = ({
...
@@ -222,7 +224,7 @@ const TabViewComponent = ({
<
View
style
=
{[
styles
.
container
,
style
]}
>
<
View
style
=
{[
styles
.
container
,
style
]}
>
<
TabContainer
{...
containerProps
}
>
<
TabContainer
{...
containerProps
}
>
{
data
.
map
((
item
,
index
)
=>
renderTab
(
item
,
index
))}
{
data
.
map
((
item
,
index
)
=>
renderTab
(
item
,
index
))}
<
/TabContainer>
<
/TabContainer
>
<
/View
>
<
/View
>
);
);
};
};
...
@@ -306,4 +308,4 @@ const styles = StyleSheet.create({
...
@@ -306,4 +308,4 @@ const styles = StyleSheet.create({
});
});
// Export component để sử dụng ở nơi khác
// Export component để sử dụng ở nơi khác
export
default
TabViewComponent
;
export
default
React
.
memo
(
TabViewComponent
)
;
src/config/Functions.js
View file @
8a6ebbb7
import
React
from
'react'
;
import
React
,
{
useRef
,
useEffect
}
from
'react'
;
import
{
import
{
Dimensions
,
Dimensions
,
Platform
,
Platform
,
...
@@ -234,9 +234,8 @@ export const removeItemFromArr2 = (items, index) => {
...
@@ -234,9 +234,8 @@ export const removeItemFromArr2 = (items, index) => {
return
fill
;
return
fill
;
};
};
export
const
isValidEmail
=
(
email
)
=>
export
const
isValidEmail
=
email
=>
email
.
length
>
0
&&
/
(
.+
)
@
(
.+
){2,}\.(
.+
){2,}
/gmi
.
test
(
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
);
...
@@ -607,7 +606,7 @@ export const getMimeType = fileExt => {
...
@@ -607,7 +606,7 @@ export const getMimeType = fileExt => {
};
};
//Calendar
//Calendar
export
const
parseMinutes
=
(
timeStr
)
=>
{
export
const
parseMinutes
=
timeStr
=>
{
if
(
!
timeStr
||
typeof
timeStr
!==
'string'
)
return
0
;
if
(
!
timeStr
||
typeof
timeStr
!==
'string'
)
return
0
;
const
parts
=
timeStr
.
split
(
':'
);
const
parts
=
timeStr
.
split
(
':'
);
if
(
parts
.
length
!==
2
)
return
0
;
if
(
parts
.
length
!==
2
)
return
0
;
...
@@ -616,7 +615,7 @@ export const parseMinutes = (timeStr) => {
...
@@ -616,7 +615,7 @@ export const parseMinutes = (timeStr) => {
return
h
*
60
+
m
;
return
h
*
60
+
m
;
};
};
export
const
formatDateToString
=
(
date
)
=>
{
export
const
formatDateToString
=
date
=>
{
const
y
=
date
.
getFullYear
();
const
y
=
date
.
getFullYear
();
const
m
=
String
(
date
.
getMonth
()
+
1
).
padStart
(
2
,
'0'
);
const
m
=
String
(
date
.
getMonth
()
+
1
).
padStart
(
2
,
'0'
);
const
d
=
String
(
date
.
getDate
()).
padStart
(
2
,
'0'
);
const
d
=
String
(
date
.
getDate
()).
padStart
(
2
,
'0'
);
...
@@ -652,7 +651,7 @@ export const layoutDayEvents = (events, hourHeight = 80) => {
...
@@ -652,7 +651,7 @@ export const layoutDayEvents = (events, hourHeight = 80) => {
let
cur
=
null
;
let
cur
=
null
;
for
(
const
ev
of
mapped
)
{
for
(
const
ev
of
mapped
)
{
if
(
!
cur
||
ev
.
start
>=
cur
.
maxEnd
)
{
if
(
!
cur
||
ev
.
start
>=
cur
.
maxEnd
)
{
cur
=
{
id
:
groups
.
length
,
items
:
[
ev
],
maxEnd
:
ev
.
end
};
cur
=
{
id
:
groups
.
length
,
items
:
[
ev
],
maxEnd
:
ev
.
end
};
groups
.
push
(
cur
);
groups
.
push
(
cur
);
}
else
{
}
else
{
cur
.
items
.
push
(
ev
);
cur
.
items
.
push
(
ev
);
...
@@ -664,8 +663,8 @@ export const layoutDayEvents = (events, hourHeight = 80) => {
...
@@ -664,8 +663,8 @@ export const layoutDayEvents = (events, hourHeight = 80) => {
// Gán cột theo greedy interval partitioning
// Gán cột theo greedy interval partitioning
for
(
const
g
of
groups
)
{
for
(
const
g
of
groups
)
{
const
colEnds
=
[];
// end time cuối mỗi cột
const
colEnds
=
[];
// end time cuối mỗi cột
const
colOf
=
{};
// event.id -> columnIndex
const
colOf
=
{};
// event.id -> columnIndex
const
items
=
[...
g
.
items
].
sort
((
a
,
b
)
=>
a
.
start
-
b
.
start
);
const
items
=
[...
g
.
items
].
sort
((
a
,
b
)
=>
a
.
start
-
b
.
start
);
for
(
const
ev
of
items
)
{
for
(
const
ev
of
items
)
{
...
@@ -709,4 +708,19 @@ export const layoutDayEvents = (events, hourHeight = 80) => {
...
@@ -709,4 +708,19 @@ export const layoutDayEvents = (events, hourHeight = 80) => {
// Trả về theo thứ tự ban đầu để render ổn định
// Trả về theo thứ tự ban đầu để render ổn định
return
out
.
sort
((
a
,
b
)
=>
a
.
_idx
-
b
.
_idx
);
return
out
.
sort
((
a
,
b
)
=>
a
.
_idx
-
b
.
_idx
);
};
\ No newline at end of file
/**
* 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
;
};
src/screens/list_work/view.js
View file @
8a6ebbb7
...
@@ -176,6 +176,7 @@ const ListWorkView = props => {
...
@@ -176,6 +176,7 @@ const ListWorkView = props => {
const
renderBody
=
()
=>
{
const
renderBody
=
()
=>
{
return
(
return
(
<
View
style
=
{
styles
.
body
}
>
<
View
style
=
{
styles
.
body
}
>
{
renderTabView
()}
{
renderTabView
()}
{
renderCard
()}
{
renderCard
()}
<
View
style
=
{
styles
.
listContainer
}
>
<
View
style
=
{
styles
.
listContainer
}
>
...
...
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