diff --git a/client/src/api/assets.ts b/client/src/api/assets.ts index 93050cd1..d34bde0d 100644 --- a/client/src/api/assets.ts +++ b/client/src/api/assets.ts @@ -106,7 +106,7 @@ export type AssetListQuery = { mine?: boolean; }; -export type AssetPayload = { +export type CreateAssetPayload = { name: string; categoryId: string; legalEntityId: string; @@ -120,6 +120,20 @@ export type AssetPayload = { retiredAt?: string | null; }; +export type PatchAssetPayload = Partial< + Omit +>; + +export type AssetMovementResponse = { + assetId: string; + assetNo: string; + status: AssetStatus; + assigneeEmployeeId: string | null; + location: string | null; + assignmentId?: string | null; + transferId?: string | null; +}; + function toQueryString(query: Record) { const params = new URLSearchParams(); for (const [key, value] of Object.entries(query)) { @@ -181,16 +195,40 @@ export async function fetchAssetDetail(assetId: string) { return apiRequestData<{ asset: AssetItem }>(`/api/assets/${assetId}`); } -export async function createAsset(payload: AssetPayload) { +export async function createAsset(payload: CreateAssetPayload) { return apiRequestData<{ asset: AssetItem }>("/api/assets", { method: "POST", body: JSON.stringify(payload), }); } -export async function patchAsset(assetId: string, payload: Partial>) { +export async function patchAsset(assetId: string, payload: PatchAssetPayload) { return apiRequestData<{ asset: AssetItem }>(`/api/assets/${assetId}`, { method: "PATCH", body: JSON.stringify(payload), }); } + +export async function assignAsset(assetId: string, payload: { employeeId: string; note?: string | null }) { + return apiRequestData(`/api/assets/${assetId}/assign`, { + method: "POST", + body: JSON.stringify(payload), + }); +} + +export async function returnAsset(assetId: string, payload: { note?: string | null } = {}) { + return apiRequestData(`/api/assets/${assetId}/return`, { + method: "POST", + body: JSON.stringify(payload), + }); +} + +export async function transferAsset( + assetId: string, + payload: { toEmployeeId?: string | null; toLocation?: string | null; note?: string | null }, +) { + return apiRequestData(`/api/assets/${assetId}/transfer`, { + method: "POST", + body: JSON.stringify(payload), + }); +} diff --git a/client/src/api/inventoryChecks.ts b/client/src/api/inventoryChecks.ts new file mode 100644 index 00000000..9ec1aeb8 --- /dev/null +++ b/client/src/api/inventoryChecks.ts @@ -0,0 +1,83 @@ +import { apiRequestData } from "./http"; +import type { AssetStatus } from "./assets"; + +export type InventoryCheckScopeType = "all" | "legal_entity" | "cost_center" | "location"; +export type InventoryCheckStatus = "draft" | "counting" | "completed"; +export type InventoryCheckResult = "matched" | "missing" | "extra" | "damaged"; + +export type InventoryCheckItem = { + id: string; + assetId: string; + result: InventoryCheckResult; + note: string | null; + asset: { + id: string; + assetNo: string; + name: string; + status: AssetStatus; + legalEntityId: string; + costCenterId: string; + location: string | null; + assigneeEmployeeId: string | null; + } | null; +}; + +export type InventoryCheck = { + id: string; + checkNo: string; + scopeType: InventoryCheckScopeType; + scopeRefId: string | null; + status: InventoryCheckStatus; + startedAt: string | null; + completedAt: string | null; + createdAt: string | null; + updatedAt: string | null; + items: InventoryCheckItem[]; +}; + +function toQueryString(query: Record) { + const params = new URLSearchParams(); + for (const [key, value] of Object.entries(query)) { + if (!value) { + continue; + } + params.set(key, value); + } + const encoded = params.toString(); + return encoded ? `?${encoded}` : ""; +} + +export async function fetchInventoryChecks(params: { status?: InventoryCheckStatus; scopeType?: InventoryCheckScopeType } = {}) { + return apiRequestData<{ items: InventoryCheck[] }>( + `/api/inventory-checks${toQueryString(params as Record)}`, + ); +} + +export async function createInventoryCheck(payload: { scopeType: InventoryCheckScopeType; scopeRefId?: string | null }) { + return apiRequestData<{ inventoryCheck: InventoryCheck }>("/api/inventory-checks", { + method: "POST", + body: JSON.stringify(payload), + }); +} + +export async function startInventoryCheck(inventoryCheckId: string) { + return apiRequestData<{ inventoryCheck: InventoryCheck }>(`/api/inventory-checks/${inventoryCheckId}/start`, { + method: "POST", + }); +} + +export async function recordInventoryItems( + inventoryCheckId: string, + payload: { items: Array<{ assetId: string; result: InventoryCheckResult; note?: string | null }> }, +) { + return apiRequestData<{ inventoryCheck: InventoryCheck }>(`/api/inventory-checks/${inventoryCheckId}/items`, { + method: "POST", + body: JSON.stringify(payload), + }); +} + +export async function completeInventoryCheck(inventoryCheckId: string) { + return apiRequestData<{ inventoryCheck: InventoryCheck }>(`/api/inventory-checks/${inventoryCheckId}/complete`, { + method: "POST", + }); +} diff --git a/client/src/components/navigation/NavLinks.tsx b/client/src/components/navigation/NavLinks.tsx index 1524bc1f..af902882 100644 --- a/client/src/components/navigation/NavLinks.tsx +++ b/client/src/components/navigation/NavLinks.tsx @@ -42,6 +42,12 @@ export const NAV_ITEMS: NavItem[] = [ icon: "inventory_2", anyOf: ["asset.view_own", "asset.view_all", "asset.manage", "asset.transfer", "asset.inventory"], }, + { + to: "/inventory-checks", + label: "资产盘点", + icon: "fact_check", + anyOf: ["asset.inventory"], + }, { to: "/invoice-pool", label: "票据中心", diff --git a/client/src/pages/AssetRegistryPage.tsx b/client/src/pages/AssetRegistryPage.tsx index 7b97e5e5..682b9f36 100644 --- a/client/src/pages/AssetRegistryPage.tsx +++ b/client/src/pages/AssetRegistryPage.tsx @@ -3,6 +3,7 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { toast } from "sonner"; import { mapApiErrorMessage } from "../api/errorMessages"; import { + assignAsset, createAsset, createAssetCategory, fetchAssetAssignees, @@ -11,6 +12,8 @@ import { fetchAssets, patchAsset, patchAssetCategory, + returnAsset, + transferAsset, type AssetCategoryItem, type AssetStatus, } from "../api/assets"; @@ -77,6 +80,7 @@ export function AssetRegistryPage() { const { permissions } = useAuth(); const canManage = hasPermission(permissions, "asset.manage"); + const canTransfer = hasPermission(permissions, "asset.transfer"); const canViewAll = hasAnyPermission(permissions, ["asset.view_all", "asset.manage", "asset.transfer", "asset.inventory"]); const canReadProcurementSources = canManage && hasAnyPermission(permissions, ["procurement.view_all", "procurement.manage"]); @@ -94,6 +98,10 @@ export function AssetRegistryPage() { const [isCreatingAsset, setIsCreatingAsset] = useState(false); const [assetForm, setAssetForm] = useState(initialAssetForm); const [assetFormError, setAssetFormError] = useState(""); + const [movementEmployeeId, setMovementEmployeeId] = useState(""); + const [movementLocation, setMovementLocation] = useState(""); + const [movementNote, setMovementNote] = useState(""); + const [movementError, setMovementError] = useState(""); const categoriesQuery = useQuery({ queryKey: ["assets", "categories", canManage], @@ -182,6 +190,10 @@ export function AssetRegistryPage() { activatedAt: asset.activatedAt ?? "", retiredAt: asset.retiredAt ?? "", }); + setMovementEmployeeId(""); + setMovementLocation(asset.location ?? ""); + setMovementNote(""); + setMovementError(""); setAssetFormError(""); }, [assetDetailQuery.data]); @@ -224,7 +236,7 @@ export function AssetRegistryPage() { setAssetFormError(""); } - function buildAssetPayload() { + function buildCreateAssetPayload() { return { name: assetForm.name.trim(), categoryId: assetForm.categoryId, @@ -240,6 +252,20 @@ export function AssetRegistryPage() { }; } + function buildPatchAssetPayload() { + return { + name: assetForm.name.trim(), + categoryId: assetForm.categoryId, + legalEntityId: assetForm.legalEntityId, + costCenterId: assetForm.costCenterId, + serialNo: assetForm.serialNo.trim() || null, + location: assetForm.location.trim() || null, + status: assetForm.status, + activatedAt: assetForm.activatedAt || null, + retiredAt: assetForm.retiredAt || null, + }; + } + function validateAssetForm() { if (!assetForm.name.trim()) { throw new Error("请填写资产名称"); @@ -284,14 +310,13 @@ export function AssetRegistryPage() { const saveAssetMutation = useMutation({ mutationFn: async () => { validateAssetForm(); - const payload = buildAssetPayload(); if (isCreatingAsset) { - return createAsset(payload); + return createAsset(buildCreateAssetPayload()); } if (!selectedAssetId) { throw new Error("请先选择资产卡片"); } - return patchAsset(selectedAssetId, payload); + return patchAsset(selectedAssetId, buildPatchAssetPayload()); }, onSuccess: async (data) => { await Promise.all([ @@ -310,6 +335,94 @@ export function AssetRegistryPage() { }, }); + + const assignAssetMutation = useMutation({ + mutationFn: async () => { + if (!currentAsset) { + throw new Error("请先选择资产卡片"); + } + if (!movementEmployeeId) { + throw new Error("请选择领用人"); + } + return assignAsset(currentAsset.id, { + employeeId: movementEmployeeId, + note: movementNote.trim() || null, + }); + }, + onSuccess: async () => { + await Promise.all([ + queryClient.invalidateQueries({ queryKey: ["assets", "list"] }), + queryClient.invalidateQueries({ queryKey: ["assets", "detail"] }), + ]); + setMovementEmployeeId(""); + setMovementNote(""); + setMovementError(""); + toast.success("资产已领用"); + }, + onError: (error) => { + const message = resolveError(error); + setMovementError(message); + toast.error(message); + }, + }); + + const returnAssetMutation = useMutation({ + mutationFn: async () => { + if (!currentAsset) { + throw new Error("请先选择资产卡片"); + } + return returnAsset(currentAsset.id, { note: movementNote.trim() || null }); + }, + onSuccess: async () => { + await Promise.all([ + queryClient.invalidateQueries({ queryKey: ["assets", "list"] }), + queryClient.invalidateQueries({ queryKey: ["assets", "detail"] }), + ]); + setMovementEmployeeId(""); + setMovementNote(""); + setMovementError(""); + toast.success("资产已归还"); + }, + onError: (error) => { + const message = resolveError(error); + setMovementError(message); + toast.error(message); + }, + }); + + const transferAssetMutation = useMutation({ + mutationFn: async () => { + if (!currentAsset) { + throw new Error("请先选择资产卡片"); + } + const nextLocation = movementLocation.trim() || null; + const nextEmployeeId = movementEmployeeId || undefined; + if (!nextEmployeeId && nextLocation === (currentAsset.location ?? null)) { + throw new Error("请至少调整归属人或位置"); + } + return transferAsset(currentAsset.id, { + toEmployeeId: nextEmployeeId, + toLocation: nextLocation, + note: movementNote.trim() || null, + }); + }, + onSuccess: async () => { + await Promise.all([ + queryClient.invalidateQueries({ queryKey: ["assets", "list"] }), + queryClient.invalidateQueries({ queryKey: ["assets", "detail"] }), + ]); + setMovementEmployeeId(""); + setMovementNote(""); + setMovementError(""); + toast.success("资产调拨已完成"); + }, + onError: (error) => { + const message = resolveError(error); + setMovementError(message); + toast.error(message); + }, + }); + const purchaseOptions = useMemo(() => { const items = approvedPurchasesQuery.data?.items ?? []; const currentPurchase = currentAsset?.purchaseRequest; @@ -356,7 +469,7 @@ export function AssetRegistryPage() { subtitle="维护资产分类与资产卡片,支持法体、成本中心和采购来源引用。" actions={ canManage ? ( - ) : null @@ -368,7 +481,7 @@ export function AssetRegistryPage() {

资产分类

{canManage ? ( - ) : null} @@ -377,11 +490,11 @@ export function AssetRegistryPage() {
- setCategoryForm((current) => ({ ...current, code: event.target.value }))} /> + setCategoryForm((current) => ({ ...current, code: event.target.value }))} />
- setCategoryForm((current) => ({ ...current, name: event.target.value }))} /> + setCategoryForm((current) => ({ ...current, name: event.target.value }))} />
@@ -399,7 +512,7 @@ export function AssetRegistryPage() { 启用分类 {canManage ? ( - ) : null} @@ -511,7 +624,7 @@ export function AssetRegistryPage() { {(assetsQuery.data?.items ?? []).length ? ( assetsQuery.data?.items.map((item) => ( - { + { setIsCreatingAsset(false); setSelectedAssetId(item.id); }} className={item.id === selectedAssetId && !isCreatingAsset ? "bg-zinc-50" : "cursor-pointer"}> @@ -566,11 +679,11 @@ export function AssetRegistryPage() {
- setAssetForm((current) => ({ ...current, name: event.target.value }))} /> + setAssetForm((current) => ({ ...current, name: event.target.value }))} />
- setAssetForm((current) => ({ ...current, status: event.target.value as AssetStatus }))}> {assetStatusOptions().filter((item) => item.value).map((item) => (
- setAssetForm((current) => ({ ...current, categoryId: event.target.value }))}> {(categoriesQuery.data?.items ?? []).filter((item) => item.enabled).map((item) => (
- { const legalEntityId = event.target.value; setAssetForm((current) => ({ ...current, @@ -611,7 +724,7 @@ export function AssetRegistryPage() {
- setAssetForm((current) => ({ ...current, costCenterId: event.target.value }))}> {filteredAssetCostCenters.map((item) => (
- setAssetForm((current) => ({ ...current, assigneeEmployeeId: event.target.value }))}> {(assigneesQuery.data?.items ?? []).map((item) => (
- setAssetForm((current) => ({ ...current, serialNo: event.target.value }))} /> + setAssetForm((current) => ({ ...current, serialNo: event.target.value }))} />
- setAssetForm((current) => ({ ...current, location: event.target.value }))} /> + setAssetForm((current) => ({ ...current, location: event.target.value }))} />
@@ -660,9 +773,95 @@ export function AssetRegistryPage() {
+ + {currentAsset && canTransfer && !isCreatingAsset ? ( +
+
+
+

资产流转

+

通过受控动作执行领用、归还和调拨,并写入审计记录。

+
+
+ 当前归属人:{currentAsset.assignee?.name ?? "未分配"} · 位置:{currentAsset.location ?? "未设置"} +
+
+ + {movementError ? ( +

{movementError}

+ ) : null} + +
+
+ + +
+
+ + setMovementLocation(event.target.value)} + /> +
+
+ + setMovementNote(event.target.value)} + /> +
+
+ +
+ + + +
+
+ ) : null} +
{canManage ? ( - ) : null} diff --git a/client/src/pages/InventoryChecksPage.tsx b/client/src/pages/InventoryChecksPage.tsx new file mode 100644 index 00000000..2586bc2c --- /dev/null +++ b/client/src/pages/InventoryChecksPage.tsx @@ -0,0 +1,416 @@ +import { useEffect, useMemo, useState } from "react"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { toast } from "sonner"; +import { fetchAssets } from "../api/assets"; +import { mapApiErrorMessage } from "../api/errorMessages"; +import { + completeInventoryCheck, + createInventoryCheck, + fetchInventoryChecks, + recordInventoryItems, + startInventoryCheck, + type InventoryCheckResult, + type InventoryCheckScopeType, +} from "../api/inventoryChecks"; +import { fetchCostCenters, fetchLegalEntities } from "../api/masterData"; +import { isApiError } from "../api/types"; +import { PageHeader } from "../components/common/PageHeader"; +import { TableSkeleton } from "../components/common/TableSkeleton"; +import { Button } from "../components/ui/button"; +import { Input } from "../components/ui/input"; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../components/ui/table"; + +function resolveError(error: unknown) { + if (isApiError(error)) { + return `${error.code}: ${mapApiErrorMessage(error.code, error.details, error.message)}`; + } + if (error instanceof Error) { + return error.message; + } + return "请求失败,请稍后重试。"; +} + +function scopeLabel(scopeType: InventoryCheckScopeType) { + switch (scopeType) { + case "all": + return "全量盘点"; + case "legal_entity": + return "法体盘点"; + case "cost_center": + return "成本中心盘点"; + case "location": + return "位置盘点"; + default: + return scopeType; + } +} + +function resultLabel(result: InventoryCheckResult) { + switch (result) { + case "matched": + return "一致"; + case "missing": + return "缺失"; + case "extra": + return "盘盈"; + case "damaged": + return "损坏"; + default: + return result; + } +} + +function statusLabel(status: string) { + switch (status) { + case "draft": + return "草稿"; + case "counting": + return "盘点中"; + case "completed": + return "已完成"; + default: + return status; + } +} + +export function InventoryChecksPage() { + const queryClient = useQueryClient(); + + const [scopeType, setScopeType] = useState("all"); + const [scopeRefId, setScopeRefId] = useState(""); + const [selectedCheckId, setSelectedCheckId] = useState(""); + const [recordAssetId, setRecordAssetId] = useState(""); + const [recordResult, setRecordResult] = useState("matched"); + const [recordNote, setRecordNote] = useState(""); + + const inventoryChecksQuery = useQuery({ + queryKey: ["inventory-checks"], + queryFn: () => fetchInventoryChecks(), + }); + + const legalEntitiesQuery = useQuery({ + queryKey: ["inventory-checks", "legal-entities"], + queryFn: () => fetchLegalEntities({ enabled: true }), + }); + + const costCentersQuery = useQuery({ + queryKey: ["inventory-checks", "cost-centers"], + queryFn: () => fetchCostCenters({ enabled: true }), + }); + + const assetsQuery = useQuery({ + queryKey: ["inventory-checks", "assets"], + queryFn: () => fetchAssets({ mine: false }), + }); + + useEffect(() => { + const nextId = inventoryChecksQuery.data?.items?.[0]?.id ?? ""; + if (!selectedCheckId && nextId) { + setSelectedCheckId(nextId); + } + if (selectedCheckId && inventoryChecksQuery.data?.items?.some((item) => item.id === selectedCheckId)) { + return; + } + if (nextId !== selectedCheckId) { + setSelectedCheckId(nextId); + } + }, [inventoryChecksQuery.data?.items, selectedCheckId]); + + const selectedCheck = useMemo( + () => (inventoryChecksQuery.data?.items ?? []).find((item) => item.id === selectedCheckId) ?? null, + [inventoryChecksQuery.data?.items, selectedCheckId], + ); + + const scopedAssets = useMemo(() => { + const items = assetsQuery.data?.items ?? []; + const scope = selectedCheck ?? { scopeType, scopeRefId: scopeRefId || null }; + if (scope.scopeType === "all") { + return items; + } + if (scope.scopeType === "legal_entity") { + return items.filter((item) => item.legalEntityId === scope.scopeRefId); + } + if (scope.scopeType === "cost_center") { + return items.filter((item) => item.costCenterId === scope.scopeRefId); + } + return items.filter((item) => (item.location ?? "") === (scope.scopeRefId ?? "")); + }, [assetsQuery.data?.items, scopeRefId, scopeType, selectedCheck]); + + const createCheckMutation = useMutation({ + mutationFn: async () => { + const payload = { + scopeType, + scopeRefId: scopeType === "all" ? null : scopeRefId || null, + }; + return createInventoryCheck(payload); + }, + onSuccess: async (data) => { + await queryClient.invalidateQueries({ queryKey: ["inventory-checks"] }); + setSelectedCheckId(data.inventoryCheck.id); + toast.success("盘点任务已创建"); + }, + onError: (error) => { + toast.error(resolveError(error)); + }, + }); + + const startCheckMutation = useMutation({ + mutationFn: async () => { + if (!selectedCheck) { + throw new Error("请先选择盘点任务"); + } + return startInventoryCheck(selectedCheck.id); + }, + onSuccess: async () => { + await queryClient.invalidateQueries({ queryKey: ["inventory-checks"] }); + toast.success("盘点任务已启动"); + }, + onError: (error) => { + toast.error(resolveError(error)); + }, + }); + + const recordItemMutation = useMutation({ + mutationFn: async () => { + if (!selectedCheck) { + throw new Error("请先选择盘点任务"); + } + if (!recordAssetId) { + throw new Error("请选择资产卡片"); + } + return recordInventoryItems(selectedCheck.id, { + items: [ + { + assetId: recordAssetId, + result: recordResult, + note: recordNote.trim() || null, + }, + ], + }); + }, + onSuccess: async () => { + await queryClient.invalidateQueries({ queryKey: ["inventory-checks"] }); + setRecordAssetId(""); + setRecordNote(""); + toast.success("盘点结果已记录"); + }, + onError: (error) => { + toast.error(resolveError(error)); + }, + }); + + const completeCheckMutation = useMutation({ + mutationFn: async () => { + if (!selectedCheck) { + throw new Error("请先选择盘点任务"); + } + return completeInventoryCheck(selectedCheck.id); + }, + onSuccess: async () => { + await queryClient.invalidateQueries({ queryKey: ["inventory-checks"] }); + toast.success("盘点任务已完成"); + }, + onError: (error) => { + toast.error(resolveError(error)); + }, + }); + + const scopeOptions = useMemo(() => { + if (scopeType === "legal_entity") { + return (legalEntitiesQuery.data?.items ?? []).map((item) => ({ id: item.id, label: item.name })); + } + if (scopeType === "cost_center") { + return (costCentersQuery.data?.items ?? []).map((item) => ({ id: item.id, label: item.name })); + } + return []; + }, [costCentersQuery.data?.items, legalEntitiesQuery.data?.items, scopeType]); + + return ( +
+ + +
+
+

新建盘点任务

+
+ + +
+ + {scopeType === "location" ? ( +
+ + setScopeRefId(event.target.value)} /> +
+ ) : null} + + {scopeType !== "all" && scopeType !== "location" ? ( +
+ + +
+ ) : null} + + + +
+ + + + 单号 + 范围 + 状态 + + + + {(inventoryChecksQuery.data?.items ?? []).map((item) => ( + setSelectedCheckId(item.id)} + > + {item.checkNo} + {scopeLabel(item.scopeType)} + {statusLabel(item.status)} + + ))} + {!inventoryChecksQuery.isLoading && !(inventoryChecksQuery.data?.items ?? []).length ? ( + + + 暂无盘点任务 + + + ) : null} + +
+
+
+ +
+ {inventoryChecksQuery.isLoading ? : null} + {inventoryChecksQuery.isError ? ( +

{resolveError(inventoryChecksQuery.error)}

+ ) : null} + {!inventoryChecksQuery.isLoading && !selectedCheck ? ( +
+ 请先创建或选择盘点任务。 +
+ ) : null} + + {selectedCheck ? ( + <> +
+
+

{selectedCheck.checkNo}

+

+ {scopeLabel(selectedCheck.scopeType)} · {selectedCheck.scopeRefId ?? "全量"} +

+
+
+ {statusLabel(selectedCheck.status)} + {selectedCheck.status === "draft" ? ( + + ) : null} + {selectedCheck.status === "counting" ? ( + + ) : null} +
+
+ + {selectedCheck.status === "counting" ? ( +
+

记录盘点结果

+
+
+ + +
+
+ + +
+
+ + setRecordNote(event.target.value)} /> +
+
+
+ +
+
+ ) : null} + +
+ + + + 资产 + 结果 + 备注 + + + + {selectedCheck.items.length ? ( + selectedCheck.items.map((item) => ( + + +
{item.asset?.assetNo ?? item.assetId}
+
{item.asset?.name ?? "未知资产"}
+
+ {resultLabel(item.result)} + {item.note ?? "-"} +
+ )) + ) : ( + + + 暂无盘点结果 + + + )} +
+
+
+ + ) : null} +
+
+
+ ); +} diff --git a/client/src/router/index.tsx b/client/src/router/index.tsx index c30983e8..b835e90e 100644 --- a/client/src/router/index.tsx +++ b/client/src/router/index.tsx @@ -19,6 +19,7 @@ import { LeaveRequestPage } from "../pages/LeaveRequestPage"; import { NotificationsPage } from "../pages/NotificationsPage"; import { PurchaseRequestsPage } from "../pages/PurchaseRequestsPage"; import { AssetRegistryPage } from "../pages/AssetRegistryPage"; +import { InventoryChecksPage } from "../pages/InventoryChecksPage"; import { NotFoundPage } from "../pages/NotFoundPage"; import { ProfilePage } from "../pages/ProfilePage"; import { ReimbursementDetailPage } from "../pages/ReimbursementDetailPage"; @@ -118,6 +119,15 @@ export function AppRouter() { } /> + + + + } + /> +