fix(analysis): preserve tab panel state
This commit is contained in:
@@ -19,27 +19,63 @@ import "dayjs/locale/zh-cn";
|
||||
import { useNotification } from "@refinedev/core";
|
||||
import { api } from "@/lib/api";
|
||||
import { NETWORK_NAME, config } from "@config/config";
|
||||
import { useControllableObjectState } from "@components/olmap/core/useControllableState";
|
||||
import { LeakageResultDetail } from "./types";
|
||||
import { FLOW_DISPLAY_UNIT, toM3s } from "@utils/units";
|
||||
|
||||
interface Props {
|
||||
onResult: (result: LeakageResultDetail) => void;
|
||||
state?: DMALeakAnalysisParametersState;
|
||||
onStateChange?: (state: DMALeakAnalysisParametersState) => void;
|
||||
}
|
||||
|
||||
const AnalysisParameters: React.FC<Props> = ({ onResult }) => {
|
||||
export interface DMALeakAnalysisParametersState {
|
||||
schemeName: string;
|
||||
dmaCount: number;
|
||||
startTime: Dayjs | null;
|
||||
endTime: Dayjs | null;
|
||||
popSize: number;
|
||||
maxGen: number;
|
||||
qSum: number;
|
||||
advancedOpen: boolean;
|
||||
}
|
||||
|
||||
export const createDMALeakAnalysisParametersState =
|
||||
(): DMALeakAnalysisParametersState => ({
|
||||
schemeName: `DMA_Leak_${Date.now()}`,
|
||||
dmaCount: 5,
|
||||
startTime: dayjs().subtract(2, "hour"),
|
||||
endTime: dayjs(),
|
||||
popSize: 10,
|
||||
maxGen: 50,
|
||||
qSum: 1440,
|
||||
advancedOpen: false,
|
||||
});
|
||||
|
||||
const AnalysisParameters: React.FC<Props> = ({
|
||||
onResult,
|
||||
state,
|
||||
onStateChange,
|
||||
}) => {
|
||||
const { open } = useNotification();
|
||||
const [schemeName, setSchemeName] = useState(`DMA_Leak_${Date.now()}`);
|
||||
const [dmaCount, setDmaCount] = useState<number>(5);
|
||||
const [startTime, setStartTime] = useState<Dayjs | null>(
|
||||
dayjs().subtract(2, "hour"),
|
||||
const [parametersState, , setFormField] = useControllableObjectState(
|
||||
state,
|
||||
onStateChange,
|
||||
createDMALeakAnalysisParametersState(),
|
||||
);
|
||||
const [endTime, setEndTime] = useState<Dayjs | null>(dayjs());
|
||||
const [popSize, setPopSize] = useState<number>(10);
|
||||
const [maxGen, setMaxGen] = useState<number>(50);
|
||||
const [qSum, setQSum] = useState<number>(1440);
|
||||
const [advancedOpen, setAdvancedOpen] = useState(false);
|
||||
const {
|
||||
schemeName,
|
||||
dmaCount,
|
||||
startTime,
|
||||
endTime,
|
||||
popSize,
|
||||
maxGen,
|
||||
qSum,
|
||||
advancedOpen,
|
||||
} = parametersState;
|
||||
const [running, setRunning] = useState(false);
|
||||
|
||||
|
||||
const isValid = useMemo(() => {
|
||||
if (!schemeName.trim() || !startTime || !endTime) return false;
|
||||
return startTime.isBefore(endTime) && qSum >= 360;
|
||||
@@ -105,7 +141,7 @@ const AnalysisParameters: React.FC<Props> = ({ onResult }) => {
|
||||
</Typography>
|
||||
<TextField
|
||||
value={schemeName}
|
||||
onChange={(e) => setSchemeName(e.target.value)}
|
||||
onChange={(e) => setFormField("schemeName", e.target.value)}
|
||||
placeholder="请输入方案名称"
|
||||
fullWidth
|
||||
size="small"
|
||||
@@ -123,11 +159,11 @@ const AnalysisParameters: React.FC<Props> = ({ onResult }) => {
|
||||
const value = Number.parseInt(e.target.value, 10);
|
||||
// Limit between 3 and 10
|
||||
if (Number.isNaN(value)) {
|
||||
setDmaCount(5);
|
||||
setFormField("dmaCount", 5);
|
||||
} else if (value > 10) {
|
||||
setDmaCount(10);
|
||||
setFormField("dmaCount", 10);
|
||||
} else {
|
||||
setDmaCount(Math.max(3, value));
|
||||
setFormField("dmaCount", Math.max(3, value));
|
||||
}
|
||||
}}
|
||||
fullWidth
|
||||
@@ -150,7 +186,7 @@ const AnalysisParameters: React.FC<Props> = ({ onResult }) => {
|
||||
</Typography>
|
||||
<DateTimePicker
|
||||
value={startTime}
|
||||
onChange={setStartTime}
|
||||
onChange={(value) => setFormField("startTime", value)}
|
||||
maxDateTime={endTime ?? undefined}
|
||||
format="YYYY-MM-DD HH:mm"
|
||||
slotProps={{ textField: { size: "small", fullWidth: true } }}
|
||||
@@ -162,7 +198,7 @@ const AnalysisParameters: React.FC<Props> = ({ onResult }) => {
|
||||
</Typography>
|
||||
<DateTimePicker
|
||||
value={endTime}
|
||||
onChange={setEndTime}
|
||||
onChange={(value) => setFormField("endTime", value)}
|
||||
minDateTime={startTime ?? undefined}
|
||||
format="YYYY-MM-DD HH:mm"
|
||||
slotProps={{ textField: { size: "small", fullWidth: true } }}
|
||||
@@ -180,7 +216,7 @@ const AnalysisParameters: React.FC<Props> = ({ onResult }) => {
|
||||
value={qSum}
|
||||
onChange={(e) => {
|
||||
const value = Number(e.target.value);
|
||||
setQSum(Number.isNaN(value) ? 1440 : Math.max(360, value));
|
||||
setFormField("qSum", Number.isNaN(value) ? 1440 : Math.max(360, value));
|
||||
}}
|
||||
inputProps={{ min: 360, step: 10 }}
|
||||
/>
|
||||
@@ -195,9 +231,9 @@ const AnalysisParameters: React.FC<Props> = ({ onResult }) => {
|
||||
<Box
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={() => setAdvancedOpen((prev) => !prev)}
|
||||
onClick={() => setFormField("advancedOpen", !advancedOpen)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") setAdvancedOpen((prev) => !prev);
|
||||
if (e.key === "Enter" || e.key === " ") setFormField("advancedOpen", !advancedOpen);
|
||||
}}
|
||||
sx={{
|
||||
display: "flex",
|
||||
@@ -235,14 +271,14 @@ const AnalysisParameters: React.FC<Props> = ({ onResult }) => {
|
||||
label="种群规模"
|
||||
size="small"
|
||||
value={popSize}
|
||||
onChange={(e) => setPopSize(Number(e.target.value))}
|
||||
onChange={(e) => setFormField("popSize", Number(e.target.value))}
|
||||
/>
|
||||
<TextField
|
||||
type="number"
|
||||
label="最大代数"
|
||||
size="small"
|
||||
value={maxGen}
|
||||
onChange={(e) => setMaxGen(Number(e.target.value))}
|
||||
onChange={(e) => setFormField("maxGen", Number(e.target.value))}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
@@ -19,8 +19,14 @@ import {
|
||||
} from "@mui/icons-material";
|
||||
import { useMap } from "@components/olmap/core/MapComponent";
|
||||
import StyleLegend from "@components/olmap/core/Controls/StyleLegend";
|
||||
import AnalysisParameters from "./AnalysisParameters";
|
||||
import SchemeQuery from "./SchemeQuery";
|
||||
import AnalysisParameters, {
|
||||
createDMALeakAnalysisParametersState,
|
||||
type DMALeakAnalysisParametersState,
|
||||
} from "./AnalysisParameters";
|
||||
import SchemeQuery, {
|
||||
createDMALeakSchemeQueryState,
|
||||
type DMALeakSchemeQueryState,
|
||||
} from "./SchemeQuery";
|
||||
import RecognitionResults from "./RecognitionResults";
|
||||
import { applyJunctionAreaRender } from "./applyJunctionAreaRender";
|
||||
import { getAreaColor } from "./utils";
|
||||
@@ -49,6 +55,13 @@ const DMALeakDetectionPanel: React.FC = () => {
|
||||
const [result, setResult] = useState<LeakageResultDetail | null>(null);
|
||||
const [loadedResult, setLoadedResult] = useState<LeakageResultDetail | null>(null);
|
||||
const [schemes, setSchemes] = useState<LeakageSchemeRecord[]>([]);
|
||||
const [analysisState, setAnalysisState] =
|
||||
useState<DMALeakAnalysisParametersState>(
|
||||
createDMALeakAnalysisParametersState,
|
||||
);
|
||||
const [queryState, setQueryState] = useState<DMALeakSchemeQueryState>(
|
||||
createDMALeakSchemeQueryState,
|
||||
);
|
||||
|
||||
const drawerWidth = 450;
|
||||
const panelTitle = "DMA 漏损识别";
|
||||
@@ -196,10 +209,20 @@ const DMALeakDetectionPanel: React.FC = () => {
|
||||
</Tabs>
|
||||
</Box>
|
||||
<TabPanel value={tab} index={0}>
|
||||
<AnalysisParameters onResult={handleAnalysisResult} />
|
||||
<AnalysisParameters
|
||||
onResult={handleAnalysisResult}
|
||||
state={analysisState}
|
||||
onStateChange={setAnalysisState}
|
||||
/>
|
||||
</TabPanel>
|
||||
<TabPanel value={tab} index={1}>
|
||||
<SchemeQuery onViewResult={handleViewResult} schemes={schemes} onSchemesChange={setSchemes} />
|
||||
<SchemeQuery
|
||||
onViewResult={handleViewResult}
|
||||
schemes={schemes}
|
||||
onSchemesChange={setSchemes}
|
||||
state={queryState}
|
||||
onStateChange={setQueryState}
|
||||
/>
|
||||
</TabPanel>
|
||||
<TabPanel value={tab} index={2}>
|
||||
<RecognitionResults result={result} />
|
||||
|
||||
@@ -23,6 +23,7 @@ import dayjs, { Dayjs } from "dayjs";
|
||||
import { useNotification } from "@refinedev/core";
|
||||
import { api } from "@/lib/api";
|
||||
import { NETWORK_NAME, config } from "@config/config";
|
||||
import { useControllableObjectState } from "@components/olmap/core/useControllableState";
|
||||
import { LeakageResultDetail, LeakageSchemeRecord } from "./types";
|
||||
import { FLOW_DISPLAY_UNIT, toM3h } from "@utils/units";
|
||||
|
||||
@@ -30,15 +31,38 @@ interface Props {
|
||||
onViewResult: (result: LeakageResultDetail) => void;
|
||||
schemes?: LeakageSchemeRecord[];
|
||||
onSchemesChange?: (schemes: LeakageSchemeRecord[]) => void;
|
||||
state?: DMALeakSchemeQueryState;
|
||||
onStateChange?: (state: DMALeakSchemeQueryState) => void;
|
||||
}
|
||||
|
||||
const SchemeQuery: React.FC<Props> = ({ onViewResult, schemes: externalSchemes, onSchemesChange }) => {
|
||||
export interface DMALeakSchemeQueryState {
|
||||
queryAll: boolean;
|
||||
queryDate: Dayjs | null;
|
||||
expandedId: number | null;
|
||||
}
|
||||
|
||||
export const createDMALeakSchemeQueryState = (): DMALeakSchemeQueryState => ({
|
||||
queryAll: true,
|
||||
queryDate: dayjs(),
|
||||
expandedId: null,
|
||||
});
|
||||
|
||||
const SchemeQuery: React.FC<Props> = ({
|
||||
onViewResult,
|
||||
schemes: externalSchemes,
|
||||
onSchemesChange,
|
||||
state,
|
||||
onStateChange,
|
||||
}) => {
|
||||
const { open } = useNotification();
|
||||
const [queryAll, setQueryAll] = useState(true);
|
||||
const [queryDate, setQueryDate] = useState<Dayjs | null>(dayjs());
|
||||
const [queryState, , setQueryField] = useControllableObjectState(
|
||||
state,
|
||||
onStateChange,
|
||||
createDMALeakSchemeQueryState(),
|
||||
);
|
||||
const { queryAll, queryDate, expandedId } = queryState;
|
||||
const [internalSchemes, setInternalSchemes] = useState<LeakageSchemeRecord[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [expandedId, setExpandedId] = useState<number | null>(null);
|
||||
const schemes = externalSchemes !== undefined ? externalSchemes : internalSchemes;
|
||||
const setSchemes = onSchemesChange || setInternalSchemes;
|
||||
|
||||
@@ -91,7 +115,7 @@ const SchemeQuery: React.FC<Props> = ({ onViewResult, schemes: externalSchemes,
|
||||
<Checkbox
|
||||
size="small"
|
||||
checked={queryAll}
|
||||
onChange={(e) => setQueryAll(e.target.checked)}
|
||||
onChange={(e) => setQueryField("queryAll", e.target.checked)}
|
||||
/>
|
||||
}
|
||||
label={<Typography variant="body2">查询全部</Typography>}
|
||||
@@ -100,7 +124,7 @@ const SchemeQuery: React.FC<Props> = ({ onViewResult, schemes: externalSchemes,
|
||||
<LocalizationProvider dateAdapter={AdapterDayjs} adapterLocale="zh-cn">
|
||||
<DatePicker
|
||||
value={queryDate}
|
||||
onChange={setQueryDate}
|
||||
onChange={(value) => setQueryField("queryDate", value)}
|
||||
disabled={queryAll}
|
||||
format="YYYY-MM-DD"
|
||||
slotProps={{ textField: { size: "small", sx: { width: 200 } } }}
|
||||
@@ -178,7 +202,12 @@ const SchemeQuery: React.FC<Props> = ({ onViewResult, schemes: externalSchemes,
|
||||
<Tooltip title={expandedId === scheme.scheme_id ? "收起详情" : "查看详情"}>
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={() => setExpandedId(expandedId === scheme.scheme_id ? null : scheme.scheme_id)}
|
||||
onClick={() =>
|
||||
setQueryField(
|
||||
"expandedId",
|
||||
expandedId === scheme.scheme_id ? null : scheme.scheme_id,
|
||||
)
|
||||
}
|
||||
color="primary"
|
||||
className="p-1"
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user