Refactor ModalHistory time filtering and UI

Adjusted time period logic in ModalHistory to use correct time ranges for filtering history data. Improved code formatting, grouping, and UI rendering for station history. Added validation in backend addHistory to ensure pid and sn are present before saving history items.
This commit is contained in:
nguyentrungthat 2025-11-28 15:40:33 +07:00
parent 6f8aa1d69b
commit 2ba35743fd
2 changed files with 109 additions and 65 deletions

View File

@ -696,6 +696,7 @@ export default class LineConnection {
}
async addHistory(stationId: number, lineId: number, item: HistoryItem) {
if (!item.pid || !item.sn) return
const key = `station:${stationId}:line:${lineId}:history`
const now = Date.now()

View File

@ -42,7 +42,12 @@ const TIME_PERIODS = [
{ label: "last 48h", value: "last_48h" },
];
function ModalHistory({ opened, onClose, socket, stationIds = [] }: ModalHistoryProps) {
function ModalHistory({
opened,
onClose,
socket,
stationIds = [],
}: ModalHistoryProps) {
const [historyData, setHistoryData] = useState<StationHistory[]>([]);
const [activeStation, setActiveStation] = useState<string>("");
const [activeTimePeriod, setActiveTimePeriod] = useState<string>("current");
@ -71,7 +76,7 @@ function ModalHistory({ opened, onClose, socket, stationIds = [] }: ModalHistory
// Request history when modal opens
useEffect(() => {
if (!socket || !opened) return;
// Request history with station IDs
socket.emit("get_list_history", { stationIds });
// eslint-disable-next-line react-hooks/exhaustive-deps
@ -86,7 +91,6 @@ function ModalHistory({ opened, onClose, socket, stationIds = [] }: ModalHistory
}
}, [opened]);
// Utility function to format timestamp (can be used later if needed)
// const formatTimestamp = (timestamp: number) => {
// const date = new Date(timestamp);
@ -97,23 +101,23 @@ function ModalHistory({ opened, onClose, socket, stationIds = [] }: ModalHistory
const getTimeRange = (period: string): [number, number] => {
const now = Date.now();
const HOUR = 60 * 60 * 1000;
switch (period) {
case "current":
// Current: chỉ hiện tại (4h gần nhất)
return [now - 4 * HOUR, now];
return [0, now];
case "last_4h":
// Last 4h: bao gồm current + last 4h (0-8h)
return [now - 8 * HOUR, now];
return [now - 4 * HOUR, now];
case "last_8h":
// Last 8h: bao gồm current + last 4h + last 8h (0-24h)
return [now - 24 * HOUR, now];
return [now - 8 * HOUR, now];
case "last_24h":
// Last 24h: bao gồm current + last 4h + last 8h + last 24h (0-48h)
return [now - 48 * HOUR, now];
return [now - 24 * HOUR, now];
case "last_48h":
// Last 48h: tất cả data (từ đầu đến giờ)
return [0, now];
return [now - 48 * HOUR, now];
default:
return [0, now];
}
@ -122,27 +126,29 @@ function ModalHistory({ opened, onClose, socket, stationIds = [] }: ModalHistory
// Filter history based on time period
const filterHistoryByTime = (history: HistoryItem[]): HistoryItem[] => {
const [startTime, endTime] = getTimeRange(activeTimePeriod);
return history.filter((item) =>
item.timestamp >= startTime && item.timestamp < endTime
return history.filter(
(item) => item.timestamp >= startTime && item.timestamp < endTime
);
};
// Group history items by line number
const groupHistoryByLine = (history: HistoryItem[]): Map<number, HistoryItem[]> => {
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;
};
@ -154,7 +160,7 @@ function ModalHistory({ opened, onClose, socket, stationIds = [] }: ModalHistory
const filteredHistory = currentStationData
? filterHistoryByTime(currentStationData.history)
: [];
// Group filtered history by line number
const groupedHistory = groupHistoryByLine(filteredHistory);
@ -214,8 +220,8 @@ function ModalHistory({ opened, onClose, socket, stationIds = [] }: ModalHistory
<div
style={{
padding: "20px",
overflowY: "auto",
overflowX: "hidden",
// overflowY: "auto",
// overflowX: "hidden",
flex: 1,
}}
className={classes.hideScrollBar}
@ -231,7 +237,7 @@ function ModalHistory({ opened, onClose, socket, stationIds = [] }: ModalHistory
}}
>
<Text fw={600} size="sm" mb="xs" c="dimmed">
HISTORY
List stations
</Text>
<ScrollArea h="calc(100% - 30px)">
<Flex direction="column" gap="xs">
@ -284,11 +290,15 @@ function ModalHistory({ opened, onClose, socket, stationIds = [] }: ModalHistory
backgroundColor: "white",
}}
>
<ScrollArea
<ScrollArea
h="calc(75vh - 80px)"
viewportRef={scrollViewportRef}
onScrollPositionChange={(position) => {
if (!scrollViewportRef.current || isAutoSwitchingRef.current) return;
if (
!scrollViewportRef.current ||
isAutoSwitchingRef.current
)
return;
const scrollTop = position.y;
const target = scrollViewportRef.current;
@ -296,19 +306,28 @@ function ModalHistory({ opened, onClose, socket, stationIds = [] }: ModalHistory
const clientHeight = target.clientHeight;
// Check if scrolled to bottom
const isAtBottom = Math.abs(scrollTop + clientHeight - scrollHeight) < 5;
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
(station) =>
station.stationId.toString() === activeStation
);
if (currentStationIndex !== -1 && currentStationIndex < historyData.length - 1) {
if (
currentStationIndex !== -1 &&
currentStationIndex < historyData.length - 1
) {
// Chuyển sang station tiếp theo
setActiveStation(historyData[currentStationIndex + 1].stationId.toString());
setActiveStation(
historyData[
currentStationIndex + 1
].stationId.toString()
);
// Reset scroll to top
setTimeout(() => {
if (scrollViewportRef.current) {
@ -330,52 +349,73 @@ function ModalHistory({ opened, onClose, socket, stationIds = [] }: ModalHistory
{Array.from(groupedHistory.entries())
.sort(([lineA], [lineB]) => lineA - lineB)
.map(([lineNumber, items]) => (
<Box
key={`line-group-${lineNumber}`}
style={{
marginBottom: "8px",
border: "1px solid #dee2e6",
borderRadius: "4px",
overflow: "hidden",
}}
>
{/* Header của nhóm - hiển thị line number */}
<Box
key={`line-group-${lineNumber}`}
style={{
padding: "8px 16px",
backgroundColor: "#e9ecef",
fontWeight: 600,
marginBottom: "8px",
border: "1px solid #dee2e6",
borderRadius: "4px",
overflow: "hidden",
}}
>
<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) => (
{/* Header của nhóm - hiển thị line number */}
<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",
padding: "8px 16px",
backgroundColor: "#e9ecef",
fontWeight: 600,
}}
>
<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 size="sm" fw={600}>
Line {lineNumber} ({items.length}{" "}
{items.length > 1 ? "records" : "record"})
</Text>
</Box>
))}
</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" }} />
</>
@ -383,11 +423,15 @@ function ModalHistory({ opened, onClose, socket, stationIds = [] }: ModalHistory
<Flex
align="center"
justify="center"
style={{ minHeight: "calc(75vh - 80px)" }}
style={{ height: "calc(75vh - 80px)" }}
>
<Text c="dimmed" size="lg">
{currentStationData
? `No history data available for ${TIME_PERIODS.find((p) => p.value === activeTimePeriod)?.label}`
? `No history data available for ${
TIME_PERIODS.find(
(p) => p.value === activeTimePeriod
)?.label
}`
: "No history data available"}
</Text>
</Flex>
@ -404,4 +448,3 @@ function ModalHistory({ opened, onClose, socket, stationIds = [] }: ModalHistory
}
export default ModalHistory;