Commit 8a6ebbb7 by tungnq

TODO: Bổ sung hàm check render

parent 1f57d4df
...@@ -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,
......
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);
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;
};
...@@ -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}>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment