ソースを参照

1. Update Financial Summary, (Total Cum Expenditure = manhour expenditure + porject expense)

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
MSI\2Fi 11ヶ月前
コミット
4864efb9f5
6個のファイルの変更116行の追加19行の削除
  1. +1
    -0
      src/app/api/projectExpenses/index.ts
  2. +10
    -2
      src/components/ExpenseSearch/CreateExpenseModal.tsx
  3. +11
    -9
      src/components/ExpenseSearch/ExpenseSearch.tsx
  4. +2
    -1
      src/components/ExpenseSearch/ExpenseSearchWrapper.tsx
  5. +21
    -0
      src/components/ProjectFinancialSummary/ProjectFinancialCard.tsx
  6. +71
    -7
      src/components/ProjectFinancialSummary/ProjectFinancialSummary.tsx

+ 1
- 0
src/app/api/projectExpenses/index.ts ファイルの表示

@@ -15,6 +15,7 @@ export type ProjectExpensesResult = {
issueDate: number[] issueDate: number[]
receiptDate: number[] receiptDate: number[]
remarks?: string remarks?: string
team: string
} }


export type ProjectExpensesResultFormatted = Omit<ProjectExpensesResult, 'issueDate' | 'receiptDate'> & { export type ProjectExpensesResultFormatted = Omit<ProjectExpensesResult, 'issueDate' | 'receiptDate'> & {


+ 10
- 2
src/components/ExpenseSearch/CreateExpenseModal.tsx ファイルの表示

@@ -23,6 +23,7 @@ import dayjs from "dayjs";
import { INPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; import { INPUT_DATE_FORMAT } from "@/app/utils/formatUtil";
import { submitDialog, successDialog } from "../Swal/CustomAlerts"; import { submitDialog, successDialog } from "../Swal/CustomAlerts";
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { ProjectExpensesResultFormatted } from "@/app/api/projectExpenses";


interface Props { interface Props {
isOpen: boolean; isOpen: boolean;
@@ -41,9 +42,12 @@ const modalSx: SxProps = {
bgcolor: "background.paper", bgcolor: "background.paper",
}; };




type postData = { type postData = {
data: PostExpenseData[];
};
data: (PostExpenseData & { _error: any })[];
}

const CreateExpenseModal: React.FC<Props> = ({ isOpen, onClose, projects }) => { const CreateExpenseModal: React.FC<Props> = ({ isOpen, onClose, projects }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const formProps = useForm<postData>(); const formProps = useForm<postData>();
@@ -51,6 +55,10 @@ const CreateExpenseModal: React.FC<Props> = ({ isOpen, onClose, projects }) => {


const onSubmit = useCallback<SubmitHandler<postData>>((data) => { const onSubmit = useCallback<SubmitHandler<postData>>((data) => {
const _data = data.data; const _data = data.data;
// console.log(_data.some(data => data._error))
if(_data.some(data => data._error)){
return
}
try { try {
const postData: PostExpenseData[] = _data.map((item) => { const postData: PostExpenseData[] = _data.map((item) => {
return { return {


+ 11
- 9
src/components/ExpenseSearch/ExpenseSearch.tsx ファイルの表示

@@ -76,12 +76,12 @@ const ExpenseSearch: React.FC<Props> = ({ expenses, projects }) => {
// { 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("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 />, buttonIcon: <EditNote />,
// disabled: !abilities.includes(MAINTAIN_PROJECT), // disabled: !abilities.includes(MAINTAIN_PROJECT),
}, },
{ name: "expenseNo", label: t("Expense No.")},
{ 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: "amount", label: t("Amount (HKD)"), type: 'money', needTranslation: true}, { 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: "issueDate", label: t("Issue Date") },
{ name: "remarks", label: t("Remarks")} { name: "remarks", label: t("Remarks")}
], ],
[t] [t]
); );
const onReset = useCallback(() => { const onReset = useCallback(() => {
// setFilteredExpenses();
setFilteredExpenses(expenses);
}, []); }, []);


/** /**
@@ -287,7 +288,8 @@ const ExpenseSearch: React.FC<Props> = ({ expenses, projects }) => {
expenses.filter( expenses.filter(
(e) => (e) =>
e.projectCode.toLowerCase().includes(query.projectCode.toLowerCase()) && 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()))
), ),
); );
}} }}


+ 2
- 1
src/components/ExpenseSearch/ExpenseSearchWrapper.tsx ファイルの表示

@@ -43,7 +43,8 @@ const ExpenseSearchWrapper: React.FC & SubComponents = async () => {
...e, ...e,
issuedDate: e.issueDate, issuedDate: e.issueDate,
issueDate: formattedIssueDate, issueDate: formattedIssueDate,
receiptDate: formattedReceiptDate
receiptDate: formattedReceiptDate,
team: `${e.teamCode} - ${e.teamName}`
}) })
}) })
return <ExpenseSearch return <ExpenseSearch


+ 21
- 0
src/components/ProjectFinancialSummary/ProjectFinancialCard.tsx ファイルの表示

@@ -28,6 +28,7 @@ interface Props {
TotalFees: number; TotalFees: number;
TotalBudget: number; TotalBudget: number;
TotalCumulative: number; TotalCumulative: number;
TotalProjectExpense: number;
TotalInvoicedAmount: number; TotalInvoicedAmount: number;
TotalUnInvoicedAmount: number; TotalUnInvoicedAmount: number;
TotalReceivedAmount: number; TotalReceivedAmount: number;
@@ -50,6 +51,7 @@ const ProjectFinancialCard: React.FC<Props> = ({
TotalFees, TotalFees,
TotalBudget, TotalBudget,
TotalCumulative, TotalCumulative,
TotalProjectExpense,
TotalInvoicedAmount, TotalInvoicedAmount,
TotalUnInvoicedAmount, TotalUnInvoicedAmount,
TotalReceivedAmount, TotalReceivedAmount,
@@ -138,10 +140,29 @@ const ProjectFinancialCard: React.FC<Props> = ({
<div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}> <div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}>
{"(d) " + t("Total Cumulative Expenditure")} {"(d) " + t("Total Cumulative Expenditure")}
</div> </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}> <div className="text-lg font-medium mx-5" style={dataBaseStyle}>
{TotalCumulative.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} {TotalCumulative.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
</div> </div>
<hr /> <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" }}> <div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}>
{"(e) " + t("Total Invoiced Amount")} {"(e) " + t("Total Invoiced Amount")}
</div> </div>


+ 71
- 7
src/components/ProjectFinancialSummary/ProjectFinancialSummary.tsx ファイルの表示

@@ -59,6 +59,7 @@ const ProjectFinancialSummary: React.FC = () => {


const fetchData = async () => { const fetchData = async () => {
const financialSummaryCard = await fetchFinancialSummaryCard(); const financialSummaryCard = await fetchFinancialSummaryCard();
console.log(financialSummaryCard)
setProjectFinancialData(financialSummaryCard) setProjectFinancialData(financialSummaryCard)
} }
const fetchTableData = async (teamId?:any) => { 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"), headerName: t("Total Cumulative Expenditure")+t("HKD"),
minWidth:280, minWidth:280,
type: "number", type: "number",
renderCell: (params:any) => { renderCell: (params:any) => {
return ( 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"), headerName: t("Total Cumulative Expenditure")+t("HKD"),
minWidth:250, minWidth:250,
type: "number", type: "number",
renderCell: (params:any) => { renderCell: (params:any) => {
return ( 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 fetchProjectTableData = async (teamId?:any,customerId?:any) => {
const financialSummaryByProject = await searchFinancialSummaryByProject(teamId); const financialSummaryByProject = await searchFinancialSummaryByProject(teamId);
setProjectFinancialRows(financialSummaryByProject) setProjectFinancialRows(financialSummaryByProject)
console.log(financialSummaryByProject)
setFilteredProjectResult(financialSummaryByProject) setFilteredProjectResult(financialSummaryByProject)
} }


@@ -546,7 +596,21 @@ const columns2 = [
<div className="ml-10 mr-10" style={{ display: 'flex', flexWrap: 'wrap', justifyContent: 'start'}}> <div className="ml-10 mr-10" style={{ display: 'flex', flexWrap: 'wrap', justifyContent: 'start'}}>
{projectFinancialData.map((record:any, index:any) => ( {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)}> <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>
))} ))}
</div> </div>


読み込み中…
キャンセル
保存