diff --git a/client/src/app/projects/[id]/page.tsx b/client/src/app/projects/[id]/page.tsx index c85b17fe..5d111ed1 100644 --- a/client/src/app/projects/[id]/page.tsx +++ b/client/src/app/projects/[id]/page.tsx @@ -1,5 +1,5 @@ -import CustomProject from "@/containers/projects/custom-project"; - export default function CustomProjectPage() { - return ; + return null; + // TODO: Add when API is ready + // return ; } diff --git a/client/src/app/projects/preview/mock-response.ts b/client/src/app/projects/preview/mock-response.ts new file mode 100644 index 00000000..abfc1e71 --- /dev/null +++ b/client/src/app/projects/preview/mock-response.ts @@ -0,0 +1,911 @@ +const mockResponse = { + data: { + projectName: "My custom project", + country: { + code: "IND", + }, + totalCostNPV: 2503854.27918858, + totalCost: 3332201.598883546, + projectSize: 1000, + projectLength: 20, + ecosystem: "Mangrove", + activity: "Conservation", + output: { + lossRate: -0.0016, + carbonRevenuesToCover: "Opex", + initialCarbonPrice: 1000, + emissionFactors: { + emissionFactor: null, + emissionFactorAgb: 67.7, + emissionFactorSoc: 85.5, + }, + totalProjectCost: { + total: { + total: 3332201.598883546, + capex: 1600616.6666666667, + opex: 1731584.9322168794, + }, + npv: { + total: 2503854.27918858, + capex: 1505525.2721514185, + opex: 998329.0070371614, + }, + }, + summary: { + "$/tCO2e (total cost, NPV)": 61.470410294099835, + "$/ha": 2503.8542791885798, + "NPV covering cost": -493830.3621939037, + "Leftover after OpEx / total cost": null, + "IRR when priced to cover OpEx": 0.0560813585617166, + "IRR when priced to cover total cost": 85683156958.8259, + "Total cost (NPV)": 2503854.27918858, + "Capital expenditure (NPV)": 1505525.2721514185, + "Operating expenditure (NPV)": 998329.0070371614, + "Credits issued": 26038.815407423757, + "Total revenue (NPV)": 504498.6448432577, + "Total revenue (non-discounted)": 956754.3382707891, + "Financing cost": 80030.83333333334, + "Funding gap": 493830.3621939037, + "Funding gap (NPV)": 493830.3621939037, + "Funding gap per tCO2e (NPV)": 18.965162372675024, + "Community benefit sharing fund": 0.5, + }, + costDetails: { + total: { + capitalExpenditure: 1600616.6666666667, + operationalExpenditure: 1731584.9322168794, + totalCost: 3201233.3333333335, + feasibilityAnalysis: 50000, + conservationPlanningAndAdmin: 667066.6666666666, + dataCollectionAndFieldCost: 80000, + communityRepresentation: 213550, + blueCarbonProjectPlanning: 300000, + establishingCarbonRights: 140000, + validation: 50000, + implementationLabor: 100000, + monitoring: 300000, + maintenance: 0, + communityBenefitSharingFund: 478377.16913539456, + carbonStandardFees: 5207.763081484753, + baselineReassessment: 120000, + mrv: 300000, + longTermProjectOperatingCost: 528000, + }, + npv: { + capitalExpenditure: 1505525.2721514185, + operationalExpenditure: 998329.0070371614, + totalCost: 2503854.27918858, + feasibilityAnalysis: 50000, + conservationPlanningAndAdmin: 629559.3479745106, + dataCollectionAndFieldCost: 76962.52465483235, + communityRepresentation: 197540.230048551, + blueCarbonProjectPlanning: 288609.4674556213, + establishingCarbonRights: 129504.24821726595, + validation: 44449.81793354574, + implementationLabor: 88899.63586709148, + monitoring: 181226.25950738514, + maintenance: 0, + communityBenefitSharingFund: 252249.32242162884, + carbonStandardFees: 2786.931340270489, + baselineReassessment: 75811.8711249392, + mrv: 167296.40590994002, + longTermProjectOperatingCost: 318958.2167329978, + }, + }, + yearlyBreakdown: [ + { + costName: "feasibilityAnalysis", + totalCost: -50000, + totalNPV: -50000, + costValues: { + "0": 0, + "1": 0, + "2": 0, + "3": 0, + "4": 0, + "5": 0, + "6": 0, + "7": 0, + "8": 0, + "9": 0, + "10": 0, + "11": 0, + "12": 0, + "13": 0, + "14": 0, + "15": 0, + "16": 0, + "17": 0, + "18": 0, + "19": 0, + "20": 0, + "-4": -50000, + "-3": 0, + "-2": 0, + "-1": 0, + }, + }, + { + costName: "conservationPlanningAndAdmin", + totalCost: -667066.6666666666, + totalNPV: -629559.3479745106, + costValues: { + "0": 0, + "1": 0, + "2": 0, + "3": 0, + "4": 0, + "5": 0, + "6": 0, + "7": 0, + "8": 0, + "9": 0, + "10": 0, + "11": 0, + "12": 0, + "13": 0, + "14": 0, + "15": 0, + "16": 0, + "17": 0, + "18": 0, + "19": 0, + "20": 0, + "-4": -166766.66666666666, + "-3": -166766.66666666666, + "-2": -166766.66666666666, + "-1": -166766.66666666666, + }, + }, + { + costName: "dataCollectionAndFieldCost", + totalCost: -80000, + totalNPV: -76962.52465483235, + costValues: { + "0": 0, + "1": 0, + "2": 0, + "3": 0, + "4": 0, + "5": 0, + "6": 0, + "7": 0, + "8": 0, + "9": 0, + "10": 0, + "11": 0, + "12": 0, + "13": 0, + "14": 0, + "15": 0, + "16": 0, + "17": 0, + "18": 0, + "19": 0, + "20": 0, + "-4": -26666.666666666668, + "-3": -26666.666666666668, + "-2": -26666.666666666668, + "-1": 0, + }, + }, + { + costName: "blueCarbonProjectPlanning", + totalCost: -300000, + totalNPV: -288609.4674556213, + costValues: { + "0": 0, + "1": 0, + "2": 0, + "3": 0, + "4": 0, + "5": 0, + "6": 0, + "7": 0, + "8": 0, + "9": 0, + "10": 0, + "11": 0, + "12": 0, + "13": 0, + "14": 0, + "15": 0, + "16": 0, + "17": 0, + "18": 0, + "19": 0, + "20": 0, + "-4": -100000, + "-3": -100000, + "-2": -100000, + "-1": 0, + }, + }, + { + costName: "communityRepresentation", + totalCost: -213550, + totalNPV: -197540.230048551, + costValues: { + "0": 0, + "1": 0, + "2": 0, + "3": 0, + "4": 0, + "5": 0, + "6": 0, + "7": 0, + "8": 0, + "9": 0, + "10": 0, + "11": 0, + "12": 0, + "13": 0, + "14": 0, + "15": 0, + "16": 0, + "17": 0, + "18": 0, + "19": 0, + "20": 0, + "-4": 0, + "-3": -71183.33333333333, + "-2": -71183.33333333333, + "-1": -71183.33333333333, + }, + }, + { + costName: "establishingCarbonRights", + totalCost: -140000, + totalNPV: -129504.24821726595, + costValues: { + "0": 0, + "1": 0, + "2": 0, + "3": 0, + "4": 0, + "5": 0, + "6": 0, + "7": 0, + "8": 0, + "9": 0, + "10": 0, + "11": 0, + "12": 0, + "13": 0, + "14": 0, + "15": 0, + "16": 0, + "17": 0, + "18": 0, + "19": 0, + "20": 0, + "-4": 0, + "-3": -46666.666666666664, + "-2": -46666.666666666664, + "-1": -46666.666666666664, + }, + }, + { + costName: "validation", + totalCost: -50000, + totalNPV: -44449.81793354574, + costValues: { + "0": 0, + "1": 0, + "2": 0, + "3": 0, + "4": 0, + "5": 0, + "6": 0, + "7": 0, + "8": 0, + "9": 0, + "10": 0, + "11": 0, + "12": 0, + "13": 0, + "14": 0, + "15": 0, + "16": 0, + "17": 0, + "18": 0, + "19": 0, + "20": 0, + "-4": 0, + "-3": 0, + "-2": 0, + "-1": -50000, + }, + }, + { + costName: "implementationLabor", + totalCost: -100000, + totalNPV: -88899.63586709148, + costValues: { + "0": 0, + "1": 0, + "2": 0, + "3": 0, + "4": 0, + "5": 0, + "6": 0, + "7": 0, + "8": 0, + "9": 0, + "10": 0, + "11": 0, + "12": 0, + "13": 0, + "14": 0, + "15": 0, + "16": 0, + "17": 0, + "18": 0, + "19": 0, + "20": 0, + "-4": 0, + "-3": 0, + "-2": 0, + "-1": -100000, + }, + }, + { + costName: "monitoring", + totalCost: -300000, + totalNPV: -181226.25950738514, + costValues: { + "0": 0, + "1": -15000, + "2": -15000, + "3": -15000, + "4": -15000, + "5": -15000, + "6": -15000, + "7": -15000, + "8": -15000, + "9": -15000, + "10": -15000, + "11": -15000, + "12": -15000, + "13": -15000, + "14": -15000, + "15": -15000, + "16": -15000, + "17": -15000, + "18": -15000, + "19": -15000, + "20": -15000, + "-4": 0, + "-3": 0, + "-2": 0, + "-1": 0, + }, + }, + { + costName: "maintenance", + totalCost: 0, + totalNPV: 0, + costValues: { + "0": 0, + "1": 0, + "2": 0, + "3": 0, + "4": 0, + "5": 0, + "6": 0, + "7": 0, + "8": 0, + "9": 0, + "10": 0, + "11": 0, + "12": 0, + "13": 0, + "14": 0, + "15": 0, + "16": 0, + "17": 0, + "18": 0, + "19": 0, + "20": 0, + "-4": 0, + "-3": 0, + "-2": 0, + "-1": 0, + }, + }, + { + costName: "communityBenefitSharingFund", + totalCost: -478377.16913539456, + totalNPV: -252249.32242162884, + costValues: { + "0": 0, + "1": -3101.320320000044, + "2": -4951.5160414005795, + "3": -6853.590667682226, + "4": -8808.645091381013, + "5": -10817.801034974973, + "6": -12882.20142107311, + "7": -15003.01074892755, + "8": -17181.41547737641, + "9": -19418.624414322498, + "10": -21715.869112862427, + "11": -24074.40427416561, + "12": -26495.508157226817, + "13": -28980.482995600523, + "14": -31530.655421230902, + "15": -34147.37689550309, + "16": -36832.024147625285, + "17": -39585.99962047368, + "18": -42410.73192401404, + "19": -45307.67629643649, + "20": -48278.31507311732, + "-4": 0, + "-3": 0, + "-2": 0, + "-1": 0, + }, + }, + { + costName: "carbonStandardFees", + totalCost: -5207.763081484753, + totalNPV: -2786.931340270489, + costValues: { + "0": 0, + "1": -40.739840000000584, + "2": -64.08329625600338, + "3": -87.38940298199216, + "4": -110.65821993722336, + "5": -133.88980678532448, + "6": -157.08422309446732, + "7": -180.24152833751594, + "8": -203.3617818921798, + "9": -226.44504304114957, + "10": -249.49137097228606, + "11": -272.5008247787324, + "12": -295.473463459085, + "13": -318.4093459175526, + "14": -341.3085309640828, + "15": -364.17107731454234, + "16": -386.9970435908389, + "17": -409.7864883210964, + "18": -432.53946993977934, + "19": -455.25604678787926, + "20": -477.93627711301974, + "-4": 0, + "-3": 0, + "-2": 0, + "-1": 0, + }, + }, + { + costName: "baselineReassessment", + totalCost: -120000, + totalNPV: -75811.8711249392, + costValues: { + "0": 0, + "1": 0, + "2": 0, + "3": 0, + "4": 0, + "5": 0, + "6": 0, + "7": 0, + "8": 0, + "9": 0, + "10": -40000, + "11": 0, + "12": 0, + "13": 0, + "14": 0, + "15": 0, + "16": 0, + "17": 0, + "18": 0, + "19": 0, + "20": -40000, + "-4": 0, + "-3": 0, + "-2": 0, + "-1": -40000, + }, + }, + { + costName: "mrv", + totalCost: -300000, + totalNPV: -167296.40590994002, + costValues: { + "0": 0, + "1": 0, + "2": 0, + "3": 0, + "4": 0, + "5": -75000, + "6": 0, + "7": 0, + "8": 0, + "9": 0, + "10": -75000, + "11": 0, + "12": 0, + "13": 0, + "14": 0, + "15": -75000, + "16": 0, + "17": 0, + "18": 0, + "19": 0, + "20": -75000, + "-4": 0, + "-3": 0, + "-2": 0, + "-1": 0, + }, + }, + { + costName: "longTermProjectOperatingCost", + totalCost: -528000, + totalNPV: -318958.2167329978, + costValues: { + "0": 0, + "1": -26400, + "2": -26400, + "3": -26400, + "4": -26400, + "5": -26400, + "6": -26400, + "7": -26400, + "8": -26400, + "9": -26400, + "10": -26400, + "11": -26400, + "12": -26400, + "13": -26400, + "14": -26400, + "15": -26400, + "16": -26400, + "17": -26400, + "18": -26400, + "19": -26400, + "20": -26400, + "-4": 0, + "-3": 0, + "-2": 0, + "-1": 0, + }, + }, + { + costName: "opexTotalCostPlan", + totalCost: -1731584.9322168794, + totalNPV: -998329.0070371614, + costValues: { + "0": 0, + "1": -44542.060160000045, + "2": -46415.59933765658, + "3": -48340.98007066422, + "4": -50319.30331131823, + "5": -127351.6908417603, + "6": -54439.28564416758, + "7": -56583.25227726507, + "8": -58784.777259268594, + "9": -61045.06945736365, + "10": -178365.36048383472, + "11": -65746.90509894435, + "12": -68190.9816206859, + "13": -70698.89234151808, + "14": -73271.96395219499, + "15": -150911.5479728176, + "16": -78619.02119121613, + "17": -81395.78610879477, + "18": -84243.27139395382, + "19": -87162.93234322437, + "20": -205156.25135023033, + "-4": 0, + "-3": 0, + "-2": 0, + "-1": -40000, + }, + }, + { + costName: "capexTotalCostPlan", + totalCost: -1600616.6666666667, + totalNPV: -1505525.2721514185, + costValues: { + "0": 0, + "1": 0, + "2": 0, + "3": 0, + "4": 0, + "5": 0, + "6": 0, + "7": 0, + "8": 0, + "9": 0, + "10": 0, + "11": 0, + "12": 0, + "13": 0, + "14": 0, + "15": 0, + "16": 0, + "17": 0, + "18": 0, + "19": 0, + "20": 0, + "-4": -343433.3333333333, + "-3": -411283.3333333333, + "-2": -411283.3333333333, + "-1": -434616.6666666667, + }, + }, + { + costName: "totalCostPlan", + totalCost: -3332201.598883546, + totalNPV: -2503854.27918858, + costValues: { + "0": 0, + "1": -44542.060160000045, + "2": -46415.59933765658, + "3": -48340.98007066422, + "4": -50319.30331131823, + "5": -127351.6908417603, + "6": -54439.28564416758, + "7": -56583.25227726507, + "8": -58784.777259268594, + "9": -61045.06945736365, + "10": -178365.36048383472, + "11": -65746.90509894435, + "12": -68190.9816206859, + "13": -70698.89234151808, + "14": -73271.96395219499, + "15": -150911.5479728176, + "16": -78619.02119121613, + "17": -81395.78610879477, + "18": -84243.27139395382, + "19": -87162.93234322437, + "20": -205156.25135023033, + "-4": -343433.3333333333, + "-3": -411283.3333333333, + "-2": -411283.3333333333, + "-1": -474616.6666666667, + }, + }, + { + costName: "estimatedRevenuePlan", + totalCost: 956754.3382707891, + totalNPV: 504498.6448432577, + costValues: { + "0": 0, + "1": 6202.640640000088, + "2": 9903.032082801159, + "3": 13707.181335364452, + "4": 17617.290182762026, + "5": 21635.602069949946, + "6": 25764.40284214622, + "7": 30006.0214978551, + "8": 34362.83095475282, + "9": 38837.248828644995, + "10": 43431.738225724854, + "11": 48148.80854833122, + "12": 52991.016314453635, + "13": 57960.96599120105, + "14": 63061.310842461804, + "15": 68294.75379100618, + "16": 73664.04829525057, + "17": 79171.99924094736, + "18": 84821.46384802808, + "19": 90615.35259287298, + "20": 96556.63014623465, + "-4": 0, + "-3": 0, + "-2": 0, + "-1": 0, + }, + }, + { + costName: "creditsIssuedPlan", + totalCost: 26038.815407423757, + totalNPV: 13934.656701352445, + costValues: { + "0": 0, + "1": 203.6992000000029, + "2": 320.4164812800169, + "3": 436.9470149099608, + "4": 553.2910996861168, + "5": 669.4490339266224, + "6": 785.4211154723365, + "7": 901.2076416875797, + "8": 1016.808909460899, + "9": 1132.2252152057479, + "10": 1247.4568548614302, + "11": 1362.504123893662, + "12": 1477.367317295425, + "13": 1592.046729587763, + "14": 1706.542654820414, + "15": 1820.8553865727117, + "16": 1934.9852179541945, + "17": 2048.932441605482, + "18": 2162.6973496988967, + "19": 2276.280233939396, + "20": 2389.6813855650985, + "-4": 0, + "-3": 0, + "-2": 0, + "-1": 0, + }, + }, + { + costName: "cumulativeNetIncomePlan", + totalCost: 817876.6499772413, + totalNPV: 528307.1257344105, + costValues: { + "0": 0, + "1": 35559.85434683659, + "2": 35559.85434683659, + "3": 35559.85434683659, + "4": 35559.85434683659, + "5": 35559.85434683659, + "6": 35559.85434683659, + "7": 35559.85434683659, + "8": 35559.85434683659, + "9": 35559.85434683659, + "10": 35559.85434683659, + "11": 35559.85434683659, + "12": 35559.85434683659, + "13": 35559.85434683659, + "14": 35559.85434683659, + "15": 35559.85434683659, + "16": 35559.85434683659, + "17": 35559.85434683659, + "18": 35559.85434683659, + "19": 35559.85434683659, + "20": 35559.85434683659, + "-4": 0, + "-3": 35559.85434683659, + "-2": 35559.85434683659, + "-1": 35559.85434683659, + }, + }, + { + costName: "cumulativeNetIncomeCapexOpex", + totalCost: 43687357.90945987, + totalNPV: 28341425.709689442, + costValues: { + "0": 0, + "1": 1884518.4598315884, + "2": 1884518.4598315884, + "3": 1884518.4598315884, + "4": 1884518.4598315884, + "5": 1884518.4598315884, + "6": 1884518.4598315884, + "7": 1884518.4598315884, + "8": 1884518.4598315884, + "9": 1884518.4598315884, + "10": 1884518.4598315884, + "11": 1884518.4598315884, + "12": 1884518.4598315884, + "13": 1884518.4598315884, + "14": 1884518.4598315884, + "15": 1884518.4598315884, + "16": 1884518.4598315884, + "17": 1884518.4598315884, + "18": 1884518.4598315884, + "19": 1884518.4598315884, + "20": 1884518.4598315884, + "-4": 343433.3333333333, + "-3": 1884518.4598315884, + "-2": 1884518.4598315884, + "-1": 1884518.4598315884, + }, + }, + { + costName: "annualNetCashFlow", + totalCost: 4288955.937154336, + totalNPV: 3008352.9240318374, + costValues: { + "0": 0, + "1": 50744.70080000013, + "2": 56318.63142045774, + "3": 62048.161406028674, + "4": 67936.59349408025, + "5": 148987.29291171025, + "6": 80203.6884863138, + "7": 86589.27377512018, + "8": 93147.60821402141, + "9": 99882.31828600864, + "10": 221797.0987095596, + "11": 113895.71364727557, + "12": 121181.99793513953, + "13": 128659.85833271913, + "14": 136333.27479465678, + "15": 219206.30176382378, + "16": 152283.0694864667, + "17": 160567.78534974213, + "18": 169064.73524198192, + "19": 177778.28493609733, + "20": 301712.88149646495, + "-4": 343433.3333333333, + "-3": 411283.3333333333, + "-2": 411283.3333333333, + "-1": 474616.6666666667, + }, + }, + { + costName: "annualNetIncome", + totalCost: 2688339.2704876685, + totalNPV: 1502827.6518804193, + costValues: { + "0": 0, + "1": 50744.70080000013, + "2": 56318.63142045774, + "3": 62048.161406028674, + "4": 67936.59349408025, + "5": 148987.29291171025, + "6": 80203.6884863138, + "7": 86589.27377512018, + "8": 93147.60821402141, + "9": 99882.31828600864, + "10": 221797.0987095596, + "11": 113895.71364727557, + "12": 121181.99793513953, + "13": 128659.85833271913, + "14": 136333.27479465678, + "15": 219206.30176382378, + "16": 152283.0694864667, + "17": 160567.78534974213, + "18": 169064.73524198192, + "19": 177778.28493609733, + "20": 301712.88149646495, + "-4": 0, + "-3": 0, + "-2": 0, + "-1": 40000, + }, + }, + ], + }, + input: { + countryCode: "IND", + activity: "Conservation", + ecosystem: "Mangrove", + projectName: "My custom project", + projectSizeHa: 1000, + initialCarbonPriceAssumption: 1000, + carbonRevenuesToCover: "Opex", + parameters: { + lossRateUsed: "National average", + emissionFactorUsed: "Tier 2 - Country-specific emission factor", + ecosystem: "Mangrove", + }, + costInputs: { + feasibilityAnalysis: 50000, + conservationPlanningAndAdmin: 166766.66666666666, + dataCollectionAndFieldCost: 26666.666666666668, + communityRepresentation: 71183.33333333333, + blueCarbonProjectPlanning: 100000, + establishingCarbonRights: 46666.666666666664, + financingCost: 0.05, + validation: 50000, + implementationLaborHybrid: null, + implementationLabor: 100, + monitoring: 15000, + maintenance: 0.0833, + carbonStandardFees: 0.2, + communityBenefitSharingFund: 0.5, + baselineReassessment: 40000, + mrv: 75000, + longTermProjectOperatingCost: 26400, + }, + assumptions: { + verificationFrequency: 5, + baselineReassessmentFrequency: 10, + discountRate: 0.04, + restorationRate: 250, + carbonPriceIncrease: 0.015, + buffer: 0.2, + projectLength: 20, + }, + }, + }, +} as const; + +export default mockResponse; diff --git a/client/src/app/projects/preview/page.tsx b/client/src/app/projects/preview/page.tsx new file mode 100644 index 00000000..95c5f751 --- /dev/null +++ b/client/src/app/projects/preview/page.tsx @@ -0,0 +1,54 @@ +"use client"; + +import { useEffect, useState } from "react"; + +import { useRouter } from "next/navigation"; + +import { useQueryClient } from "@tanstack/react-query"; +import { parseAsBoolean, useQueryState } from "nuqs"; + +import mockResponse from "@/app/projects/preview/mock-response"; + +import CustomProject from "@/containers/projects/custom-project"; + +export default function CustomProjectPage() { + /** + * + * Mimic queryCache from createCustomProject response + * + * This is only for testing purpose with the current mock data, + * and should be replaced when the complete user flow is done + */ + const router = useRouter(); + const QUERYKEY = "custom-project-mock-data"; + const [withMockData] = useQueryState( + "mock", + parseAsBoolean.withDefault(false), + ); + const queryClient = useQueryClient(); + const data = queryClient.getQueryData([QUERYKEY]); + // To trigger re-render: + const [done, setDone] = useState(false); + + useEffect(() => { + if (done || !withMockData || data) return; + + queryClient.setQueryData([QUERYKEY], mockResponse); + setDone(true); + }, [withMockData, data, queryClient, done, setDone]); + /** + * + */ + + useEffect(() => { + // Mimic redirect when no queryCache found + if (!withMockData) { + router.push("/new"); + } + }, [withMockData, router]); + + // TODO: probably show a spinner or skeleton? + if (!data) return null; + + return ; +} diff --git a/client/src/app/projects/[id]/store.ts b/client/src/app/projects/store.ts similarity index 100% rename from client/src/app/projects/[id]/store.ts rename to client/src/app/projects/store.ts diff --git a/client/src/app/projects/[id]/url-store.ts b/client/src/app/projects/url-store.ts similarity index 100% rename from client/src/app/projects/[id]/url-store.ts rename to client/src/app/projects/url-store.ts diff --git a/client/src/constants/tooltip.tsx b/client/src/constants/tooltip.tsx index f4421715..5842f4e8 100644 --- a/client/src/constants/tooltip.tsx +++ b/client/src/constants/tooltip.tsx @@ -1,3 +1,7 @@ +import { CustomProjectSummary } from "@shared/dtos/custom-projects/custom-project-output.dto"; + +import { ScrollArea } from "@/components/ui/scroll-area"; + export const OVERVIEW = { SCORECARD_RATING: "The individual non-economic scores, in addition to the economic feasibility and abatement potential, are weighted using the weights on the left to an overall score per project", @@ -567,39 +571,58 @@ export const CUSTOM_PROJECT_OUTPUTS = { "The net amount of cash generated or consumed by the project on an annual basis, accounting for revenues, CAPEX, and OPEX.", }; -export const PROJECT_SUMMARY = { - COST_PER_TCOE_NPV: +export const PROJECT_SUMMARY: Record = { + // COST_PER_TCOE_NPV: + "$/tCO2e (total cost, NPV)": "The NPV of the total cost (CAPEX & OPEX, excl. financing cost) divided by the total credits the project will generate.", - COST_PER_HA: + // COST_PER_HA: + "$/ha": "The NPV of the total cost (CAPEX & OPEX, excl. financing cost) divided by the total ha of the project", - NPV_COVERING_TOTAL_COST: + // NPV_COVERING_TOTAL_COST: + "NPV covering cost": 'The NPV of the carbon credit revenues subtracted by either the OPEX or the total cost (depending on parameter in "carbon revenues to cover")', - IRR_WHEN_PRICED_TO_COVER_OPEX: + // IRR_WHEN_PRICED_TO_COVER_OPEX: + "IRR when priced to cover OpEx": "The internal rate of return (IRR) calculated when carbon credits are priced to only cover the operating expenses (OPEX).", - IRR_WHEN_PRICED_TO_COVER_TOTAL_COST: + // IRR_WHEN_PRICED_TO_COVER_TOTAL_COST: + "IRR when priced to cover total cost": "The internal rate of return (IRR) calculated when carbon credits are priced to cover both capital (CAPEX) and operating expenses (OPEX).", - TOTAL_COST_NPV: + //TOTAL_COST_NPV: + "Total cost (NPV)": "The NPV of the total cost associated with the hypothetical blue carbon project (incl. CAPEX and OPEX, excl. financing cost)", - CAPITAL_EXPENDITURE_NPV: + // CAPITAL_EXPENDITURE_NPV: + "Capital expenditure (NPV)": "The NPV of the CAPEX associated with the hypothetical blue carbon project", - OPERATING_EXPENDITURE_NPV: + // OPERATING_EXPENDITURE_NPV: + "Operating expenditure (NPV)": "The NPV of the OPEX associated with the hypothetical blue carbon project", - CREDITS_ISSUED: + // CREDITS_ISSUED: + "Credits issued": "The carbon credits issued as part of the project. The buffer has already been subtracted from this total number", - TOTAL_REVENUE_NPV: "The NPV of the carbon credit revenues", - TOTAL_REVENUE_NON_DISCOUNTED: "The non-discounted carbon credit revenues", - FINANCING_COST: + // TOTAL_REVENUE_NPV: + "Total revenue (NPV)": "The NPV of the carbon credit revenues", + // TOTAL_REVENUE_NON_DISCOUNTED: + "Total revenue (non-discounted)": "The non-discounted carbon credit revenues", + // FINANCING_COST: + "Financing cost": "The financing cost is the time, effort and cost associated with securing financing for the set up (pre-revenue) phase of the project. Calculated as the financing cost assumption (default 5%) multiplied by the non-discounted CAPEX total.", - FUNDING_GAP_NPV: + // FUNDING_GAP_NPV: + "Funding gap (NPV)": 'The reverse of the "NPV covering OPEX" or "NPV covering total cost" metric.', - FUNDING_GAP_PER_TCOE_NPV: + // FUNDING_GAP_PER_TCOE_NPV: + "Funding gap per tCO2e (NPV)": 'The reverse of the "NPV covering OPEX" or "NPV covering total cost" metric.', - COMMUNITY_BENEFIT_SHARING_FUND: + // COMMUNITY_BENEFIT_SHARING_FUND: + "Community benefit sharing fund": "The percentage of the revenues assumed to go back to the community as part of the community benefit sharing fund.", + // TODO: + "Leftover after OpEx / total cost": "", + // TODO: + "Funding gap": "", }; export const COST_DETAILS = ( -
+

The cost details provide a comprehensive breakdown of the financial requirements for the project, divided into capital expenditure (CAPEX) and @@ -696,7 +719,7 @@ export const COST_DETAILS = ( stakeholders a clear view of financial investment required for the hypothetical blue carbon project.

-
+ ); export const ANNUAL_PROJECT_CASHFLOW = diff --git a/client/src/containers/projects/custom-project/annual-project-cash-flow/chart/index.tsx b/client/src/containers/projects/custom-project/annual-project-cash-flow/chart/index.tsx index f19d327e..d8392a6d 100644 --- a/client/src/containers/projects/custom-project/annual-project-cash-flow/chart/index.tsx +++ b/client/src/containers/projects/custom-project/annual-project-cash-flow/chart/index.tsx @@ -13,7 +13,7 @@ import { import { formatCurrency } from "@/lib/format"; -import { chartData } from "@/containers/projects/custom-project/annual-project-cash-flow/table/columns"; +import { YearlyBreakdownChartData } from "@/containers/projects/custom-project/annual-project-cash-flow/utils"; import { ChartContainer, @@ -24,21 +24,26 @@ import { } from "@/components/ui/chart"; const CHART_COLORS = { - estimatedRevenue: "hsl(var(--chart-1))", - totalOpEx: "hsl(var(--chart-2))", + estimatedRevenuePlan: "hsl(var(--chart-1))", + opexTotalCostPlan: "hsl(var(--chart-2))", annualNetCashFlow: "hsl(var(--chart-3))", - revenueOpEx: "hsl(var(--chart-4))", + cumulativeNetIncomeCapexOpex: "hsl(var(--chart-5))", + cumulativeNetIncomePlan: "hsl(var(--chart-4))", } as const; -const CashflowChart: FC = () => { +interface CashflowChartProps { + data: YearlyBreakdownChartData; +} + +const CashflowChart: FC = ({ data }) => { return ( { color: "hsl(var(--chart-3))", icon: () =>
, }, - revenueOpEx: { + cumulativeNetIncomeCapexOpex: { + label: "Annual net cash flow", + color: "hsl(var(--chart-3))", + icon: () =>
, + }, + cumulativeNetIncomePlan: { label: "Revenue OpEx", color: "hsl(var(--chart-4))", icon: () =>
, @@ -55,7 +65,7 @@ const CashflowChart: FC = () => { }} className="cashflow-chart min-h-[200px] w-full" > - + { /> { /> + diff --git a/client/src/containers/projects/custom-project/annual-project-cash-flow/header/index.tsx b/client/src/containers/projects/custom-project/annual-project-cash-flow/header/index.tsx index 4c18c1b1..152595c4 100644 --- a/client/src/containers/projects/custom-project/annual-project-cash-flow/header/index.tsx +++ b/client/src/containers/projects/custom-project/annual-project-cash-flow/header/index.tsx @@ -1,5 +1,7 @@ import { FC } from "react"; +import { ANNUAL_PROJECT_CASHFLOW } from "@/constants/tooltip"; + import Tabs from "@/containers/projects/custom-project/annual-project-cash-flow/header/tabs"; import InfoButton from "@/components/ui/info-button"; @@ -10,7 +12,9 @@ const Header: FC = () => {

Annual project cash flow

- tooltip.content + + {ANNUAL_PROJECT_CASHFLOW} +
); diff --git a/client/src/containers/projects/custom-project/annual-project-cash-flow/header/tabs/index.tsx b/client/src/containers/projects/custom-project/annual-project-cash-flow/header/tabs/index.tsx index 461821bb..acfdaee8 100644 --- a/client/src/containers/projects/custom-project/annual-project-cash-flow/header/tabs/index.tsx +++ b/client/src/containers/projects/custom-project/annual-project-cash-flow/header/tabs/index.tsx @@ -1,6 +1,6 @@ import { ChartNoAxesColumnIcon, Table2Icon } from "lucide-react"; -import { useProjectCashFlowTab } from "@/app/projects/[id]/url-store"; +import { useProjectCashFlowTab } from "@/app/projects/url-store"; import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; diff --git a/client/src/containers/projects/custom-project/annual-project-cash-flow/index.tsx b/client/src/containers/projects/custom-project/annual-project-cash-flow/index.tsx index df259992..c6bc5cc8 100644 --- a/client/src/containers/projects/custom-project/annual-project-cash-flow/index.tsx +++ b/client/src/containers/projects/custom-project/annual-project-cash-flow/index.tsx @@ -1,20 +1,33 @@ import { FC } from "react"; -import { useProjectCashFlowTab } from "@/app/projects/[id]/url-store"; +import { YearlyBreakdown } from "@shared/dtos/custom-projects/custom-project-output.dto"; + +import { useProjectCashFlowTab } from "@/app/projects/url-store"; import CashflowChart from "@/containers/projects/custom-project/annual-project-cash-flow/chart"; import Header from "@/containers/projects/custom-project/annual-project-cash-flow/header"; import CashFlowTable from "@/containers/projects/custom-project/annual-project-cash-flow/table"; +import { YearlyBreakdownChartData } from "@/containers/projects/custom-project/annual-project-cash-flow/utils"; import { Card } from "@/components/ui/card"; -const AnnualProjectCashFlow: FC = () => { +interface AnnualProjectCashFlowProps { + chartData: YearlyBreakdownChartData; + tableData: YearlyBreakdown[]; +} +const AnnualProjectCashFlow: FC = ({ + tableData, + chartData, +}) => { const [tab] = useProjectCashFlowTab(); - return (
- {tab === "table" ? : } + {tab === "table" ? ( + + ) : ( + + )} ); }; diff --git a/client/src/containers/projects/custom-project/annual-project-cash-flow/table/columns.tsx b/client/src/containers/projects/custom-project/annual-project-cash-flow/table/columns.tsx index 0a709c8e..3d0a30b3 100644 --- a/client/src/containers/projects/custom-project/annual-project-cash-flow/table/columns.tsx +++ b/client/src/containers/projects/custom-project/annual-project-cash-flow/table/columns.tsx @@ -1,3 +1,4 @@ +import { OverridableCostInputs } from "@shared/dtos/custom-projects/cost.inputs"; import { createColumnHelper } from "@tanstack/react-table"; import { formatCurrency } from "@/lib/format"; @@ -29,6 +30,24 @@ const getRandomNumber = (type?: "negative" | "positive"): number => { }; const columnHelper = createColumnHelper(); +const cashflowCostNameMap: Record = { + financingCost: "Financing cost", + monitoring: "Monitoring", + maintenance: "Maintenance", + communityBenefitSharingFund: "Community benefit sharing fund", + carbonStandardFees: "Carbon standard fees", + baselineReassessment: "Baseline reassessment", + mrv: "MRV", + longTermProjectOperatingCost: "Long-term project operating", + feasibilityAnalysis: "Feasibility analysis", + conservationPlanningAndAdmin: "Conservation planning and admin", + dataCollectionAndFieldCost: "Data collection and field costs", + communityRepresentation: "Community representation", + blueCarbonProjectPlanning: "Blue carbon project planning", + establishingCarbonRights: "Establishing carbon rights", + validation: "Validation", + implementationLabor: "Implementation labor", +}; export const chartData = YEARS.map((y) => ({ year: y, @@ -39,9 +58,14 @@ export const chartData = YEARS.map((y) => ({ })); export const tableData = mockData.data.yearlyBreakdown; -export const columns = (years: string[]) => [ +export const columns = (years: number[]) => [ columnHelper.accessor("costName", { header: () => Project, + cell: (info) => ( + + {cashflowCostNameMap[info.getValue() as keyof OverridableCostInputs]} + + ), }), ...years.map((year) => columnHelper.accessor(`costValues.${year}`, { diff --git a/client/src/containers/projects/custom-project/annual-project-cash-flow/table/index.tsx b/client/src/containers/projects/custom-project/annual-project-cash-flow/table/index.tsx index 567e31f3..61dfc334 100644 --- a/client/src/containers/projects/custom-project/annual-project-cash-flow/table/index.tsx +++ b/client/src/containers/projects/custom-project/annual-project-cash-flow/table/index.tsx @@ -1,5 +1,6 @@ import { FC, useState } from "react"; +import { YearlyBreakdown } from "@shared/dtos/custom-projects/custom-project-output.dto"; import { flexRender, getCoreRowModel, @@ -14,7 +15,6 @@ import { columns, } from "@/containers/projects/custom-project/annual-project-cash-flow/table/columns"; import { getBreakdownYears } from "@/containers/projects/custom-project/annual-project-cash-flow/utils"; -import mockData from "@/containers/projects/custom-project/mock-data"; import { ScrollableTable, @@ -28,14 +28,17 @@ import TablePagination, { PAGINATION_SIZE_OPTIONS, } from "@/components/ui/table-pagination"; -const CashFlowTable: FC = () => { +interface CashFlowTableProps { + data: YearlyBreakdown[]; +} +const CashFlowTable: FC = ({ data }) => { const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: Number.parseInt(PAGINATION_SIZE_OPTIONS[0]), }); const table = useReactTable({ - data: tableData, - columns: columns(getBreakdownYears(mockData.data.yearlyBreakdown)), + data, + columns: columns(getBreakdownYears(data)), getCoreRowModel: getCoreRowModel(), manualPagination: true, state: { @@ -56,7 +59,7 @@ const CashFlowTable: FC = () => { key={header.id} className={cn({ "text-xs font-normal": true, - "text-center": header.id !== "label", + "text-center": header.id !== "costName", })} > {flexRender( @@ -73,18 +76,17 @@ const CashFlowTable: FC = () => { {table.getRowModel().rows?.length ? ( table.getRowModel().rows.map((row) => ( - {row.getVisibleCells().map((cell, index) => ( + {row.getVisibleCells().map((cell) => ( 0 - ? { - minWidth: cell.column.columnDef.size, - maxWidth: cell.column.columnDef.size, - } - : undefined - } + className={cn({ + "px-2 py-2.5": true, + "text-center": cell.column.id !== "costName", + })} + style={{ + minWidth: cell.column.columnDef.size, + maxWidth: cell.column.columnDef.size, + }} > {flexRender(cell.column.columnDef.cell, cell.getContext())} diff --git a/client/src/containers/projects/custom-project/annual-project-cash-flow/utils.ts b/client/src/containers/projects/custom-project/annual-project-cash-flow/utils.ts index 68daed1c..c9993a47 100644 --- a/client/src/containers/projects/custom-project/annual-project-cash-flow/utils.ts +++ b/client/src/containers/projects/custom-project/annual-project-cash-flow/utils.ts @@ -1,41 +1,70 @@ -import { YearlyBreakdown } from "@shared/dtos/custom-projects/custom-project-output.dto"; +import { + CostPlanMap, + YearlyBreakdown, +} from "@shared/dtos/custom-projects/custom-project-output.dto"; -function getBreakdownYears(data: YearlyBreakdown[]): string[] { +function getBreakdownYears(data: YearlyBreakdown[]): number[] { if (data.length === 0) return []; return Object.keys(data[0].costValues) - .map((y) => y) - .sort((a, b) => Number(a) - Number(b)); + .map((y) => Number(y)) + .sort((a, b) => a - b); } -// TODO: This functionality will be used when backend API response is updated -// function parseYearlyBreakdownForChart( -// data: YearlyBreakdown[], -// years: string[], -// ) { -// if (data.length === 0) return []; - -// let estimatedRenevueValues = {}; -// let annualNetCashFlowValues = {}; - -// data.forEach((d) => { -// switch (d.costName) { -// case "estimatedRenevue": -// estimatedRenevueValues = d.costValues; -// break; -// case "annualNetCashFlow": -// annualNetCashFlowValues = d.costValues; -// break; -// default: -// break; -// } -// }); - -// return years.map((y) => ({ -// year: y, -// estimatedRevenue: estimatedRenevueValues[y], -// annualNetCashFlow: annualNetCashFlowValues[y], -// })); -// } - -export { getBreakdownYears }; +type ChartDataKeys = + | "estimatedRevenuePlan" + | "opexTotalCostPlan" + | "annualNetCashFlow" + | "cumulativeNetIncomePlan" + | "cumulativeNetIncomeCapexOpex"; + +type ChartData = Record; +type YearlyBreakdownData = { + estimatedRevenuePlan: number; + opexTotalCostPlan: number; + annualNetCashFlow: number; + cumulativeNetIncomePlan: number; + cumulativeNetIncomeCapexOpex: number; + year: number; +}; +type YearlyBreakdownChartData = YearlyBreakdownData[]; + +function parseYearlyBreakdownForChart( + data: YearlyBreakdown[], + years: number[], +): YearlyBreakdownChartData { + if (data.length === 0) return []; + + const chartData: ChartData = { + estimatedRevenuePlan: {}, + opexTotalCostPlan: {}, + annualNetCashFlow: {}, + cumulativeNetIncomePlan: {}, + cumulativeNetIncomeCapexOpex: {}, + }; + + // Populate chart data based on yearly breakdown + data.forEach(({ costName, costValues }) => { + if (costName in chartData) { + chartData[costName as ChartDataKeys] = costValues; + } + }); + + // Transform data for each year + return years.map((year) => ({ + year, + ...Object.keys(chartData).reduce( + (acc, key) => ({ + ...acc, + [key]: chartData[key as ChartDataKeys][year], + }), + {} as Record, + ), + })); +} + +export { + getBreakdownYears, + parseYearlyBreakdownForChart, + type YearlyBreakdownChartData, +}; diff --git a/client/src/containers/projects/custom-project/cost-details/index.tsx b/client/src/containers/projects/custom-project/cost-details/index.tsx index 07c1a194..e9729df8 100644 --- a/client/src/containers/projects/custom-project/cost-details/index.tsx +++ b/client/src/containers/projects/custom-project/cost-details/index.tsx @@ -2,7 +2,9 @@ import { FC } from "react"; import { useAtom } from "jotai"; -import { showCostDetailsAtom } from "@/app/projects/[id]/store"; +import { showCostDetailsAtom } from "@/app/projects/store"; + +import { COST_DETAILS } from "@/constants/tooltip"; import CostDetailsParameters from "@/containers/projects/custom-project/cost-details/parameters"; import CostDetailTable from "@/containers/projects/custom-project/cost-details/table"; @@ -31,7 +33,12 @@ const CostDetails: FC = ({ data }) => {

Cost details

- tooltip.content + + {COST_DETAILS} +
diff --git a/client/src/containers/projects/custom-project/cost-details/parameters/index.tsx b/client/src/containers/projects/custom-project/cost-details/parameters/index.tsx index f378f316..c3bada29 100644 --- a/client/src/containers/projects/custom-project/cost-details/parameters/index.tsx +++ b/client/src/containers/projects/custom-project/cost-details/parameters/index.tsx @@ -2,7 +2,7 @@ import { COST_TYPE_SELECTOR } from "@shared/entities/projects.entity"; import { useAtom } from "jotai"; import { FILTER_KEYS } from "@/app/(overview)/constants"; -import { costDetailsFilterAtom } from "@/app/projects/[id]/store"; +import { costDetailsFilterAtom } from "@/app/projects/store"; import { Label } from "@/components/ui/label"; import { diff --git a/client/src/containers/projects/custom-project/cost/index.tsx b/client/src/containers/projects/custom-project/cost/index.tsx index 8be1c996..60306f8c 100644 --- a/client/src/containers/projects/custom-project/cost/index.tsx +++ b/client/src/containers/projects/custom-project/cost/index.tsx @@ -4,7 +4,9 @@ import { useSetAtom } from "jotai"; import { renderCurrency } from "@/lib/format"; -import { showCostDetailsAtom } from "@/app/projects/[id]/store"; +import { showCostDetailsAtom } from "@/app/projects/store"; + +import { CUSTOM_PROJECT_OUTPUTS } from "@/constants/tooltip"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader } from "@/components/ui/card"; @@ -28,8 +30,7 @@ const ProjectCost: FC = ({ total, capex, opex }) => { className="text-md font-medium" tooltip={{ title: "Total project cost", - content: - "Refers to the summary of Capital Expenditure and Operating Expenditure", + content: CUSTOM_PROJECT_OUTPUTS.TOTAL_PROJECT_COST, }} asChild > diff --git a/client/src/containers/projects/custom-project/details/index.tsx b/client/src/containers/projects/custom-project/details/index.tsx index 107a4a71..3e595ea5 100644 --- a/client/src/containers/projects/custom-project/details/index.tsx +++ b/client/src/containers/projects/custom-project/details/index.tsx @@ -6,8 +6,6 @@ import { ECOSYSTEM } from "@shared/entities/ecosystem.enum"; import DetailItem from "@/containers/projects/custom-project/details/detail-item"; -import FileEdit from "@/components/icons/file-edit"; -import { Button } from "@/components/ui/button"; import { Card } from "@/components/ui/card"; // Will refector this interface after we decide state management: @@ -42,10 +40,11 @@ const ProjectDetails: FC = ({

Project details

- + */}
diff --git a/client/src/containers/projects/custom-project/header/index.tsx b/client/src/containers/projects/custom-project/header/index.tsx index c1f2e2d2..b27c0fcb 100644 --- a/client/src/containers/projects/custom-project/header/index.tsx +++ b/client/src/containers/projects/custom-project/header/index.tsx @@ -6,7 +6,7 @@ import { useSession } from "next-auth/react"; import { cn } from "@/lib/utils"; -import { projectsUIState } from "@/app/projects/[id]/store"; +import { projectsUIState } from "@/app/projects/store"; import AuthDialog from "@/containers/auth/dialog"; import CustomProjectParameters from "@/containers/projects/custom-project/header/parameters"; diff --git a/client/src/containers/projects/custom-project/header/parameters/index.tsx b/client/src/containers/projects/custom-project/header/parameters/index.tsx index 91e25f9f..1282bad8 100644 --- a/client/src/containers/projects/custom-project/header/parameters/index.tsx +++ b/client/src/containers/projects/custom-project/header/parameters/index.tsx @@ -26,6 +26,7 @@ export const PROJECT_PARAMETERS = [ key: FILTER_KEYS[3], label: "Project size", className: "w-[125px]", + disabled: false, options: [ { label: COST_TYPE_SELECTOR.NPV, @@ -41,6 +42,7 @@ export const PROJECT_PARAMETERS = [ key: FILTER_KEYS[2], label: "Carbon pricing type", className: "w-[195px]", + disabled: true, options: [ { label: PROJECT_PRICE_TYPE.MARKET_PRICE, @@ -81,6 +83,7 @@ export default function CustomProjectParameters() { onValueChange={(v) => { handleParameters(v, parameter.key); }} + disabled={parameter.disabled} > diff --git a/client/src/containers/projects/custom-project/index.tsx b/client/src/containers/projects/custom-project/index.tsx index 0b081132..d37fdddb 100644 --- a/client/src/containers/projects/custom-project/index.tsx +++ b/client/src/containers/projects/custom-project/index.tsx @@ -1,16 +1,18 @@ "use client"; import { FC } from "react"; +import { CustomProject as CustomProjectEntity } from "@shared/entities/custom-project.entity"; import { motion } from "framer-motion"; import { useAtomValue } from "jotai"; import { LAYOUT_TRANSITIONS } from "@/app/(overview)/constants"; -import { - costDetailsFilterAtom, - projectsUIState, -} from "@/app/projects/[id]/store"; +import { costDetailsFilterAtom, projectsUIState } from "@/app/projects/store"; import AnnualProjectCashFlow from "@/containers/projects/custom-project/annual-project-cash-flow"; +import { + getBreakdownYears, + parseYearlyBreakdownForChart, +} from "@/containers/projects/custom-project/annual-project-cash-flow/utils"; import ProjectCost from "@/containers/projects/custom-project/cost"; import CostDetails from "@/containers/projects/custom-project/cost-details"; import { parseCostDetailsForTable } from "@/containers/projects/custom-project/cost-details/table/utils"; @@ -23,34 +25,27 @@ import { useCustomProjectFilters } from "@/containers/projects/url-store"; import { useSidebar } from "@/components/ui/sidebar"; -// Temporary use of mock data until we decide on state management -const { - country, - projectSize, - projectLength, - ecosystem, - activity, - lossRate, - carbonRevenuesToCover, - initialCarbonPrice, - emissionFactors, - totalProjectCost, - summary, - costDetails, - leftover, -} = mockData.data; +// Temporary use of mock data until response from API is ready +const { costDetails, leftover } = mockData.data; const costDetailsData = { total: parseCostDetailsForTable(costDetails.total), npv: parseCostDetailsForTable(costDetails.npv), }; export const SUMMARY_SIDEBAR_WIDTH = 460; -const CustomProject: FC = () => { + +interface CustomProjectProps { + data: InstanceType; +} + +const CustomProject: FC = ({ data }) => { const [{ costRangeSelector }] = useCustomProjectFilters(); const costDetailsRangeSelector = useAtomValue(costDetailsFilterAtom); const { projectSummaryOpen } = useAtomValue(projectsUIState); const { open: navOpen } = useSidebar(); - const projectCostProps = totalProjectCost[costRangeSelector]; + // TODO: should be replaced with correct type when available; + const output = data.output as any; + const projectCostProps = output.totalProjectCost[costRangeSelector]; const costDetailsProps = costDetailsData[costDetailsRangeSelector]; const leftOverProps = leftover[costRangeSelector]; @@ -76,27 +71,33 @@ const CustomProject: FC = () => { transition={LAYOUT_TRANSITIONS} className="overflow-hidden" > - +
- +
); diff --git a/client/src/containers/projects/custom-project/left-over/index.tsx b/client/src/containers/projects/custom-project/left-over/index.tsx index 2a3d8229..4cf858d4 100644 --- a/client/src/containers/projects/custom-project/left-over/index.tsx +++ b/client/src/containers/projects/custom-project/left-over/index.tsx @@ -2,6 +2,8 @@ import { FC } from "react"; import { renderCurrency } from "@/lib/format"; +import { CUSTOM_PROJECT_OUTPUTS } from "@/constants/tooltip"; + import { Card, CardContent, CardHeader } from "@/components/ui/card"; import { Graph, GraphLegend } from "@/components/ui/graph"; import { Label } from "@/components/ui/label"; @@ -22,9 +24,8 @@ const LeftOver: FC = ({ total, leftover, opex }) => { htmlFor="totalProjectCost" className="text-md font-medium" tooltip={{ - title: "Total project cost", - content: - "Refers to the summary of Capital Expenditure and Operating Expenditure", + title: "Leftover after OpEx", + content: CUSTOM_PROJECT_OUTPUTS.LEFTOVER_AFTER_OPEX, }} >

Leftover after OpEx

diff --git a/client/src/containers/projects/custom-project/summary/index.tsx b/client/src/containers/projects/custom-project/summary/index.tsx index ba763cab..813780bf 100644 --- a/client/src/containers/projects/custom-project/summary/index.tsx +++ b/client/src/containers/projects/custom-project/summary/index.tsx @@ -4,11 +4,12 @@ import { type CustomProjectSummary } from "@shared/dtos/custom-projects/custom-p import { useSetAtom } from "jotai"; import { XIcon } from "lucide-react"; -import { projectsUIState } from "@/app/projects/[id]/store"; +import { projectsUIState } from "@/app/projects/store"; + +import { PROJECT_SUMMARY } from "@/constants/tooltip"; import { SUMMARY_SIDEBAR_WIDTH } from "@/containers/projects/custom-project"; -import FileEdit from "@/components/icons/file-edit"; import { Button } from "@/components/ui/button"; import InfoButton from "@/components/ui/info-button"; import Metric from "@/components/ui/metric"; @@ -71,9 +72,7 @@ const ProjectSummary: FC = ({ data }) => { >
{key}
- - tooltip.content - + {PROJECT_SUMMARY[key]}
= ({ data }) => { Calculations based on project setup parameters. For new calculations, edit project details.

- + */}
); diff --git a/shared/dtos/custom-projects/custom-project-output.dto.ts b/shared/dtos/custom-projects/custom-project-output.dto.ts index 2649e9d6..5ed4c768 100644 --- a/shared/dtos/custom-projects/custom-project-output.dto.ts +++ b/shared/dtos/custom-projects/custom-project-output.dto.ts @@ -41,7 +41,7 @@ export type CustomProjectCostDetails = { longTermProjectOperatingCost: number; }; -export type OutputCostNames = [ +export type OutputCostNames = keyof [ "opexTotalCostPlan", "capexTotalCostPlan", "totalCostPlan",