Browse Source

all select /unselect do sarch

production
CANCERYS\kw093 2 weeks ago
parent
commit
c5507fa4e0
3 changed files with 174 additions and 86 deletions
  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 View File

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

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

const [searchAllDos, setSearchAllDos] = useState<DoSearchAll[]>([]);
const [totalCount, setTotalCount] = useState(0);
@@ -135,36 +133,12 @@ const DoSearch: React.FC<Props> = ({ filterArgs, searchQuery, onDeliveryOrderSea
const [hasSearched, setHasSearched] = 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(() => {
@@ -212,7 +186,7 @@ const DoSearch: React.FC<Props> = ({ filterArgs, searchQuery, onDeliveryOrderSea
setTotalCount(0);
setHasSearched(false);
setHasResults(false);
setExcludedRowIds([]);
resetSelection();
setPagingController({ pageNum: 1, pageSize: 10 });
}
catch (error) {
@@ -220,7 +194,7 @@ const DoSearch: React.FC<Props> = ({ filterArgs, searchQuery, onDeliveryOrderSea
setSearchAllDos([]);
setTotalCount(0);
}
}, []);
}, [resetSelection]);

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

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

useEffect(() => {
@@ -584,9 +558,9 @@ const DoSearch: React.FC<Props> = ({ filterArgs, searchQuery, onDeliveryOrderSea
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) {
await Swal.fire({
@@ -705,7 +679,7 @@ const DoSearch: React.FC<Props> = ({ filterArgs, searchQuery, onDeliveryOrderSea
confirmButtonText: t("OK")
});
}
}, [t, currentUserId, currentSearchParams, handleSearch, excludedIdSet, activeTab, resolveTabFilter]);
}, [t, currentUserId, currentSearchParams, handleSearch, resolveIdsForBatchRelease, activeTab, resolveTabFilter]);

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



+ 139
- 0
src/components/DoSearch/useDoSearchRowSelection.ts View File

@@ -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 View File

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

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

const [searchAllDos, setSearchAllDos] = useState<DoSearchAll[]>([]);
const [totalCount, setTotalCount] = useState(0);
@@ -118,36 +116,12 @@ const DoSearchWorkbench: React.FC<Props> = ({
const [hasSearched, setHasSearched] = 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(() => {
@@ -204,7 +178,7 @@ const DoSearchWorkbench: React.FC<Props> = ({
setTotalCount(0);
setHasSearched(false);
setHasResults(false);
setExcludedRowIds([]);
resetSelection();
setPagingController({ pageNum: 1, pageSize: 10 });
}
catch (error) {
@@ -212,7 +186,7 @@ const DoSearchWorkbench: React.FC<Props> = ({
setSearchAllDos([]);
setTotalCount(0);
}
}, []);
}, [resetSelection]);

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

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

useEffect(() => {
if (typeof window !== 'undefined') {
@@ -632,9 +606,9 @@ const handleSearch = useCallback(async (query: SearchBoxInputs) => {
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) {
await Swal.fire({
@@ -749,7 +723,7 @@ const handleSearch = useCallback(async (query: SearchBoxInputs) => {
confirmButtonText: t("OK")
});
}
}, [t, currentUserId, currentSearchParams, handleSearch, excludedIdSet]);
}, [t, currentUserId, currentSearchParams, handleSearch, resolveIdsForBatchRelease]);

return (
<>


Loading…
Cancel
Save