@@ -1,4 +1,7 @@ | |||
"use server"; | |||
import { cache } from "react"; | |||
import { serverFetchJson } from "@/app/utils/fetchUtil"; | |||
import { BASE_API_URL } from "@/config/api"; | |||
export interface CashFlow { | |||
id: number; | |||
@@ -19,21 +22,5 @@ export const preloadProjects = () => { | |||
}; | |||
export const fetchProjectsCashFlow = cache(async () => { | |||
return mockProjects; | |||
return serverFetchJson<CashFlow[]>(`${BASE_API_URL}/dashboard/searchCashFlowProject`); | |||
}); | |||
const mockProjects: CashFlow[] = [ | |||
{ | |||
id: 1, | |||
projectCode: "CUST-001", | |||
projectName: "Client A", | |||
team: "N/A", | |||
teamLeader: "N/A", | |||
startDate: "5", | |||
startDateFrom: "5", | |||
startDateTo: "5", | |||
targetEndDate: "s", | |||
client: "ss", | |||
subsidiary: "ss", | |||
}, | |||
]; |
@@ -39,16 +39,28 @@ export interface FinancialSummaryByProjectResult { | |||
totalUninvoiced: number; | |||
} | |||
export const searchFinancialSummaryByClient = cache(async (teamId: number) => { | |||
return serverFetchJson<FinancialSummaryByClientResult[]>( | |||
`${BASE_API_URL}/dashboard/searchFinancialSummaryByClient?teamId=${teamId}` | |||
); | |||
export const searchFinancialSummaryByClient = cache(async (teamId?: number) => { | |||
if (teamId === undefined) { | |||
return serverFetchJson<FinancialSummaryByClientResult[]>( | |||
`${BASE_API_URL}/dashboard/searchFinancialSummaryByClient` | |||
); | |||
} else { | |||
return serverFetchJson<FinancialSummaryByClientResult[]>( | |||
`${BASE_API_URL}/dashboard/searchFinancialSummaryByClient?teamId=${teamId}` | |||
); | |||
} | |||
}); | |||
export const searchFinancialSummaryByProject = cache(async (teamId: number) => { | |||
export const searchFinancialSummaryByProject = cache(async (teamId?: number, customerId?:number) => { | |||
if (teamId === undefined) { | |||
return serverFetchJson<FinancialSummaryByProjectResult[]>( | |||
`${BASE_API_URL}/dashboard/searchFinancialSummaryByProject` | |||
); | |||
} else { | |||
return serverFetchJson<FinancialSummaryByProjectResult[]>( | |||
`${BASE_API_URL}/dashboard/searchFinancialSummaryByProject?teamId=${teamId}&customerId=${customerId}` | |||
); | |||
} | |||
return serverFetchJson<FinancialSummaryByProjectResult[]>( | |||
`${BASE_API_URL}/dashboard/searchFinancialSummaryByProject?teamId=${teamId}` | |||
); | |||
}); |
@@ -13,6 +13,7 @@ import { I18nProvider } from "@/i18n"; | |||
const pathToLabelMap: { [path: string]: string } = { | |||
"": "Overview", | |||
"/home": "User Workspace", | |||
"/dashboard": "Dashboard", | |||
"/projects": "Projects", | |||
"/projects/create": "Create Project", | |||
"/projects/createSub": "Sub Project", | |||
@@ -15,6 +15,7 @@ interface CustomDatagridProps { | |||
dataGridHeight?: number | string; | |||
[key: string]: any; | |||
checkboxSelection?: boolean; | |||
onRowClick?: any; | |||
onRowSelectionModelChange?: ( | |||
newSelectionModel: GridRowSelectionModel, | |||
) => void; | |||
@@ -34,6 +35,7 @@ const CustomDatagrid: React.FC<CustomDatagridProps> = ({ | |||
checkboxSelection, // Destructure the new prop | |||
onRowSelectionModelChange, // Destructure the new prop | |||
selectionModel, | |||
onRowClick, | |||
columnGroupingModel, | |||
pageSize, | |||
...props | |||
@@ -195,6 +197,7 @@ const CustomDatagrid: React.FC<CustomDatagridProps> = ({ | |||
rows={rowsWithDefaultValues} | |||
columns={modifiedColumns} | |||
editMode="row" | |||
onRowClick={onRowClick} | |||
checkboxSelection={checkboxSelection} | |||
onRowSelectionModelChange={onRowSelectionModelChange} | |||
experimentalFeatures={{ columnGrouping: true }} | |||
@@ -226,6 +229,7 @@ const CustomDatagrid: React.FC<CustomDatagridProps> = ({ | |||
rows={rowsWithDefaultValues} | |||
columns={modifiedColumns} | |||
editMode="row" | |||
onRowClick={onRowClick} | |||
checkboxSelection={checkboxSelection} | |||
onRowSelectionModelChange={onRowSelectionModelChange} | |||
experimentalFeatures={{ columnGrouping: true }} | |||
@@ -257,6 +261,7 @@ const CustomDatagrid: React.FC<CustomDatagridProps> = ({ | |||
rows={rowsWithDefaultValues} | |||
columns={modifiedColumns} | |||
editMode="row" | |||
onRowClick={onRowClick} | |||
checkboxSelection={checkboxSelection} | |||
onRowSelectionModelChange={onRowSelectionModelChange} | |||
experimentalFeatures={{ columnGrouping: true }} | |||
@@ -289,6 +294,7 @@ const CustomDatagrid: React.FC<CustomDatagridProps> = ({ | |||
rows={rowsWithDefaultValues} | |||
columns={modifiedColumns} | |||
editMode="row" | |||
onRowClick={onRowClick} | |||
style={{ marginRight: 0 }} | |||
checkboxSelection={checkboxSelection} | |||
onRowSelectionModelChange={onRowSelectionModelChange} | |||
@@ -19,17 +19,34 @@ import SearchBox, { Criterion } from "../SearchBox"; | |||
import ProgressByClientSearch from "@/components/ProgressByClientSearch"; | |||
import { Suspense } from "react"; | |||
import ProgressCashFlowSearch from "@/components/ProgressCashFlowSearch"; | |||
import { fetchProjectsCashFlow} from "@/app/api/cashflow"; | |||
import { Input, Label } from "reactstrap"; | |||
import { CashFlow } from "@/app/api/cashflow"; | |||
interface Props { | |||
projects: CashFlow[]; | |||
} | |||
type SearchQuery = Partial<Omit<CashFlow, "id">>; | |||
type SearchParamNames = keyof SearchQuery; | |||
const ProjectCashFlow: React.FC = () => { | |||
const { t } = useTranslation("projects"); | |||
const todayDate = new Date(); | |||
const [selectionModel, setSelectionModel]: any[] = React.useState([]); | |||
const [projectData, setProjectData]: any[] = React.useState([]); | |||
const [cashFlowYear, setCashFlowYear]: any[] = React.useState( | |||
todayDate.getFullYear(), | |||
); | |||
const [anticipateCashFlowYear, setAnticipateCashFlowYear]: any[] = React.useState( | |||
todayDate.getFullYear(), | |||
); | |||
const fetchData = async () => { | |||
const cashFlowProject = await fetchProjectsCashFlow(); | |||
setProjectData(cashFlowProject) | |||
} | |||
useEffect(() => { | |||
fetchData() | |||
}, []); | |||
const columns = [ | |||
{ | |||
id: "projectCode", | |||
@@ -50,8 +67,8 @@ const ProjectCashFlow: React.FC = () => { | |||
flex: 1, | |||
}, | |||
{ | |||
id: "teamLeader", | |||
field: "teamLeader", | |||
id: "teamLead", | |||
field: "teamLead", | |||
headerName: "Team Leader", | |||
flex: 1, | |||
}, | |||
@@ -530,8 +547,6 @@ const ProjectCashFlow: React.FC = () => { | |||
remarks: "Monthly Manpower Expenditure", | |||
}, | |||
]; | |||
const [projectData, setProjectData]: any[] = React.useState(rows); | |||
const [ledgerData, setLedgerData]: any[] = React.useState(ledgerRows); | |||
const handleSelectionChange = (newSelectionModel: GridRowSelectionModel) => { | |||
const selectedRowsData = projectData.filter((row: any) => | |||
@@ -540,11 +555,31 @@ const ProjectCashFlow: React.FC = () => { | |||
console.log(selectedRowsData); | |||
}; | |||
const searchCriteria: Criterion<SearchParamNames>[] = useMemo( | |||
() => [ | |||
{ label: "Project Code", paramName: "projectCode", type: "text" }, | |||
{ label: "Project Name", paramName: "projectName", type: "text" }, | |||
{ | |||
label: "Start Date From", | |||
label2: "Start Date To", | |||
paramName: "startDateFrom", | |||
type: "dateRange", | |||
}, | |||
], | |||
[t], | |||
); | |||
return ( | |||
<> | |||
<Suspense fallback={<ProgressCashFlowSearch.Loading />}> | |||
{/* <Suspense fallback={<ProgressCashFlowSearch.Loading />}> | |||
<ProgressCashFlowSearch /> | |||
</Suspense> | |||
</Suspense> */} | |||
<SearchBox | |||
criteria={searchCriteria} | |||
onSearch={(query) => { | |||
console.log(query); | |||
}} | |||
/> | |||
<CustomDatagrid | |||
rows={projectData} | |||
columns={columns} | |||
@@ -53,8 +53,6 @@ const ProjectFinancialCard: React.FC<Props> = ({ | |||
: "border-green-200 border-solid"; | |||
const selectedBackgroundColor = | |||
ClickedIndex === Index ? "rgb(235 235 235)" : "rgb(255 255 255)"; | |||
console.log(ClickedIndex); | |||
console.log(Index); | |||
return ( | |||
<Card | |||
style={{ | |||
@@ -84,35 +82,35 @@ const ProjectFinancialCard: React.FC<Props> = ({ | |||
Total Fees | |||
</div> | |||
<div className="text-lg font-medium ml-5" style={{ color: "#6b87cf" }}> | |||
{TotalFees.toLocaleString()} | |||
{TotalFees.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} | |||
</div> | |||
<hr /> | |||
<div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}> | |||
Total Budget | |||
</div> | |||
<div className="text-lg font-medium ml-5" style={{ color: "#6b87cf" }}> | |||
{TotalBudget.toLocaleString()} | |||
{TotalBudget.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} | |||
</div> | |||
<hr /> | |||
<div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}> | |||
Total Cumulative Expenditure | |||
</div> | |||
<div className="text-lg font-medium ml-5" style={{ color: "#6b87cf" }}> | |||
{TotalCumulative.toLocaleString()} | |||
{TotalCumulative.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} | |||
</div> | |||
<hr /> | |||
<div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}> | |||
Total Invoiced Amount | |||
</div> | |||
<div className="text-lg font-medium ml-5" style={{ color: "#6b87cf" }}> | |||
{TotalInvoicedAmount.toLocaleString()} | |||
{TotalInvoicedAmount.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} | |||
</div> | |||
<hr /> | |||
<div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}> | |||
Total Received Amount | |||
</div> | |||
<div className="text-lg font-medium ml-5" style={{ color: "#6b87cf" }}> | |||
{TotalReceivedAmount.toLocaleString()} | |||
{TotalReceivedAmount.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} | |||
</div> | |||
<hr /> | |||
<div className="text-sm font-medium ml-5" style={{ color: "#898d8d" }}> | |||
@@ -21,6 +21,7 @@ import { Suspense } from "react"; | |||
import { fetchFinancialSummaryCard } from "@/app/api/financialsummary"; | |||
import { searchFinancialSummaryByClient,searchFinancialSummaryByProject } from "@/app/api/financialsummary/actions"; | |||
import ProjectFinancialCard from "./ProjectFinancialCard"; | |||
import VisibilityIcon from '@mui/icons-material/Visibility'; | |||
const ProjectFinancialSummary: React.FC = () => { | |||
const [SearchCriteria, setSearchCriteria] = React.useState({}); | |||
@@ -33,16 +34,16 @@ const ProjectFinancialSummary: React.FC = () => { | |||
const financialSummaryCard = await fetchFinancialSummaryCard(); | |||
setProjectFinancialData(financialSummaryCard) | |||
} | |||
const fetchTableData = async (teamId:any) => { | |||
const fetchTableData = async (teamId?:any) => { | |||
const financialSummaryByClient = await searchFinancialSummaryByClient(teamId); | |||
const financialSummaryByProject = await searchFinancialSummaryByProject(teamId); | |||
console.log(financialSummaryByClient) | |||
console.log(financialSummaryByProject) | |||
// console.log(financialSummaryByProject) | |||
setClientFinancialRows(financialSummaryByClient) | |||
setProjectFinancialRows(financialSummaryByProject) | |||
} | |||
useEffect(() => { | |||
fetchData() | |||
fetchTableData(undefined) | |||
}, []); | |||
const rows0 = [{id: 1,projectCode:"M1201",projectName:"Consultancy Project C", team:"XXX", teamLeader:"XXX", startDate:"01/08/2022", targetEndDate: "01/05/2024", client:"Client A", subsidiary:"N/A"}, | |||
@@ -73,16 +74,9 @@ const ProjectFinancialSummary: React.FC = () => { | |||
const [selectedTeamData, setSelectedTeamData]: any[] = React.useState(rows0); | |||
const handleCardClick = (r: any) => { | |||
const handleCardClick = (r: any, index:any) => { | |||
fetchTableData(r.teamId) | |||
// setIsCardClickedIndex(r); | |||
// if (r === 0) { | |||
// setSelectedTeamData(rows0); | |||
// } else if (r === 1) { | |||
// setSelectedTeamData(rows1); | |||
// } else if (r === 2) { | |||
// setSelectedTeamData(rows2); | |||
// } | |||
setIsCardClickedIndex(index) | |||
}; | |||
const columns = [ | |||
@@ -380,6 +374,16 @@ const columns2 = [ | |||
); | |||
console.log(selectedRowsData); | |||
}; | |||
const fetchProjectTableData = async (teamId?:any,customerId?:any) => { | |||
const financialSummaryByProject = await searchFinancialSummaryByProject(teamId,customerId); | |||
setProjectFinancialRows(financialSummaryByProject) | |||
} | |||
const handleRowClick = (params:any) => { | |||
console.log(params.row.teamId); | |||
fetchProjectTableData(params.row.teamId,params.row.cid) | |||
}; | |||
return ( | |||
<Grid item sm> | |||
@@ -387,7 +391,7 @@ const columns2 = [ | |||
<CardHeader className="text-slate-500" title="Active Project Financial Status"/> | |||
<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)}> | |||
<div className="hover:cursor-pointer ml-4 mt-5 mb-4 inline-block" key={index} onClick={(r) => handleCardClick(record,index)}> | |||
<ProjectFinancialCard Title={record.teamName} TotalActiveProjectNumber={record.projectNo} TotalFees={record.totalFee} TotalBudget={record.totalBudget} TotalCumulative={record.cumulativeExpenditure} TotalInvoicedAmount={record.totalInvoiced} TotalReceivedAmount={record.totalReceived} CashFlowStatus={record.cashFlowStatus} CostPerformanceIndex={record.cpi} ClickedIndex={isCardClickedIndex} Index={index}/> | |||
</div> | |||
))} | |||
@@ -397,7 +401,7 @@ const columns2 = [ | |||
<CardHeader className="text-slate-500" title="Financial Status (by Client)"/> | |||
<div style={{display:"inline-block",width:"99%",marginLeft:10}}> | |||
{/* <CustomDatagrid rows={clientFinancialRows} columns={columns} columnWidth={200} dataGridHeight={300} checkboxSelection={true} onRowSelectionModelChange={handleSelectionChange} selectionModel={selectionModel}/> */} | |||
<CustomDatagrid rows={clientFinancialRows} columns={columns} columnWidth={200} dataGridHeight={300}/> | |||
<CustomDatagrid onRowClick={handleRowClick} rows={clientFinancialRows} columns={columns} columnWidth={200} dataGridHeight={300}/> | |||
</div> | |||
</Card> | |||
<Card className="mt-5"> | |||