This commit is contained in:
Truong Vo 2025-11-28 15:23:23 +07:00
parent 5fb9c14db5
commit 6f8aa1d69b
1 changed files with 94 additions and 126 deletions

View File

@ -48,8 +48,6 @@ function ModalHistory({ opened, onClose, socket, stationIds = [] }: ModalHistory
const [activeTimePeriod, setActiveTimePeriod] = useState<string>("current");
const scrollViewportRef = useRef<HTMLDivElement>(null);
const isAutoSwitchingRef = useRef(false);
const lastScrollTopRef = useRef(0);
const scrollTimeoutRef = useRef<NodeJS.Timeout | null>(null);
useEffect(() => {
if (!socket || !opened) return;
@ -88,20 +86,6 @@ function ModalHistory({ opened, onClose, socket, stationIds = [] }: ModalHistory
}
}, [opened]);
// Initialize scroll position when content loads
useEffect(() => {
if (scrollViewportRef.current && historyData.length > 0) {
setTimeout(() => {
if (scrollViewportRef.current) {
const scrollHeight = scrollViewportRef.current.scrollHeight;
const clientHeight = scrollViewportRef.current.clientHeight;
// Start at middle position
scrollViewportRef.current.scrollTop = (scrollHeight - clientHeight) / 2;
lastScrollTopRef.current = scrollViewportRef.current.scrollTop;
}
}, 200);
}
}, [historyData]);
// Utility function to format timestamp (can be used later if needed)
// const formatTimestamp = (timestamp: number) => {
@ -109,27 +93,27 @@ function ModalHistory({ opened, onClose, socket, stationIds = [] }: ModalHistory
// return date.toLocaleString();
// };
// Get time range based on selected period (returns [startTime, endTime])
// Get time range based on selected period (cumulative - tích lũy)
const getTimeRange = (period: string): [number, number] => {
const now = Date.now();
const HOUR = 60 * 60 * 1000;
switch (period) {
case "current":
// Current: từ 0-4h gần nhất
// Current: chỉ hiện tại (4h gần nhất)
return [now - 4 * HOUR, now];
case "last_4h":
// Last 4h: từ 4h-8h trước
return [now - 8 * HOUR, now - 4 * HOUR];
// Last 4h: bao gồm current + last 4h (0-8h)
return [now - 8 * HOUR, now];
case "last_8h":
// Last 8h: từ 8h-24h trước
return [now - 24 * HOUR, now - 8 * HOUR];
// Last 8h: bao gồm current + last 4h + last 8h (0-24h)
return [now - 24 * HOUR, now];
case "last_24h":
// Last 24h: từ 24h-48h trước
return [now - 48 * HOUR, now - 24 * HOUR];
// Last 24h: bao gồm current + last 4h + last 8h + last 24h (0-48h)
return [now - 48 * HOUR, now];
case "last_48h":
// Last 48h: từ 48h trở về trước (tất cả data cũ hơn 48h)
return [0, now - 48 * HOUR];
// Last 48h: tất cả data (từ đầu đến giờ)
return [0, now];
default:
return [0, now];
}
@ -143,6 +127,25 @@ function ModalHistory({ opened, onClose, socket, stationIds = [] }: ModalHistory
);
};
// Group history items by line number
const groupHistoryByLine = (history: HistoryItem[]): Map<number, HistoryItem[]> => {
const grouped = new Map<number, HistoryItem[]>();
history.forEach((item) => {
if (!grouped.has(item.number)) {
grouped.set(item.number, []);
}
grouped.get(item.number)!.push(item);
});
// Sort items within each group by timestamp (newest first)
grouped.forEach((items) => {
items.sort((a, b) => b.timestamp - a.timestamp);
});
return grouped;
};
const currentStationData = historyData.find(
(station) => station.stationId.toString() === activeStation
);
@ -151,6 +154,9 @@ function ModalHistory({ opened, onClose, socket, stationIds = [] }: ModalHistory
const filteredHistory = currentStationData
? filterHistoryByTime(currentStationData.history)
: [];
// Group filtered history by line number
const groupedHistory = groupHistoryByLine(filteredHistory);
if (!opened) return null;
@ -282,134 +288,96 @@ function ModalHistory({ opened, onClose, socket, stationIds = [] }: ModalHistory
h="calc(75vh - 80px)"
viewportRef={scrollViewportRef}
onScrollPositionChange={(position) => {
if (!scrollViewportRef.current) return;
if (!scrollViewportRef.current || isAutoSwitchingRef.current) return;
const scrollTop = position.y;
const target = scrollViewportRef.current;
const scrollHeight = target.scrollHeight;
const clientHeight = target.clientHeight;
// Clear existing timeout
if (scrollTimeoutRef.current) {
clearTimeout(scrollTimeoutRef.current);
}
// Check if scrolled to bottom
const isAtBottom = Math.abs(scrollTop + clientHeight - scrollHeight) < 5;
// Debounce scroll handling
scrollTimeoutRef.current = setTimeout(() => {
if (isAutoSwitchingRef.current) {
return;
}
if (isAtBottom && historyData.length > 0) {
isAutoSwitchingRef.current = true;
// Determine scroll direction
const isScrollingUp = scrollTop < lastScrollTopRef.current;
const isScrollingDown = scrollTop > lastScrollTopRef.current;
// Update last scroll position AFTER checking direction
lastScrollTopRef.current = scrollTop;
const currentStationIndex = historyData.findIndex(
(station) => station.stationId.toString() === activeStation
);
// Check if at absolute top or bottom (no spacer needed)
const isAtTop = scrollTop === 0;
const isAtBottom = Math.abs(scrollTop + clientHeight - scrollHeight) < 5;
// Can only trigger one direction at a time
const canScrollUp = isAtTop && isScrollingUp && historyData.length > 0;
const canScrollDown = isAtBottom && isScrollingDown && historyData.length > 0;
// Handle scroll up - khi scroll lên đến đầu
if (canScrollUp) {
isAutoSwitchingRef.current = true;
const currentPeriodIndex = TIME_PERIODS.findIndex(
(p) => p.value === activeTimePeriod
);
if (currentPeriodIndex > 0) {
setActiveTimePeriod(TIME_PERIODS[currentPeriodIndex - 1].value);
} else {
const currentStationIndex = historyData.findIndex(
(station) => station.stationId.toString() === activeStation
);
if (currentStationIndex > 0) {
setActiveStation(historyData[currentStationIndex - 1].stationId.toString());
setActiveTimePeriod(TIME_PERIODS[TIME_PERIODS.length - 1].value);
} else {
isAutoSwitchingRef.current = false;
return;
}
}
if (currentStationIndex !== -1 && currentStationIndex < historyData.length - 1) {
// Chuyển sang station tiếp theo
setActiveStation(historyData[currentStationIndex + 1].stationId.toString());
// Scroll to bottom of new content
setTimeout(() => {
if (scrollViewportRef.current) {
const newScrollHeight = scrollViewportRef.current.scrollHeight;
const newClientHeight = scrollViewportRef.current.clientHeight;
scrollViewportRef.current.scrollTop = newScrollHeight - newClientHeight;
lastScrollTopRef.current = scrollViewportRef.current.scrollTop;
}
setTimeout(() => {
isAutoSwitchingRef.current = false;
}, 100);
}, 150);
}
// Handle scroll down - khi scroll xuống đến cuối
else if (canScrollDown) {
isAutoSwitchingRef.current = true;
const currentPeriodIndex = TIME_PERIODS.findIndex(
(p) => p.value === activeTimePeriod
);
if (currentPeriodIndex < TIME_PERIODS.length - 1) {
setActiveTimePeriod(TIME_PERIODS[currentPeriodIndex + 1].value);
} else {
const currentStationIndex = historyData.findIndex(
(station) => station.stationId.toString() === activeStation
);
if (currentStationIndex !== -1 && currentStationIndex < historyData.length - 1) {
setActiveStation(historyData[currentStationIndex + 1].stationId.toString());
setActiveTimePeriod(TIME_PERIODS[0].value);
} else {
isAutoSwitchingRef.current = false;
return;
}
}
// Scroll to top of new content
// Reset scroll to top
setTimeout(() => {
if (scrollViewportRef.current) {
scrollViewportRef.current.scrollTop = 0;
lastScrollTopRef.current = 0;
}
setTimeout(() => {
isAutoSwitchingRef.current = false;
}, 100);
}, 150);
} else {
isAutoSwitchingRef.current = false;
}
}, 100); // Debounce 100ms
}
}}
>
<Box>
{filteredHistory.length > 0 ? (
<Box style={{ minHeight: "calc(75vh - 60px)" }}>
{groupedHistory.size > 0 ? (
<>
{filteredHistory.map((item, index) => (
{Array.from(groupedHistory.entries())
.sort(([lineA], [lineB]) => lineA - lineB)
.map(([lineNumber, items]) => (
<Box
key={`${item.stationId}-${item.number}-${item.timestamp}-${index}`}
key={`line-group-${lineNumber}`}
style={{
padding: "10px 16px",
borderBottom: "1px solid #dee2e6",
backgroundColor: index % 2 === 0 ? "#f8f9fa" : "white",
marginBottom: "8px",
border: "1px solid #dee2e6",
borderRadius: "4px",
overflow: "hidden",
}}
>
<Text size="sm" style={{ fontFamily: "monospace" }}>
Line {item.number}: {item.pid} {item.vid} SN: {item.sn}
<span style={{ marginLeft: "20px", color: "#868e96" }}>
| {item.scenario}
</span>
</Text>
{/* Header của nhóm - hiển thị line number */}
<Box
style={{
padding: "8px 16px",
backgroundColor: "#e9ecef",
fontWeight: 600,
}}
>
<Text size="sm" fw={600}>
Line {lineNumber} ({items.length} {items.length > 1 ? 'records' : 'record'})
</Text>
</Box>
{/* Các items trong nhóm */}
{items.map((item, itemIndex) => (
<Box
key={`${item.stationId}-${item.number}-${item.timestamp}-${itemIndex}`}
style={{
padding: "8px 16px 8px 32px", // Tăng padding-left lên 32px
borderTop: itemIndex > 0 ? "1px solid #f1f3f5" : "none",
backgroundColor: itemIndex % 2 === 0 ? "white" : "#f8f9fa",
}}
>
<Text size="sm" style={{ fontFamily: "monospace" }}>
{item.pid} {item.vid} SN: {item.sn}
<span style={{ marginLeft: "20px", color: "#868e96" }}>
| {item.scenario}
</span>
<span style={{ marginLeft: "10px", color: "#adb5bd", fontSize: "11px" }}>
{new Date(item.timestamp).toLocaleString()}
</span>
</Text>
</Box>
))}
</Box>
))}
{/* Spacer để đảm bảo có thể scroll ngay cả khi content ít */}
<Box style={{ height: "200px" }} />
</>
) : (
<Flex