FPSMS-frontend
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 

215 строки
7.1 KiB

  1. "use client";
  2. import { useCallback, useEffect, useMemo, useState } from "react";
  3. import SearchBox, { Criterion } from "../SearchBox";
  4. import { ItemsResult, ItemsResultResponse } from "@/app/api/settings/item";
  5. import { useTranslation } from "react-i18next";
  6. import SearchResults, { Column } from "../SearchResults";
  7. import { EditNote } from "@mui/icons-material";
  8. import { useRouter, useSearchParams } from "next/navigation";
  9. import { Chip } from "@mui/material";
  10. import { TypeEnum } from "@/app/utils/typeEnum";
  11. import axios from "axios";
  12. import { BASE_API_URL, NEXT_PUBLIC_API_URL } from "@/config/api";
  13. import axiosInstance from "@/app/(main)/axios/axiosInstance";
  14. type Props = {
  15. items: ItemsResult[];
  16. };
  17. type SearchQuery = Partial<Omit<ItemsResult, "id">>;
  18. type SearchParamNames = keyof SearchQuery;
  19. type ItemsResultWithStatus = ItemsResult & {
  20. status?: "complete" | "missing";
  21. };
  22. const ItemsSearch: React.FC<Props> = ({ items }) => {
  23. const [filteredItems, setFilteredItems] = useState<ItemsResult[]>(items);
  24. const { t } = useTranslation("items");
  25. const router = useRouter();
  26. const [filterObj, setFilterObj] = useState({});
  27. const [pagingController, setPagingController] = useState({
  28. pageNum: 1,
  29. pageSize: 10,
  30. // totalCount: 0,
  31. });
  32. const [totalCount, setTotalCount] = useState(0);
  33. const searchCriteria: Criterion<SearchParamNames>[] = useMemo(() => {
  34. const searchCriteria: Criterion<SearchParamNames>[] = [
  35. { label: t("Code"), paramName: "code", type: "text" },
  36. { label: t("Name"), paramName: "name", type: "text" },
  37. ];
  38. return searchCriteria;
  39. }, [t]);
  40. const onDetailClick = useCallback(
  41. (item: ItemsResult) => {
  42. router.push(`/settings/items/edit?id=${item.id}`);
  43. },
  44. [router],
  45. );
  46. const checkItemStatus = useCallback((item: ItemsResult): "complete" | "missing" => {
  47. // Check if type exists and is not empty
  48. const hasType = item.type != null && String(item.type).trim() !== "";
  49. // Check if qcCategory exists (can be object or id) - handle case sensitivity
  50. const itemAny = item as any;
  51. const hasQcCategory = item.qcCategory != null ||
  52. itemAny.qcCategoryId != null ||
  53. itemAny.qcCategoryid != null ||
  54. itemAny.qccategoryid != null;
  55. // Check if LocationCode exists and is not empty - handle case sensitivity
  56. const hasLocationCode = (item.LocationCode != null && String(item.LocationCode).trim() !== "") ||
  57. (itemAny.LocationCode != null && String(itemAny.LocationCode).trim() !== "") ||
  58. (itemAny.locationCode != null && String(itemAny.locationCode).trim() !== "") ||
  59. (itemAny.locationcode != null && String(itemAny.locationcode).trim() !== "");
  60. // If all three are present, return "complete", otherwise "missing"
  61. if (hasType && hasQcCategory && hasLocationCode) {
  62. return "complete";
  63. }
  64. return "missing";
  65. }, []);
  66. const refetchData = useCallback(
  67. async (filterObj: SearchQuery) => {
  68. const authHeader = axiosInstance.defaults.headers["Authorization"];
  69. if (!authHeader) {
  70. return; // Exit the function if the token is not set
  71. }
  72. const params = {
  73. pageNum: pagingController.pageNum,
  74. pageSize: pagingController.pageSize,
  75. ...filterObj,
  76. };
  77. try {
  78. const response = await axiosInstance.get<ItemsResultResponse>(
  79. `${NEXT_PUBLIC_API_URL}/items/getRecordByPage`,
  80. { params },
  81. );
  82. if (response.status == 200) {
  83. // Normalize field names and add status to each item
  84. const itemsWithStatus: ItemsResultWithStatus[] = response.data.records.map((item: any) => {
  85. // Normalize field names (handle case sensitivity from MySQL)
  86. // Check all possible case variations
  87. const locationCode = item.LocationCode || item.locationCode || item.locationcode || item.Locationcode || item.Location_Code || item.location_code;
  88. const qcCategoryId = item.qcCategoryId || item.qcCategoryid || item.qccategoryid || item.QcCategoryId || item.qc_category_id;
  89. const normalizedItem: ItemsResult = {
  90. ...item,
  91. LocationCode: locationCode,
  92. qcCategory: item.qcCategory || (qcCategoryId ? { id: qcCategoryId } : undefined),
  93. };
  94. return {
  95. ...normalizedItem,
  96. status: checkItemStatus(normalizedItem),
  97. };
  98. });
  99. console.log("Fetched items data:", itemsWithStatus);
  100. setFilteredItems(itemsWithStatus as ItemsResult[]);
  101. setTotalCount(response.data.total);
  102. return response; // Return the data from the response
  103. } else {
  104. throw "400";
  105. }
  106. } catch (error) {
  107. console.error("Error fetching items:", error);
  108. throw error; // Rethrow the error for further handling
  109. }
  110. },
  111. [pagingController.pageNum, pagingController.pageSize, checkItemStatus],
  112. );
  113. useEffect(() => {
  114. // Only refetch when paging changes AND we have already searched (filterObj has been set by search)
  115. if (Object.keys(filterObj).length > 0 || filteredItems.length > 0) {
  116. refetchData(filterObj);
  117. }
  118. }, [
  119. pagingController.pageNum,
  120. pagingController.pageSize,
  121. ]);
  122. const columns = useMemo<Column<ItemsResultWithStatus>[]>(
  123. () => [
  124. {
  125. name: "id",
  126. label: t("Details"),
  127. onClick: onDetailClick,
  128. buttonIcon: <EditNote />,
  129. sx: { width: 80 },
  130. },
  131. {
  132. name: "code",
  133. label: t("Code"),
  134. sx: { width: 150 },
  135. },
  136. {
  137. name: "name",
  138. label: t("Name"),
  139. sx: { width: 250 },
  140. },
  141. {
  142. name: "LocationCode",
  143. label: t("LocationCode"),
  144. sx: { width: 150 },
  145. },
  146. {
  147. name: "type",
  148. label: t("Type"),
  149. sx: { width: 120 },
  150. },
  151. {
  152. name: "status",
  153. label: t("Status"),
  154. align: "center",
  155. headerAlign: "center",
  156. sx: { width: 120 },
  157. renderCell: (item) => {
  158. const status = item.status || checkItemStatus(item);
  159. if (status === "complete") {
  160. return <Chip label={t("Complete")} color="success" size="small" />;
  161. } else {
  162. return <Chip label={t("Missing Data")} color="warning" size="small" />;
  163. }
  164. },
  165. },
  166. ],
  167. [onDetailClick, t, checkItemStatus],
  168. );
  169. const onReset = useCallback(() => {
  170. setFilteredItems([]);
  171. setFilterObj({});
  172. setTotalCount(0);
  173. }, []);
  174. return (
  175. <>
  176. <SearchBox
  177. criteria={searchCriteria}
  178. onSearch={(query) => {
  179. setFilterObj({
  180. ...query,
  181. });
  182. refetchData(query);
  183. }}
  184. onReset={onReset}
  185. />
  186. <SearchResults<ItemsResultWithStatus>
  187. items={filteredItems as ItemsResultWithStatus[]}
  188. columns={columns}
  189. setPagingController={setPagingController}
  190. pagingController={pagingController}
  191. totalCount={totalCount}
  192. isAutoPaging={false}
  193. />
  194. </>
  195. );
  196. };
  197. export default ItemsSearch;