Просмотр исходного кода

all select /unselect do sarch

production
CANCERYS\kw093 2 недель назад
Родитель
Сommit
c5507fa4e0
3 измененных файлов: 174 добавлений и 86 удалений
  1. +19
    -44
      src/components/DoSearch/DoSearch.tsx
  2. +139
    -0
      src/components/DoSearch/useDoSearchRowSelection.ts
  3. +16
    -42
      src/components/DoSearchWorkbench/DoSearchWorkbench.tsx

+ 19
- 44
src/components/DoSearch/DoSearch.tsx Просмотреть файл

@@ -33,10 +33,10 @@ import {
} from "react-hook-form"; } from "react-hook-form";
import { Box, Button, Paper, Stack, Tab, Tabs, TablePagination, Typography } from "@mui/material"; import { Box, Button, Paper, Stack, Tab, Tabs, TablePagination, Typography } from "@mui/material";
import StyledDataGrid from "../StyledDataGrid"; import StyledDataGrid from "../StyledDataGrid";
import { GridRowSelectionModel } from "@mui/x-data-grid";
import Swal from "sweetalert2"; import Swal from "sweetalert2";
import { useSession } from "next-auth/react"; import { useSession } from "next-auth/react";
import { SessionWithTokens } from "@/config/authConfig"; import { SessionWithTokens } from "@/config/authConfig";
import { useDoSearchRowSelection } from "./useDoSearchRowSelection";


type Props = { type Props = {
filterArgs?: Record<string, any>; filterArgs?: Record<string, any>;
@@ -79,8 +79,6 @@ const DoSearch: React.FC<Props> = ({ filterArgs, searchQuery, onDeliveryOrderSea
//console.log("🔍 DoSearch - session:", session); //console.log("🔍 DoSearch - session:", session);
//console.log("🔍 DoSearch - currentUserId:", currentUserId); //console.log("🔍 DoSearch - currentUserId:", currentUserId);
const [searchTimeout, setSearchTimeout] = useState<NodeJS.Timeout | null>(null); const [searchTimeout, setSearchTimeout] = useState<NodeJS.Timeout | null>(null);
/** 使用者明確取消勾選的送貨單 id;未在此集合中的搜索結果視為「已選」以便跨頁記憶 */
const [excludedRowIds, setExcludedRowIds] = useState<number[]>([]);


const [searchAllDos, setSearchAllDos] = useState<DoSearchAll[]>([]); const [searchAllDos, setSearchAllDos] = useState<DoSearchAll[]>([]);
const [totalCount, setTotalCount] = useState(0); const [totalCount, setTotalCount] = useState(0);
@@ -135,36 +133,12 @@ const DoSearch: React.FC<Props> = ({ filterArgs, searchQuery, onDeliveryOrderSea
const [hasSearched, setHasSearched] = useState(false); const [hasSearched, setHasSearched] = useState(false);
const [hasResults, setHasResults] = useState(false); const [hasResults, setHasResults] = useState(false);


const excludedIdSet = useMemo(() => new Set(excludedRowIds), [excludedRowIds]);

const rowSelectionModel = useMemo<GridRowSelectionModel>(() => {
return searchAllDos
.map((r) => r.id)
.filter((id) => !excludedIdSet.has(id));
}, [searchAllDos, excludedIdSet]);

const applyRowSelectionChange = useCallback(
(newModel: GridRowSelectionModel) => {
const pageIds = searchAllDos.map((r) => r.id);
const selectedSet = new Set(
newModel.map((id) => (typeof id === "string" ? Number(id) : id)),
);
setExcludedRowIds((prev) => {
const next = new Set(prev);
for (const id of pageIds) {
next.delete(id);
}
for (const id of pageIds) {
if (!selectedSet.has(id)) {
next.add(id);
}
}
return Array.from(next);
});
setValue("ids", newModel);
},
[searchAllDos, setValue],
);
const {
rowSelectionModel,
applyRowSelectionChange,
resetSelection,
resolveIdsForBatchRelease,
} = useDoSearchRowSelection(searchAllDos, setValue);


// 当搜索条件变化时,重置到第一页 // 当搜索条件变化时,重置到第一页
useEffect(() => { useEffect(() => {
@@ -212,7 +186,7 @@ const DoSearch: React.FC<Props> = ({ filterArgs, searchQuery, onDeliveryOrderSea
setTotalCount(0); setTotalCount(0);
setHasSearched(false); setHasSearched(false);
setHasResults(false); setHasResults(false);
setExcludedRowIds([]);
resetSelection();
setPagingController({ pageNum: 1, pageSize: 10 }); setPagingController({ pageNum: 1, pageSize: 10 });
} }
catch (error) { catch (error) {
@@ -220,7 +194,7 @@ const DoSearch: React.FC<Props> = ({ filterArgs, searchQuery, onDeliveryOrderSea
setSearchAllDos([]); setSearchAllDos([]);
setTotalCount(0); setTotalCount(0);
} }
}, []);
}, [resetSelection]);


const onDetailClick = useCallback( const onDetailClick = useCallback(
(doResult: DoResult) => { (doResult: DoResult) => {
@@ -400,11 +374,11 @@ const DoSearch: React.FC<Props> = ({ filterArgs, searchQuery, onDeliveryOrderSea
setHasResults(response.records.length > 0); setHasResults(response.records.length > 0);
} }
if (options?.resetExcludedRows ?? false) { if (options?.resetExcludedRows ?? false) {
setExcludedRowIds([]);
resetSelection();
} }
return true; return true;
}, },
[activeTab, resolveTabFilter, t],
[activeTab, resolveTabFilter, t, resetSelection],
); );


//SEARCH FUNCTION //SEARCH FUNCTION
@@ -422,10 +396,10 @@ const DoSearch: React.FC<Props> = ({ filterArgs, searchQuery, onDeliveryOrderSea
setTotalCount(0); setTotalCount(0);
setHasSearched(true); setHasSearched(true);
setHasResults(false); setHasResults(false);
setExcludedRowIds([]);
resetSelection();
} }
}, },
[pagingController.pageNum, pagingController.pageSize, performSearch],
[pagingController.pageNum, pagingController.pageSize, performSearch, resetSelection],
); );


useEffect(() => { useEffect(() => {
@@ -584,9 +558,9 @@ const DoSearch: React.FC<Props> = ({ filterArgs, searchQuery, onDeliveryOrderSea
return; return;
} }


const idsToRelease = allMatchingDos
.map((d) => d.id)
.filter((id) => !excludedIdSet.has(id));
const idsToRelease = resolveIdsForBatchRelease(
allMatchingDos.map((d) => d.id),
);


if (idsToRelease.length === 0) { if (idsToRelease.length === 0) {
await Swal.fire({ await Swal.fire({
@@ -705,7 +679,7 @@ const DoSearch: React.FC<Props> = ({ filterArgs, searchQuery, onDeliveryOrderSea
confirmButtonText: t("OK") confirmButtonText: t("OK")
}); });
} }
}, [t, currentUserId, currentSearchParams, handleSearch, excludedIdSet, activeTab, resolveTabFilter]);
}, [t, currentUserId, currentSearchParams, handleSearch, resolveIdsForBatchRelease, activeTab, resolveTabFilter]);


const handleTabChange = useCallback( const handleTabChange = useCallback(
(_: React.SyntheticEvent, nextTab: DoSearchTab) => { (_: React.SyntheticEvent, nextTab: DoSearchTab) => {
@@ -715,7 +689,7 @@ const DoSearch: React.FC<Props> = ({ filterArgs, searchQuery, onDeliveryOrderSea
setCurrentSearchParams(nextSearchParams); setCurrentSearchParams(nextSearchParams);
setSearchBoxResetKey((prev) => prev + 1); setSearchBoxResetKey((prev) => prev + 1);
setPagingController((prev) => ({ ...prev, pageNum: 1 })); setPagingController((prev) => ({ ...prev, pageNum: 1 }));
setExcludedRowIds([]);
resetSelection();
// 切換 tab 僅重置搜索條件與結果;由使用者再次按「搜索」後才查詢。 // 切換 tab 僅重置搜索條件與結果;由使用者再次按「搜索」後才查詢。
setSearchAllDos([]); setSearchAllDos([]);
setTotalCount(0); setTotalCount(0);
@@ -726,6 +700,7 @@ const DoSearch: React.FC<Props> = ({ filterArgs, searchQuery, onDeliveryOrderSea
activeTab, activeTab,
currentSearchParams, currentSearchParams,
createClearedSearchParams, createClearedSearchParams,
resetSelection,
], ],
); );




+ 139
- 0
src/components/DoSearch/useDoSearchRowSelection.ts Просмотреть файл

@@ -0,0 +1,139 @@
import { useCallback, useMemo, useState } from "react";
import { GridRowSelectionModel } from "@mui/x-data-grid";

/** all = 搜索結果默認全選(排除列表 opt-out);none = 全不選;include = 從全不選起逐項勾選;exclude = 從全選起逐項取消 */
export type DoSelectionMode = "all" | "none" | "include" | "exclude";

type RowWithId = { id: number };

export function useDoSearchRowSelection(
searchAllDos: RowWithId[],
setValue: (name: "ids", value: GridRowSelectionModel) => void,
) {
const [selectionMode, setSelectionMode] = useState<DoSelectionMode>("all");
const [excludedRowIds, setExcludedRowIds] = useState<number[]>([]);
const [includedRowIds, setIncludedRowIds] = useState<number[]>([]);

const resetSelection = useCallback(() => {
setSelectionMode("all");
setExcludedRowIds([]);
setIncludedRowIds([]);
}, []);

const excludedIdSet = useMemo(() => new Set(excludedRowIds), [excludedRowIds]);
const includedIdSet = useMemo(() => new Set(includedRowIds), [includedRowIds]);

const rowSelectionModel = useMemo<GridRowSelectionModel>(() => {
const pageIds = searchAllDos.map((r) => r.id);
if (selectionMode === "none") {
return [];
}
if (selectionMode === "include") {
return pageIds.filter((id) => includedIdSet.has(id));
}
return pageIds.filter((id) => !excludedIdSet.has(id));
}, [selectionMode, searchAllDos, excludedIdSet, includedIdSet]);

const applyRowSelectionChange = useCallback(
(newModel: GridRowSelectionModel) => {
const pageIds = searchAllDos.map((r) => r.id);
const selectedSet = new Set(
newModel.map((id) => (typeof id === "string" ? Number(id) : id)),
);

const allPageWasSelected =
selectionMode !== "none" &&
selectionMode !== "include" &&
pageIds.length > 0 &&
pageIds.every((id) => !excludedIdSet.has(id));

const allPageWasSelectedInIncludeMode =
selectionMode === "include" &&
pageIds.length > 0 &&
pageIds.every((id) => includedIdSet.has(id));

const allPageNowSelected =
pageIds.length > 0 && pageIds.every((id) => selectedSet.has(id));

if (
newModel.length === 0 &&
(allPageWasSelected || allPageWasSelectedInIncludeMode)
) {
setSelectionMode("none");
setExcludedRowIds([]);
setIncludedRowIds([]);
setValue("ids", []);
return;
}

if (allPageNowSelected) {
setSelectionMode("all");
setExcludedRowIds([]);
setIncludedRowIds([]);
setValue("ids", newModel);
return;
}

if (selectionMode === "all" || selectionMode === "exclude") {
setSelectionMode("exclude");
setExcludedRowIds((prev) => {
const next = new Set(prev);
for (const id of pageIds) {
next.delete(id);
}
for (const id of pageIds) {
if (!selectedSet.has(id)) {
next.add(id);
}
}
return Array.from(next);
});
} else {
setSelectionMode("include");
setIncludedRowIds((prev) => {
const next = new Set(prev);
for (const id of pageIds) {
next.delete(id);
}
for (const id of pageIds) {
if (selectedSet.has(id)) {
next.add(id);
}
}
return Array.from(next);
});
}
setValue("ids", newModel);
},
[
searchAllDos,
setValue,
selectionMode,
excludedIdSet,
includedIdSet,
],
);

const resolveIdsForBatchRelease = useCallback(
(allMatchingIds: number[]) => {
if (selectionMode === "none") {
return [];
}
if (selectionMode === "include") {
return allMatchingIds.filter((id) => includedIdSet.has(id));
}
return allMatchingIds.filter((id) => !excludedIdSet.has(id));
},
[selectionMode, excludedIdSet, includedIdSet],
);

return {
selectionMode,
excludedRowIds,
excludedIdSet,
rowSelectionModel,
applyRowSelectionChange,
resetSelection,
resolveIdsForBatchRelease,
};
}

+ 16
- 42
src/components/DoSearchWorkbench/DoSearchWorkbench.tsx Просмотреть файл

@@ -33,10 +33,10 @@ import {
} from "react-hook-form"; } from "react-hook-form";
import { Box, Button, Paper, Stack, Typography, TablePagination } from "@mui/material"; import { Box, Button, Paper, Stack, Typography, TablePagination } from "@mui/material";
import StyledDataGrid from "../StyledDataGrid"; import StyledDataGrid from "../StyledDataGrid";
import { GridRowSelectionModel } from "@mui/x-data-grid";
import Swal from "sweetalert2"; import Swal from "sweetalert2";
import { useSession } from "next-auth/react"; import { useSession } from "next-auth/react";
import { SessionWithTokens } from "@/config/authConfig"; import { SessionWithTokens } from "@/config/authConfig";
import { useDoSearchRowSelection } from "../DoSearch/useDoSearchRowSelection";


type Props = { type Props = {
filterArgs?: Record<string, any>; filterArgs?: Record<string, any>;
@@ -83,8 +83,6 @@ const DoSearchWorkbench: React.FC<Props> = ({
//console.log("🔍 DoSearch - session:", session); //console.log("🔍 DoSearch - session:", session);
//console.log("🔍 DoSearch - currentUserId:", currentUserId); //console.log("🔍 DoSearch - currentUserId:", currentUserId);
const [searchTimeout, setSearchTimeout] = useState<NodeJS.Timeout | null>(null); const [searchTimeout, setSearchTimeout] = useState<NodeJS.Timeout | null>(null);
/** 使用者明確取消勾選的送貨單 id;未在此集合中的搜索結果視為「已選」以便跨頁記憶 */
const [excludedRowIds, setExcludedRowIds] = useState<number[]>([]);


const [searchAllDos, setSearchAllDos] = useState<DoSearchAll[]>([]); const [searchAllDos, setSearchAllDos] = useState<DoSearchAll[]>([]);
const [totalCount, setTotalCount] = useState(0); const [totalCount, setTotalCount] = useState(0);
@@ -118,36 +116,12 @@ const DoSearchWorkbench: React.FC<Props> = ({
const [hasSearched, setHasSearched] = useState(false); const [hasSearched, setHasSearched] = useState(false);
const [hasResults, setHasResults] = useState(false); const [hasResults, setHasResults] = useState(false);


const excludedIdSet = useMemo(() => new Set(excludedRowIds), [excludedRowIds]);

const rowSelectionModel = useMemo<GridRowSelectionModel>(() => {
return searchAllDos
.map((r) => r.id)
.filter((id) => !excludedIdSet.has(id));
}, [searchAllDos, excludedIdSet]);

const applyRowSelectionChange = useCallback(
(newModel: GridRowSelectionModel) => {
const pageIds = searchAllDos.map((r) => r.id);
const selectedSet = new Set(
newModel.map((id) => (typeof id === "string" ? Number(id) : id)),
);
setExcludedRowIds((prev) => {
const next = new Set(prev);
for (const id of pageIds) {
next.delete(id);
}
for (const id of pageIds) {
if (!selectedSet.has(id)) {
next.add(id);
}
}
return Array.from(next);
});
setValue("ids", newModel);
},
[searchAllDos, setValue],
);
const {
rowSelectionModel,
applyRowSelectionChange,
resetSelection,
resolveIdsForBatchRelease,
} = useDoSearchRowSelection(searchAllDos, setValue);


// 当搜索条件变化时,重置到第一页 // 当搜索条件变化时,重置到第一页
useEffect(() => { useEffect(() => {
@@ -204,7 +178,7 @@ const DoSearchWorkbench: React.FC<Props> = ({
setTotalCount(0); setTotalCount(0);
setHasSearched(false); setHasSearched(false);
setHasResults(false); setHasResults(false);
setExcludedRowIds([]);
resetSelection();
setPagingController({ pageNum: 1, pageSize: 10 }); setPagingController({ pageNum: 1, pageSize: 10 });
} }
catch (error) { catch (error) {
@@ -212,7 +186,7 @@ const DoSearchWorkbench: React.FC<Props> = ({
setSearchAllDos([]); setSearchAllDos([]);
setTotalCount(0); setTotalCount(0);
} }
}, []);
}, [resetSelection]);


const onDetailClick = useCallback( const onDetailClick = useCallback(
(doResult: DoResult) => { (doResult: DoResult) => {
@@ -367,7 +341,7 @@ const handleSearch = useCallback(async (query: SearchBoxInputs) => {
setTotalCount(response.total); // 设置总记录数 setTotalCount(response.total); // 设置总记录数
setHasSearched(true); setHasSearched(true);
setHasResults(response.records.length > 0); setHasResults(response.records.length > 0);
setExcludedRowIds([]);
resetSelection();


} catch (error) { } catch (error) {
console.error("Error: ", error); console.error("Error: ", error);
@@ -375,9 +349,9 @@ const handleSearch = useCallback(async (query: SearchBoxInputs) => {
setTotalCount(0); setTotalCount(0);
setHasSearched(true); setHasSearched(true);
setHasResults(false); setHasResults(false);
setExcludedRowIds([]);
resetSelection();
} }
}, [pagingController, t]);
}, [pagingController, t, resetSelection]);


useEffect(() => { useEffect(() => {
if (typeof window !== 'undefined') { if (typeof window !== 'undefined') {
@@ -632,9 +606,9 @@ const handleSearch = useCallback(async (query: SearchBoxInputs) => {
return; return;
} }


const idsToRelease = allMatchingDos
.map((d) => d.id)
.filter((id) => !excludedIdSet.has(id));
const idsToRelease = resolveIdsForBatchRelease(
allMatchingDos.map((d) => d.id),
);


if (idsToRelease.length === 0) { if (idsToRelease.length === 0) {
await Swal.fire({ await Swal.fire({
@@ -749,7 +723,7 @@ const handleSearch = useCallback(async (query: SearchBoxInputs) => {
confirmButtonText: t("OK") confirmButtonText: t("OK")
}); });
} }
}, [t, currentUserId, currentSearchParams, handleSearch, excludedIdSet]);
}, [t, currentUserId, currentSearchParams, handleSearch, resolveIdsForBatchRelease]);


return ( return (
<> <>


Загрузка…
Отмена
Сохранить