diff --git a/src/app/(main)/settings/equipment/page.tsx b/src/app/(main)/settings/equipment/page.tsx
index f55631c..3ef292d 100644
--- a/src/app/(main)/settings/equipment/page.tsx
+++ b/src/app/(main)/settings/equipment/page.tsx
@@ -12,6 +12,7 @@ import { Suspense } from "react";
import { fetchAllEquipments } from "@/app/api/settings/equipment";
import { I18nProvider } from "@/i18n";
import EquipmentSearchWrapper from "@/components/EquipmentSearch/EquipmentSearchWrapper";
+import EquipmentSearchLoading from "@/components/EquipmentSearch/EquipmentSearchLoading";
export const metadata: Metadata = {
title: "Equipment Type",
@@ -33,7 +34,7 @@ const productSetting: React.FC = async () => {
{t("Equipment")}
- }>
+ }>
diff --git a/src/app/api/settings/equipment/client.ts b/src/app/api/settings/equipment/client.ts
new file mode 100644
index 0000000..8ab3998
--- /dev/null
+++ b/src/app/api/settings/equipment/client.ts
@@ -0,0 +1,33 @@
+"use client";
+
+import { NEXT_PUBLIC_API_URL } from "@/config/api";
+import { EquipmentResult } from "./index";
+
+export const exportEquipmentQrCode = async (equipmentIds: number[]): Promise<{ blobValue: Uint8Array; filename: string }> => {
+
+ const token = localStorage.getItem("accessToken");
+
+ const response = await fetch(`${NEXT_PUBLIC_API_URL}/Equipment/export-qrcode`, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ ...(token && { Authorization: `Bearer ${token}` }),
+ },
+ body: JSON.stringify({ equipmentIds }),
+ });
+
+ if (!response.ok) {
+ if (response.status === 401) {
+ throw new Error("Unauthorized: Please log in again");
+ }
+ throw new Error(`Failed to export QR code: ${response.status} ${response.statusText}`);
+ }
+
+ const filename = response.headers.get("Content-Disposition")?.split("filename=")[1]?.replace(/"/g, "") || "equipment_qrcode.pdf";
+
+ const blob = await response.blob();
+ const arrayBuffer = await blob.arrayBuffer();
+ const blobValue = new Uint8Array(arrayBuffer);
+
+ return { blobValue, filename };
+};
diff --git a/src/app/api/settings/equipmentDetail/client.ts b/src/app/api/settings/equipmentDetail/client.ts
new file mode 100644
index 0000000..8627b52
--- /dev/null
+++ b/src/app/api/settings/equipmentDetail/client.ts
@@ -0,0 +1,33 @@
+"use client";
+
+import { NEXT_PUBLIC_API_URL } from "@/config/api";
+import { EquipmentDetailResult } from "./index";
+
+export const exportEquipmentQrCode = async (equipmentDetailIds: number[]): Promise<{ blobValue: Uint8Array; filename: string }> => {
+
+ const token = localStorage.getItem("accessToken");
+
+ const response = await fetch(`${NEXT_PUBLIC_API_URL}/Equipment/export-qrcode`, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ ...(token && { Authorization: `Bearer ${token}` }),
+ },
+ body: JSON.stringify({ equipmentDetailIds }),
+ });
+
+ if (!response.ok) {
+ if (response.status === 401) {
+ throw new Error("Unauthorized: Please log in again");
+ }
+ throw new Error(`Failed to export QR code: ${response.status} ${response.statusText}`);
+ }
+
+ const filename = response.headers.get("Content-Disposition")?.split("filename=")[1]?.replace(/"/g, "") || "equipment_qrcode.pdf";
+
+ const blob = await response.blob();
+ const arrayBuffer = await blob.arrayBuffer();
+ const blobValue = new Uint8Array(arrayBuffer);
+
+ return { blobValue, filename };
+};
diff --git a/src/app/api/settings/equipmentDetail/index.ts b/src/app/api/settings/equipmentDetail/index.ts
new file mode 100644
index 0000000..393442c
--- /dev/null
+++ b/src/app/api/settings/equipmentDetail/index.ts
@@ -0,0 +1,32 @@
+import { cache } from "react";
+import "server-only";
+import { serverFetchJson } from "../../../utils/fetchUtil";
+import { BASE_API_URL } from "../../../../config/api";
+
+export type EquipmentDetailResult = {
+ id: string | number;
+ code: string;
+ name: string;
+ description: string | undefined;
+ equipmentCode?: string;
+ equipmentTypeId?: string | number | undefined;
+ repairAndMaintenanceStatus?: boolean | number;
+ latestRepairAndMaintenanceDate?: string | Date;
+ lastRepairAndMaintenanceDate?: string | Date;
+ repairAndMaintenanceRemarks?: string;
+};
+
+export const fetchAllEquipmentDetails = cache(async () => {
+ return serverFetchJson(`${BASE_API_URL}/EquipmentDetail`, {
+ next: { tags: ["equipmentDetails"] },
+ });
+});
+
+export const fetchEquipmentDetail = cache(async (id: number) => {
+ return serverFetchJson(
+ `${BASE_API_URL}/EquipmentDetail/details/${id}`,
+ {
+ next: { tags: ["equipmentDetails"] },
+ },
+ );
+});
diff --git a/src/components/Breadcrumb/Breadcrumb.tsx b/src/components/Breadcrumb/Breadcrumb.tsx
index 23d378d..114f98c 100644
--- a/src/components/Breadcrumb/Breadcrumb.tsx
+++ b/src/components/Breadcrumb/Breadcrumb.tsx
@@ -17,6 +17,7 @@ const pathToLabelMap: { [path: string]: string } = {
"/settings/qrCodeHandle": "QR Code Handle",
"/settings/rss": "Demand Forecast Setting",
"/settings/equipment": "Equipment",
+ "/settings/equipment/MaintenanceEdit": "MaintenanceEdit",
"/settings/shop": "ShopAndTruck",
"/settings/shop/detail": "Shop Detail",
"/settings/shop/truckdetail": "Truck Lane Detail",
diff --git a/src/components/EquipmentSearch/EquipmentSearch.tsx b/src/components/EquipmentSearch/EquipmentSearch.tsx
index 735b2a8..c850014 100644
--- a/src/components/EquipmentSearch/EquipmentSearch.tsx
+++ b/src/components/EquipmentSearch/EquipmentSearch.tsx
@@ -1,20 +1,35 @@
"use client";
-import { useCallback, useEffect, useMemo, useState } from "react";
+import React, { useCallback, useEffect, useMemo, useState } from "react";
import SearchBox, { Criterion } from "../SearchBox";
import { EquipmentResult } from "@/app/api/settings/equipment";
import { useTranslation } from "react-i18next";
import EquipmentSearchResults, { Column } from "./EquipmentSearchResults";
import { EditNote } from "@mui/icons-material";
-import { useRouter, useSearchParams } from "next/navigation";
-import { GridDeleteIcon } from "@mui/x-data-grid";
-import { TypeEnum } from "@/app/utils/typeEnum";
-import axios from "axios";
+import { useRouter } from "next/navigation";
import { BASE_API_URL, NEXT_PUBLIC_API_URL } from "@/config/api";
import axiosInstance from "@/app/(main)/axios/axiosInstance";
import { arrayToDateTimeString } from "@/app/utils/formatUtil";
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
+import IconButton from "@mui/material/IconButton";
+import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown";
+import KeyboardArrowUpIcon from "@mui/icons-material/KeyboardArrowUp";
+import CircularProgress from "@mui/material/CircularProgress";
+import TableRow from "@mui/material/TableRow";
+import TableCell from "@mui/material/TableCell";
+import Collapse from "@mui/material/Collapse";
+import Grid from "@mui/material/Grid";
+import DeleteIcon from "@mui/icons-material/Delete";
+import AddIcon from "@mui/icons-material/Add";
+import Button from "@mui/material/Button";
+import Dialog from "@mui/material/Dialog";
+import DialogTitle from "@mui/material/DialogTitle";
+import DialogContent from "@mui/material/DialogContent";
+import DialogContentText from "@mui/material/DialogContentText";
+import DialogActions from "@mui/material/DialogActions";
+import TextField from "@mui/material/TextField";
+import Autocomplete from "@mui/material/Autocomplete";
type Props = {
equipments: EquipmentResult[];
@@ -28,14 +43,37 @@ const EquipmentSearch: React.FC = ({ equipments, tabIndex = 0 }) => {
useState([]);
const { t } = useTranslation("common");
const router = useRouter();
- const [filterObj, setFilterObj] = useState({});
- const [pagingController, setPagingController] = useState({
- pageNum: 1,
- pageSize: 10,
+ const [filterObjByTab, setFilterObjByTab] = useState>({
+ 0: {},
+ 1: {},
+ });
+ const [pagingControllerByTab, setPagingControllerByTab] = useState>({
+ 0: { pageNum: 1, pageSize: 10 },
+ 1: { pageNum: 1, pageSize: 10 },
});
const [totalCount, setTotalCount] = useState(0);
const [isLoading, setIsLoading] = useState(true);
const [isReady, setIsReady] = useState(false);
+
+ const filterObj = filterObjByTab[tabIndex] || {};
+ const pagingController = pagingControllerByTab[tabIndex] || { pageNum: 1, pageSize: 10 };
+
+ const [expandedRows, setExpandedRows] = useState>(new Set());
+ const [equipmentDetailsMap, setEquipmentDetailsMap] = useState