Loading src/app/app/portfolios/view/[id]/portfolioViewDetailClient.tsx +53 −33 Original line number Diff line number Diff line Loading @@ -28,27 +28,12 @@ export default function PortfolioViewDetailClient(props: { minimumFractionDigits: 2, maximumFractionDigits: 2, }); const getEntryPrice = (position: any) => { const qty = getPositionQuantity(position); const unitPrice = Number(position.buyUnitPrice); const fees = Number(position.buyFees ?? 0); const taxes = Number(position.buyTaxes ?? 0); if (qty > 0) { return unitPrice + (fees + taxes) / qty; } return unitPrice; }; const getEntryPrice = (position: any) => Number(position.buyUnitPrice); const getCurrentPrice = (position: any) => Number(position.currentPrice); const getPositionValue = (position: any) => getPositionQuantity(position) * getCurrentPrice(position); const getOpenPositionFeesAndTaxes = (position: any) => Number(position?.buyFees ?? 0) + Number(position?.buyTaxes ?? 0) const getPositionValue = (position: any) => { const explicitValue = Number(position?.currentValue ?? position?.marketValue ?? position?.value ?? 0); if (Number.isFinite(explicitValue) && explicitValue !== 0) { return explicitValue; } const qty = getPositionQuantity(position); const price = getCurrentPrice(position); return qty * price; }; const fetchPortfolioDetail = async () => { setIsLoading(true); Loading Loading @@ -137,26 +122,33 @@ export default function PortfolioViewDetailClient(props: { } }; const activePortfolioValue = Number(portfolio?.totalOpenPositionsValue ?? 0); const profitLosses = Number(activePortfolioValue + Number(portfolio?.currentCash ?? 0) - Number(portfolio?.initialCash ?? 0)); const activePortfolioValue = Number(portfolio?.totalOpenPositionsCurrentValue ?? 0); const profitLosses = activePortfolioValue - Number(portfolio?.totalOpenPositionsOriginalBuyValue ?? 0); const profitLossesPercent = (profitLosses / Number(portfolio?.initialCash ?? 0)) * 100; const totalProfitLosses = activePortfolioValue + Number(portfolio?.currentCash ?? 0) - Number(portfolio?.initialCash ?? 0); const groupedPositionsList = Object.entries(positionsByTicker).map(([ticker, items]) => { const positions = Array.isArray(items) ? items : []; const summary = positions.reduce((acc, position) => { const qty = getPositionQuantity(position); const previousQuantity = acc.quantity; const entryPrice = getEntryPrice(position); const currentPrice = getCurrentPrice(position); const positionValue = getPositionValue(position); const pnl = Number(positionValue - qty * entryPrice); const feesAndTaxes = getOpenPositionFeesAndTaxes(position); const pnlFeesAndTaxesIncl = pnl - feesAndTaxes; acc.quantity += qty; acc.entryValue += qty * entryPrice; acc.AvgEntryValue = ((acc.AvgEntryValue * previousQuantity) + (qty * entryPrice)) / (previousQuantity + qty); acc.currentValue += positionValue; acc.pnl += pnl; acc.lastPrice = currentPrice || acc.lastPrice; acc.feesAndTaxes += feesAndTaxes; acc.pnlFeesAndTaxesIncl += pnlFeesAndTaxesIncl; return acc; }, { ticker, name: items[0]?.name || '', quantity: 0, entryValue: 0, currentValue: 0, pnl: 0, lastPrice: 0 }); }, { ticker, name: items[0]?.name || '', quantity: 0, AvgEntryValue: 0, currentValue: 0, pnl: 0, lastPrice: 0, feesAndTaxes: 0, pnlFeesAndTaxesIncl: 0 }); return summary; }); Loading Loading @@ -193,22 +185,30 @@ export default function PortfolioViewDetailClient(props: { <div className="text-2xl font-semibold text-gray-900">{formatPrice(activePortfolioValue)}</div> </div> <div className="rounded-lg border border-gray-200 bg-white p-4 shadow-sm"> <div className="text-sm text-gray-500">Profit / losses</div> <div className="text-sm text-gray-500">Profit / losses (portfolio without Fees & Taxes)</div> <div className={`text-2xl font-semibold text-gray-900 ${profitLosses >= 0 ? 'text-green-600' : 'text-red-600'}`}>{formatPrice(profitLosses)} / {formatPrice(profitLossesPercent)}%</div> </div> <div className="rounded-lg border border-gray-200 bg-white p-4 shadow-sm"> <div className="text-sm text-gray-500">Fees & Taxes</div> <div className={`text-2xl font-semibold text-gray-900 text-red-600`}>-{formatPrice(portfolio.totalFeesAndTaxes)}</div> </div> <div className="rounded-lg border border-gray-200 bg-white p-4 shadow-sm"> <div className="text-sm text-gray-500">Total Profit / losses </div> <div className={`text-2xl font-semibold text-gray-900 ${totalProfitLosses >= 0 ? 'text-green-600' : 'text-red-600'}`}>{formatPrice(totalProfitLosses)}</div> </div> </div> <div className="rounded-lg border border-gray-200 bg-white p-4 shadow-sm"> <div className=""> {portfolio.rateLimit && portfolio.rateRemaining && portfolio.usageLimit && portfolio.usageRemaining && ( <div className="mb-4 rounded-md bg-blue-50 p-4"> <div className="mb-4 rounded-md bg-sky-50 p-4"> <div className="flex"> <div className="flex-shrink-0"> <i className="fa-solid fa-triangle-exclamation text-blue-400"></i> <i className="fa-solid fa-triangle-exclamation text-sky-400"></i> </div> <div className="ml-3"> <h3 className="text-sm font-medium text-blue-800">Stock Data Info</h3> <h3 className="text-sm font-medium text-sky-800">Stock Data Info</h3> Rate limit Remaining: {portfolio.rateRemaining} / {portfolio.rateLimit} <br /> Usage limit Remaining: {portfolio.usageRemaining} / {portfolio.usageLimit} </div> Loading Loading @@ -268,26 +268,33 @@ export default function PortfolioViewDetailClient(props: { <th className="border border-gray-200 px-4 py-2 text-right">Last Price</th> <th className="border border-gray-200 px-4 py-2 text-right">Current Value</th> <th className="border border-gray-200 px-4 py-2 text-right">P/L</th> <th className="border border-gray-200 px-4 py-2 text-right">Fees & Taxes</th> <th className="border border-gray-200 px-4 py-2 text-right">P/L (fees & taxes incl.)</th> <th className="border border-gray-200 px-4 py-2 text-right"></th> </tr> </thead> <tbody> {groupedPositionsList.map((group: any) => { const avgEntry = group.quantity ? group.entryValue / group.quantity : 0; return ( <tr key={group.ticker}> <td className="border border-gray-200 px-4 py-2">{`${group.name} (${group.ticker})`}</td> <td className="border border-gray-200 px-4 py-2 text-right">{Number(group.quantity).toLocaleString()}</td> <td className="border border-gray-200 px-4 py-2 text-right">{formatPrice(avgEntry)}</td> <td className="border border-gray-200 px-4 py-2 text-right">{formatPrice(group.AvgEntryValue)}</td> <td className="border border-gray-200 px-4 py-2 text-right">{formatPrice(group.lastPrice)}</td> <td className="border border-gray-200 px-4 py-2 text-right">{formatPrice(group.currentValue)}</td> <td className={`border border-gray-200 px-4 py-2 text-right ${group.pnl >= 0 ? 'text-green-600' : 'text-red-600'}`}> {formatPrice(group.pnl)} </td> <td className={`border border-gray-200 px-4 py-2 text-right text-red-600`}> -{formatPrice(group.feesAndTaxes)} </td> <td className={`border border-gray-200 px-4 py-2 text-right ${group.pnlFeesAndTaxesIncl >= 0 ? 'text-green-600' : 'text-red-600'}`}> {formatPrice(group.pnlFeesAndTaxesIncl)} </td> <td className="border border-gray-200 px-4 py-2 text-right"> <Link href={`/app/portfolios/view/${portfolio.id}/chat?ticker=${group.ticker}`} className="rounded-full bg-blue-500 px-3 py-1 text-sm leading-5 font-semibold text-white hover:bg-blue-700 items-center gap-2 cursor-pointer" className="rounded-full bg-sky-500 px-3 py-1 text-sm leading-5 font-semibold text-white hover:bg-sky-700 items-center gap-2 cursor-pointer" > <i className="fa-solid fa-eye mr-2"></i> <span>Go to chat history</span> Loading Loading @@ -347,6 +354,9 @@ export default function PortfolioViewDetailClient(props: { <th className="border border-gray-200 px-4 py-2 text-right">Entry Price</th> <th className="border border-gray-200 px-4 py-2 text-right">Exit Price</th> <th className="border border-gray-200 px-4 py-2 text-right">P/L</th> <th className="border border-gray-200 px-4 py-2 text-right">Buy Fees & Taxes</th> <th className="border border-gray-200 px-4 py-2 text-right">Sell Fees & Taxes</th> <th className="border border-gray-200 px-4 py-2 text-right">P/L (Fees & Taxes Incl.)</th> <th className="border border-gray-200 px-4 py-2 text-right">Closed At</th> </tr> </thead> Loading @@ -359,17 +369,27 @@ export default function PortfolioViewDetailClient(props: { const sellPrice = Number(position?.sellUnitPrice ?? 0); const sellFees = Number(position?.sellFees ?? 0); const sellTaxes = Number(position?.sellTaxes ?? 0); const pnl = sellPrice * qty - (sellFees + sellTaxes) - (buyPrice * qty + buyFees + buyTaxes); const pnl = sellPrice * qty - buyPrice * qty; const pnlFeesAndTaxesIncl = sellPrice * qty - (sellFees + sellTaxes) - (buyPrice * qty + buyFees + buyTaxes); const closedAt = position?.sellDate || position?.closedAt || position?.closeDate || position?.closedDate; return ( <tr key={position?.id ?? `${getPositionTicker(position)}-${closedAt || Math.random()}`}> <td className="border border-gray-200 px-4 py-2">{getPositionTicker(position)}</td> <td className="border border-gray-200 px-4 py-2 text-right">{getPositionQuantity(position).toLocaleString()}</td> <td className="border border-gray-200 px-4 py-2 text-right">{formatPrice(getEntryPrice(position))}</td> <td className="border border-gray-200 px-4 py-2 text-right">{formatPrice(position?.sellUnitPrice ?? position?.exitPrice ?? position?.closePrice ?? position?.sellPrice ?? 0)}</td> <td className="border border-gray-200 px-4 py-2 text-right">{formatPrice(position?.sellUnitPrice ?? 0)}</td> <td className={`border border-gray-200 px-4 py-2 text-right ${pnl >= 0 ? 'text-green-600' : 'text-red-600'}`}> {formatPrice(pnl)} </td> <td className="border border-gray-200 px-4 py-2 text-right text-red-600"> -{formatPrice(buyFees + buyTaxes)} </td> <td className="border border-gray-200 px-4 py-2 text-right text-red-600"> -{formatPrice(sellFees + sellTaxes)} </td> <td className={`border border-gray-200 px-4 py-2 text-right ${pnlFeesAndTaxesIncl >= 0 ? 'text-green-600' : 'text-red-600'}`}> {formatPrice(pnlFeesAndTaxesIncl)} </td> <td className="border border-gray-200 px-4 py-2 text-right"> {closedAt ? new Date(closedAt).toLocaleString() : '-'} </td> Loading Loading
src/app/app/portfolios/view/[id]/portfolioViewDetailClient.tsx +53 −33 Original line number Diff line number Diff line Loading @@ -28,27 +28,12 @@ export default function PortfolioViewDetailClient(props: { minimumFractionDigits: 2, maximumFractionDigits: 2, }); const getEntryPrice = (position: any) => { const qty = getPositionQuantity(position); const unitPrice = Number(position.buyUnitPrice); const fees = Number(position.buyFees ?? 0); const taxes = Number(position.buyTaxes ?? 0); if (qty > 0) { return unitPrice + (fees + taxes) / qty; } return unitPrice; }; const getEntryPrice = (position: any) => Number(position.buyUnitPrice); const getCurrentPrice = (position: any) => Number(position.currentPrice); const getPositionValue = (position: any) => getPositionQuantity(position) * getCurrentPrice(position); const getOpenPositionFeesAndTaxes = (position: any) => Number(position?.buyFees ?? 0) + Number(position?.buyTaxes ?? 0) const getPositionValue = (position: any) => { const explicitValue = Number(position?.currentValue ?? position?.marketValue ?? position?.value ?? 0); if (Number.isFinite(explicitValue) && explicitValue !== 0) { return explicitValue; } const qty = getPositionQuantity(position); const price = getCurrentPrice(position); return qty * price; }; const fetchPortfolioDetail = async () => { setIsLoading(true); Loading Loading @@ -137,26 +122,33 @@ export default function PortfolioViewDetailClient(props: { } }; const activePortfolioValue = Number(portfolio?.totalOpenPositionsValue ?? 0); const profitLosses = Number(activePortfolioValue + Number(portfolio?.currentCash ?? 0) - Number(portfolio?.initialCash ?? 0)); const activePortfolioValue = Number(portfolio?.totalOpenPositionsCurrentValue ?? 0); const profitLosses = activePortfolioValue - Number(portfolio?.totalOpenPositionsOriginalBuyValue ?? 0); const profitLossesPercent = (profitLosses / Number(portfolio?.initialCash ?? 0)) * 100; const totalProfitLosses = activePortfolioValue + Number(portfolio?.currentCash ?? 0) - Number(portfolio?.initialCash ?? 0); const groupedPositionsList = Object.entries(positionsByTicker).map(([ticker, items]) => { const positions = Array.isArray(items) ? items : []; const summary = positions.reduce((acc, position) => { const qty = getPositionQuantity(position); const previousQuantity = acc.quantity; const entryPrice = getEntryPrice(position); const currentPrice = getCurrentPrice(position); const positionValue = getPositionValue(position); const pnl = Number(positionValue - qty * entryPrice); const feesAndTaxes = getOpenPositionFeesAndTaxes(position); const pnlFeesAndTaxesIncl = pnl - feesAndTaxes; acc.quantity += qty; acc.entryValue += qty * entryPrice; acc.AvgEntryValue = ((acc.AvgEntryValue * previousQuantity) + (qty * entryPrice)) / (previousQuantity + qty); acc.currentValue += positionValue; acc.pnl += pnl; acc.lastPrice = currentPrice || acc.lastPrice; acc.feesAndTaxes += feesAndTaxes; acc.pnlFeesAndTaxesIncl += pnlFeesAndTaxesIncl; return acc; }, { ticker, name: items[0]?.name || '', quantity: 0, entryValue: 0, currentValue: 0, pnl: 0, lastPrice: 0 }); }, { ticker, name: items[0]?.name || '', quantity: 0, AvgEntryValue: 0, currentValue: 0, pnl: 0, lastPrice: 0, feesAndTaxes: 0, pnlFeesAndTaxesIncl: 0 }); return summary; }); Loading Loading @@ -193,22 +185,30 @@ export default function PortfolioViewDetailClient(props: { <div className="text-2xl font-semibold text-gray-900">{formatPrice(activePortfolioValue)}</div> </div> <div className="rounded-lg border border-gray-200 bg-white p-4 shadow-sm"> <div className="text-sm text-gray-500">Profit / losses</div> <div className="text-sm text-gray-500">Profit / losses (portfolio without Fees & Taxes)</div> <div className={`text-2xl font-semibold text-gray-900 ${profitLosses >= 0 ? 'text-green-600' : 'text-red-600'}`}>{formatPrice(profitLosses)} / {formatPrice(profitLossesPercent)}%</div> </div> <div className="rounded-lg border border-gray-200 bg-white p-4 shadow-sm"> <div className="text-sm text-gray-500">Fees & Taxes</div> <div className={`text-2xl font-semibold text-gray-900 text-red-600`}>-{formatPrice(portfolio.totalFeesAndTaxes)}</div> </div> <div className="rounded-lg border border-gray-200 bg-white p-4 shadow-sm"> <div className="text-sm text-gray-500">Total Profit / losses </div> <div className={`text-2xl font-semibold text-gray-900 ${totalProfitLosses >= 0 ? 'text-green-600' : 'text-red-600'}`}>{formatPrice(totalProfitLosses)}</div> </div> </div> <div className="rounded-lg border border-gray-200 bg-white p-4 shadow-sm"> <div className=""> {portfolio.rateLimit && portfolio.rateRemaining && portfolio.usageLimit && portfolio.usageRemaining && ( <div className="mb-4 rounded-md bg-blue-50 p-4"> <div className="mb-4 rounded-md bg-sky-50 p-4"> <div className="flex"> <div className="flex-shrink-0"> <i className="fa-solid fa-triangle-exclamation text-blue-400"></i> <i className="fa-solid fa-triangle-exclamation text-sky-400"></i> </div> <div className="ml-3"> <h3 className="text-sm font-medium text-blue-800">Stock Data Info</h3> <h3 className="text-sm font-medium text-sky-800">Stock Data Info</h3> Rate limit Remaining: {portfolio.rateRemaining} / {portfolio.rateLimit} <br /> Usage limit Remaining: {portfolio.usageRemaining} / {portfolio.usageLimit} </div> Loading Loading @@ -268,26 +268,33 @@ export default function PortfolioViewDetailClient(props: { <th className="border border-gray-200 px-4 py-2 text-right">Last Price</th> <th className="border border-gray-200 px-4 py-2 text-right">Current Value</th> <th className="border border-gray-200 px-4 py-2 text-right">P/L</th> <th className="border border-gray-200 px-4 py-2 text-right">Fees & Taxes</th> <th className="border border-gray-200 px-4 py-2 text-right">P/L (fees & taxes incl.)</th> <th className="border border-gray-200 px-4 py-2 text-right"></th> </tr> </thead> <tbody> {groupedPositionsList.map((group: any) => { const avgEntry = group.quantity ? group.entryValue / group.quantity : 0; return ( <tr key={group.ticker}> <td className="border border-gray-200 px-4 py-2">{`${group.name} (${group.ticker})`}</td> <td className="border border-gray-200 px-4 py-2 text-right">{Number(group.quantity).toLocaleString()}</td> <td className="border border-gray-200 px-4 py-2 text-right">{formatPrice(avgEntry)}</td> <td className="border border-gray-200 px-4 py-2 text-right">{formatPrice(group.AvgEntryValue)}</td> <td className="border border-gray-200 px-4 py-2 text-right">{formatPrice(group.lastPrice)}</td> <td className="border border-gray-200 px-4 py-2 text-right">{formatPrice(group.currentValue)}</td> <td className={`border border-gray-200 px-4 py-2 text-right ${group.pnl >= 0 ? 'text-green-600' : 'text-red-600'}`}> {formatPrice(group.pnl)} </td> <td className={`border border-gray-200 px-4 py-2 text-right text-red-600`}> -{formatPrice(group.feesAndTaxes)} </td> <td className={`border border-gray-200 px-4 py-2 text-right ${group.pnlFeesAndTaxesIncl >= 0 ? 'text-green-600' : 'text-red-600'}`}> {formatPrice(group.pnlFeesAndTaxesIncl)} </td> <td className="border border-gray-200 px-4 py-2 text-right"> <Link href={`/app/portfolios/view/${portfolio.id}/chat?ticker=${group.ticker}`} className="rounded-full bg-blue-500 px-3 py-1 text-sm leading-5 font-semibold text-white hover:bg-blue-700 items-center gap-2 cursor-pointer" className="rounded-full bg-sky-500 px-3 py-1 text-sm leading-5 font-semibold text-white hover:bg-sky-700 items-center gap-2 cursor-pointer" > <i className="fa-solid fa-eye mr-2"></i> <span>Go to chat history</span> Loading Loading @@ -347,6 +354,9 @@ export default function PortfolioViewDetailClient(props: { <th className="border border-gray-200 px-4 py-2 text-right">Entry Price</th> <th className="border border-gray-200 px-4 py-2 text-right">Exit Price</th> <th className="border border-gray-200 px-4 py-2 text-right">P/L</th> <th className="border border-gray-200 px-4 py-2 text-right">Buy Fees & Taxes</th> <th className="border border-gray-200 px-4 py-2 text-right">Sell Fees & Taxes</th> <th className="border border-gray-200 px-4 py-2 text-right">P/L (Fees & Taxes Incl.)</th> <th className="border border-gray-200 px-4 py-2 text-right">Closed At</th> </tr> </thead> Loading @@ -359,17 +369,27 @@ export default function PortfolioViewDetailClient(props: { const sellPrice = Number(position?.sellUnitPrice ?? 0); const sellFees = Number(position?.sellFees ?? 0); const sellTaxes = Number(position?.sellTaxes ?? 0); const pnl = sellPrice * qty - (sellFees + sellTaxes) - (buyPrice * qty + buyFees + buyTaxes); const pnl = sellPrice * qty - buyPrice * qty; const pnlFeesAndTaxesIncl = sellPrice * qty - (sellFees + sellTaxes) - (buyPrice * qty + buyFees + buyTaxes); const closedAt = position?.sellDate || position?.closedAt || position?.closeDate || position?.closedDate; return ( <tr key={position?.id ?? `${getPositionTicker(position)}-${closedAt || Math.random()}`}> <td className="border border-gray-200 px-4 py-2">{getPositionTicker(position)}</td> <td className="border border-gray-200 px-4 py-2 text-right">{getPositionQuantity(position).toLocaleString()}</td> <td className="border border-gray-200 px-4 py-2 text-right">{formatPrice(getEntryPrice(position))}</td> <td className="border border-gray-200 px-4 py-2 text-right">{formatPrice(position?.sellUnitPrice ?? position?.exitPrice ?? position?.closePrice ?? position?.sellPrice ?? 0)}</td> <td className="border border-gray-200 px-4 py-2 text-right">{formatPrice(position?.sellUnitPrice ?? 0)}</td> <td className={`border border-gray-200 px-4 py-2 text-right ${pnl >= 0 ? 'text-green-600' : 'text-red-600'}`}> {formatPrice(pnl)} </td> <td className="border border-gray-200 px-4 py-2 text-right text-red-600"> -{formatPrice(buyFees + buyTaxes)} </td> <td className="border border-gray-200 px-4 py-2 text-right text-red-600"> -{formatPrice(sellFees + sellTaxes)} </td> <td className={`border border-gray-200 px-4 py-2 text-right ${pnlFeesAndTaxesIncl >= 0 ? 'text-green-600' : 'text-red-600'}`}> {formatPrice(pnlFeesAndTaxesIncl)} </td> <td className="border border-gray-200 px-4 py-2 text-right"> {closedAt ? new Date(closedAt).toLocaleString() : '-'} </td> Loading