Pārlūkot izejas kodu

Merge branch 'MergeProblem1' of http://svn.2fi-solutions.com:8300/derek/FPSMS-frontend into MergeProblem1

# Conflicts:
#	src/components/ProductionProcess/ProductionProcessList.tsx
MergeProblem1
CANCERYS\kw093 pirms 17 stundām
vecāks
revīzija
947f471ed8
2 mainītis faili ar 123 papildinājumiem un 57 dzēšanām
  1. +4
    -1
      src/app/api/jo/actions.ts
  2. +119
    -56
      src/components/ProductionProcess/ProductionProcessList.tsx

+ 4
- 1
src/app/api/jo/actions.ts Parādīt failu

@@ -779,6 +779,7 @@ export const fetchAllJoborderProductProcessInfo = cache(async (isDrink?: boolean
});

export const fetchJoborderProductProcessesPage = cache(async (params: {
/** Job order planStart 區間起(YYYY-MM-DD,含當日) */
date?: string | null;
itemCode?: string | null;
jobOrderCode?: string | null;
@@ -800,7 +801,9 @@ export const fetchJoborderProductProcessesPage = cache(async (params: {
} = params;

const queryParts: string[] = [];
if (date) queryParts.push(`date=${encodeURIComponent(date)}`);
if (date) {
queryParts.push(`date=${encodeURIComponent(date)}`);
}
if (itemCode) queryParts.push(`itemCode=${encodeURIComponent(itemCode)}`);
if (jobOrderCode) queryParts.push(`jobOrderCode=${encodeURIComponent(jobOrderCode)}`);
if (bomIds && bomIds.length > 0) queryParts.push(`bomIds=${bomIds.join(",")}`);


+ 119
- 56
src/components/ProductionProcess/ProductionProcessList.tsx Parādīt failu

@@ -32,14 +32,7 @@ import { SessionWithTokens } from "@/config/authConfig";
import dayjs from "dayjs";
import { OUTPUT_DATE_FORMAT } from "@/app/utils/formatUtil";
import SearchBox, { Criterion } from "@/components/SearchBox/SearchBox";
export type ProductionProcessListPersistedState = {
date: string;
itemCode: string | null;
jobOrderCode: string | null;
filter: "all" | "drink" | "other";
page: number;
selectedItemCodes: string[];
};


import {
AllJoborderProductProcessInfoResponse,
@@ -52,6 +45,15 @@ import {
import { StockInLineInput } from "@/app/api/stockIn";
import { PrinterCombo } from "@/app/api/settings/printer";
import JobPickExecutionsecondscan from "../Jodetail/JobPickExecutionsecondscan";
export type ProductionProcessListPersistedState = {
date: string;
itemCode: string | null;
jobOrderCode: string | null;
filter: "all" | "drink" | "other";
page: number;
selectedItemCodes: string[];
};

interface ProductProcessListProps {
onSelectProcess: (jobOrderId: number|undefined, productProcessId: number|undefined) => void;
onSelectMatchingStock: (jobOrderId: number|undefined, productProcessId: number|undefined,pickOrderId: number|undefined) => void;
@@ -62,6 +64,18 @@ interface ProductProcessListProps {
React.SetStateAction<ProductionProcessListPersistedState>
>;
}
export type SearchParam = "date" | "itemCode" | "jobOrderCode" | "processType";

const PAGE_SIZE = 50;

/** 預設依 JobOrder.planStart 搜尋:今天往前 3 天~往後 3 天(含當日) */
function defaultPlanStartRange() {
return {
from: dayjs().subtract(0, "day").format("YYYY-MM-DD"),
to: dayjs().add(0, "day").format("YYYY-MM-DD"),
};
}

export function createDefaultProductionProcessListPersistedState(): ProductionProcessListPersistedState {
return {
date: dayjs().format("YYYY-MM-DD"),
@@ -72,36 +86,43 @@ export function createDefaultProductionProcessListPersistedState(): ProductionPr
selectedItemCodes: [],
};
}
type SearchParam = "date" | "itemCode" | "jobOrderCode" | "processType";

const PAGE_SIZE = 50;

const ProductProcessList: React.FC<ProductProcessListProps> = ({ onSelectProcess, printerCombo ,onSelectMatchingStock, qcReady}) => {
const ProductProcessList: React.FC<ProductProcessListProps> = ({
onSelectProcess,
printerCombo,
onSelectMatchingStock,
qcReady,
listPersistedState,
onListPersistedStateChange,
}) => {
const { t } = useTranslation( ["common", "production","purchaseOrder","dashboard"]);
const { data: session } = useSession() as { data: SessionWithTokens | null };
const sessionToken = session as SessionWithTokens | null;
const [loading, setLoading] = useState(false);
const [processes, setProcesses] = useState<AllJoborderProductProcessInfoResponse[]>([]);
const [page, setPage] = useState(0);
const [openModal, setOpenModal] = useState<boolean>(false);
const [modalInfo, setModalInfo] = useState<StockInLineInput>();
const currentUserId = session?.id ? parseInt(session.id) : undefined;
type ProcessFilter = "all" | "drink" | "other";
const [filter, setFilter] = useState<ProcessFilter>("all");
const [suggestedLocationCode, setSuggestedLocationCode] = useState<string | null>(null);

const [appliedSearch, setAppliedSearch] = useState<{
date: string;
itemCode: string | null;
jobOrderCode: string | null;
}>(() => ({
date: dayjs().format("YYYY-MM-DD"),
itemCode: null,
jobOrderCode: null,
}));
const appliedSearch = useMemo(
() => ({
date: listPersistedState.date,
itemCode: listPersistedState.itemCode,
jobOrderCode: listPersistedState.jobOrderCode,
}),
[
listPersistedState.date,
listPersistedState.itemCode,
listPersistedState.jobOrderCode,
],
);
const filter = listPersistedState.filter;
const page = listPersistedState.page;
const selectedItemCodes = listPersistedState.selectedItemCodes;

const [totalJobOrders, setTotalJobOrders] = useState(0);
const [selectedItemCodes, setSelectedItemCodes] = useState<string[]>([]);

// Generic confirm dialog for actions (update job order / etc.)
const [confirmOpen, setConfirmOpen] = useState(false);
@@ -192,28 +213,42 @@ const ProductProcessList: React.FC<ProductProcessListProps> = ({ onSelectProcess
setOpenModal(true);
}, [t]);

const handleApplySearch = useCallback((inputs: Record<SearchParam | `${SearchParam}To`, string>) => {
const selectedProcessType = (inputs.processType || "all") as ProcessFilter;
setFilter(selectedProcessType);
setAppliedSearch({
date: inputs.date || dayjs().format("YYYY-MM-DD"),
itemCode: inputs.itemCode?.trim() ? inputs.itemCode.trim() : null,
jobOrderCode: inputs.jobOrderCode?.trim() ? inputs.jobOrderCode.trim() : null,
});
setSelectedItemCodes([]);
setPage(0);
}, []);
const handleApplySearch = useCallback(
(inputs: Record<SearchParam | `${SearchParam}To`, string>) => {
const selectedProcessType = (inputs.processType || "all") as ProcessFilter;
const fallback = defaultPlanStartRange();
let from = (inputs.date || "").trim() || fallback.from;
let to = (inputs.dateTo || "").trim() || fallback.to;
if (dayjs(from).isAfter(dayjs(to), "day")) {
[from, to] = [to, from];
}
onListPersistedStateChange((prev) => ({
...prev,
filter: selectedProcessType,
planStartFrom: from,
planStartTo: to,
itemCode: inputs.itemCode?.trim() ? inputs.itemCode.trim() : null,
jobOrderCode: inputs.jobOrderCode?.trim() ? inputs.jobOrderCode.trim() : null,
selectedItemCodes: [],
page: 0,
}));
},
[onListPersistedStateChange],
);

const handleResetSearch = useCallback(() => {
setFilter("all");
setAppliedSearch({
date: dayjs().format("YYYY-MM-DD"),
const r = defaultPlanStartRange();
onListPersistedStateChange((prev) => ({
...prev,
filter: "all",
planStartFrom: r.from,
planStartTo: r.to,
itemCode: null,
jobOrderCode: null,
});
setSelectedItemCodes([]);
setPage(0);
}, []);
selectedItemCodes: [],
page: 0,
}));
}, [onListPersistedStateChange]);

const fetchProcesses = useCallback(async () => {
setLoading(true);
@@ -240,7 +275,7 @@ const ProductProcessList: React.FC<ProductProcessListProps> = ({ onSelectProcess
} finally {
setLoading(false);
}
}, [filter, appliedSearch, qcReady, page]);
}, [listPersistedState, qcReady]);

useEffect(() => {
fetchProcesses();
@@ -328,39 +363,64 @@ const ProductProcessList: React.FC<ProductProcessListProps> = ({ onSelectProcess
return processes.filter((p) => selectedItemCodes.includes(p.itemCode));
}, [processes, selectedItemCodes]);

const searchCriteria: Criterion<SearchParam>[] = useMemo(
() => [
/** Reset 用 ±3 天;preFilled 用目前已套用的條件(與列表查詢一致) */
const searchCriteria: Criterion<SearchParam>[] = useMemo(() => {
const r = defaultPlanStartRange();
return [
{
type: "date",
label: "Production date",
type: "dateRange",
label: t("Plan start (from)"),
label2: t("Plan start (to)"),
paramName: "date",
preFilledValue: dayjs().format("YYYY-MM-DD"),
defaultValue: r.from,
defaultValueTo: r.to,
preFilledValue: {
from: appliedSearch.date,
to: appliedSearch.date,
},
},
{
type: "text",
label: "Item Code",
paramName: "itemCode",
preFilledValue: appliedSearch.itemCode ?? "",
},
{
type: "text",
label: "Job Order Code",
paramName: "jobOrderCode",
preFilledValue: appliedSearch.jobOrderCode ?? "",
},
{
type: "select",
label: "Type",
paramName: "processType",
options: ["all", "drink", "other"],
preFilledValue: "all",
preFilledValue: filter,
},
],
[],
];
}, [appliedSearch, filter, t]);

/** SearchBox 內部 state 只在掛載時讀 preFilled;套用搜尋後需 remount 才會與 appliedSearch 一致 */
const searchBoxKey = useMemo(
() =>
[
appliedSearch.date,
appliedSearch.itemCode ?? "",
appliedSearch.jobOrderCode ?? "",
filter,
].join("|"),
[appliedSearch, filter],
);

const handleSelectedItemCodesChange = useCallback((e: SelectChangeEvent<string[]>) => {
const nextValue = e.target.value;
setSelectedItemCodes(typeof nextValue === "string" ? nextValue.split(",") : nextValue);
}, []);
const handleSelectedItemCodesChange = useCallback(
(e: SelectChangeEvent<string[]>) => {
const nextValue = e.target.value;
const codes = typeof nextValue === "string" ? nextValue.split(",") : nextValue;
onListPersistedStateChange((prev) => ({ ...prev, selectedItemCodes: codes }));
},
[onListPersistedStateChange],
);

return (
<Box>
@@ -371,6 +431,7 @@ const ProductProcessList: React.FC<ProductProcessListProps> = ({ onSelectProcess
) : (
<Box>
<SearchBox<SearchParam>
key={searchBoxKey}
criteria={searchCriteria}
onSearch={handleApplySearch}
onReset={handleResetSearch}
@@ -599,7 +660,9 @@ const ProductProcessList: React.FC<ProductProcessListProps> = ({ onSelectProcess
count={totalJobOrders}
page={page}
rowsPerPage={PAGE_SIZE}
onPageChange={(e, p) => setPage(p)}
onPageChange={(e, p) =>
onListPersistedStateChange((prev) => ({ ...prev, page: p }))
}
rowsPerPageOptions={[PAGE_SIZE]}
/>
)}


Notiek ielāde…
Atcelt
Saglabāt