fix chức năng history
This commit is contained in:
parent
77027d4f8a
commit
f60be2f543
|
|
@ -53,7 +53,8 @@ function ModalHistory({
|
||||||
const [activeStation, setActiveStation] = useState<string>("");
|
const [activeStation, setActiveStation] = useState<string>("");
|
||||||
const [activeTimePeriod, setActiveTimePeriod] = useState<string>("current");
|
const [activeTimePeriod, setActiveTimePeriod] = useState<string>("current");
|
||||||
const scrollViewportRef = useRef<HTMLDivElement>(null);
|
const scrollViewportRef = useRef<HTMLDivElement>(null);
|
||||||
const isAutoSwitchingRef = useRef(false);
|
const stationRefs = useRef<Map<number, HTMLDivElement>>(new Map());
|
||||||
|
const scrollTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!socket || !opened) return;
|
if (!socket || !opened) return;
|
||||||
|
|
@ -89,15 +90,32 @@ function ModalHistory({
|
||||||
setHistoryData([]);
|
setHistoryData([]);
|
||||||
setActiveStation("");
|
setActiveStation("");
|
||||||
setActiveTimePeriod("current");
|
setActiveTimePeriod("current");
|
||||||
|
stationRefs.current.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (scrollTimeoutRef.current) {
|
||||||
|
clearTimeout(scrollTimeoutRef.current);
|
||||||
|
}
|
||||||
|
};
|
||||||
}, [opened]);
|
}, [opened]);
|
||||||
|
|
||||||
// Change station or filter will reset scroll
|
// Scroll to station when activeStation changes (only when clicked, not when auto-detected)
|
||||||
|
const isManualScrollRef = useRef(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (scrollViewportRef?.current) {
|
if (activeStation && isManualScrollRef.current) {
|
||||||
scrollViewportRef.current.scrollTop = 0;
|
setTimeout(() => {
|
||||||
|
scrollToStation(Number(activeStation));
|
||||||
|
// Reset flag after scroll completes
|
||||||
|
setTimeout(() => {
|
||||||
|
isManualScrollRef.current = false;
|
||||||
|
}, 500);
|
||||||
|
}, 100);
|
||||||
}
|
}
|
||||||
}, [activeStation, activeTimePeriod]);
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [activeStation]);
|
||||||
|
|
||||||
|
|
||||||
// Utility function to format timestamp (can be used later if needed)
|
// Utility function to format timestamp (can be used later if needed)
|
||||||
// const formatTimestamp = (timestamp: number) => {
|
// const formatTimestamp = (timestamp: number) => {
|
||||||
|
|
@ -163,17 +181,35 @@ function ModalHistory({
|
||||||
return grouped;
|
return grouped;
|
||||||
};
|
};
|
||||||
|
|
||||||
const currentStationData = historyData.find(
|
// Apply time filter to ALL stations data (không filter bỏ stations không có data)
|
||||||
(station) => station.stationId.toString() === activeStation
|
const allFilteredStations = historyData.map((station) => ({
|
||||||
);
|
...station,
|
||||||
|
filteredHistory: filterHistoryByTime(station.history),
|
||||||
|
}));
|
||||||
|
|
||||||
// Apply time filter to current station data
|
// Group each station's history by line number
|
||||||
const filteredHistory = currentStationData
|
const allGroupedStations = allFilteredStations.map((station) => ({
|
||||||
? filterHistoryByTime(currentStationData.history)
|
...station,
|
||||||
: [];
|
groupedHistory: station.filteredHistory.length > 0
|
||||||
|
? groupHistoryByLine(station.filteredHistory)
|
||||||
|
: new Map<number, HistoryItem[]>(),
|
||||||
|
}));
|
||||||
|
|
||||||
// Group filtered history by line number
|
// Function to scroll to a specific station
|
||||||
const groupedHistory = groupHistoryByLine(filteredHistory);
|
const scrollToStation = (stationId: number) => {
|
||||||
|
const stationElement = stationRefs.current.get(stationId);
|
||||||
|
if (stationElement && scrollViewportRef.current) {
|
||||||
|
const scrollContainer = scrollViewportRef.current;
|
||||||
|
const containerTop = scrollContainer.getBoundingClientRect().top;
|
||||||
|
const elementTop = stationElement.getBoundingClientRect().top;
|
||||||
|
const scrollTop = scrollContainer.scrollTop;
|
||||||
|
|
||||||
|
scrollContainer.scrollTo({
|
||||||
|
top: scrollTop + elementTop - containerTop - 10, // 10px offset from top
|
||||||
|
behavior: "smooth",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if (!opened) return null;
|
if (!opened) return null;
|
||||||
|
|
||||||
|
|
@ -262,9 +298,11 @@ function ModalHistory({
|
||||||
? "filled"
|
? "filled"
|
||||||
: "outline"
|
: "outline"
|
||||||
}
|
}
|
||||||
onClick={() =>
|
onClick={() => {
|
||||||
setActiveStation(station.stationId.toString())
|
isManualScrollRef.current = true;
|
||||||
}
|
setActiveStation(station.stationId.toString());
|
||||||
|
scrollToStation(station.stationId);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{station.stationName}
|
{station.stationName}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -304,71 +342,101 @@ function ModalHistory({
|
||||||
<ScrollArea
|
<ScrollArea
|
||||||
h="calc(75vh - 80px)"
|
h="calc(75vh - 80px)"
|
||||||
viewportRef={scrollViewportRef}
|
viewportRef={scrollViewportRef}
|
||||||
onScrollPositionChange={(position) => {
|
onScrollPositionChange={() => {
|
||||||
if (
|
if (isManualScrollRef.current || !scrollViewportRef.current) return;
|
||||||
!scrollViewportRef.current ||
|
|
||||||
isAutoSwitchingRef.current
|
|
||||||
)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const scrollTop = position.y;
|
// Debounce để scroll mượt hơn
|
||||||
const target = scrollViewportRef.current;
|
if (scrollTimeoutRef.current) {
|
||||||
const scrollHeight = target.scrollHeight;
|
clearTimeout(scrollTimeoutRef.current);
|
||||||
const clientHeight = target.clientHeight;
|
|
||||||
|
|
||||||
// Check if scrolled to bottom
|
|
||||||
const isAtBottom =
|
|
||||||
Math.abs(scrollTop + clientHeight - scrollHeight) < 5;
|
|
||||||
|
|
||||||
if (isAtBottom && historyData.length > 0) {
|
|
||||||
isAutoSwitchingRef.current = true;
|
|
||||||
|
|
||||||
const currentStationIndex = historyData.findIndex(
|
|
||||||
(station) =>
|
|
||||||
station.stationId.toString() === activeStation
|
|
||||||
);
|
|
||||||
|
|
||||||
if (
|
|
||||||
currentStationIndex !== -1 &&
|
|
||||||
currentStationIndex < historyData.length - 1
|
|
||||||
) {
|
|
||||||
// Chuyển sang station tiếp theo
|
|
||||||
setActiveStation(
|
|
||||||
historyData[
|
|
||||||
currentStationIndex + 1
|
|
||||||
].stationId.toString()
|
|
||||||
);
|
|
||||||
|
|
||||||
// Reset scroll to top
|
|
||||||
setTimeout(() => {
|
|
||||||
if (scrollViewportRef.current) {
|
|
||||||
scrollViewportRef.current.scrollTop = 0;
|
|
||||||
}
|
|
||||||
setTimeout(() => {
|
|
||||||
isAutoSwitchingRef.current = false;
|
|
||||||
}, 100);
|
|
||||||
}, 150);
|
|
||||||
} else {
|
|
||||||
isAutoSwitchingRef.current = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
scrollTimeoutRef.current = setTimeout(() => {
|
||||||
|
if (!scrollViewportRef.current) return;
|
||||||
|
|
||||||
|
const scrollContainer = scrollViewportRef.current;
|
||||||
|
const containerRect = scrollContainer.getBoundingClientRect();
|
||||||
|
const topThreshold = containerRect.top + 10; // Sát top với threshold 10px
|
||||||
|
|
||||||
|
// Find which station header is closest to top
|
||||||
|
// Ưu tiên: header đã vượt qua top threshold (ở trên) > header chưa đến (ở dưới)
|
||||||
|
type StationInfo = { id: number; distance: number; isAbove: boolean };
|
||||||
|
let bestStation: StationInfo | null = null;
|
||||||
|
|
||||||
|
Array.from(stationRefs.current.entries()).forEach(([stationId, element]) => {
|
||||||
|
const elementRect = element.getBoundingClientRect();
|
||||||
|
const elementTop = elementRect.top;
|
||||||
|
const elementBottom = elementRect.bottom;
|
||||||
|
|
||||||
|
// Check if station header is visible (có phần nào đó trong viewport)
|
||||||
|
const isVisible = elementBottom >= containerRect.top && elementTop <= containerRect.bottom;
|
||||||
|
|
||||||
|
if (isVisible) {
|
||||||
|
const isAbove = elementTop <= topThreshold; // Header đã vượt qua top
|
||||||
|
const distance = Math.abs(elementTop - topThreshold);
|
||||||
|
const stationIdNum = Number(stationId);
|
||||||
|
|
||||||
|
// Ưu tiên header đã vượt qua top (isAbove = true)
|
||||||
|
if (!bestStation ||
|
||||||
|
(isAbove && !bestStation.isAbove) ||
|
||||||
|
(isAbove === bestStation.isAbove && distance < bestStation.distance)) {
|
||||||
|
bestStation = { id: stationIdNum, distance, isAbove };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update active station if found
|
||||||
|
if (bestStation) {
|
||||||
|
const stationIdStr = String((bestStation as StationInfo).id);
|
||||||
|
if (stationIdStr !== activeStation) {
|
||||||
|
setActiveStation(stationIdStr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 150); // Debounce 150ms để mượt hơn
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box style={{ minHeight: "calc(75vh - 60px)" }}>
|
<Box style={{ minHeight: "calc(75vh - 60px)" }}>
|
||||||
{groupedHistory.size > 0 ? (
|
{allGroupedStations.length > 0 ? (
|
||||||
<>
|
<>
|
||||||
{Array.from(groupedHistory.entries())
|
{allGroupedStations.map((station) => (
|
||||||
.sort(([lineA], [lineB]) => lineA - lineB)
|
<Box key={`station-${station.stationId}`} style={{ marginBottom: "24px" }}>
|
||||||
.map(([lineNumber, items]) => (
|
{/* Station Title */}
|
||||||
<Box
|
<Box
|
||||||
key={`line-group-${lineNumber}`}
|
ref={(el) => {
|
||||||
|
if (el) {
|
||||||
|
stationRefs.current.set(station.stationId, el);
|
||||||
|
}
|
||||||
|
}}
|
||||||
style={{
|
style={{
|
||||||
|
padding: "12px 16px",
|
||||||
|
backgroundColor: "#495057",
|
||||||
|
color: "white",
|
||||||
|
fontWeight: 700,
|
||||||
marginBottom: "8px",
|
marginBottom: "8px",
|
||||||
border: "1px solid #dee2e6",
|
|
||||||
borderRadius: "4px",
|
borderRadius: "4px",
|
||||||
overflow: "hidden",
|
position: "sticky",
|
||||||
|
top: 0,
|
||||||
|
zIndex: 10,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<Text size="md" fw={700} c="white">
|
||||||
|
📍 {station.stationName}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Line groups for this station */}
|
||||||
|
{station.groupedHistory.size > 0 ? (
|
||||||
|
Array.from(station.groupedHistory.entries())
|
||||||
|
.sort(([lineA], [lineB]) => lineA - lineB)
|
||||||
|
.map(([lineNumber, items]) => (
|
||||||
|
<Box
|
||||||
|
key={`station-${station.stationId}-line-${lineNumber}`}
|
||||||
|
style={{
|
||||||
|
marginBottom: "8px",
|
||||||
|
border: "1px solid #dee2e6",
|
||||||
|
borderRadius: "4px",
|
||||||
|
overflow: "hidden",
|
||||||
|
}}
|
||||||
|
>
|
||||||
{/* Header của nhóm - hiển thị line number */}
|
{/* Header của nhóm - hiển thị line number */}
|
||||||
<Box
|
<Box
|
||||||
style={{
|
style={{
|
||||||
|
|
@ -449,9 +517,26 @@ function ModalHistory({
|
||||||
</span>
|
</span>
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
))}
|
))}
|
||||||
</Box>
|
</Box>
|
||||||
))}
|
))
|
||||||
|
) : (
|
||||||
|
<Box
|
||||||
|
style={{
|
||||||
|
padding: "20px",
|
||||||
|
textAlign: "center",
|
||||||
|
color: "#868e96",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text size="sm" c="dimmed">
|
||||||
|
No history data available for {TIME_PERIODS.find(
|
||||||
|
(p) => p.value === activeTimePeriod
|
||||||
|
)?.label}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
|
||||||
{/* Spacer để đảm bảo có thể scroll ngay cả khi content ít */}
|
{/* Spacer để đảm bảo có thể scroll ngay cả khi content ít */}
|
||||||
<Box style={{ height: "700px" }} />
|
<Box style={{ height: "700px" }} />
|
||||||
|
|
@ -463,7 +548,7 @@ function ModalHistory({
|
||||||
style={{ height: "calc(75vh - 80px)" }}
|
style={{ height: "calc(75vh - 80px)" }}
|
||||||
>
|
>
|
||||||
<Text c="dimmed" size="lg">
|
<Text c="dimmed" size="lg">
|
||||||
{currentStationData
|
{historyData.length > 0
|
||||||
? `No history data available for ${
|
? `No history data available for ${
|
||||||
TIME_PERIODS.find(
|
TIME_PERIODS.find(
|
||||||
(p) => p.value === activeTimePeriod
|
(p) => p.value === activeTimePeriod
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue