FPSMS-frontend
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

700 line
22 KiB

  1. 'use client';
  2. import { InventoryLotLineResult, InventoryResult } from '@/app/api/inventory';
  3. import { useTranslation } from 'react-i18next';
  4. import SearchBox, { Criterion } from '../SearchBox';
  5. import { useCallback, useEffect, useMemo, useState } from 'react';
  6. import { uniq, uniqBy } from 'lodash';
  7. import InventoryTable from './InventoryTable';
  8. import { defaultPagingController } from '../SearchResults/SearchResults';
  9. import InventoryLotLineTable from './InventoryLotLineTable';
  10. import { useQrCodeScannerContext } from '@/components/QrCodeScannerProvider/QrCodeScannerProvider';
  11. import {
  12. analyzeQrCode,
  13. SearchInventory,
  14. SearchInventoryLotLine,
  15. fetchInventoriesLatest,
  16. fetchInventoryLotLines,
  17. } from '@/app/api/inventory/actions';
  18. import { PrinterCombo } from '@/app/api/settings/printer';
  19. import { ItemCombo, fetchItemsWithDetails, ItemWithDetails } from '@/app/api/settings/item/actions';
  20. import {
  21. Button,
  22. Dialog,
  23. DialogActions,
  24. DialogContent,
  25. DialogTitle,
  26. TextField,
  27. Box,
  28. CircularProgress,
  29. Table,
  30. TableBody,
  31. TableCell,
  32. TableHead,
  33. TableRow,
  34. Radio,
  35. } from '@mui/material';
  36. interface Props {
  37. inventories: InventoryResult[];
  38. printerCombo?: PrinterCombo[];
  39. }
  40. type SearchQuery = Partial<
  41. Omit<
  42. InventoryResult,
  43. | "id"
  44. | "qty"
  45. | "uomCode"
  46. | "uomUdfudesc"
  47. | "germPerSmallestUnit"
  48. | "qtyPerSmallestUnit"
  49. | "itemSmallestUnit"
  50. | "price"
  51. | "description"
  52. | "category"
  53. >
  54. >;
  55. type SearchParamNames = keyof SearchQuery;
  56. /** FP-MTMS Version Checklist | Functions Ref. No. 18 | v1.0.0 | 2026-07-17 */
  57. const InventorySearch: React.FC<Props> = ({ inventories, printerCombo }) => {
  58. const { t } = useTranslation(['inventory', 'common', 'item']);
  59. const buildSyntheticInventory = useCallback(
  60. (item: ItemWithDetails): InventoryResult => ({
  61. id: 0,
  62. itemId: item.id,
  63. itemCode: item.code,
  64. itemName: item.name,
  65. itemType: 'Material',
  66. onHandQty: 0,
  67. onHoldQty: 0,
  68. unavailableQty: 0,
  69. availableQty: 0,
  70. uomCode: item.uom,
  71. uomUdfudesc: item.uomDesc,
  72. uomShortDesc: item.uom,
  73. qtyPerSmallestUnit: 1,
  74. baseUom: item.uom,
  75. price: 0,
  76. currencyName: '',
  77. status: 'active',
  78. latestMarketUnitPrice: undefined,
  79. latestMupUpdatedDate: undefined,
  80. }),
  81. [],
  82. );
  83. const getFirstItemRecord = useCallback((res: any): ItemWithDetails | null => {
  84. if (!res) return null;
  85. if (Array.isArray(res)) return (res[0] as ItemWithDetails) ?? null;
  86. if (Array.isArray(res?.records)) return (res.records[0] as ItemWithDetails) ?? null;
  87. return null;
  88. }, []);
  89. // Inventory
  90. const [filteredInventories, setFilteredInventories] = useState<InventoryResult[]>([]);
  91. const [inventoriesPagingController, setInventoriesPagingController] = useState(defaultPagingController)
  92. const [inventoriesTotalCount, setInventoriesTotalCount] = useState(0)
  93. const [selectedInventory, setSelectedInventory] = useState<InventoryResult | null>(null)
  94. // Inventory Lot Line
  95. const [filteredInventoryLotLines, setFilteredInventoryLotLines] = useState<InventoryLotLineResult[]>([]);
  96. const [inventoryLotLinesPagingController, setInventoryLotLinesPagingController] = useState(defaultPagingController)
  97. const [inventoryLotLinesTotalCount, setInventoryLotLinesTotalCount] = useState(0)
  98. // Scan-mode UI (hardware QR scanner via QrCodeScannerProvider)
  99. const qrScanner = useQrCodeScannerContext();
  100. const [scanUiMode, setScanUiMode] = useState<'idle' | 'scanning'>('idle');
  101. const [scanHoverCancel, setScanHoverCancel] = useState(false);
  102. // Resolved lot no for filtering
  103. const [lotNoFilter, setLotNoFilter] = useState('');
  104. const [scannedItemId, setScannedItemId] = useState<number | null>(null);
  105. // Opening inventory (pure opening stock for items without existing inventory)
  106. const [openingItems, setOpeningItems] = useState<ItemCombo[]>([]);
  107. const [openingModalOpen, setOpeningModalOpen] = useState(false);
  108. const [openingSelectedItem, setOpeningSelectedItem] = useState<ItemCombo | null>(null);
  109. const [openingLoading, setOpeningLoading] = useState(false);
  110. const [openingSearchText, setOpeningSearchText] = useState('');
  111. const defaultInputs = useMemo(
  112. () => ({
  113. itemId: '',
  114. itemCode: '',
  115. itemName: '',
  116. itemType: '',
  117. onHandQty: '',
  118. onHoldQty: '',
  119. unavailableQty: '',
  120. availableQty: '',
  121. currencyName: '',
  122. status: '',
  123. baseUom: '',
  124. uomShortDesc: '',
  125. latestMarketUnitPrice: '',
  126. latestMupUpdatedDate: '',
  127. }),
  128. [],
  129. );
  130. const [inputs, setInputs] = useState<Record<SearchParamNames, string>>(defaultInputs);
  131. const searchCriteria: Criterion<SearchParamNames>[] = useMemo(
  132. () => [
  133. { label: t('Code'), paramName: 'itemCode', type: 'text' },
  134. { label: t('Name'), paramName: 'itemName', type: 'text' },
  135. {
  136. label: t('Type'),
  137. paramName: 'itemType',
  138. type: 'select-labelled',
  139. options: uniq(inventories.map((i) => i.itemType)).map((type) => ({
  140. value: type,
  141. label: t(type),
  142. })),
  143. },
  144. // {
  145. // label: t("Status"),
  146. // paramName: "status",
  147. // type: "select",
  148. // options: uniq(inventories.map((i) => i.status)),
  149. // },
  150. ],
  151. [t, inventories],
  152. );
  153. // Inventory
  154. const refetchInventoryData = useCallback(
  155. async (
  156. query: Record<SearchParamNames, string>,
  157. actionType: 'reset' | 'search' | 'paging' | 'init',
  158. pagingController: typeof defaultPagingController,
  159. lotNo: string,
  160. ) => {
  161. console.log('%c Action Type 1.', 'color:red', actionType);
  162. // Avoid loading data again
  163. if (actionType === 'paging' && pagingController === defaultPagingController) {
  164. return;
  165. }
  166. console.log('%c Action Type 2.', 'color:blue', actionType);
  167. const params: SearchInventory = {
  168. code: query?.itemCode ?? '',
  169. name: query?.itemName ?? '',
  170. type: query?.itemType.toLowerCase() === 'all' ? '' : query?.itemType ?? '',
  171. lotNo: lotNo?.trim() ? lotNo.trim() : undefined,
  172. pageNum: pagingController.pageNum - 1,
  173. pageSize: pagingController.pageSize,
  174. };
  175. const response = await fetchInventoriesLatest(params);
  176. if (response) {
  177. setInventoriesTotalCount(response.total);
  178. switch (actionType) {
  179. case 'init':
  180. case 'reset':
  181. case 'search':
  182. setFilteredInventories(() => response.records);
  183. break;
  184. case 'paging':
  185. setFilteredInventories((fi) =>
  186. uniqBy([...fi, ...response.records], 'itemId'),
  187. );
  188. }
  189. }
  190. return response;
  191. },
  192. [],
  193. );
  194. useEffect(() => {
  195. refetchInventoryData(defaultInputs, 'init', defaultPagingController, '');
  196. }, [defaultInputs, refetchInventoryData]);
  197. useEffect(() => {
  198. // if (!isEqual(inventoriesPagingController, defaultPagingController)) {
  199. refetchInventoryData(inputs, 'paging', inventoriesPagingController, lotNoFilter)
  200. // }
  201. }, [inventoriesPagingController, inputs, lotNoFilter, refetchInventoryData])
  202. // Inventory Lot Line
  203. const refetchInventoryLotLineData = useCallback(
  204. async (
  205. itemId: number | null,
  206. actionType: 'reset' | 'search' | 'paging',
  207. pagingController: typeof defaultPagingController,
  208. ) => {
  209. if (!itemId) {
  210. setSelectedInventory(null);
  211. setInventoryLotLinesTotalCount(0);
  212. setFilteredInventoryLotLines([]);
  213. return;
  214. }
  215. // Avoid loading data again
  216. if (actionType === 'paging' && pagingController === defaultPagingController) {
  217. return;
  218. }
  219. const params: SearchInventoryLotLine = {
  220. itemId,
  221. pageNum: pagingController.pageNum - 1,
  222. pageSize: pagingController.pageSize,
  223. };
  224. const response = await fetchInventoryLotLines(params);
  225. if (response) {
  226. setInventoryLotLinesTotalCount(response.total);
  227. switch (actionType) {
  228. case 'reset':
  229. case 'search':
  230. setFilteredInventoryLotLines(() => response.records);
  231. break;
  232. case 'paging':
  233. setFilteredInventoryLotLines((fi) => uniqBy([...fi, ...response.records], 'id'));
  234. }
  235. }
  236. },
  237. [],
  238. );
  239. useEffect(() => {
  240. // if (!isEqual(inventoryLotLinesPagingController, defaultPagingController)) {
  241. refetchInventoryLotLineData(selectedInventory?.itemId ?? null, 'paging', inventoryLotLinesPagingController)
  242. // }
  243. }, [inventoryLotLinesPagingController])
  244. // Reset
  245. const onReset = useCallback(() => {
  246. refetchInventoryData(defaultInputs, 'reset', defaultPagingController, '');
  247. refetchInventoryLotLineData(null, 'reset', defaultPagingController);
  248. // setFilteredInventories(inventories);
  249. setLotNoFilter('');
  250. setScannedItemId(null);
  251. setScanUiMode('idle');
  252. setScanHoverCancel(false);
  253. qrScanner.stopScan();
  254. qrScanner.resetScan();
  255. setInputs(() => defaultInputs)
  256. setInventoriesPagingController(() => defaultPagingController)
  257. setInventoryLotLinesPagingController(() => defaultPagingController)
  258. }, [defaultInputs, qrScanner, refetchInventoryData, refetchInventoryLotLineData]);
  259. // Click Row
  260. const onInventoryRowClick = useCallback(
  261. (item: InventoryResult) => {
  262. refetchInventoryLotLineData(item.itemId, 'search', defaultPagingController);
  263. setSelectedInventory(item);
  264. setInventoryLotLinesPagingController(() => defaultPagingController);
  265. },
  266. [refetchInventoryLotLineData],
  267. );
  268. // On Search
  269. const onSearch = useCallback(
  270. async (query: Record<SearchParamNames, string>) => {
  271. setLotNoFilter('');
  272. setScannedItemId(null);
  273. setScanUiMode('idle');
  274. setScanHoverCancel(false);
  275. qrScanner.stopScan();
  276. qrScanner.resetScan();
  277. const invRes = await refetchInventoryData(query, 'search', defaultPagingController, '');
  278. await refetchInventoryLotLineData(null, 'search', defaultPagingController);
  279. setInputs(() => query);
  280. setInventoriesPagingController(() => defaultPagingController);
  281. setInventoryLotLinesPagingController(() => defaultPagingController);
  282. // If there are no inventory rows, render a synthetic inventory so the "Stock Adjustment" chip can be used.
  283. if (invRes?.records?.length === 0) {
  284. try {
  285. const code = query.itemCode?.trim?.();
  286. const name = query.itemName?.trim?.();
  287. const lookupParams = code ? { code } : name ? { name } : null;
  288. if (lookupParams) {
  289. const itemRes = await fetchItemsWithDetails(lookupParams);
  290. const firstItem = getFirstItemRecord(itemRes);
  291. if (firstItem) {
  292. setSelectedInventory(buildSyntheticInventory(firstItem));
  293. setFilteredInventoryLotLines([]);
  294. setInventoryLotLinesPagingController(() => defaultPagingController);
  295. }
  296. }
  297. } catch (e) {
  298. console.error('Failed to build synthetic inventory:', e);
  299. }
  300. }
  301. },
  302. [
  303. qrScanner,
  304. refetchInventoryData,
  305. refetchInventoryLotLineData,
  306. buildSyntheticInventory,
  307. getFirstItemRecord,
  308. ],
  309. );
  310. const startLotScan = useCallback(() => {
  311. setScanHoverCancel(false);
  312. setScanUiMode('scanning');
  313. qrScanner.resetScan();
  314. qrScanner.startScan();
  315. }, [qrScanner]);
  316. const cancelLotScan = useCallback(() => {
  317. qrScanner.stopScan();
  318. qrScanner.resetScan();
  319. setScanUiMode('idle');
  320. setScanHoverCancel(false);
  321. }, [qrScanner]);
  322. useEffect(() => {
  323. if (scanUiMode !== 'scanning') return;
  324. const itemId = qrScanner.result?.itemId;
  325. const stockInLineId = qrScanner.result?.stockInLineId;
  326. if (!itemId || !stockInLineId) return;
  327. (async () => {
  328. try {
  329. const res = await analyzeQrCode({
  330. itemId: Number(itemId),
  331. stockInLineId: Number(stockInLineId),
  332. });
  333. const resolvedLotNo = res?.scanned?.lotNo?.trim?.() ? res.scanned.lotNo.trim() : '';
  334. if (!resolvedLotNo) return;
  335. setLotNoFilter(resolvedLotNo);
  336. setScannedItemId(res?.itemId ?? Number(itemId));
  337. const invRes = await refetchInventoryData(inputs, 'search', defaultPagingController, resolvedLotNo);
  338. const records = invRes?.records ?? [];
  339. const target = records.find((r) => r.itemId === (res?.itemId ?? Number(itemId))) ?? null;
  340. if (target) {
  341. onInventoryRowClick(target);
  342. } else {
  343. refetchInventoryLotLineData(null, 'search', defaultPagingController);
  344. // No inventory rows for this scanned item => show synthetic inventory with the existing chip workflow.
  345. const itemRes = await fetchItemsWithDetails({ code: res?.itemCode });
  346. const firstItem = getFirstItemRecord(itemRes);
  347. if (firstItem) {
  348. setSelectedInventory(buildSyntheticInventory(firstItem));
  349. setFilteredInventoryLotLines([]);
  350. setInventoryLotLinesPagingController(() => defaultPagingController);
  351. } else {
  352. setSelectedInventory(null);
  353. }
  354. }
  355. setInventoriesPagingController(() => defaultPagingController);
  356. setInventoryLotLinesPagingController(() => defaultPagingController);
  357. } catch (e) {
  358. console.error('Failed to analyze QR code:', e);
  359. } finally {
  360. // Always go back to initial state after a scan attempt
  361. cancelLotScan();
  362. }
  363. })();
  364. }, [
  365. cancelLotScan,
  366. inputs,
  367. onInventoryRowClick,
  368. qrScanner.result,
  369. refetchInventoryData,
  370. refetchInventoryLotLineData,
  371. buildSyntheticInventory,
  372. getFirstItemRecord,
  373. scanUiMode,
  374. ]);
  375. console.log('', 'color: #666', inventoriesPagingController);
  376. const handleOpenOpeningInventoryModal = useCallback(() => {
  377. setOpeningSelectedItem(null);
  378. setOpeningItems([]);
  379. setOpeningSearchText('');
  380. setOpeningModalOpen(true);
  381. }, []);
  382. const handleOpeningSearch = useCallback(async () => {
  383. const trimmed = openingSearchText.trim();
  384. if (!trimmed) {
  385. setOpeningItems([]);
  386. return;
  387. }
  388. setOpeningLoading(true);
  389. try {
  390. const searchParams: Record<string, any> = {
  391. pageSize: 50,
  392. pageNum: 1,
  393. };
  394. // Heuristic: if input contains space, treat as name; otherwise treat as code.
  395. if (trimmed.includes(' ')) {
  396. searchParams.name = trimmed;
  397. } else {
  398. searchParams.code = trimmed;
  399. }
  400. const response = await fetchItemsWithDetails(searchParams);
  401. let records: any[] = [];
  402. if (response && typeof response === 'object') {
  403. const anyRes = response as any;
  404. if (Array.isArray(anyRes.records)) {
  405. records = anyRes.records;
  406. } else if (Array.isArray(anyRes)) {
  407. records = anyRes;
  408. }
  409. }
  410. const combos: ItemCombo[] = records.map((item: any) => ({
  411. id: item.id,
  412. label: `${item.code} - ${item.name}`,
  413. uomId: item.uomId,
  414. uom: item.uom,
  415. uomDesc: item.uomDesc,
  416. group: item.group,
  417. currentStockBalance: item.currentStockBalance,
  418. }));
  419. setOpeningItems(combos);
  420. } catch (e) {
  421. console.error('Failed to search items for opening inventory:', e);
  422. setOpeningItems([]);
  423. } finally {
  424. setOpeningLoading(false);
  425. }
  426. }, [openingSearchText]);
  427. const handleConfirmOpeningInventory = useCallback(() => {
  428. if (!openingSelectedItem) {
  429. setOpeningModalOpen(false);
  430. return;
  431. }
  432. const rawLabel = openingSelectedItem.label ?? '';
  433. const [codePart, ...nameParts] = rawLabel.split(' - ');
  434. const itemCode = codePart?.trim() || rawLabel;
  435. const itemName = nameParts.join(' - ').trim() || itemCode;
  436. const syntheticInventory: InventoryResult = {
  437. id: 0,
  438. itemId: Number(openingSelectedItem.id),
  439. itemCode,
  440. itemName,
  441. itemType: 'Material',
  442. onHandQty: 0,
  443. onHoldQty: 0,
  444. unavailableQty: 0,
  445. availableQty: 0,
  446. uomCode: openingSelectedItem.uom,
  447. uomUdfudesc: openingSelectedItem.uomDesc,
  448. uomShortDesc: openingSelectedItem.uom,
  449. qtyPerSmallestUnit: 1,
  450. baseUom: openingSelectedItem.uom,
  451. price: 0,
  452. currencyName: '',
  453. status: 'active',
  454. latestMarketUnitPrice: undefined,
  455. latestMupUpdatedDate: undefined,
  456. };
  457. // Use this synthetic inventory to drive the stock adjustment UI
  458. setSelectedInventory(syntheticInventory);
  459. setFilteredInventoryLotLines([]);
  460. setInventoryLotLinesPagingController(() => defaultPagingController);
  461. setOpeningModalOpen(false);
  462. }, [openingSelectedItem]);
  463. return (
  464. <>
  465. <SearchBox
  466. criteria={searchCriteria}
  467. onSearch={(query) => {
  468. onSearch(query);
  469. }}
  470. onReset={onReset}
  471. extraActions={
  472. <Box sx={{ display: 'flex', gap: 1, alignItems: 'center', flexWrap: 'wrap' }}>
  473. {scanUiMode === 'idle' ? (
  474. <Button variant="contained" onClick={startLotScan}>
  475. {t('Search lot by QR code')}
  476. </Button>
  477. ) : (
  478. <>
  479. <Button variant="contained" disabled sx={{ bgcolor: 'grey.400', color: 'grey.800' }}>
  480. {t('Please scan...')}
  481. </Button>
  482. <Button variant="contained" color="error" onClick={cancelLotScan}>
  483. {t('Stop QR Scan')}
  484. </Button>
  485. </>
  486. )}
  487. <Button
  488. variant="outlined"
  489. color="secondary"
  490. onClick={handleOpenOpeningInventoryModal}
  491. sx={{ display: 'none' }}
  492. >
  493. {t('Add entry for items without inventory')}
  494. </Button>
  495. </Box>
  496. }
  497. />
  498. <InventoryTable
  499. inventories={filteredInventories}
  500. pagingController={inventoriesPagingController}
  501. setPagingController={setInventoriesPagingController}
  502. totalCount={inventoriesTotalCount}
  503. onRowClick={onInventoryRowClick}
  504. />
  505. <InventoryLotLineTable
  506. inventoryLotLines={filteredInventoryLotLines}
  507. pagingController={inventoryLotLinesPagingController}
  508. setPagingController={setInventoryLotLinesPagingController}
  509. totalCount={inventoryLotLinesTotalCount}
  510. inventory={selectedInventory}
  511. filterLotNo={lotNoFilter}
  512. printerCombo={printerCombo ?? []}
  513. onStockTransferSuccess={() =>
  514. refetchInventoryLotLineData(
  515. selectedInventory?.itemId ?? null,
  516. 'search',
  517. inventoryLotLinesPagingController,
  518. )
  519. }
  520. onStockAdjustmentSuccess={async () => {
  521. const itemId = selectedInventory?.itemId ?? null;
  522. // Refresh both blocks:
  523. // - middle: InventoryTable (inventories list)
  524. // - bottom: InventoryLotLineTable (lot lines for selected item)
  525. const invRes = await refetchInventoryData(
  526. inputs,
  527. 'search',
  528. inventoriesPagingController,
  529. lotNoFilter,
  530. );
  531. await refetchInventoryLotLineData(
  532. itemId,
  533. 'search',
  534. inventoryLotLinesPagingController,
  535. );
  536. // If inventory becomes available again after OPEN/ADJ, sync selected row.
  537. if (itemId != null && invRes?.records?.length) {
  538. const target = invRes.records.find((r) => r.itemId === itemId);
  539. if (target) setSelectedInventory(target);
  540. }
  541. }}
  542. />
  543. <Dialog
  544. open={openingModalOpen}
  545. onClose={() => setOpeningModalOpen(false)}
  546. fullWidth
  547. maxWidth="md"
  548. >
  549. <DialogTitle>{t('Add entry for items without inventory')}</DialogTitle>
  550. <DialogContent sx={{ pt: 2 }}>
  551. <Box sx={{ display: 'flex', gap: 1, mb: 2 }}>
  552. <TextField
  553. label={t('Item')}
  554. fullWidth
  555. value={openingSearchText}
  556. onChange={(e) => setOpeningSearchText(e.target.value)}
  557. onKeyDown={(e) => {
  558. if (e.key === 'Enter') {
  559. e.preventDefault();
  560. handleOpeningSearch();
  561. }
  562. }}
  563. sx={{ flex: 2 }}
  564. />
  565. <Button
  566. variant="contained"
  567. onClick={handleOpeningSearch}
  568. disabled={openingLoading}
  569. sx={{ flex: 1 }}
  570. >
  571. {openingLoading ? <CircularProgress size={20} /> : t('common:Search')}
  572. </Button>
  573. </Box>
  574. {openingItems.length === 0 && !openingLoading ? (
  575. <Box sx={{ py: 1, color: 'text.secondary', fontSize: 14 }}>
  576. {openingSearchText
  577. ? t('No data')
  578. : t('Enter item code or name to search')}
  579. </Box>
  580. ) : (
  581. <Table size="small">
  582. <TableHead>
  583. <TableRow>
  584. <TableCell />
  585. <TableCell>{t('Code')}</TableCell>
  586. <TableCell>{t('Name')}</TableCell>
  587. <TableCell>{t('UoM')}</TableCell>
  588. <TableCell align="right">{t('Current Stock')}</TableCell>
  589. </TableRow>
  590. </TableHead>
  591. <TableBody>
  592. {openingItems.map((it) => {
  593. const [code, ...nameParts] = (it.label ?? '').split(' - ');
  594. const name = nameParts.join(' - ');
  595. const selected = openingSelectedItem?.id === it.id;
  596. return (
  597. <TableRow
  598. key={it.id}
  599. hover
  600. selected={selected}
  601. onClick={() => setOpeningSelectedItem(it)}
  602. sx={{ cursor: 'pointer' }}
  603. >
  604. <TableCell padding="checkbox">
  605. <Radio checked={selected} />
  606. </TableCell>
  607. <TableCell>{code}</TableCell>
  608. <TableCell>{name}</TableCell>
  609. <TableCell>{it.uomDesc || it.uom}</TableCell>
  610. <TableCell align="right">
  611. {it.currentStockBalance != null ? it.currentStockBalance : '-'}
  612. </TableCell>
  613. </TableRow>
  614. );
  615. })}
  616. </TableBody>
  617. </Table>
  618. )}
  619. </DialogContent>
  620. <DialogActions>
  621. <Button onClick={() => setOpeningModalOpen(false)}>
  622. {t('common:Cancel')}
  623. </Button>
  624. <Button
  625. variant="contained"
  626. onClick={handleConfirmOpeningInventory}
  627. disabled={!openingSelectedItem}
  628. >
  629. {t('common:Confirm')}
  630. </Button>
  631. </DialogActions>
  632. </Dialog>
  633. </>
  634. );
  635. };
  636. export default InventorySearch;