Преглед на файлове

updatestock issue

MergeProblem1
CANCERYS\kw093 преди 2 седмици
родител
ревизия
6aefd923c5
променени са 4 файла, в които са добавени 299 реда и са изтрити 68 реда
  1. +3
    -3
      src/app/(main)/stockIssue/page.tsx
  2. +278
    -29
      src/components/StockIssue/SearchPage.tsx
  3. +17
    -33
      src/components/StockIssue/action.ts
  4. +1
    -3
      src/components/StockIssue/index.tsx

+ 3
- 3
src/app/(main)/stockIssue/page.tsx Целия файл

@@ -7,17 +7,17 @@ import { Metadata } from "next";
import { Suspense } from "react";

export const metadata: Metadata = {
title: "Pick Order",
title: "Stock Issue",
};

const SearchView: React.FC = async () => {
const { t } = await getServerI18n("pickOrder");
const { t } = await getServerI18n("inventory");

PreloadList();

return (
<>
<I18nProvider namespaces={["pickOrder", "common"]}>
<I18nProvider namespaces={["inventory", "common"]}>
<Suspense fallback={<SearchPage.Loading />}>
<SearchPage />
</Suspense>


+ 278
- 29
src/components/StockIssue/SearchPage.tsx Целия файл

@@ -4,53 +4,302 @@ import SearchBox, { Criterion } from "../SearchBox";
import { useCallback, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import SearchResults, { Column } from "../SearchResults/index";
import { StockIssueResult } from "./action";
import { SessionWithTokens } from "@/config/authConfig";
import {
batchSubmitBadItem,
batchSubmitExpiryItem,
batchSubmitMissItem,
ExpiryItemResult,
StockIssueLists,
StockIssueResult,
submitBadItem,
submitExpiryItem,
submitMissItem,
} from "@/app/api/stockIssue/actions";
import { Box, Button, Tab, Tabs } from "@mui/material";
import { useSession } from "next-auth/react";

interface Props {
dataList: StockIssueResult[];
dataList: StockIssueLists;
}

type SearchQuery = {
lotNo: string;
};
type SearchQuery = Partial<Omit<StockIssueResult, "id">>;
type SearchParamNames = keyof SearchQuery;

const SearchPage: React.FC<Props> = ({dataList}) => {
const { t } = useTranslation("user");
const [filteredList, setFilteredList] = useState(dataList);

const searchCriteria: Criterion<SearchParamNames>[] = useMemo(
() => [
{
label: t("Lot No."),
paramName: "lotNo",
type: "text",
},
],
[t],
const SearchPage: React.FC<Props> = ({ dataList }) => {
const { t } = useTranslation("inventory");
const [tab, setTab] = useState<"miss" | "bad" | "expiry">("miss");
const [search, setSearch] = useState<SearchQuery>({ lotNo: "" });
const { data: session } = useSession() as { data: SessionWithTokens | null };
const currentUserId = session?.id ? parseInt(session.id) : undefined;
const [missItems, setMissItems] = useState<StockIssueResult[]>(
dataList.missItems,
);
const [badItems, setBadItems] = useState<StockIssueResult[]>(
dataList.badItems,
);
const [expiryItems, setExpiryItems] = useState<ExpiryItemResult[]>(
dataList.expiryItems,
);
const [selectedIds, setSelectedIds] = useState<(string | number)[]>([]);
const [submittingIds, setSubmittingIds] = useState<Set<number>>(new Set());
const [batchSubmitting, setBatchSubmitting] = useState(false);

const searchCriteria: Criterion<SearchParamNames>[] = useMemo(
() => [
{
label: t("Lot No."),
paramName: "lotNo",
type: "text",
},
],
[t],
);

const filterBySearch = useCallback(
<T extends { lotNo: string | null }>(items: T[]): T[] => {
if (!search.lotNo) return items;
const keyword = search.lotNo.toLowerCase();
return items.filter(
(i) => i.lotNo && i.lotNo.toLowerCase().includes(keyword),
);
},
[search.lotNo],
);

const handleSubmitSingle = useCallback(
async (id: number) => {
if (!currentUserId) {
alert(t("User ID is required"));
return;
}

const columns = useMemo<Column<StockIssueResult>[]>(
setSubmittingIds((prev) => new Set(prev).add(id));
try {
if (tab === "miss") {
await submitMissItem(id, currentUserId);
setMissItems((prev) => prev.filter((i) => i.id !== id));
} else if (tab === "bad") {
await submitBadItem(id, currentUserId);
setBadItems((prev) => prev.filter((i) => i.id !== id));
} else {
await submitExpiryItem(id, currentUserId);
setExpiryItems((prev) => prev.filter((i) => i.id !== id));
}
// Remove from selectedIds if it was selected
setSelectedIds((prev) => prev.filter((selectedId) => selectedId !== id));
} catch (error) {
console.error("Failed to submit item:", error);
alert(`Failed to submit: ${error instanceof Error ? error.message : "Unknown error"}`);
} finally {
setSubmittingIds((prev) => {
const newSet = new Set(prev);
newSet.delete(id);
return newSet;
});
}
},
[tab, currentUserId, t],
);

const handleSubmitSelected = useCallback(async () => {
if (!currentUserId) return;
// Get all IDs from the current tab's filtered items
let allIds: number[] = [];
if (tab === "miss") {
const items = filterBySearch(missItems);
allIds = items.map((item) => item.id);
} else if (tab === "bad") {
const items = filterBySearch(badItems);
allIds = items.map((item) => item.id);
} else {
const items = filterBySearch(expiryItems);
allIds = items.map((item) => item.id);
}

if (allIds.length === 0) return;

setBatchSubmitting(true);
try {
if (tab === "miss") {
await batchSubmitMissItem(allIds, currentUserId);
setMissItems((prev) => prev.filter((i) => !allIds.includes(i.id)));
} else if (tab === "bad") {
await batchSubmitBadItem(allIds, currentUserId);
setBadItems((prev) => prev.filter((i) => !allIds.includes(i.id)));
} else {
await batchSubmitExpiryItem(allIds, currentUserId);
setExpiryItems((prev) => prev.filter((i) => !allIds.includes(i.id)));
}

setSelectedIds([]);
} catch (error) {
console.error("Failed to submit selected items:", error);
alert(`Failed to submit: ${error instanceof Error ? error.message : "Unknown error"}`);
} finally {
setBatchSubmitting(false);
}
}, [tab, currentUserId, missItems, badItems, expiryItems, filterBySearch]);

const missColumns = useMemo<Column<StockIssueResult>[]>(
() => [
{ name: "itemCode", label: t("Item Code") },
{ name: "itemDescription", label: t("Item") },
{ name: "lotNo", label: t("Lot No.") },
{ name: "storeLocation", label: t("Location") },
{ name: "badItemQty", label: t("Defective Qty") }
{ name: "missQty", label: t("Miss Qty") },
{
name: "id",
label: t("Action"),
renderCell: (item) => (
<Button
size="small"
variant="contained"
color="primary"
onClick={() => handleSubmitSingle(item.id)}
disabled={submittingIds.has(item.id) || !currentUserId}
>
{submittingIds.has(item.id) ? t("Processing...") : t("Looked")}
</Button>
),
},
],
[t],
[t, handleSubmitSingle, submittingIds, currentUserId],
);

const badColumns = useMemo<Column<StockIssueResult>[]>(
() => [
{ name: "itemCode", label: t("Item Code") },
{ name: "itemDescription", label: t("Item") },
{ name: "lotNo", label: t("Lot No.") },
{ name: "storeLocation", label: t("Location") },
{ name: "badItemQty", label: t("Defective Qty") },
{
name: "id",
label: t("Action"),
renderCell: (item) => (
<Button
size="small"
variant="contained"
color="primary"
onClick={() => handleSubmitSingle(item.id)}
disabled={submittingIds.has(item.id) || !currentUserId}
>
{submittingIds.has(item.id) ? t("Disposing...") : t("Disposed")}
</Button>
),
},
],
[t, handleSubmitSingle, submittingIds, currentUserId],
);

const expiryColumns = useMemo<Column<ExpiryItemResult>[]>(
() => [
{ name: "itemCode", label: t("Item Code") },
{ name: "itemDescription", label: t("Item") },
{ name: "lotNo", label: t("Lot No.") },
{ name: "storeLocation", label: t("Location") },
{ name: "expiryDate", label: t("Expiry Date") },
{ name: "remainingQty", label: t("Remaining Qty") },
{
name: "id",
label: t("Action"),
renderCell: (item) => (
<Button
size="small"
variant="contained"
color="primary"
onClick={() => handleSubmitSingle(item.id)}
disabled={submittingIds.has(item.id) || !currentUserId}
>
{submittingIds.has(item.id) ? t("Disposing...") : t("Disposed")}
</Button>
),
},
],
[t, handleSubmitSingle, submittingIds, currentUserId],
);

const handleSearch = useCallback((query: Record<SearchParamNames, string>) => {
setSearch(query);
}, []);

const handleTabChange = useCallback(
(_: React.SyntheticEvent, value: string) => {
setTab(value as "miss" | "bad" | "expiry");
setSelectedIds([]);
},
[],
);

const renderCurrentTab = () => {
if (tab === "miss") {
const items = filterBySearch(missItems);
return (
<SearchResults<StockIssueResult>
items={items}
columns={missColumns}
pagingController={{ pageNum: 1, pageSize: 10 }}
checkboxIds={selectedIds}
setCheckboxIds={setSelectedIds}
/>
);
}

if (tab === "bad") {
const items = filterBySearch(badItems);
return (
<SearchResults<StockIssueResult>
items={items}
columns={badColumns}
pagingController={{ pageNum: 1, pageSize: 10 }}
checkboxIds={selectedIds}
setCheckboxIds={setSelectedIds}
/>
);
}

const items = filterBySearch(expiryItems);
return (
<SearchResults<ExpiryItemResult>
items={items}
columns={expiryColumns}
pagingController={{ pageNum: 1, pageSize: 10 }}
checkboxIds={selectedIds}
setCheckboxIds={setSelectedIds}
/>
);
};

return (
<>
<SearchBox
<Box>
<Tabs value={tab} onChange={handleTabChange} sx={{ mb: 2 }}>
<Tab value="miss" label={t("Miss Item")} />
<Tab value="bad" label={t("Bad Item")} />
<Tab value="expiry" label={t("Expiry Item")} />
</Tabs>

<SearchBox<SearchParamNames>
criteria={searchCriteria}
onSearch={(query) => {
}}
/>
<SearchResults<StockIssueResult>
items={filteredList}
columns={columns}
pagingController={{ pageNum: 1, pageSize: 10 }}
onSearch={handleSearch}
/>
</>

<Box sx={{ display: "flex", justifyContent: "flex-end", mb: 1 }}>
<Button
variant="contained"
color="primary"
onClick={handleSubmitSelected}
disabled={batchSubmitting || !currentUserId}
>
{batchSubmitting ? tab === "miss" ? t("Processing...") : tab === "bad" ? t("Disposing...") : t("Disposing...") : t("Batch Disposed All")}
</Button>
</Box>

{renderCurrentTab()}
</Box>
);
};


+ 17
- 33
src/components/StockIssue/action.ts Целия файл

@@ -1,33 +1,17 @@
import "server-only";

import { BASE_API_URL } from "@/config/api";
import { serverFetchJson } from "@/app/utils/fetchUtil";
import { cache } from "react";

export interface StockIssueResult {
action: any;
id: number;
itemId: number;
itemCode: string;
itemDescription: string;
lotId: number;
lotNo: string;
storeLocation: string;
requiredQty: number;
badItemQty: number;
issueRemark: string;
pickerName: string;
handleStatus: string;
handleDate: string;
handledBy: number;
}

export const PreloadList = () => {
fetchList();
};

export const fetchList = cache(async () => {
return serverFetchJson<StockIssueResult[]>(`${BASE_API_URL}/pickExecution/badItemList`, {
next: { tags: ["Bad Item List"] },
});
});
// Re-export types and functions from the main actions file
export {
type StockIssueResult,
type ExpiryItemResult,
type StockIssueLists,
fetchList,
fetchMissItemList,
fetchBadItemList,
fetchExpiryItemList,
submitMissItem,
submitBadItem,
submitExpiryItem,
batchSubmitMissItem,
batchSubmitBadItem,
batchSubmitExpiryItem,
PreloadList,
} from "@/app/api/stockIssue/actions";

+ 1
- 3
src/components/StockIssue/index.tsx Целия файл

@@ -1,7 +1,6 @@
import GeneralLoading from "../General/GeneralLoading";
import SearchPage from "./SearchPage";
import { fetchList } from "./action";

import { fetchList } from "@/app/api/stockIssue/actions";

interface SubComponents {
Loading: typeof GeneralLoading;
@@ -9,7 +8,6 @@ interface SubComponents {

const Wrapper: React.FC & SubComponents = async () => {
const dataList = await fetchList();
console.log(dataList);

return <SearchPage dataList={dataList} />;
};


Зареждане…
Отказ
Запис