Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.
 
 

168 rader
5.3 KiB

  1. "use client";
  2. import { ProjectExpensesResult, ProjectExpensesResultFormatted } from "@/app/api/projectExpenses";
  3. import { useCallback, useEffect, useMemo, useState } from "react";
  4. import { useTranslation } from "react-i18next";
  5. import SearchBox, { Criterion } from "../SearchBox";
  6. import SearchResults, { Column } from "../SearchResults";
  7. import { useRouter } from "next/navigation";
  8. import {
  9. Button,
  10. ButtonGroup,
  11. Card,
  12. CardContent,
  13. Divider,
  14. Grid,
  15. Stack,
  16. Typography,
  17. } from "@mui/material";
  18. import { moneyFormatter } from "@/app/utils/formatUtil";
  19. import { EditNote } from "@mui/icons-material";
  20. import AddIcon from '@mui/icons-material/Add';
  21. import { uniq } from "lodash";
  22. import CreateExpenseModal from "./CreateExpenseModal";
  23. import { ProjectResult } from "@/app/api/projects";
  24. interface Props {
  25. expenses: ProjectExpensesResultFormatted[]
  26. projects: ProjectResult[];
  27. }
  28. type SearchQuery = Partial<Omit<ProjectExpensesResultFormatted, "id">>;
  29. type SearchParamNames = keyof SearchQuery;
  30. type Modals = {
  31. createInvoiceModal: boolean
  32. }
  33. const initState: Modals = {
  34. createInvoiceModal: false,
  35. }
  36. const ExpenseSearch: React.FC<Props> = ({ expenses, projects }) => {
  37. const router = useRouter();
  38. const { t } = useTranslation("expenses");
  39. const [filteredExpenses, setFilteredExpenses] = useState(expenses);
  40. const [modalsOpen, setModalsOpen] = useState(initState)
  41. const toggleModals = useCallback((key: keyof Modals) => {
  42. setModalsOpen((prev) => ({...prev, [key]: !prev[key]}))
  43. }, [modalsOpen]);
  44. const searchCriteria: Criterion<SearchParamNames>[] = useMemo(
  45. () => [
  46. // { label: t("Expense No"), paramName: "ExpenseNo", type: "text" },
  47. { label: t("Project Code"), paramName: "projectCode", type: "text" },
  48. { label: t("Project Name"), paramName: "projectName", type: "text" },
  49. // {
  50. // label: t("Team"),
  51. // paramName: "team",
  52. // type: "select",
  53. // options: uniq(expenses.map((expenses) => expenses.teamCode)),
  54. // },
  55. ],
  56. []
  57. );
  58. const onExpenseClick = useCallback(
  59. (expenses?: ProjectExpensesResultFormatted) => {},
  60. [router]
  61. );
  62. const columns = useMemo<Column<ProjectExpensesResultFormatted>[]>(
  63. () => [
  64. {
  65. name: "id",
  66. label: t("Details"),
  67. onClick: onExpenseClick,
  68. buttonIcon: <EditNote />,
  69. // disabled: !abilities.includes(MAINTAIN_PROJECT),
  70. },
  71. { name: "projectCode", label: t("Project Code") },
  72. { name: "projectName", label: t("Project Name") },
  73. { name: "amount", label: t("Amount") },
  74. { name: "teamCode", label: t("Team") },
  75. { name: "issueDate", label: t("Issue Date") },
  76. ],
  77. [t, onExpenseClick]
  78. );
  79. const onReset = useCallback(() => {
  80. // setFilteredExpenses();
  81. }, []);
  82. return (
  83. <>
  84. <Stack
  85. spacing={2}
  86. >
  87. <Stack
  88. direction="row"
  89. justifyContent="right"
  90. flexWrap="wrap"
  91. spacing={2}
  92. >
  93. <Grid xs={12} justifyContent="right">
  94. <ButtonGroup variant="contained">
  95. <Button
  96. startIcon={<AddIcon />}
  97. variant="outlined"
  98. component="label"
  99. onClick={() => toggleModals("createInvoiceModal")}
  100. >
  101. {t("Create expense")}
  102. </Button>
  103. </ButtonGroup>
  104. </Grid>
  105. </Stack>
  106. <SearchBox
  107. criteria={searchCriteria}
  108. onSearch={(query) => {
  109. // setFilteredExpenses(
  110. // projects.filter(
  111. // (p) =>
  112. // p.code.toLowerCase().includes(query.code.toLowerCase()) &&
  113. // p.name.toLowerCase().includes(query.name.toLowerCase()) &&
  114. // (query.client === "All" || p.client === query.client) &&
  115. // (query.category === "All" || p.category === query.category) &&
  116. // // (query.team === "All" || p.team === query.team) &&
  117. // (query.team === "All" || query.team.toLowerCase().includes(p.team.toLowerCase())) &&
  118. // (query.status === "All" || p.status === query.status),
  119. // ),
  120. // );
  121. }}
  122. onReset={onReset}
  123. />
  124. <Divider sx={{ paddingBlockStart: 2 }} />
  125. <Card sx={{ display: "block" }}>
  126. <CardContent>
  127. <Stack direction="row" justifyContent="space-between">
  128. <Typography variant="h6">
  129. {t("Total Issued Amount (HKD)")}:
  130. </Typography>
  131. <Typography variant="h6">
  132. {/* {moneyFormatter.format(filteredExpenses.reduce((acc, curr) => (acc + curr.issuedAmount), 0))} */}
  133. </Typography>
  134. </Stack>
  135. <Stack direction="row" justifyContent="space-between">
  136. <Typography variant="h6">
  137. {t("Total Received Amount (HKD)")}:
  138. </Typography>
  139. <Typography variant="h6">
  140. {/* {moneyFormatter.format(filteredExpenses.reduce((acc, curr) => (acc + curr.receivedAmount), 0))} */}
  141. </Typography>
  142. </Stack>
  143. </CardContent>
  144. </Card>
  145. <Divider sx={{ paddingBlockEnd: 2 }} />
  146. <SearchResults<ProjectExpensesResultFormatted>
  147. items={filteredExpenses}
  148. columns={columns}
  149. />
  150. </Stack>
  151. <CreateExpenseModal
  152. isOpen={modalsOpen.createInvoiceModal}
  153. onClose={() => toggleModals("createInvoiceModal")}
  154. projects={projects}
  155. />
  156. </>
  157. );
  158. };
  159. export default ExpenseSearch;