@@ -1,11 +1,11 @@ | |||||
import { preloadExpense } from "@/app/api/expenses" | |||||
import { preloadProjectExpenses } from "@/app/api/projectExpenses" | |||||
import ExpenseSearch from "@/components/ExpenseSearch" | import ExpenseSearch from "@/components/ExpenseSearch" | ||||
import { getServerI18n, I18nProvider } from "@/i18n" | import { getServerI18n, I18nProvider } from "@/i18n" | ||||
import { Stack, Typography } from "@mui/material" | import { Stack, Typography } from "@mui/material" | ||||
import { Suspense } from "react" | import { Suspense } from "react" | ||||
const Expense: React.FC = async () => { | const Expense: React.FC = async () => { | ||||
preloadExpense() | |||||
preloadProjectExpenses() | |||||
const { t } = await getServerI18n("expense") | const { t } = await getServerI18n("expense") | ||||
return( | return( | ||||
@@ -1,28 +0,0 @@ | |||||
"use server"; | |||||
import { cache } from "react"; | |||||
import { serverFetchJson } from "@/app/utils/fetchUtil"; | |||||
import { BASE_API_URL } from "@/config/api"; | |||||
export type ExpensesResult = { | |||||
id: number | |||||
projectCode: string | |||||
projectName: string | |||||
staffCode: string | |||||
staffName: string | |||||
description: string | |||||
amount: number | |||||
approvedAmount: number | |||||
verifiedDatetime: number[] | |||||
remark: string | |||||
} | |||||
export const preloadExpense = () => { | |||||
fetchExpenses() | |||||
}; | |||||
export const fetchExpenses = cache(async () => { | |||||
return serverFetchJson<ExpensesResult[]>(`${BASE_API_URL}/expense`, { | |||||
next: { tags: ["expense"] }, | |||||
}); | |||||
}); |
@@ -0,0 +1,25 @@ | |||||
"use server" | |||||
import { serverFetchJson, serverFetchWithNoContent } from "@/app/utils/fetchUtil"; | |||||
import { BASE_API_URL } from "@/config/api"; | |||||
import { cache } from "react"; | |||||
import { revalidateTag } from "next/cache"; | |||||
export type CreateNewExpense = { | |||||
expenseNo: string | undefined, | |||||
projectCode: string | undefined, | |||||
issueDate: Date | |||||
issuedAmount: number, | |||||
receiptDate: Date | |||||
receivedAmount: number | |||||
} | |||||
export type PostExpenseData = { | |||||
expenseNo?: string | |||||
projectId: number | |||||
projectCode: string, | |||||
amount: number | |||||
issueDate: string | |||||
receiptDate?: string | |||||
} |
@@ -0,0 +1,32 @@ | |||||
"use server"; | |||||
import { cache } from "react"; | |||||
import { serverFetchJson } from "@/app/utils/fetchUtil"; | |||||
import { BASE_API_URL } from "@/config/api"; | |||||
export type ProjectExpensesResult = { | |||||
id: number | |||||
expenseNo?: string | |||||
projectCode: string | |||||
projectName: string | |||||
teamId: number | |||||
teamCode: string | |||||
teamName: string | |||||
amount: number | |||||
issueDate: number[] | |||||
receiptDate: number[] | |||||
} | |||||
export type ProjectExpensesResultFormatted = Omit<ProjectExpensesResult, 'issueDate' | 'receiptDate'> & { | |||||
issueDate: string; | |||||
receiptDate: string; | |||||
}; | |||||
export const preloadProjectExpenses = () => { | |||||
fetchProjectExpenses() | |||||
}; | |||||
export const fetchProjectExpenses = cache(async () => { | |||||
return serverFetchJson<ProjectExpensesResult[]>(`${BASE_API_URL}/project-expense`, { | |||||
next: { tags: ["projectExpenses"] }, | |||||
}); | |||||
}); |
@@ -12,8 +12,11 @@ import { | |||||
import { useTranslation } from 'react-i18next'; | import { useTranslation } from 'react-i18next'; | ||||
import { FormProvider, SubmitHandler, useForm } from 'react-hook-form'; | import { FormProvider, SubmitHandler, useForm } from 'react-hook-form'; | ||||
import { Check, Close } from "@mui/icons-material"; | import { Check, Close } from "@mui/icons-material"; | ||||
import InvoiceTable from './ExpenseTable'; | |||||
import { ProjectResult } from '@/app/api/projects'; | import { ProjectResult } from '@/app/api/projects'; | ||||
import { CreateNewExpense, PostExpenseData } from '@/app/api/projectExpenses/actions'; | |||||
import ExpenseTable from './ExpenseTable'; | |||||
import dayjs from 'dayjs'; | |||||
import { INPUT_DATE_FORMAT } from '@/app/utils/formatUtil'; | |||||
interface Props { | interface Props { | ||||
isOpen: boolean, | isOpen: boolean, | ||||
@@ -32,13 +35,30 @@ const modalSx: SxProps= { | |||||
bgcolor: 'background.paper', | bgcolor: 'background.paper', | ||||
}; | }; | ||||
const CreateInvoiceModal: React.FC<Props> = ({isOpen, onClose, projects}) => { | |||||
type postData = { | |||||
data: PostExpenseData[] | |||||
} | |||||
const CreateExpenseModal: React.FC<Props> = ({isOpen, onClose, projects}) => { | |||||
const { t } = useTranslation() | const { t } = useTranslation() | ||||
const formProps = useForm<any>(); | |||||
const formProps = useForm<postData>(); | |||||
const onSubmit = useCallback<SubmitHandler<any>>( | |||||
const onSubmit = useCallback<SubmitHandler<postData>>( | |||||
(data) => { | (data) => { | ||||
console.log(data) | |||||
const _data = data.data | |||||
console.log(_data[0]) | |||||
console.log(_data[0].issueDate) | |||||
console.log(_data[1].issueDate) | |||||
const postData: PostExpenseData[] = _data.map(item => { | |||||
console.log(item.issueDate) | |||||
return ({ | |||||
expenseNo: item.expenseNo, | |||||
issueDate: dayjs(item.issueDate).format(INPUT_DATE_FORMAT), | |||||
amount: item.amount, | |||||
projectId: projects.find(p => p.code === item.projectCode)!.id, | |||||
projectCode: item.projectCode, | |||||
})} | |||||
) | |||||
console.log(postData) | |||||
} | } | ||||
, []) | , []) | ||||
@@ -63,7 +83,7 @@ const CreateInvoiceModal: React.FC<Props> = ({isOpen, onClose, projects}) => { | |||||
marginBlock: 2, | marginBlock: 2, | ||||
}} | }} | ||||
> | > | ||||
<InvoiceTable projects={projects}/> | |||||
<ExpenseTable projects={projects}/> | |||||
</Box> | </Box> | ||||
<CardActions sx={{ justifyContent: "flex-end" }}> | <CardActions sx={{ justifyContent: "flex-end" }}> | ||||
<Button | <Button | ||||
@@ -84,4 +104,4 @@ const CreateInvoiceModal: React.FC<Props> = ({isOpen, onClose, projects}) => { | |||||
); | ); | ||||
}; | }; | ||||
export default CreateInvoiceModal; | |||||
export default CreateExpenseModal; |
@@ -1,6 +1,6 @@ | |||||
"use client"; | "use client"; | ||||
import { ExpensesResult } from "@/app/api/expenses"; | |||||
import { useCallback, useMemo, useState } from "react"; | |||||
import { ProjectExpensesResult, ProjectExpensesResultFormatted } from "@/app/api/projectExpenses"; | |||||
import { useCallback, useEffect, useMemo, useState } from "react"; | |||||
import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
import SearchBox, { Criterion } from "../SearchBox"; | import SearchBox, { Criterion } from "../SearchBox"; | ||||
import SearchResults, { Column } from "../SearchResults"; | import SearchResults, { Column } from "../SearchResults"; | ||||
@@ -18,41 +18,55 @@ import { | |||||
import { moneyFormatter } from "@/app/utils/formatUtil"; | import { moneyFormatter } from "@/app/utils/formatUtil"; | ||||
import { EditNote } from "@mui/icons-material"; | import { EditNote } from "@mui/icons-material"; | ||||
import AddIcon from '@mui/icons-material/Add'; | import AddIcon from '@mui/icons-material/Add'; | ||||
import { uniq } from "lodash"; | |||||
import CreateExpenseModal from "./CreateExpenseModal"; | |||||
import { ProjectResult } from "@/app/api/projects"; | |||||
interface Props { | interface Props { | ||||
expenses: ExpensesResult[] | |||||
expenses: ProjectExpensesResultFormatted[] | |||||
projects: ProjectResult[]; | |||||
} | } | ||||
type SearchQuery = Partial<Omit<ExpensesResult, "id">>; | |||||
type SearchQuery = Partial<Omit<ProjectExpensesResultFormatted, "id">>; | |||||
type SearchParamNames = keyof SearchQuery; | type SearchParamNames = keyof SearchQuery; | ||||
const ExpenseSearch: React.FC<Props> = ({ expenses }) => { | |||||
console.log(expenses) | |||||
type Modals = { | |||||
createInvoiceModal: boolean | |||||
} | |||||
const initState: Modals = { | |||||
createInvoiceModal: false, | |||||
} | |||||
const ExpenseSearch: React.FC<Props> = ({ expenses, projects }) => { | |||||
const router = useRouter(); | const router = useRouter(); | ||||
const { t } = useTranslation("expenses"); | const { t } = useTranslation("expenses"); | ||||
const [filteredExpenses, setFilteredExpenses] = useState(expenses); | const [filteredExpenses, setFilteredExpenses] = useState(expenses); | ||||
const [modalsOpen, setModalsOpen] = useState(initState) | |||||
const toggleModals = useCallback((key: keyof Modals) => { | |||||
setModalsOpen((prev) => ({...prev, [key]: !prev[key]})) | |||||
}, [modalsOpen]); | |||||
const searchCriteria: Criterion<SearchParamNames>[] = useMemo( | const searchCriteria: Criterion<SearchParamNames>[] = useMemo( | ||||
() => [ | () => [ | ||||
// { label: t("Expense No"), paramName: "ExpenseNo", type: "text" }, | // { label: t("Expense No"), paramName: "ExpenseNo", type: "text" }, | ||||
{ label: t("Project Code"), paramName: "projectCode", type: "text" }, | { label: t("Project Code"), paramName: "projectCode", type: "text" }, | ||||
{ label: t("Project Name"), paramName: "projectName", type: "text" }, | { label: t("Project Name"), paramName: "projectName", type: "text" }, | ||||
{ | |||||
label: t("Verified Date"), | |||||
label2: t("Verified Date To"), | |||||
paramName: "verifiedDatetime", | |||||
type: "dateRange", | |||||
}, | |||||
// { | |||||
// label: t("Team"), | |||||
// paramName: "team", | |||||
// type: "select", | |||||
// options: uniq(expenses.map((expenses) => expenses.teamCode)), | |||||
// }, | |||||
], | ], | ||||
[] | [] | ||||
); | ); | ||||
const onExpenseClick = useCallback( | const onExpenseClick = useCallback( | ||||
(expenses?: ExpensesResult) => {}, | |||||
(expenses?: ProjectExpensesResultFormatted) => {}, | |||||
[router] | [router] | ||||
); | ); | ||||
const columns = useMemo<Column<ExpensesResult>[]>( | |||||
const columns = useMemo<Column<ProjectExpensesResultFormatted>[]>( | |||||
() => [ | () => [ | ||||
{ | { | ||||
name: "id", | name: "id", | ||||
@@ -63,7 +77,9 @@ const ExpenseSearch: React.FC<Props> = ({ expenses }) => { | |||||
}, | }, | ||||
{ name: "projectCode", label: t("Project Code") }, | { name: "projectCode", label: t("Project Code") }, | ||||
{ name: "projectName", label: t("Project Name") }, | { name: "projectName", label: t("Project Name") }, | ||||
{ name: "verifiedDatetime", label: t("verifiedDatetime") }, | |||||
{ name: "amount", label: t("Amount") }, | |||||
{ name: "teamCode", label: t("Team") }, | |||||
{ name: "issueDate", label: t("Issue Date") }, | |||||
], | ], | ||||
[t, onExpenseClick] | [t, onExpenseClick] | ||||
); | ); | ||||
@@ -72,6 +88,7 @@ const ExpenseSearch: React.FC<Props> = ({ expenses }) => { | |||||
}, []); | }, []); | ||||
return ( | return ( | ||||
<> | |||||
<Stack | <Stack | ||||
spacing={2} | spacing={2} | ||||
> | > | ||||
@@ -87,7 +104,7 @@ const ExpenseSearch: React.FC<Props> = ({ expenses }) => { | |||||
startIcon={<AddIcon />} | startIcon={<AddIcon />} | ||||
variant="outlined" | variant="outlined" | ||||
component="label" | component="label" | ||||
onClick={() => console.log()} | |||||
onClick={() => toggleModals("createInvoiceModal")} | |||||
> | > | ||||
{t("Create expense")} | {t("Create expense")} | ||||
</Button> | </Button> | ||||
@@ -134,11 +151,17 @@ const ExpenseSearch: React.FC<Props> = ({ expenses }) => { | |||||
</CardContent> | </CardContent> | ||||
</Card> | </Card> | ||||
<Divider sx={{ paddingBlockEnd: 2 }} /> | <Divider sx={{ paddingBlockEnd: 2 }} /> | ||||
<SearchResults<ExpensesResult> | |||||
<SearchResults<ProjectExpensesResultFormatted> | |||||
items={filteredExpenses} | items={filteredExpenses} | ||||
columns={columns} | columns={columns} | ||||
/> | /> | ||||
</Stack> | </Stack> | ||||
<CreateExpenseModal | |||||
isOpen={modalsOpen.createInvoiceModal} | |||||
onClose={() => toggleModals("createInvoiceModal")} | |||||
projects={projects} | |||||
/> | |||||
</> | |||||
); | ); | ||||
}; | }; | ||||
export default ExpenseSearch; | export default ExpenseSearch; |
@@ -1,10 +1,12 @@ | |||||
import React from "react"; | import React from "react"; | ||||
import ExpenseSearch from "./ExpenseSearch"; | import ExpenseSearch from "./ExpenseSearch"; | ||||
import ExpenseSearchLoading from "./ExpenseSearchLoading"; | import ExpenseSearchLoading from "./ExpenseSearchLoading"; | ||||
import { fetchExpenses } from "@/app/api/expenses"; | |||||
import { fetchProjectExpenses } from "@/app/api/projectExpenses"; | |||||
import dayjs from "dayjs"; | import dayjs from "dayjs"; | ||||
import arraySupport from "dayjs/plugin/arraySupport"; | import arraySupport from "dayjs/plugin/arraySupport"; | ||||
import { OUTPUT_DATE_FORMAT, OUTPUT_TIME_FORMAT } from "@/app/utils/formatUtil"; | import { OUTPUT_DATE_FORMAT, OUTPUT_TIME_FORMAT } from "@/app/utils/formatUtil"; | ||||
import { fetchUserStaff } from "@/app/utils/fetchUtil"; | |||||
import { fetchProjects } from "@/app/api/projects"; | |||||
interface SubComponents { | interface SubComponents { | ||||
@@ -14,20 +16,36 @@ dayjs.extend(arraySupport) | |||||
const ExpenseSearchWrapper: React.FC & SubComponents = async () => { | const ExpenseSearchWrapper: React.FC & SubComponents = async () => { | ||||
const [ | const [ | ||||
Expenses | |||||
expenses, | |||||
projects | |||||
] = await Promise.all([ | ] = await Promise.all([ | ||||
fetchExpenses(), | |||||
fetchProjectExpenses(), | |||||
fetchProjects(), | |||||
]); | ]); | ||||
const _expenses = Expenses.map((e) => { | |||||
const date: number[] = e.verifiedDatetime as number[]; | |||||
const formattedDate = dayjs([date[0], date[1], date[2]].join()).format(OUTPUT_DATE_FORMAT) | |||||
const userStaff = await fetchUserStaff() | |||||
const teamId = userStaff?.teamId | |||||
let filteredExpenses = expenses | |||||
if (teamId) { | |||||
filteredExpenses = expenses.filter(e => e.teamId === teamId) | |||||
} else { | |||||
filteredExpenses = [] | |||||
} | |||||
const _expenses = filteredExpenses.map((e) => { | |||||
const issueDate = e.issueDate; | |||||
const receiptDate = e.receiptDate; | |||||
const formattedIssueDate = dayjs([issueDate[0], issueDate[1], issueDate[2]].join()).format(OUTPUT_DATE_FORMAT) | |||||
const formattedReceiptDate = dayjs([receiptDate[0], receiptDate[1], receiptDate[2]].join()).format(OUTPUT_DATE_FORMAT) | |||||
return ({ | return ({ | ||||
...e, | ...e, | ||||
verifiedDatetime: formattedDate | |||||
issueDate: formattedIssueDate, | |||||
receiptDate: formattedReceiptDate | |||||
}) | }) | ||||
}) | }) | ||||
return <ExpenseSearch | return <ExpenseSearch | ||||
expenses={_expenses} | expenses={_expenses} | ||||
projects={projects} | |||||
/> | /> | ||||
}; | }; | ||||
@@ -7,8 +7,6 @@ import { moneyFormatter } from "@/app/utils/formatUtil" | |||||
import { Button, ButtonGroup, Stack, Tab, Tabs, TabsProps, Dialog, DialogTitle, DialogContent, DialogContentText, DialogActions, TextField, CardContent, Typography, Divider, Card, Box, Autocomplete, MenuItem } from "@mui/material"; | import { Button, ButtonGroup, Stack, Tab, Tabs, TabsProps, Dialog, DialogTitle, DialogContent, DialogContentText, DialogActions, TextField, CardContent, Typography, Divider, Card, Box, Autocomplete, MenuItem } from "@mui/material"; | ||||
import FileUploadIcon from '@mui/icons-material/FileUpload'; | import FileUploadIcon from '@mui/icons-material/FileUpload'; | ||||
import { Add, Check, Close, Delete } from "@mui/icons-material"; | import { Add, Check, Close, Delete } from "@mui/icons-material"; | ||||
import { deleteInvoice, importIssuedInovice, importReceivedInovice, updateInvoice } from "@/app/api/invoices/actions"; | |||||
import { deleteDialog, errorDialogWithContent, successDialog } from "../Swal/CustomAlerts"; | |||||
import { invoiceList, issuedInvoiceList, issuedInvoiceSearchForm, receivedInvoiceList, receivedInvoiceSearchForm } from "@/app/api/invoices"; | import { invoiceList, issuedInvoiceList, issuedInvoiceSearchForm, receivedInvoiceList, receivedInvoiceSearchForm } from "@/app/api/invoices"; | ||||
import EditOutlinedIcon from '@mui/icons-material/EditOutlined'; | import EditOutlinedIcon from '@mui/icons-material/EditOutlined'; | ||||
import { | import { | ||||
@@ -19,7 +17,8 @@ import { | |||||
GridRowModel, | GridRowModel, | ||||
GridRowModes, | GridRowModes, | ||||
GridRowModesModel, | GridRowModesModel, | ||||
GridRenderEditCellParams, | |||||
GridRenderEditCellParams, | |||||
useGridApiContext, | |||||
} from "@mui/x-data-grid"; | } from "@mui/x-data-grid"; | ||||
import { useGridApiRef } from "@mui/x-data-grid"; | import { useGridApiRef } from "@mui/x-data-grid"; | ||||
import StyledDataGrid from "../StyledDataGrid"; | import StyledDataGrid from "../StyledDataGrid"; | ||||
@@ -32,15 +31,17 @@ import { th } from "@faker-js/faker"; | |||||
import { GridRowIdGetter } from "@mui/x-data-grid"; | import { GridRowIdGetter } from "@mui/x-data-grid"; | ||||
import { useFormContext } from "react-hook-form"; | import { useFormContext } from "react-hook-form"; | ||||
import { ProjectResult } from "@/app/api/projects"; | import { ProjectResult } from "@/app/api/projects"; | ||||
import { ProjectExpensesResultFormatted } from "@/app/api/projectExpenses"; | |||||
import { GridRenderCellParams } from "@mui/x-data-grid"; | |||||
type InvoiceListError = { | |||||
[field in keyof invoiceList]?: string; | |||||
type ExpenseListError = { | |||||
[field in keyof ProjectExpensesResultFormatted]?: string; | |||||
}; | }; | ||||
type invoiceListRow = Partial< | |||||
invoiceList & { | |||||
type ExpenseListRow = Partial< | |||||
ProjectExpensesResultFormatted & { | |||||
_isNew: boolean; | _isNew: boolean; | ||||
_error: InvoiceListError; | |||||
_error: ExpenseListError; | |||||
} | } | ||||
>; | >; | ||||
@@ -49,12 +50,12 @@ interface Props { | |||||
} | } | ||||
class ProcessRowUpdateError extends Error { | class ProcessRowUpdateError extends Error { | ||||
public readonly row: invoiceListRow; | |||||
public readonly errors: InvoiceListError | undefined; | |||||
public readonly row: ExpenseListRow; | |||||
public readonly errors: ExpenseListError | undefined; | |||||
constructor( | constructor( | ||||
row: invoiceListRow, | |||||
row: ExpenseListRow, | |||||
message?: string, | message?: string, | ||||
errors?: InvoiceListError, | |||||
errors?: ExpenseListError, | |||||
) { | ) { | ||||
super(message); | super(message); | ||||
this.row = row; | this.row = row; | ||||
@@ -67,34 +68,25 @@ type project = { | |||||
label: string; | label: string; | ||||
value: number; | value: number; | ||||
} | } | ||||
const InvoiceTable: React.FC<Props> = ({ projects }) => { | |||||
const ExpenseTable: React.FC<Props> = ({ projects }) => { | |||||
console.log(projects) | console.log(projects) | ||||
const projectCombos = projects.map(item => item.code) | |||||
const { t } = useTranslation() | const { t } = useTranslation() | ||||
const [rowModesModel, setRowModesModel] = useState<GridRowModesModel>({}); | const [rowModesModel, setRowModesModel] = useState<GridRowModesModel>({}); | ||||
const [selectedRow, setSelectedRow] = useState<invoiceListRow[] | []>([]); | |||||
const [selectedRow, setSelectedRow] = useState<ExpenseListRow[] | []>([]); | |||||
const { getValues, setValue, clearErrors, setError } = | const { getValues, setValue, clearErrors, setError } = | ||||
useFormContext<any>(); | useFormContext<any>(); | ||||
const apiRef = useGridApiRef(); | const apiRef = useGridApiRef(); | ||||
const [projectCode, setProjectCode] = useState<project>({label: "", value: 0}) | |||||
const validateInvoiceEntry = ( | |||||
entry: Partial<invoiceList>, | |||||
): InvoiceListError | undefined => { | |||||
const validateExpenseEntry = ( | |||||
entry: Partial<ProjectExpensesResultFormatted>, | |||||
): ExpenseListError | undefined => { | |||||
// Test for errors | // Test for errors | ||||
const error: any = {}; | |||||
console.log(entry) | |||||
if (!entry.issuedAmount) { | |||||
error.issuedAmount = "Please input issued amount "; | |||||
} else if (!entry.issuedAmount) { | |||||
error.receivedAmount = "Please input received amount"; | |||||
} else if (entry.invoiceNo === "") { | |||||
error.invoiceNo = "Please input invoice number"; | |||||
} else if (!entry.issuedDate) { | |||||
error.issuedDate = "Please input issue date"; | |||||
} else if (!entry.receiptDate){ | |||||
} | |||||
const error: ExpenseListError = {}; | |||||
if (!entry.issueDate) error.issueDate = "Please input issued date"; | |||||
if (!entry.amount) error.amount = "Please input amount"; | |||||
if (!entry.projectCode) error.projectCode = "Please input project code"; | |||||
console.log(error) | |||||
return Object.keys(error).length > 0 ? error : undefined; | return Object.keys(error).length > 0 ? error : undefined; | ||||
} | } | ||||
@@ -105,8 +97,8 @@ const InvoiceTable: React.FC<Props> = ({ projects }) => { | |||||
"", | "", | ||||
) | ) | ||||
const error = validateInvoiceEntry(row); | |||||
// console.log(error) | |||||
const error = validateExpenseEntry(row); | |||||
console.log(error) | |||||
// Test for warnings | // Test for warnings | ||||
// apiRef.current.updateRows([{ id, _error: error }]); | // apiRef.current.updateRows([{ id, _error: error }]); | ||||
@@ -159,8 +151,8 @@ const InvoiceTable: React.FC<Props> = ({ projects }) => { | |||||
const processRowUpdate = useCallback( | const processRowUpdate = useCallback( | ||||
( | ( | ||||
newRow: GridRowModel<invoiceListRow>, | |||||
originalRow: GridRowModel<invoiceListRow>, | |||||
newRow: GridRowModel<ExpenseListRow>, | |||||
originalRow: GridRowModel<ExpenseListRow>, | |||||
) => { | ) => { | ||||
const errors = validateRow(newRow.id!!); | const errors = validateRow(newRow.id!!); | ||||
if (errors) { | if (errors) { | ||||
@@ -178,7 +170,7 @@ const InvoiceTable: React.FC<Props> = ({ projects }) => { | |||||
const rowToSave = { | const rowToSave = { | ||||
...updatedRow, | ...updatedRow, | ||||
} satisfies invoiceListRow; | |||||
} satisfies ExpenseListRow; | |||||
console.log(newRow) | console.log(newRow) | ||||
@@ -198,7 +190,7 @@ const InvoiceTable: React.FC<Props> = ({ projects }) => { | |||||
(updateError: ProcessRowUpdateError) => { | (updateError: ProcessRowUpdateError) => { | ||||
const errors = updateError.errors; | const errors = updateError.errors; | ||||
const oldRow = updateError.row; | const oldRow = updateError.row; | ||||
// console.log(errors) | |||||
console.log(errors) | |||||
apiRef.current.updateRows([{ ...oldRow, _error: errors }]); | apiRef.current.updateRows([{ ...oldRow, _error: errors }]); | ||||
}, | }, | ||||
[apiRef] | [apiRef] | ||||
@@ -209,38 +201,64 @@ const InvoiceTable: React.FC<Props> = ({ projects }) => { | |||||
setValue("data", selectedRow) | setValue("data", selectedRow) | ||||
}, [selectedRow, setValue]); | }, [selectedRow, setValue]); | ||||
function renderAutocomplete(params: GridRenderCellParams<any, number>) { | |||||
return( | |||||
<Box sx={{ display: 'flex', alignItems: 'center', pr: 2 }}> | |||||
<Autocomplete | |||||
readOnly | |||||
sx={{ width: 300 }} | |||||
value={params.row.projectCode} | |||||
options={projectCombos} | |||||
renderInput={(params) => <TextField {...params} />} | |||||
/> | |||||
</Box> | |||||
) | |||||
} | |||||
function AutocompleteInput(props: GridRenderCellParams<any, number>) { | |||||
const { id, value, field, hasFocus } = props; | |||||
const apiRef = useGridApiContext(); | |||||
const ref = React.useRef<HTMLElement>(null); | |||||
const handleValueChange = useCallback((newValue: any) => { | |||||
console.log(newValue) | |||||
apiRef.current.setEditCellValue({ id, field, value: newValue }) | |||||
}, []); | |||||
return ( | |||||
<Box sx={{ display: 'flex', alignItems: 'center', pr: 2 }}> | |||||
<Autocomplete | |||||
disablePortal | |||||
options={projectCombos} | |||||
sx={{ width: 300 }} | |||||
onChange={(event: React.SyntheticEvent<Element, Event>, value: string | null, ) => handleValueChange(value)} | |||||
renderInput={(params) => <TextField {...params} />} | |||||
/> | |||||
</Box> | |||||
); | |||||
} | |||||
const renderAutocompleteInput: GridColDef['renderCell'] = (params) => { | |||||
return <AutocompleteInput {...params} />; | |||||
}; | |||||
const editCombinedColumns = useMemo<GridColDef[]>( | const editCombinedColumns = useMemo<GridColDef[]>( | ||||
() => [ | () => [ | ||||
{ field: "invoiceNo", headerName: t("Invoice No"), editable: true, flex: 0.5 }, | |||||
{ field: "expenseNo", headerName: t("Expense No"), editable: true, flex: 0.5 }, | |||||
{ field: "projectCode", | { field: "projectCode", | ||||
headerName: t("Project Code"), | headerName: t("Project Code"), | ||||
editable: true, | editable: true, | ||||
flex: 0.3, | flex: 0.3, | ||||
renderEditCell(params: GridRenderEditCellParams<invoiceListRow, number>){ | |||||
return( | |||||
<Autocomplete | |||||
disablePortal | |||||
options={[]} | |||||
sx={{width: '100%'}} | |||||
renderInput={(params) => <TextField {...params} />} | |||||
/> | |||||
) | |||||
} | |||||
renderCell: renderAutocomplete, | |||||
renderEditCell: renderAutocompleteInput | |||||
}, | }, | ||||
{ field: "issuedDate", | |||||
{ field: "issueDate", | |||||
headerName: t("Issue Date"), | headerName: t("Issue Date"), | ||||
editable: true, | editable: true, | ||||
flex: 0.4, | flex: 0.4, | ||||
// type: 'date', | |||||
// valueGetter: (params) => { | |||||
// // console.log(params.row.issuedDate) | |||||
// return new Date(params.row.issuedDate) | |||||
// }, | |||||
type: 'date', | |||||
}, | }, | ||||
{ field: "issuedAmount", | |||||
{ field: "amount", | |||||
headerName: t("Amount (HKD)"), | headerName: t("Amount (HKD)"), | ||||
editable: true, | editable: true, | ||||
flex: 0.5, | flex: 0.5, | ||||
@@ -251,13 +269,8 @@ const editCombinedColumns = useMemo<GridColDef[]>( | |||||
headerName: t("Settle Date"), | headerName: t("Settle Date"), | ||||
editable: true, | editable: true, | ||||
flex: 0.4, | flex: 0.4, | ||||
}, | |||||
{ field: "receivedAmount", | |||||
headerName: t("Actual Received Amount (HKD)"), | |||||
editable: true, | |||||
flex: 0.5, | |||||
type: 'number' | |||||
}, | |||||
type: 'date', | |||||
}, | |||||
], | ], | ||||
[t] | [t] | ||||
) | ) | ||||
@@ -271,7 +284,7 @@ const footer = ( | |||||
onClick={addRow} | onClick={addRow} | ||||
size="small" | size="small" | ||||
> | > | ||||
{t("Create Invoice")} | |||||
{t("Create Expense")} | |||||
</Button> | </Button> | ||||
</Box> | </Box> | ||||
); | ); | ||||
@@ -301,9 +314,9 @@ const footer = ( | |||||
columns={editCombinedColumns} | columns={editCombinedColumns} | ||||
processRowUpdate={processRowUpdate} | processRowUpdate={processRowUpdate} | ||||
onProcessRowUpdateError={onProcessRowUpdateError} | onProcessRowUpdateError={onProcessRowUpdateError} | ||||
getCellClassName={(params: GridCellParams<invoiceListRow>) => { | |||||
getCellClassName={(params: GridCellParams<ExpenseListRow>) => { | |||||
let classname = ""; | let classname = ""; | ||||
if (params.row._error?.[params.field as keyof invoiceList]) { | |||||
if (params.row._error?.[params.field as keyof ProjectExpensesResultFormatted]) { | |||||
classname = "hasError"; | classname = "hasError"; | ||||
} | } | ||||
return classname; | return classname; | ||||
@@ -320,7 +333,7 @@ const footer = ( | |||||
) | ) | ||||
} | } | ||||
export default InvoiceTable | |||||
export default ExpenseTable | |||||
const NoRowsOverlay: React.FC = () => { | const NoRowsOverlay: React.FC = () => { | ||||
const { t } = useTranslation("home"); | const { t } = useTranslation("home"); | ||||
@@ -23,11 +23,12 @@ const InvoiceSearchWrapper: React.FC & SubComponents = async () => { | |||||
const teamId = userStaff?.teamId | const teamId = userStaff?.teamId | ||||
const invoices = await fetchInvoicesV3() | const invoices = await fetchInvoicesV3() | ||||
const projects = await fetchProjects() | const projects = await fetchProjects() | ||||
const filteredProjects = projects.filter(project => project.teamId === teamId) | |||||
let filteredInvoice = invoices | let filteredInvoice = invoices | ||||
if (teamId) { | if (teamId) { | ||||
filteredInvoice = invoices.filter(invoice => invoice.teamId === teamId) | |||||
filteredInvoice = invoices.filter(i => i.teamId === teamId) | |||||
} else { | |||||
filteredInvoice = [] | |||||
} | } | ||||
const convertedInvoices = filteredInvoice.map((invoice)=>{ | const convertedInvoices = filteredInvoice.map((invoice)=>{ | ||||
@@ -49,7 +50,7 @@ const InvoiceSearchWrapper: React.FC & SubComponents = async () => { | |||||
return <InvoiceSearch | return <InvoiceSearch | ||||
invoices={convertedInvoices} | invoices={convertedInvoices} | ||||
projects={filteredProjects} | |||||
projects={projects} | |||||
abilities={abilities} | abilities={abilities} | ||||
/> | /> | ||||