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.
 
 

748 lines
23 KiB

  1. "use client";
  2. import { DoResult } from "@/app/api/do";
  3. import { DoSearchAll, DoSearchLiteResponse, fetchDoSearch, fetchAllDoSearch, fetchDoSearchList, releaseDo ,startBatchReleaseAsync, getBatchReleaseProgress} from "@/app/api/do/actions";
  4. import {
  5. startWorkbenchBatchReleaseAsyncV2,
  6. getWorkbenchBatchReleaseProgress,
  7. } from "@/app/api/doworkbench/actions";
  8. import { useRouter } from "next/navigation";
  9. import React, { ForwardedRef, useCallback, useEffect, useMemo, useState } from "react";
  10. import { useTranslation } from "react-i18next";
  11. import { Criterion } from "../SearchBox";
  12. import { isEmpty, sortBy, uniqBy, upperFirst } from "lodash";
  13. import { arrayToDateString, arrayToDayjs } from "@/app/utils/formatUtil";
  14. import SearchBox from "../SearchBox/SearchBox";
  15. import { EditNote } from "@mui/icons-material";
  16. import InputDataGrid from "../InputDataGrid";
  17. import { CreateConsoDoInput } from "@/app/api/do/actions";
  18. import { TableRow } from "../InputDataGrid/InputDataGrid";
  19. import {
  20. FooterPropsOverrides,
  21. GridColDef,
  22. GridRowModel,
  23. GridToolbarContainer,
  24. useGridApiRef,
  25. } from "@mui/x-data-grid";
  26. import {
  27. FormProvider,
  28. SubmitErrorHandler,
  29. SubmitHandler,
  30. useForm,
  31. } from "react-hook-form";
  32. import { Box, Button, Paper, Stack, Typography, TablePagination } from "@mui/material";
  33. import StyledDataGrid from "../StyledDataGrid";
  34. import { GridRowSelectionModel } from "@mui/x-data-grid";
  35. import Swal from "sweetalert2";
  36. import { useSession } from "next-auth/react";
  37. import { SessionWithTokens } from "@/config/authConfig";
  38. type Props = {
  39. filterArgs?: Record<string, any>;
  40. searchQuery?: Record<string, any>;
  41. onDeliveryOrderSearch?: () => void;
  42. };
  43. type SearchBoxInputs = Record<"code" | "status" | "estimatedArrivalDate" | "orderDate" | "supplierName" | "shopName" | "deliveryOrderLines" | "truckLanceCode" | "codeTo" | "statusTo" | "estimatedArrivalDateTo" | "orderDateTo" | "supplierNameTo" | "shopNameTo" | "deliveryOrderLinesTo" | "truckLanceCodeTo", string>;
  44. type SearchParamNames = keyof SearchBoxInputs;
  45. // put all this into a new component
  46. // ConsoDoForm
  47. type EntryError =
  48. | {
  49. [field in keyof DoResult]?: string;
  50. }
  51. | undefined;
  52. type DoRow = TableRow<Partial<DoResult>, EntryError>;
  53. const DoSearch: React.FC<Props> = ({ filterArgs, searchQuery, onDeliveryOrderSearch }) => {
  54. const apiRef = useGridApiRef();
  55. const formProps = useForm<CreateConsoDoInput>({
  56. defaultValues: {},
  57. });
  58. const { setValue } = formProps;
  59. const errors = formProps.formState.errors;
  60. const { t } = useTranslation("do");
  61. const router = useRouter();
  62. const { data: session } = useSession() as { data: SessionWithTokens | null };
  63. const currentUserId = session?.id ? parseInt(session.id) : undefined;
  64. console.log("🔍 DoSearch - session:", session);
  65. console.log("🔍 DoSearch - currentUserId:", currentUserId);
  66. const [searchTimeout, setSearchTimeout] = useState<NodeJS.Timeout | null>(null);
  67. /** 使用者明確取消勾選的送貨單 id;未在此集合中的搜尋結果視為「已選」以便跨頁記憶 */
  68. const [excludedRowIds, setExcludedRowIds] = useState<number[]>([]);
  69. const [searchAllDos, setSearchAllDos] = useState<DoSearchAll[]>([]);
  70. const [totalCount, setTotalCount] = useState(0);
  71. const [isWorkbench, setIsWorkbench] = useState(false);
  72. const [pagingController, setPagingController] = useState({
  73. pageNum: 1,
  74. pageSize: 10,
  75. });
  76. const [currentSearchParams, setCurrentSearchParams] = useState<SearchBoxInputs>({
  77. code: "",
  78. status: "",
  79. estimatedArrivalDate: "",
  80. orderDate: "",
  81. supplierName: "",
  82. shopName: "",
  83. deliveryOrderLines: "",
  84. truckLanceCode: "", // 添加这个字段
  85. codeTo: "",
  86. statusTo: "",
  87. estimatedArrivalDateTo: "",
  88. orderDateTo: "",
  89. supplierNameTo: "",
  90. shopNameTo: "",
  91. deliveryOrderLinesTo: "",
  92. truckLanceCodeTo: "" // 这个字段已经存在,但需要确保在类型定义中
  93. });
  94. const [hasSearched, setHasSearched] = useState(false);
  95. const [hasResults, setHasResults] = useState(false);
  96. const excludedIdSet = useMemo(() => new Set(excludedRowIds), [excludedRowIds]);
  97. const rowSelectionModel = useMemo<GridRowSelectionModel>(() => {
  98. return searchAllDos
  99. .map((r) => r.id)
  100. .filter((id) => !excludedIdSet.has(id));
  101. }, [searchAllDos, excludedIdSet]);
  102. const applyRowSelectionChange = useCallback(
  103. (newModel: GridRowSelectionModel) => {
  104. const pageIds = searchAllDos.map((r) => r.id);
  105. const selectedSet = new Set(
  106. newModel.map((id) => (typeof id === "string" ? Number(id) : id)),
  107. );
  108. setExcludedRowIds((prev) => {
  109. const next = new Set(prev);
  110. for (const id of pageIds) {
  111. next.delete(id);
  112. }
  113. for (const id of pageIds) {
  114. if (!selectedSet.has(id)) {
  115. next.add(id);
  116. }
  117. }
  118. return Array.from(next);
  119. });
  120. setValue("ids", newModel);
  121. },
  122. [searchAllDos, setValue],
  123. );
  124. // 当搜索条件变化时,重置到第一页
  125. useEffect(() => {
  126. setPagingController(p => ({
  127. ...p,
  128. pageNum: 1,
  129. }));
  130. }, [currentSearchParams.code, currentSearchParams.shopName, currentSearchParams.status, currentSearchParams.estimatedArrivalDate]);
  131. const searchCriteria: Criterion<SearchParamNames>[] = useMemo(
  132. () => [
  133. { label: t("Code"), paramName: "code", type: "text" },
  134. { label: t("Shop Name"), paramName: "shopName", type: "text" },
  135. { label: t("Truck Lance Code"), paramName: "truckLanceCode", type: "text" },
  136. {
  137. label: t("Estimated Arrival"),
  138. paramName: "estimatedArrivalDate",
  139. type: "date",
  140. },
  141. {
  142. label: t("Status"),
  143. paramName: "status",
  144. type: "autocomplete",
  145. options:[
  146. {label: t('Pending'), value: 'pending'},
  147. {label: t('Receiving'), value: 'receiving'},
  148. {label: t('Completed'), value: 'completed'}
  149. ]
  150. }
  151. ],
  152. [t],
  153. );
  154. const onReset = useCallback(async () => {
  155. try {
  156. setSearchAllDos([]);
  157. setTotalCount(0);
  158. setHasSearched(false);
  159. setHasResults(false);
  160. setExcludedRowIds([]);
  161. setPagingController({ pageNum: 1, pageSize: 10 });
  162. }
  163. catch (error) {
  164. console.error("Error: ", error);
  165. setSearchAllDos([]);
  166. setTotalCount(0);
  167. }
  168. }, []);
  169. const onDetailClick = useCallback(
  170. (doResult: DoResult) => {
  171. if (typeof window !== 'undefined') {
  172. sessionStorage.setItem('doSearchParams', JSON.stringify(currentSearchParams));
  173. }
  174. router.push(`/do/edit?id=${doResult.id}`);
  175. },
  176. [router, currentSearchParams],
  177. );
  178. const validationTest = useCallback(
  179. (
  180. newRow: GridRowModel<DoRow>,
  181. ): EntryError => {
  182. const error: EntryError = {};
  183. console.log(newRow);
  184. return Object.keys(error).length > 0 ? error : undefined;
  185. },
  186. [],
  187. );
  188. const columns = useMemo<GridColDef[]>(
  189. () => [
  190. {
  191. field: "id",
  192. headerName: t("Details"),
  193. width: 100,
  194. renderCell: (params) => (
  195. <Button
  196. variant="outlined"
  197. size="small"
  198. startIcon={<EditNote />}
  199. onClick={() => onDetailClick(params.row)}
  200. >
  201. {t("Details")}
  202. </Button>
  203. ),
  204. },
  205. {
  206. field: "code",
  207. headerName: t("code"),
  208. flex: 1.5,
  209. },
  210. {
  211. field: "shopName",
  212. headerName: t("Shop Name"),
  213. flex: 1,
  214. },
  215. {
  216. field: "supplierName",
  217. headerName: t("Supplier Name"),
  218. flex: 1,
  219. },
  220. {
  221. field: "truckLanceCode",
  222. headerName: t("Truck Lance Code"),
  223. flex: 1,
  224. renderCell: (params) => {
  225. const v = params.row.truckLanceCode;
  226. if (v == null) return "車線-X";
  227. if (typeof v === "string" && v.trim() === "") return "車線-X";
  228. return v;
  229. }
  230. },
  231. {
  232. field: "orderDate",
  233. headerName: t("Order Date"),
  234. flex: 1,
  235. renderCell: (params) => {
  236. return params.row.orderDate
  237. ? arrayToDateString(params.row.orderDate)
  238. : "N/A";
  239. },
  240. },
  241. {
  242. field: "estimatedArrivalDate",
  243. headerName: t("Estimated Arrival"),
  244. flex: 1,
  245. renderCell: (params) => {
  246. return params.row.estimatedArrivalDate
  247. ? arrayToDateString(params.row.estimatedArrivalDate)
  248. : "N/A";
  249. },
  250. },
  251. {
  252. field: "status",
  253. headerName: t("Status"),
  254. flex: 1,
  255. renderCell: (params) => {
  256. return t(upperFirst(params.row.status));
  257. },
  258. },
  259. ],
  260. [t, arrayToDateString, onDetailClick],
  261. );
  262. const onSubmit = useCallback<SubmitHandler<CreateConsoDoInput>>(
  263. async (data, event) => {
  264. const hasErrors = false;
  265. console.log(errors);
  266. },
  267. [errors],
  268. );
  269. const onSubmitError = useCallback<SubmitErrorHandler<CreateConsoDoInput>>(
  270. (errors) => {},
  271. [],
  272. );
  273. //SEARCH FUNCTION
  274. const handleSearch = useCallback(async (query: SearchBoxInputs) => {
  275. try {
  276. setCurrentSearchParams(query);
  277. let estArrStartDate = query.estimatedArrivalDate;
  278. const time = "T00:00:00";
  279. if(estArrStartDate != ""){
  280. estArrStartDate = query.estimatedArrivalDate + time;
  281. }
  282. let status = "";
  283. if(query.status == "All"){
  284. status = "";
  285. }
  286. else{
  287. status = query.status;
  288. }
  289. // 调用新的 API,传入分页参数和 truckLanceCode
  290. const response = await fetchDoSearch(
  291. query.code || "",
  292. query.shopName || "",
  293. status,
  294. "", // orderStartDate - 不再使用
  295. "", // orderEndDate - 不再使用
  296. estArrStartDate,
  297. "", // estArrEndDate - 不再使用
  298. pagingController.pageNum, // 传入当前页码
  299. pagingController.pageSize, // 传入每页大小
  300. query.truckLanceCode || "" // 添加 truckLanceCode 参数
  301. );
  302. setSearchAllDos(response.records);
  303. setTotalCount(response.total); // 设置总记录数
  304. setHasSearched(true);
  305. setHasResults(response.records.length > 0);
  306. setExcludedRowIds([]);
  307. } catch (error) {
  308. console.error("Error: ", error);
  309. setSearchAllDos([]);
  310. setTotalCount(0);
  311. setHasSearched(true);
  312. setHasResults(false);
  313. setExcludedRowIds([]);
  314. }
  315. }, [pagingController]);
  316. useEffect(() => {
  317. if (typeof window !== 'undefined') {
  318. const savedSearchParams = sessionStorage.getItem('doSearchParams');
  319. if (savedSearchParams) {
  320. try {
  321. const params = JSON.parse(savedSearchParams);
  322. setCurrentSearchParams(params);
  323. // 自动使用保存的搜索条件重新搜索,获取最新数据
  324. const timer = setTimeout(async () => {
  325. await handleSearch(params);
  326. // 搜索完成后,清除 sessionStorage
  327. if (typeof window !== 'undefined') {
  328. sessionStorage.removeItem('doSearchParams');
  329. sessionStorage.removeItem('doSearchResults');
  330. sessionStorage.removeItem('doSearchHasSearched');
  331. }
  332. }, 100);
  333. return () => clearTimeout(timer);
  334. } catch (e) {
  335. console.error('Error restoring search state:', e);
  336. // 如果出错,也清除 sessionStorage
  337. if (typeof window !== 'undefined') {
  338. sessionStorage.removeItem('doSearchParams');
  339. sessionStorage.removeItem('doSearchResults');
  340. sessionStorage.removeItem('doSearchHasSearched');
  341. }
  342. }
  343. }
  344. }
  345. }, [handleSearch]);
  346. const debouncedSearch = useCallback((query: SearchBoxInputs) => {
  347. if (searchTimeout) {
  348. clearTimeout(searchTimeout);
  349. }
  350. const timeout = setTimeout(() => {
  351. handleSearch(query);
  352. }, 300);
  353. setSearchTimeout(timeout);
  354. }, [handleSearch, searchTimeout]);
  355. // 分页变化时重新搜索
  356. const handlePageChange = useCallback((event: unknown, newPage: number) => {
  357. const newPagingController = {
  358. ...pagingController,
  359. pageNum: newPage + 1,
  360. };
  361. setPagingController(newPagingController);
  362. // 如果已经搜索过,重新搜索
  363. if (hasSearched && currentSearchParams) {
  364. // 使用新的分页参数重新搜索
  365. const searchWithNewPage = async () => {
  366. try {
  367. let estArrStartDate = currentSearchParams.estimatedArrivalDate;
  368. const time = "T00:00:00";
  369. if(estArrStartDate != ""){
  370. estArrStartDate = currentSearchParams.estimatedArrivalDate + time;
  371. }
  372. let status = "";
  373. if(currentSearchParams.status == "All"){
  374. status = "";
  375. }
  376. else{
  377. status = currentSearchParams.status;
  378. }
  379. const response = await fetchDoSearch(
  380. currentSearchParams.code || "",
  381. currentSearchParams.shopName || "",
  382. status,
  383. "",
  384. "",
  385. estArrStartDate,
  386. "",
  387. newPagingController.pageNum,
  388. newPagingController.pageSize,
  389. currentSearchParams.truckLanceCode || "" // 添加这个参数
  390. );
  391. setSearchAllDos(response.records);
  392. setTotalCount(response.total);
  393. } catch (error) {
  394. console.error("Error: ", error);
  395. }
  396. };
  397. searchWithNewPage();
  398. }
  399. }, [pagingController, hasSearched, currentSearchParams]);
  400. const handlePageSizeChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
  401. const newPageSize = parseInt(event.target.value, 10);
  402. const newPagingController = {
  403. pageNum: 1, // 改变每页大小时重置到第一页
  404. pageSize: newPageSize,
  405. };
  406. setPagingController(newPagingController);
  407. // 如果已经搜索过,重新搜索
  408. if (hasSearched && currentSearchParams) {
  409. const searchWithNewPageSize = async () => {
  410. try {
  411. let estArrStartDate = currentSearchParams.estimatedArrivalDate;
  412. const time = "T00:00:00";
  413. if(estArrStartDate != ""){
  414. estArrStartDate = currentSearchParams.estimatedArrivalDate + time;
  415. }
  416. let status = "";
  417. if(currentSearchParams.status == "All"){
  418. status = "";
  419. }
  420. else{
  421. status = currentSearchParams.status;
  422. }
  423. const response = await fetchDoSearch(
  424. currentSearchParams.code || "",
  425. currentSearchParams.shopName || "",
  426. status,
  427. "",
  428. "",
  429. estArrStartDate,
  430. "",
  431. 1, // 重置到第一页
  432. newPageSize,
  433. currentSearchParams.truckLanceCode || "" // 添加这个参数
  434. );
  435. setSearchAllDos(response.records);
  436. setTotalCount(response.total);
  437. } catch (error) {
  438. console.error("Error: ", error);
  439. }
  440. };
  441. searchWithNewPageSize();
  442. }
  443. }, [hasSearched, currentSearchParams]);
  444. const handleBatchRelease = useCallback(async (isWorkbench: boolean) => {
  445. try {
  446. // 根据当前搜索条件获取所有匹配的记录(不分页)
  447. let estArrStartDate = currentSearchParams.estimatedArrivalDate;
  448. const time = "T00:00:00";
  449. if(estArrStartDate != ""){
  450. estArrStartDate = currentSearchParams.estimatedArrivalDate + time;
  451. }
  452. let status = "";
  453. if(currentSearchParams.status == "All"){
  454. status = "";
  455. }
  456. else{
  457. status = currentSearchParams.status;
  458. }
  459. // 显示加载提示
  460. const loadingSwal = Swal.fire({
  461. title: t("Loading"),
  462. text: t("Fetching all matching records..."),
  463. allowOutsideClick: false,
  464. allowEscapeKey: false,
  465. showConfirmButton: false,
  466. didOpen: () => {
  467. Swal.showLoading();
  468. }
  469. });
  470. // 获取所有匹配的记录
  471. const allMatchingDos = await fetchAllDoSearch(
  472. currentSearchParams.code || "",
  473. currentSearchParams.shopName || "",
  474. status,
  475. estArrStartDate,
  476. currentSearchParams.truckLanceCode || ""
  477. );
  478. Swal.close();
  479. if (allMatchingDos.length === 0) {
  480. await Swal.fire({
  481. icon: "warning",
  482. title: t("No Records"),
  483. text: t("No matching records found for batch release."),
  484. confirmButtonText: t("OK")
  485. });
  486. return;
  487. }
  488. const idsToRelease = allMatchingDos
  489. .map((d) => d.id)
  490. .filter((id) => !excludedIdSet.has(id));
  491. if (idsToRelease.length === 0) {
  492. await Swal.fire({
  493. icon: "warning",
  494. title: t("No Records"),
  495. text: t("No delivery orders selected for batch release. Uncheck orders you want to exclude, or search again to reset selection."),
  496. confirmButtonText: t("OK"),
  497. });
  498. return;
  499. }
  500. // 显示确认对话框
  501. const result = await Swal.fire({
  502. icon: "question",
  503. title: t("Batch Release"),
  504. html: `
  505. <div>
  506. <p>${t("Selected Shop(s): ")}${idsToRelease.length}</p>
  507. <p style="font-size: 0.9em; color: #666; margin-top: 8px;">
  508. ${currentSearchParams.code ? `${t("Code")}: ${currentSearchParams.code} ` : ""}
  509. ${currentSearchParams.shopName ? `${t("Shop Name")}: ${currentSearchParams.shopName} ` : ""}
  510. ${currentSearchParams.estimatedArrivalDate ? `${t("Estimated Arrival")}: ${currentSearchParams.estimatedArrivalDate} ` : ""}
  511. ${status ? `${t("Status")}: ${status} ` : ""}
  512. </p>
  513. </div>
  514. `,
  515. showCancelButton: true,
  516. confirmButtonText: t("Confirm"),
  517. cancelButtonText: t("Cancel"),
  518. confirmButtonColor: "#8dba00",
  519. cancelButtonColor: "#F04438"
  520. });
  521. if (result.isConfirmed) {
  522. try {
  523. let startRes ;
  524. if(isWorkbench){
  525. startRes = await startWorkbenchBatchReleaseAsyncV2({ ids: idsToRelease, userId: currentUserId ?? 1 });
  526. }
  527. else{
  528. startRes = await startBatchReleaseAsync({ ids: idsToRelease, userId: currentUserId ?? 1 });
  529. }
  530. //await startBatchReleaseAsync({ ids: idsToRelease, userId: currentUserId ?? 1 });
  531. const jobId = startRes?.entity?.jobId;
  532. if (!jobId) {
  533. await Swal.fire({ icon: "error", title: t("Error"), text: t("Failed to start batch release") });
  534. return;
  535. }
  536. const progressSwal = Swal.fire({
  537. title: t("Releasing"),
  538. text: "0% (0 / 0)",
  539. allowOutsideClick: false,
  540. allowEscapeKey: false,
  541. showConfirmButton: false,
  542. didOpen: () => {
  543. Swal.showLoading();
  544. }
  545. });
  546. const timer = setInterval(async () => {
  547. try {
  548. const p = isWorkbench
  549. ? await getWorkbenchBatchReleaseProgress(jobId)
  550. : await getBatchReleaseProgress(jobId);
  551. const e = p?.entity || {};
  552. const total = e.total ?? 0;
  553. const finished = e.finished ?? 0;
  554. const percentage = total > 0 ? Math.round((finished / total) * 100) : 0;
  555. const textContent = document.querySelector('.swal2-html-container');
  556. if (textContent) {
  557. textContent.textContent = `${percentage}% (${finished} / ${total})`;
  558. }
  559. if (p.code === "FINISHED" || e.running === false) {
  560. clearInterval(timer);
  561. await new Promise(resolve => setTimeout(resolve, 500));
  562. Swal.close();
  563. await Swal.fire({
  564. icon: "success",
  565. title: t("Completed"),
  566. text: t("Batch release completed successfully."),
  567. confirmButtonText: t("Confirm"),
  568. confirmButtonColor: "#8dba00"
  569. });
  570. if (currentSearchParams && Object.keys(currentSearchParams).length > 0) {
  571. await handleSearch(currentSearchParams);
  572. }
  573. }
  574. } catch (err) {
  575. console.error("progress poll error:", err);
  576. }
  577. }, 800);
  578. } catch (error) {
  579. console.error("Batch release error:", error);
  580. await Swal.fire({
  581. icon: "error",
  582. title: t("Error"),
  583. text: t("An error occurred during batch release"),
  584. confirmButtonText: t("OK")
  585. });
  586. }
  587. }
  588. } catch (error) {
  589. console.error("Error fetching all matching records:", error);
  590. await Swal.fire({
  591. icon: "error",
  592. title: t("Error"),
  593. text: t("Failed to fetch matching records"),
  594. confirmButtonText: t("OK")
  595. });
  596. }
  597. }, [t, currentUserId, currentSearchParams, handleSearch, excludedIdSet]);
  598. return (
  599. <>
  600. <FormProvider {...formProps}>
  601. <Stack
  602. spacing={2}
  603. component="form"
  604. onSubmit={formProps.handleSubmit(onSubmit, onSubmitError)}
  605. >
  606. {hasSearched && hasResults && (
  607. <Stack direction="row" justifyContent="flex-end" spacing={2}sx={{ mb: 1 }}>
  608. <Button
  609. name="batch_release"
  610. variant="contained"
  611. onClick={() => handleBatchRelease(true)}
  612. >
  613. {t("Workbench Batch Release")}
  614. </Button>
  615. {/*
  616. <Button
  617. name="batch_release"
  618. variant="contained"
  619. onClick={() => handleBatchRelease(false)}
  620. >
  621. {t("Batch Release")}
  622. </Button>
  623. */}
  624. </Stack>
  625. )}
  626. <SearchBox
  627. criteria={searchCriteria}
  628. onSearch={handleSearch}
  629. onReset={onReset}
  630. />
  631. <Paper variant="outlined" sx={{ overflow: "hidden" }}>
  632. <StyledDataGrid
  633. rows={searchAllDos}
  634. columns={columns}
  635. checkboxSelection
  636. rowSelectionModel={rowSelectionModel}
  637. onRowSelectionModelChange={applyRowSelectionChange}
  638. slots={{
  639. footer: FooterToolbar,
  640. noRowsOverlay: NoRowsOverlay,
  641. }}
  642. />
  643. <TablePagination
  644. component="div"
  645. count={totalCount}
  646. page={(pagingController.pageNum - 1)}
  647. rowsPerPage={pagingController.pageSize}
  648. onPageChange={handlePageChange}
  649. onRowsPerPageChange={handlePageSizeChange}
  650. rowsPerPageOptions={[10, 25, 50]}
  651. />
  652. </Paper>
  653. </Stack>
  654. </FormProvider>
  655. </>
  656. );
  657. };
  658. const FooterToolbar: React.FC<FooterPropsOverrides> = ({ child }) => {
  659. return <GridToolbarContainer sx={{ p: 2 }}>{child}</GridToolbarContainer>;
  660. };
  661. const NoRowsOverlay: React.FC = () => {
  662. const { t } = useTranslation("home");
  663. return (
  664. <Box
  665. display="flex"
  666. justifyContent="center"
  667. alignItems="center"
  668. height="100%"
  669. >
  670. <Typography variant="caption">{t("Add some entries!")}</Typography>
  671. </Box>
  672. );
  673. };
  674. export default DoSearch;