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 [activeTimePeriod, setActiveTimePeriod] = useState<string>("current");
|
||||
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(() => {
|
||||
if (!socket || !opened) return;
|
||||
|
|
@ -89,15 +90,32 @@ function ModalHistory({
|
|||
setHistoryData([]);
|
||||
setActiveStation("");
|
||||
setActiveTimePeriod("current");
|
||||
stationRefs.current.clear();
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (scrollTimeoutRef.current) {
|
||||
clearTimeout(scrollTimeoutRef.current);
|
||||
}
|
||||
};
|
||||
}, [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(() => {
|
||||
if (scrollViewportRef?.current) {
|
||||
scrollViewportRef.current.scrollTop = 0;
|
||||
if (activeStation && isManualScrollRef.current) {
|
||||
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)
|
||||
// const formatTimestamp = (timestamp: number) => {
|
||||
|
|
@ -163,17 +181,35 @@ function ModalHistory({
|
|||
return grouped;
|
||||
};
|
||||
|
||||
const currentStationData = historyData.find(
|
||||
(station) => station.stationId.toString() === activeStation
|
||||
);
|
||||
// Apply time filter to ALL stations data (không filter bỏ stations không có data)
|
||||
const allFilteredStations = historyData.map((station) => ({
|
||||
...station,
|
||||
filteredHistory: filterHistoryByTime(station.history),
|
||||
}));
|
||||
|
||||
// Apply time filter to current station data
|
||||
const filteredHistory = currentStationData
|
||||
? filterHistoryByTime(currentStationData.history)
|
||||
: [];
|
||||
// Group each station's history by line number
|
||||
const allGroupedStations = allFilteredStations.map((station) => ({
|
||||
...station,
|
||||
groupedHistory: station.filteredHistory.length > 0
|
||||
? groupHistoryByLine(station.filteredHistory)
|
||||
: new Map<number, HistoryItem[]>(),
|
||||
}));
|
||||
|
||||
// Group filtered history by line number
|
||||
const groupedHistory = groupHistoryByLine(filteredHistory);
|
||||
// Function to scroll to a specific station
|
||||
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;
|
||||
|
||||
|
|
@ -262,9 +298,11 @@ function ModalHistory({
|
|||
? "filled"
|
||||
: "outline"
|
||||
}
|
||||
onClick={() =>
|
||||
setActiveStation(station.stationId.toString())
|
||||
}
|
||||
onClick={() => {
|
||||
isManualScrollRef.current = true;
|
||||
setActiveStation(station.stationId.toString());
|
||||
scrollToStation(station.stationId);
|
||||
}}
|
||||
>
|
||||
{station.stationName}
|
||||
</Button>
|
||||
|
|
@ -304,71 +342,101 @@ function ModalHistory({
|
|||
<ScrollArea
|
||||
h="calc(75vh - 80px)"
|
||||
viewportRef={scrollViewportRef}
|
||||
onScrollPositionChange={(position) => {
|
||||
if (
|
||||
!scrollViewportRef.current ||
|
||||
isAutoSwitchingRef.current
|
||||
)
|
||||
return;
|
||||
onScrollPositionChange={() => {
|
||||
if (isManualScrollRef.current || !scrollViewportRef.current) return;
|
||||
|
||||
const scrollTop = position.y;
|
||||
const target = scrollViewportRef.current;
|
||||
const scrollHeight = target.scrollHeight;
|
||||
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;
|
||||
}
|
||||
// Debounce để scroll mượt hơn
|
||||
if (scrollTimeoutRef.current) {
|
||||
clearTimeout(scrollTimeoutRef.current);
|
||||
}
|
||||
|
||||
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)" }}>
|
||||
{groupedHistory.size > 0 ? (
|
||||
{allGroupedStations.length > 0 ? (
|
||||
<>
|
||||
{Array.from(groupedHistory.entries())
|
||||
.sort(([lineA], [lineB]) => lineA - lineB)
|
||||
.map(([lineNumber, items]) => (
|
||||
{allGroupedStations.map((station) => (
|
||||
<Box key={`station-${station.stationId}`} style={{ marginBottom: "24px" }}>
|
||||
{/* Station Title */}
|
||||
<Box
|
||||
key={`line-group-${lineNumber}`}
|
||||
ref={(el) => {
|
||||
if (el) {
|
||||
stationRefs.current.set(station.stationId, el);
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
padding: "12px 16px",
|
||||
backgroundColor: "#495057",
|
||||
color: "white",
|
||||
fontWeight: 700,
|
||||
marginBottom: "8px",
|
||||
border: "1px solid #dee2e6",
|
||||
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 */}
|
||||
<Box
|
||||
style={{
|
||||
|
|
@ -449,9 +517,26 @@ function ModalHistory({
|
|||
</span>
|
||||
</Text>
|
||||
</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 */}
|
||||
<Box style={{ height: "700px" }} />
|
||||
|
|
@ -463,7 +548,7 @@ function ModalHistory({
|
|||
style={{ height: "calc(75vh - 80px)" }}
|
||||
>
|
||||
<Text c="dimmed" size="lg">
|
||||
{currentStationData
|
||||
{historyData.length > 0
|
||||
? `No history data available for ${
|
||||
TIME_PERIODS.find(
|
||||
(p) => p.value === activeTimePeriod
|
||||
|
|
|
|||
Loading…
Reference in New Issue