2. Update project expense, align with invoice (add team search, add expense No, and update team to full name code - name)tags/Baseline_180220205_Frontend
@@ -15,6 +15,7 @@ export type ProjectExpensesResult = { | |||
issueDate: number[] | |||
receiptDate: number[] | |||
remarks?: string | |||
team: string | |||
} | |||
export type ProjectExpensesResultFormatted = Omit<ProjectExpensesResult, 'issueDate' | 'receiptDate'> & { | |||
@@ -23,6 +23,7 @@ import dayjs from "dayjs"; | |||
import { INPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; | |||
import { submitDialog, successDialog } from "../Swal/CustomAlerts"; | |||
import { useRouter } from 'next/navigation'; | |||
import { ProjectExpensesResultFormatted } from "@/app/api/projectExpenses"; | |||
interface Props { | |||
isOpen: boolean; | |||
@@ -41,9 +42,12 @@ const modalSx: SxProps = { | |||
bgcolor: "background.paper", | |||
}; | |||
type postData = { | |||
data: PostExpenseData[]; | |||
}; | |||
data: (PostExpenseData & { _error: any })[]; | |||
} | |||
const CreateExpenseModal: React.FC<Props> = ({ isOpen, onClose, projects }) => { | |||
const { t } = useTranslation(); | |||
const formProps = useForm<postData>(); | |||
@@ -51,6 +55,10 @@ const CreateExpenseModal: React.FC<Props> = ({ isOpen, onClose, projects }) => { | |||
const onSubmit = useCallback<SubmitHandler<postData>>((data) => { | |||
const _data = data.data; | |||
// console.log(_data.some(data => data._error)) | |||
if(_data.some(data => data._error)){ | |||
return | |||
} | |||
try { | |||
const postData: PostExpenseData[] = _data.map((item) => { | |||
return { | |||
@@ -76,12 +76,12 @@ const ExpenseSearch: React.FC<Props> = ({ expenses, projects }) => { | |||
// { label: t("Expense No"), paramName: "ExpenseNo", type: "text" }, | |||
{ label: t("Project Code"), paramName: "projectCode", type: "text" }, | |||
{ label: t("Project Name"), paramName: "projectName", type: "text" }, | |||
// { | |||
// label: t("Team"), | |||
// paramName: "team", | |||
// type: "select", | |||
// options: uniq(expenses.map((expenses) => expenses.teamCode)), | |||
// }, | |||
{ | |||
label: t("Team"), | |||
paramName: "team", | |||
type: "select", | |||
options: uniq(expenses.map((expenses) => `${expenses.teamCode} - ${expenses.teamName}`)), | |||
}, | |||
], | |||
[] | |||
); | |||
@@ -97,17 +97,18 @@ const ExpenseSearch: React.FC<Props> = ({ expenses, projects }) => { | |||
buttonIcon: <EditNote />, | |||
// disabled: !abilities.includes(MAINTAIN_PROJECT), | |||
}, | |||
{ name: "expenseNo", label: t("Expense No.")}, | |||
{ name: "projectCode", label: t("Project Code") }, | |||
{ name: "projectName", label: t("Project Name") }, | |||
{ name: "amount", label: t("Amount (HKD)"), type: 'money', needTranslation: true}, | |||
{ name: "teamCode", label: t("Team") }, | |||
{ name: "team", label: t("Team") }, | |||
{ name: "issueDate", label: t("Issue Date") }, | |||
{ name: "remarks", label: t("Remarks")} | |||
], | |||
[t] | |||
); | |||
const onReset = useCallback(() => { | |||
// setFilteredExpenses(); | |||
setFilteredExpenses(expenses); | |||
}, []); | |||
/** | |||
@@ -287,7 +288,8 @@ const ExpenseSearch: React.FC<Props> = ({ expenses, projects }) => { | |||
expenses.filter( | |||
(e) => | |||
e.projectCode.toLowerCase().includes(query.projectCode.toLowerCase()) && | |||
e.projectName.toLowerCase().includes(query.projectName.toLowerCase()) | |||
e.projectName.toLowerCase().includes(query.projectName.toLowerCase()) && | |||
(query.team === "All" || query.team.toLowerCase().includes(e.team.toLowerCase())) | |||
), | |||
); | |||
}} | |||
@@ -43,7 +43,8 @@ const ExpenseSearchWrapper: React.FC & SubComponents = async () => { | |||
...e, | |||
issuedDate: e.issueDate, | |||
issueDate: formattedIssueDate, | |||
receiptDate: formattedReceiptDate | |||
receiptDate: formattedReceiptDate, | |||
team: `${e.teamCode} - ${e.teamName}` | |||
}) | |||
}) | |||
return <ExpenseSearch | |||
@@ -28,6 +28,7 @@ interface Props { | |||
TotalFees: number; | |||
TotalBudget: number; | |||
TotalCumulative: number; | |||
TotalProjectExpense: number; | |||
TotalInvoicedAmount: number; | |||
TotalUnInvoicedAmount: number; | |||
TotalReceivedAmount: number; | |||
@@ -50,6 +51,7 @@ const ProjectFinancialCard: React.FC<Props> = ({ | |||
TotalFees, | |||
TotalBudget, | |||
TotalCumulative, | |||
TotalProjectExpense, | |||
TotalInvoicedAmount, | |||
TotalUnInvoicedAmount, | |||
TotalReceivedAmount, | |||
@@ -138,10 +140,29 @@ const ProjectFinancialCard: React.FC<Props> = ({ | |||
<div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}> | |||
{"(d) " + t("Total Cumulative Expenditure")} | |||
</div> | |||
<div className="text-lg font-medium mx-5" style={dataBaseStyle}> | |||
{(TotalCumulative + TotalProjectExpense).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} | |||
</div> | |||
<div style={{ overflow: 'hidden' }}> | |||
<div className="text-sm font-medium ml-5 border-solid w-fit rounded-md mt-2 float-right mr-2" style={{ color: "#888d8f" }}> | |||
<div className="ml-2 mr-2 ">{"(d) = (d1) + (d2)"}</div> | |||
</div> | |||
</div> | |||
<hr /> | |||
<div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}> | |||
{"(d1) " + t("Manpower Expenses")} | |||
</div> | |||
<div className="text-lg font-medium mx-5" style={dataBaseStyle}> | |||
{TotalCumulative.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} | |||
</div> | |||
<hr /> | |||
<div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}> | |||
{"(d2) " + t("Project Expenses")} | |||
</div> | |||
<div className="text-lg font-medium mx-5" style={dataBaseStyle}> | |||
{TotalProjectExpense.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} | |||
</div> | |||
<hr /> | |||
<div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}> | |||
{"(e) " + t("Total Invoiced Amount")} | |||
</div> | |||
@@ -59,6 +59,7 @@ const ProjectFinancialSummary: React.FC = () => { | |||
const fetchData = async () => { | |||
const financialSummaryCard = await fetchFinancialSummaryCard(); | |||
console.log(financialSummaryCard) | |||
setProjectFinancialData(financialSummaryCard) | |||
} | |||
const fetchTableData = async (teamId?:any) => { | |||
@@ -231,14 +232,38 @@ const ProjectFinancialSummary: React.FC = () => { | |||
}, | |||
}, | |||
{ | |||
id: 'cumulativeExpenditure', | |||
field: 'cumulativeExpenditure', | |||
id: 'totalExpenditure', | |||
field: 'totalExpenditure', | |||
headerName: t("Total Cumulative Expenditure")+t("HKD"), | |||
minWidth:280, | |||
type: "number", | |||
renderCell: (params:any) => { | |||
return ( | |||
<span>${params.row.cumulativeExpenditure.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</span> | |||
<span>${(params.row.totalExpenditure).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</span> | |||
) | |||
}, | |||
}, | |||
{ | |||
id: 'manhoursExpenditure', | |||
field: 'manhoursExpenditure', | |||
headerName: t("Manpower Expenses")+t("HKD"), | |||
minWidth:280, | |||
type: "number", | |||
renderCell: (params:any) => { | |||
return ( | |||
<span>${params.row.manhoursExpenditure.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</span> | |||
) | |||
}, | |||
}, | |||
{ | |||
id: 'projectExpense', | |||
field: 'projectExpense', | |||
headerName: t("Project Expense")+t("HKD"), | |||
minWidth:280, | |||
type: "number", | |||
renderCell: (params:any) => { | |||
return ( | |||
<span>${(params.row.projectExpense ?? 0).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</span> | |||
) | |||
}, | |||
}, | |||
@@ -453,14 +478,38 @@ const columns2 = [ | |||
}, | |||
}, | |||
{ | |||
id: 'totalCumulativeExpenditure', | |||
field: 'totalCumulativeExpenditure', | |||
id: 'totalExpenditure', | |||
field: 'totalExpenditure', | |||
headerName: t("Total Cumulative Expenditure")+t("HKD"), | |||
minWidth:250, | |||
type: "number", | |||
renderCell: (params:any) => { | |||
return ( | |||
<span>${params.row.cumulativeExpenditure.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</span> | |||
<span>${(params.row.totalExpenditure).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</span> | |||
) | |||
}, | |||
}, | |||
{ | |||
id: 'manhoursExpenditure', | |||
field: 'manhoursExpenditure', | |||
headerName: t("Manpower Expenses")+t("HKD"), | |||
minWidth:280, | |||
type: "number", | |||
renderCell: (params:any) => { | |||
return ( | |||
<span>${params.row.manhoursExpenditure.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</span> | |||
) | |||
}, | |||
}, | |||
{ | |||
id: 'projectExpense', | |||
field: 'projectExpense', | |||
headerName: t("Project Expense")+t("HKD"), | |||
minWidth:280, | |||
type: "number", | |||
renderCell: (params:any) => { | |||
return ( | |||
<span>${(params.row.projectExpense ?? 0).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</span> | |||
) | |||
}, | |||
}, | |||
@@ -512,6 +561,7 @@ const columns2 = [ | |||
const fetchProjectTableData = async (teamId?:any,customerId?:any) => { | |||
const financialSummaryByProject = await searchFinancialSummaryByProject(teamId); | |||
setProjectFinancialRows(financialSummaryByProject) | |||
console.log(financialSummaryByProject) | |||
setFilteredProjectResult(financialSummaryByProject) | |||
} | |||
@@ -546,7 +596,21 @@ const columns2 = [ | |||
<div className="ml-10 mr-10" style={{ display: 'flex', flexWrap: 'wrap', justifyContent: 'start'}}> | |||
{projectFinancialData.map((record:any, index:any) => ( | |||
<div className="hover:cursor-pointer ml-4 mt-5 mb-4 inline-block" key={index} onClick={(r) => handleCardClick(record,index)}> | |||
<ProjectFinancialCard Title={record.teamName == "All Team" ? t("All Team") : record.teamName} TeamId={record.teamId} TotalActiveProjectNumber={record.projectNo} TotalFees={record.totalFee} TotalBudget={record.totalBudget} TotalCumulative={record.cumulativeExpenditure ?? 0} TotalInvoicedAmount={record.totalInvoiced ?? 0} TotalUnInvoicedAmount={record.unInvoiced ?? 0} TotalReceivedAmount={record.totalReceived ?? 0} CashFlowStatus={record.cashFlowStatus ?? "Negative"} CostPerformanceIndex={record.cpi ?? 0} ProjectedCashFlowStatus={record.projectedCashFlowStatus ?? "Negative"} ProjectedCPI={record.projectedCpi ?? 0} ClickedIndex={isCardClickedIndex} Index={index}/> | |||
<ProjectFinancialCard | |||
Title={record.teamName == "All Team" ? t("All Team") : record.teamName} | |||
TeamId={record.teamId} TotalActiveProjectNumber={record.projectNo} | |||
TotalFees={record.totalFee} TotalBudget={record.totalBudget} | |||
TotalCumulative={record.manhoursExpenditure ?? 0} | |||
TotalProjectExpense={record.projectExpense ?? 0} | |||
TotalInvoicedAmount={record.totalInvoiced ?? 0} | |||
TotalUnInvoicedAmount={record.unInvoiced ?? 0} | |||
TotalReceivedAmount={record.totalReceived ?? 0} | |||
CashFlowStatus={record.cashFlowStatus ?? "Negative"} | |||
CostPerformanceIndex={record.cpi ?? 0} | |||
ProjectedCashFlowStatus={record.projectedCashFlowStatus ?? "Negative"} | |||
ProjectedCPI={record.projectedCpi ?? 0} | |||
ClickedIndex={isCardClickedIndex} | |||
Index={index}/> | |||
</div> | |||
))} | |||
</div> | |||