/** * Copyright (c) 2026-present, Goldman Sachs * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { useMemo, useState, type JSX } from 'react'; import { observer } from 'mobx-react-lite'; import { useLegendMarketplaceBaseStore } from '../../application/providers/LegendMarketplaceFrameworkProvider.js'; import { Dialog, DialogContent, DialogActions, Button, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Box, } from '@mui/material'; import type { TraderProfile } from '@finos/legend-server-marketplace'; import { formatItemPrice, formatProfileSummaryLine, getItemSummary, groupOrderProfileItems, OrderProfileLabel, OrderProfileTableHeader, } from './orderProfileUtils.js'; import { CategoryChip, OrderProfileModalHeader, } from './OrderProfileModalHeader.js'; import { ColumnFilterButton } from '../Filters/ColumnFilterButton.js'; export const OrderProfileDetailModal = observer( (props: { profile: TraderProfile; open: boolean; onClose: () => void; multiselectTotalPrice?: number; }): JSX.Element => { const { profile, open, onClose, multiselectTotalPrice } = props; const { cartStore } = useLegendMarketplaceBaseStore(); const items = profile.items; const { terminalCount, addOnCount } = getItemSummary(items); const groupedItems = useMemo(() => groupOrderProfileItems(items), [items]); const [categoryFilter, setCategoryFilter] = useState>( () => new Set(), ); const categoryOptions = useMemo( () => Array.from(new Set(items.map((item) => item.category))).sort((a, b) => a.localeCompare(b), ), [items], ); // When a category filter hides a terminal but not its add-ons, demote // the orphaned add-ons to top-level rows (no sub-item indentation) so // they don't render as children with no parent row above them. This // tracks the model of the most recently seen visible terminal (rather // than only comparing to the immediately preceding row) so that every // add-on belonging to that terminal stays indented, not just the first. const filteredGroupedItems = useMemo(() => { const filtered = categoryFilter.size === 0 ? groupedItems : groupedItems.filter(({ item }) => categoryFilter.has(item.category), ); let visibleTerminalModel: string | null | undefined; return filtered.map((entry) => { if (!entry.isSubItem) { if (entry.item.isTerminal) { visibleTerminalModel = entry.item.model; } return entry; } const hasVisibleParent = visibleTerminalModel === entry.item.model; return hasVisibleParent ? entry : { ...entry, isSubItem: false }; }); }, [groupedItems, categoryFilter]); const displayPrice = profile.multiselect && multiselectTotalPrice !== undefined ? multiselectTotalPrice : profile.price; return ( {OrderProfileTableHeader.PRODUCT_NAME} {OrderProfileTableHeader.CATEGORY} {OrderProfileTableHeader.COST_MONTHLY} {filteredGroupedItems.map(({ item, isSubItem }) => { const isInCart = !item.isOwned && (item.isTerminal ? cartStore.isItemInCart(item.id) : cartStore.isAddOnInCartForModel(item.id, item.model)); const rowModifierClass = (() => { if (item.isOwned) { return 'order-profile-modal__table-row--owned'; } if (isInCart) { return 'order-profile-modal__table-row--in-cart'; } return ''; })(); return ( {item.productName} {item.isOwned && ( {' '} {OrderProfileLabel.OWNED_SUFFIX} )} {isInCart && ( {' '} {OrderProfileLabel.IN_CART_SUFFIX} )} {formatItemPrice(item.price)} ); })}
); }, );