| @@ -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"> | |||