handleCardClick(record.id)}>
+
handleCardClick(record.id)}>
= ({
TotalProjectExpense={record.projectExpense}
TotalInvoicedAmount={record.invoicedAmount}
TotalUnInvoicedAmount={Math.abs(record.totalFee - record.invoicedAmount)}
+ // TotalUnInvoicedAmount={Math.abs(record.uninvoicedAmount)}
TotalReceivedAmount={record.paidAmount}
CashFlowStatus={record.invoicedAmount >= (record.projectExpense + record.manhourExpense) ? "Positive" : "Negative"}
- CostPerformanceIndex={record.invoicedAmount/(record.projectExpense + record.manhourExpense) || 0}
+ CostPerformanceIndex={!isFinite(record.invoicedAmount/(record.projectExpense + record.manhourExpense)) ? 0 : record.invoicedAmount/(record.projectExpense + record.manhourExpense) || 0}
ProjectedCashFlowStatus={record.totalFee >= (record.projectExpense + record.manhourExpense) ? "Positive" : "Negative"}
- ProjectedCPI={record.totalFee/(record.projectExpense + record.manhourExpense)}
+ ProjectedCPI={!isFinite(record.totalFee/(record.projectExpense + record.manhourExpense)) ? 0 : record.totalFee/(record.projectExpense + record.manhourExpense) || 0}
ClickedIndex={isCardClickedIndex}
Index={record.id}/>
diff --git a/src/components/ProjectFinancialSummaryV2/FinnancialStatusByProject.tsx b/src/components/ProjectFinancialSummaryV2/FinnancialStatusByProject.tsx
index fe02544..cf58e73 100644
--- a/src/components/ProjectFinancialSummaryV2/FinnancialStatusByProject.tsx
+++ b/src/components/ProjectFinancialSummaryV2/FinnancialStatusByProject.tsx
@@ -8,8 +8,10 @@ import { useEffect, useMemo, useState } from "react";
import CustomDatagrid from "../CustomDatagrid";
import { useTranslation } from "react-i18next";
import { useRouter } from "next/navigation";
-import { Box } from "@mui/material";
+import { Box, Card, CardHeader } from "@mui/material";
import { SumOfByClient } from "./gptFn";
+import { exportFinancialSummaryV2ByClientExcel, exportFinancialSummaryV2ByProjectExcel } from "@/app/api/financialsummary/actions";
+import { downloadFile } from "@/app/utils/commonUtil";
// import { summarizeFinancialData } from "./gptFn";
interface Props {
@@ -99,7 +101,8 @@ const FinancialStatusByProject: React.FC
= ({
headerName: t("Cash Flow Status"),
minWidth: 80,
renderCell: (params: any) => {
- if (params.row.invoicedAmount >= params.row.cumulativeExpenditure) {
+ var cumulativeExpenditure = params.row.projectExpense + params.row.manhourExpense
+ if (params.row.invoicedAmount >= cumulativeExpenditure) {
return {t("Positive")};
} else {
return {t("Negative")};
@@ -112,7 +115,8 @@ const FinancialStatusByProject: React.FC = ({
headerName: "CPI",
minWidth: 50,
renderCell: (params: any) => {
- var cpi = params.row.invoicedAmount/(params.row.projectExpense + params.row.invoicedAmount) || 0
+ var cumulativeExpenditure = params.row.projectExpense + params.row.manhourExpense
+ var cpi = params.row.invoicedAmount/cumulativeExpenditure || 0
return (
= 1 ? greenColor : redColor}>
{cpi.toLocaleString(undefined, {
@@ -129,7 +133,7 @@ const FinancialStatusByProject: React.FC = ({
headerName: t("Projected Cash Flow Status"),
minWidth: 100,
renderCell: (params: any) => {
- var cumulativeExpenditure = params.row.projectExpense + params.row.invoicedAmount
+ var cumulativeExpenditure = params.row.projectExpense + params.row.manhourExpense
if (params.row.totalFee >= cumulativeExpenditure) {
return {t("Positive")};
} else {
@@ -143,7 +147,8 @@ const FinancialStatusByProject: React.FC = ({
headerName: t("Projected CPI"),
minWidth: 50,
renderCell: (params: any) => {
- var projectedCpi = params.row.totalFee/(params.row.projectExpense + params.row.invoicedAmount) == Infinity ? 'N/A' : params.row.totalFee/(params.row.projectExpense + params.row.invoicedAmount)
+ var cumulativeExpenditure = params.row.projectExpense + params.row.manhourExpense
+ var projectedCpi = params.row.totalFee/cumulativeExpenditure == Infinity ? 'N/A' : params.row.totalFee/cumulativeExpenditure
return (
= 1 ? greenColor : redColor)}
@@ -199,7 +204,7 @@ const FinancialStatusByProject: React.FC = ({
minWidth: 250,
type: "number",
renderCell: (params: any) => {
- var cumulativeExpenditure = params.row.projectExpense + params.row.invoicedAmount
+ var cumulativeExpenditure = params.row.projectExpense + params.row.manhourExpense
return (
$
@@ -272,7 +277,9 @@ const FinancialStatusByProject: React.FC = ({
minWidth: 250,
type: "number",
renderCell: (params: any) => {
- var nonInvoiced = params.row.totalFee - params.row.invoicedAmount
+ var fee = params.row.totalFee
+ var invoiced = params.row.invoicedAmount
+ var nonInvoiced = fee - invoiced < 0 ? 0 : fee - invoiced
return (
$
@@ -341,7 +348,7 @@ const FinancialStatusByProject: React.FC = ({
headerName: t("Cash Flow Status"),
minWidth: 100,
renderCell: (params: any) => {
- var cumulativeExpenditure = params.row.projectExpense + params.row.invoicedAmount
+ var cumulativeExpenditure = params.row.projectExpense + params.row.manhourExpense
return params.row.invoicedAmount >= cumulativeExpenditure ?
{t("Positive")}
: {t("Negative")}
@@ -353,7 +360,7 @@ const FinancialStatusByProject: React.FC = ({
headerName: t("CPI"),
minWidth: 50,
renderCell: (params: any) => {
- var cumulativeExpenditure = params.row.projectExpense + params.row.invoicedAmount
+ var cumulativeExpenditure = params.row.projectExpense + params.row.manhourExpense
var cpi = cumulativeExpenditure != 0 ? params.row.invoicedAmount/cumulativeExpenditure : 0
var cpiString = cpi.toLocaleString(undefined, {
minimumFractionDigits: 2,
@@ -370,8 +377,8 @@ const FinancialStatusByProject: React.FC = ({
headerName: t("Projected Cash Flow Status"),
minWidth: 100,
renderCell: (params: any) => {
- var cumulativeExpenditure = params.row.projectExpense + params.row.invoicedAmount
- var status = params.row.invoiceAmount >= cumulativeExpenditure
+ var cumulativeExpenditure = params.row.projectExpense + params.row.manhourExpense
+ var status = params.row.totalFee >= cumulativeExpenditure
return status ?
{t("Positive")}
: {t("Negative")}
@@ -383,7 +390,7 @@ const FinancialStatusByProject: React.FC = ({
headerName: t("Projected CPI"),
minWidth: 50,
renderCell: (params: any) => {
- var cumulativeExpenditure = params.row.projectExpense + params.row.invoicedAmount
+ var cumulativeExpenditure = params.row.projectExpense + params.row.manhourExpense
var projectCpi = cumulativeExpenditure != 0 ? params.row.totalFee/cumulativeExpenditure : 0
var projectCpiString = projectCpi.toLocaleString(undefined, {
minimumFractionDigits: 2,
@@ -439,7 +446,7 @@ const FinancialStatusByProject: React.FC = ({
minWidth: 280,
type: "number",
renderCell: (params: any) => {
- var cumulativeExpenditure = params.row.projectExpense + params.row.invoicedAmount
+ var cumulativeExpenditure = params.row.projectExpense + params.row.manhourExpense
return (
$
@@ -512,11 +519,13 @@ const FinancialStatusByProject: React.FC = ({
minWidth: 250,
type: "number",
renderCell: (params: any) => {
- var uninvoiced = params.row.totalFee - params.row.invoicedAmount
+ var fee = params.row.totalFee
+ var invoiced = params.row.invoicedAmount
+ var nonInvoiced = fee - invoiced < 0 ? 0 : fee - invoiced
return (
$
- {uninvoiced.toLocaleString(undefined, {
+ {nonInvoiced.toLocaleString(undefined, {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
})}
@@ -544,64 +553,104 @@ const FinancialStatusByProject: React.FC = ({
},
];
+ const handleExportByClient = async () => {
+ const response = await exportFinancialSummaryV2ByClientExcel(filteredByClientRows)
+ if (response) {
+ downloadFile(new Uint8Array(response.blobValue), response.filename!!)
+ }
+ console.log(filteredByClientRows)
+ };
+
+ const handleExportByProject = async () => {
+ const response = await exportFinancialSummaryV2ByProjectExcel(filteredByProjectRows)
+ if (response) {
+ downloadFile(new Uint8Array(response.blobValue), response.filename!!)
+ }
+ console.log(filteredByProjectRows)
+ };
+
return (
<>
- {
- console.log(query)
- if (query.projectCode.length > 0 || query.projectName.length > 0) {
- setFilteredByProjectRows(
- financialSummByProject.filter(
- (cp) =>
- cp.projectCode.toLowerCase().includes(query.projectCode.trim().toLowerCase()) &&
- cp.projectName.toLowerCase().includes(query.projectName.trim().toLowerCase())
- ),
- );
- } else {
- setFilteredByProjectRows(financialSummByProject)
- }
- }}
- />
-
-
+
+
+
+
+ {filteredByProjectRows.length > 0 && (
+
+ )}
+
+ {
+ console.log(query)
+ if (query.projectCode.length > 0 || query.projectName.length > 0) {
+ setFilteredByProjectRows(
+ financialSummByProject.filter(
+ (cp) =>
+ cp.projectCode.toLowerCase().includes(query.projectCode.trim().toLowerCase()) &&
+ cp.projectName.toLowerCase().includes(query.projectName.trim().toLowerCase())
+ ),
+ );
+ } else {
+ setFilteredByProjectRows(financialSummByProject)
+ }
+ }}
/>
-
+
+
+
{/* items={filteredStaff} columns={columns} /> */}
+
- {
- console.log(query)
- if (query.customerCode.length > 0 || query.customerName.length > 0) {
- setFilteredByClientRows(
- financialSummByClient.filter(
- (cp) =>
- cp.customerCode.toLowerCase().includes(query.customerCode.trim().toLowerCase()) &&
- cp.customerName.toLowerCase().includes(query.customerName.trim().toLowerCase())
- ),
- );
- } else {
- setFilteredByClientRows(financialSummByClient)
- }
- }}
- />
-
-
+
+
+
+
+ {filteredByProjectRows.length > 0 && (
+
+ )}
+
+ {
+ console.log(query)
+ if (query.customerCode.length > 0 || query.customerName.length > 0) {
+ setFilteredByClientRows(
+ financialSummByClient.filter(
+ (cp) =>
+ cp.customerCode.toLowerCase().includes(query.customerCode.trim().toLowerCase()) &&
+ cp.customerName.toLowerCase().includes(query.customerName.trim().toLowerCase())
+ ),
+ );
+ } else {
+ setFilteredByClientRows(financialSummByClient)
+ }
+ }}
/>
-
+
+
+
+
>
);
diff --git a/src/components/ProjectFinancialSummaryV2/gptFn.tsx b/src/components/ProjectFinancialSummaryV2/gptFn.tsx
index 4404029..8965484 100644
--- a/src/components/ProjectFinancialSummaryV2/gptFn.tsx
+++ b/src/components/ProjectFinancialSummaryV2/gptFn.tsx
@@ -8,6 +8,7 @@ export type SumOfByTeam = {
manhourExpense: number,
projectExpense: number,
invoicedAmount: number,
+ uninvoicedAmount: number,
paidAmount: number,
activeProject: number,
}
@@ -21,6 +22,7 @@ export type SumOfByClient = {
manhourExpense: number,
projectExpense: number,
invoicedAmount: number,
+ uninvoicedAmount: number,
paidAmount: number,
sumOfProjects: number,
}
@@ -36,6 +38,7 @@ export function sumUpByClient(data: FinancialByProject[]): SumOfByClient[] {
manhourExpense: 0,
projectExpense: 0,
invoicedAmount: 0,
+ uninvoicedAmount: 0,
paidAmount: 0,
sumOfProjects: 0
};
@@ -46,6 +49,7 @@ export function sumUpByClient(data: FinancialByProject[]): SumOfByClient[] {
acc[item.custId].manhourExpense += item.manhourExpense;
acc[item.custId].projectExpense += item.projectExpense;
acc[item.custId].invoicedAmount += item.invoicedAmount;
+ acc[item.custId].uninvoicedAmount += item.uninvoicedAmount;
acc[item.custId].paidAmount += item.paidAmount;
acc[item.custId].sumOfProjects += 1;
@@ -64,6 +68,7 @@ export function sumUpByTeam(data: FinancialByProject[]): SumOfByTeam[] {
manhourExpense: 0,
projectExpense: 0,
invoicedAmount: 0,
+ uninvoicedAmount: 0,
paidAmount: 0,
activeProject: 0
};
@@ -75,6 +80,7 @@ export function sumUpByTeam(data: FinancialByProject[]): SumOfByTeam[] {
acc[item.teamId].manhourExpense += item.manhourExpense;
acc[item.teamId].projectExpense += item.projectExpense;
acc[item.teamId].invoicedAmount += item.invoicedAmount;
+ acc[item.teamId].uninvoicedAmount += item.uninvoicedAmount;
acc[item.teamId].paidAmount += item.paidAmount;
acc[item.teamId].activeProject += 1;
diff --git a/src/components/ProjectSearch/ProjectSearch.tsx b/src/components/ProjectSearch/ProjectSearch.tsx
index dadee7b..a221ec6 100644
--- a/src/components/ProjectSearch/ProjectSearch.tsx
+++ b/src/components/ProjectSearch/ProjectSearch.tsx
@@ -13,6 +13,7 @@ import { reverse, uniqBy } from "lodash";
import { loadDrafts } from "@/app/utils/draftUtils";
import { TeamResult } from "@/app/api/team";
import { Customer } from "@/app/api/customer";
+import ContentCopyIcon from '@mui/icons-material/ContentCopy';
type ProjectResultOrDraft = ProjectResult & { isDraft?: boolean };
@@ -129,6 +130,17 @@ const ProjectSearch: React.FC = ({
[router],
);
+ const onProjectCopyClick = useCallback(
+ (project: ProjectResultOrDraft) => {
+ if (!project.isDraft) {
+ if (Boolean(project.mainProject)) {
+ router.push(`/projects/copySub?id=${project.id}`);
+ } else router.push(`/projects/copy?id=${project.id}`);
+ }
+ },
+ [router],
+ );
+
const columns = useMemo[]>(
() => [
{
@@ -138,6 +150,16 @@ const ProjectSearch: React.FC = ({
buttonIcon: ,
disabled: !abilities.includes(MAINTAIN_PROJECT),
},
+ {
+ name: "id",
+ label: t("Copy"),
+ onClick: onProjectCopyClick,
+ buttonIcon: ,
+ disabled: !abilities.includes(MAINTAIN_PROJECT),
+ disabledRows: {
+ status: ["Draft"]
+ }
+ },
{ name: "code", label: t("Project Code") },
{ name: "name", label: t("Project Name") },
{ name: "category", label: t("Project Category") },
diff --git a/src/components/SearchResults/SearchResults.tsx b/src/components/SearchResults/SearchResults.tsx
index 05bdf1c..2fe6bf3 100644
--- a/src/components/SearchResults/SearchResults.tsx
+++ b/src/components/SearchResults/SearchResults.tsx
@@ -35,6 +35,7 @@ interface ColumnWithAction extends BaseColumn {
onClick: (item: T) => void;
buttonIcon: React.ReactNode;
disabled?: boolean;
+ disabledRows?: { [columnName in keyof T]: string[] }; // Filter the row which is going to be disabled
}
export type Column =
@@ -84,6 +85,22 @@ function SearchResults({
setPage(0);
};
+ const disabledRows = (
+ column: ColumnWithAction,
+ item: T
+ ): Boolean => {
+ if (column.disabledRows) {
+ for (const [key, value] of Object.entries(column.disabledRows)) {
+ if (value
+ .map(v => v.toLowerCase())
+ .includes(String(item[key as keyof T]).toLowerCase())
+ ) return true;
+ }
+ }
+
+ return false;
+ };
+
const table = (
<>
@@ -112,7 +129,7 @@ function SearchResults({
column.onClick(item)}
- disabled={Boolean(column.disabled)}
+ disabled={Boolean(column.disabled) || Boolean(disabledRows(column, item))}
>
{column.buttonIcon}