mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	feat: invoices ui improvements (#10813)
This commit is contained in:
		
							parent
							
								
									045ef5a20e
								
							
						
					
					
						commit
						b81691b89e
					
				@ -15,14 +15,15 @@ const StyledWrapper = styled(Paper)(({ theme }) => ({
 | 
			
		||||
    padding: theme.spacing(2),
 | 
			
		||||
    borderRadius: theme.shape.borderRadiusLarge,
 | 
			
		||||
    boxShadow: theme.boxShadows.card,
 | 
			
		||||
    display: 'flex',
 | 
			
		||||
    flexDirection: 'column',
 | 
			
		||||
    gap: theme.spacing(1),
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
const StyledRow = styled('div')(({ theme }) => ({
 | 
			
		||||
    display: 'flex',
 | 
			
		||||
    justifyContent: 'space-between',
 | 
			
		||||
    marginTop: theme.spacing(1),
 | 
			
		||||
    fontSize: theme.typography.body2.fontSize,
 | 
			
		||||
    gap: theme.spacing(1),
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
const StyledItemTitle = styled('span')(({ theme }) => ({
 | 
			
		||||
@ -35,17 +36,16 @@ const StyledItemValue = styled('span')(({ theme }) => ({
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
const StyledButton = styled(Button)(({ theme }) => ({
 | 
			
		||||
    margin: theme.spacing(0, 0, 2, 0),
 | 
			
		||||
    margin: theme.spacing(0, 0, 1, 0),
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
const StyledInfoLabel = styled(Typography)(({ theme }) => ({
 | 
			
		||||
    fontSize: theme.fontSizes.smallBody,
 | 
			
		||||
    color: theme.palette.text.secondary,
 | 
			
		||||
    marginBottom: theme.spacing(1),
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
const StyledDivider = styled(Divider)(({ theme }) => ({
 | 
			
		||||
    margin: `${theme.spacing(2.5)} 0`,
 | 
			
		||||
    margin: theme.spacing(1.5, 0),
 | 
			
		||||
    borderColor: theme.palette.divider,
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
@ -70,6 +70,7 @@ export const BillingInfo: FC<BillingInfoProps> = () => {
 | 
			
		||||
    if (!instanceStatus) {
 | 
			
		||||
        return (
 | 
			
		||||
            <StyledWrapper>
 | 
			
		||||
                <Typography variant='h3'>Billing details</Typography>
 | 
			
		||||
                <StyledInfoLabel>
 | 
			
		||||
                    Your billing is managed by Unleash
 | 
			
		||||
                </StyledInfoLabel>
 | 
			
		||||
@ -86,6 +87,7 @@ export const BillingInfo: FC<BillingInfoProps> = () => {
 | 
			
		||||
    if (isCustomBilling) {
 | 
			
		||||
        return (
 | 
			
		||||
            <StyledWrapper>
 | 
			
		||||
                <Typography variant='h3'>Billing details</Typography>
 | 
			
		||||
                <StyledInfoLabel>
 | 
			
		||||
                    Your billing is managed by Unleash
 | 
			
		||||
                </StyledInfoLabel>
 | 
			
		||||
@ -123,11 +125,12 @@ export const BillingInfo: FC<BillingInfoProps> = () => {
 | 
			
		||||
            >
 | 
			
		||||
                {!inactive ? 'Edit billing details' : 'Add billing details'}
 | 
			
		||||
            </StyledButton>
 | 
			
		||||
            <StyledInfoLabel>
 | 
			
		||||
                {inactive
 | 
			
		||||
                    ? 'Once we have received your billing information we will upgrade your trial within 1 business day.'
 | 
			
		||||
                    : 'Update your credit card and business information and change which email address we send invoices to.'}
 | 
			
		||||
            </StyledInfoLabel>
 | 
			
		||||
            {inactive ? (
 | 
			
		||||
                <StyledInfoLabel>
 | 
			
		||||
                    Once we have received your billing information we will
 | 
			
		||||
                    upgrade your trial within 1 business day.
 | 
			
		||||
                </StyledInfoLabel>
 | 
			
		||||
            ) : null}
 | 
			
		||||
            <GetInTouch />
 | 
			
		||||
        </StyledWrapper>
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
@ -9,13 +9,23 @@ export const StyledSubgrid = styled('div', {
 | 
			
		||||
    background: withBackground
 | 
			
		||||
        ? theme.palette.background.elevation1
 | 
			
		||||
        : 'transparent',
 | 
			
		||||
    margin: theme.spacing(0.25, 0),
 | 
			
		||||
    padding: withBackground ? theme.spacing(0, 2, 1) : theme.spacing(0, 2),
 | 
			
		||||
    margin: theme.spacing(0.5, 0),
 | 
			
		||||
    padding: withBackground
 | 
			
		||||
        ? theme.spacing(2, 2, 3)
 | 
			
		||||
        : theme.spacing(0.5, 2, 1.5),
 | 
			
		||||
    borderRadius: theme.shape.borderRadiusLarge,
 | 
			
		||||
    gap: theme.spacing(1),
 | 
			
		||||
    gap: theme.spacing(2),
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
export const StyledAmountCell = styled('div')(({ theme }) => ({
 | 
			
		||||
    textAlign: 'right',
 | 
			
		||||
    paddingRight: theme.spacing(1.5),
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
export const StyledDescriptionCell = styled('div', {
 | 
			
		||||
    shouldForwardProp: (prop) => prop !== 'expand',
 | 
			
		||||
})<{ expand?: boolean }>(({ expand }) => ({
 | 
			
		||||
    display: 'flex',
 | 
			
		||||
    flexDirection: 'column',
 | 
			
		||||
    gridColumn: expand ? '1 / span 2' : undefined,
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
@ -6,17 +6,18 @@ import {
 | 
			
		||||
    AccordionSummary,
 | 
			
		||||
    AccordionDetails,
 | 
			
		||||
    Button,
 | 
			
		||||
    Divider,
 | 
			
		||||
} from '@mui/material';
 | 
			
		||||
import ReceiptLongOutlinedIcon from '@mui/icons-material/ReceiptLongOutlined';
 | 
			
		||||
import DownloadOutlinedIcon from '@mui/icons-material/DownloadOutlined';
 | 
			
		||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
 | 
			
		||||
import { formatCurrency } from './formatCurrency.ts';
 | 
			
		||||
import { Badge } from 'component/common/Badge/Badge.tsx';
 | 
			
		||||
import { BillingInvoiceMainRow } from './BillingInvoiceMainRow/BillingInvoiceMainRow.tsx';
 | 
			
		||||
import { BillingInvoiceUsageRow } from './BillingInvoiceUsageRow/BillingInvoiceUsageRow.tsx';
 | 
			
		||||
import { BillingInvoiceFooter } from './BillingInvoiceFooter/BillingInvoiceFooter.tsx';
 | 
			
		||||
import { StyledAmountCell, StyledSubgrid } from './BillingInvoice.styles.tsx';
 | 
			
		||||
import type { DetailedInvoicesSchemaInvoicesItem } from 'openapi';
 | 
			
		||||
import { BillingInvoiceUsageRow } from './BillingInvoiceUsageRow/BillingInvoiceUsageRow.tsx';
 | 
			
		||||
import { BillingInvoiceMainRow } from './BillingInvoiceMainRow/BillingInvoiceMainRow.tsx';
 | 
			
		||||
 | 
			
		||||
const StyledAccordion = styled(Accordion)(({ theme }) => ({
 | 
			
		||||
    background: theme.palette.background.paper,
 | 
			
		||||
@ -37,6 +38,9 @@ const HeaderRoot = styled(AccordionSummary)(({ theme }) => ({
 | 
			
		||||
        display: 'flex',
 | 
			
		||||
        alignItems: 'center',
 | 
			
		||||
        gap: theme.spacing(1.5),
 | 
			
		||||
        '&.Mui-expanded': {
 | 
			
		||||
            margin: 0,
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
@ -57,15 +61,13 @@ const HeaderRight = styled('div')(({ theme }) => ({
 | 
			
		||||
const StyledInvoiceGrid = styled('div')(({ theme }) => ({
 | 
			
		||||
    display: 'grid',
 | 
			
		||||
    gridTemplateColumns: '45% 20% 15% 20%',
 | 
			
		||||
 | 
			
		||||
    padding: theme.spacing(0, 2, 3),
 | 
			
		||||
    padding: theme.spacing(0, 2, 2),
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
const HeaderCell = styled(Typography)(({ theme }) => ({
 | 
			
		||||
    fontSize: theme.typography.body2.fontSize,
 | 
			
		||||
    fontWeight: theme.typography.fontWeightMedium,
 | 
			
		||||
    color: theme.palette.text.secondary,
 | 
			
		||||
    padding: theme.spacing(0, 0, 1),
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
const TableBody: FC<{ children: ReactNode; title?: string }> = ({
 | 
			
		||||
@ -75,24 +77,27 @@ const TableBody: FC<{ children: ReactNode; title?: string }> = ({
 | 
			
		||||
    return <StyledSubgrid withBackground={!!title}>{children}</StyledSubgrid>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const StyledSectionTitle = styled(Typography)(({ theme }) => ({
 | 
			
		||||
    gridColumn: '1 / -1',
 | 
			
		||||
    padding: theme.spacing(2, 0, 1),
 | 
			
		||||
    fontWeight: theme.fontWeight.bold,
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
const StyledTableRow = styled('div')(({ theme }) => ({
 | 
			
		||||
    display: 'grid',
 | 
			
		||||
    gridColumn: '1 / -1',
 | 
			
		||||
    gridTemplateColumns: 'subgrid',
 | 
			
		||||
    padding: theme.spacing(1, 0),
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
const StyledTableTitle = styled('span')(({ theme }) => ({
 | 
			
		||||
    color: theme.palette.text.primary,
 | 
			
		||||
    fontSize: theme.typography.body1.fontSize,
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
const StyledDivider = styled(Divider)(({ theme }) => ({
 | 
			
		||||
    gridColumn: '1 / -1',
 | 
			
		||||
    margin: theme.spacing(0, 2),
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
const CardActions = styled('div')(({ theme }) => ({
 | 
			
		||||
    display: 'flex',
 | 
			
		||||
    justifyContent: 'flex-end',
 | 
			
		||||
    gap: theme.spacing(1),
 | 
			
		||||
    padding: theme.spacing(1.5, 2, 2),
 | 
			
		||||
    padding: theme.spacing(0, 2, 2),
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
type BillingInvoiceProps = DetailedInvoicesSchemaInvoicesItem &
 | 
			
		||||
@ -118,7 +123,11 @@ export const BillingInvoice = ({
 | 
			
		||||
          })
 | 
			
		||||
        : '';
 | 
			
		||||
 | 
			
		||||
    const hasLimitsColumn = usageLines.some((line) => line.limit);
 | 
			
		||||
    const isCurrentYear =
 | 
			
		||||
        new Date(invoiceDate).getFullYear() === new Date().getFullYear();
 | 
			
		||||
    const year = isCurrentYear
 | 
			
		||||
        ? `, ${new Date(invoiceDate).getFullYear()}`
 | 
			
		||||
        : '';
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <StyledAccordion defaultExpanded={Boolean(defaultExpanded)}>
 | 
			
		||||
@ -133,6 +142,7 @@ export const BillingInvoice = ({
 | 
			
		||||
                        sx={{ fontWeight: 700 }}
 | 
			
		||||
                    >
 | 
			
		||||
                        {formattedTitle}
 | 
			
		||||
                        {year}
 | 
			
		||||
                    </Typography>
 | 
			
		||||
                </HeaderLeft>
 | 
			
		||||
                <HeaderRight>
 | 
			
		||||
@ -155,35 +165,35 @@ export const BillingInvoice = ({
 | 
			
		||||
            </HeaderRoot>
 | 
			
		||||
            <AccordionDetails
 | 
			
		||||
                sx={(theme) => ({
 | 
			
		||||
                    padding: theme.spacing(3, 0, 0),
 | 
			
		||||
                    padding: theme.spacing(2, 0, 0),
 | 
			
		||||
                    borderTop: `1px solid ${theme.palette.divider}`,
 | 
			
		||||
                })}
 | 
			
		||||
            >
 | 
			
		||||
                <StyledInvoiceGrid>
 | 
			
		||||
                    <StyledSubgrid>
 | 
			
		||||
                        <HeaderCell>Description</HeaderCell>
 | 
			
		||||
                        <HeaderCell />
 | 
			
		||||
                        <HeaderCell>Quantity</HeaderCell>
 | 
			
		||||
                        <HeaderCell>
 | 
			
		||||
                            <StyledAmountCell>Amount</StyledAmountCell>
 | 
			
		||||
                        </HeaderCell>
 | 
			
		||||
                    </StyledSubgrid>
 | 
			
		||||
                    {mainLines.map((line) => (
 | 
			
		||||
                        <TableBody key={line.description}>
 | 
			
		||||
                    <TableBody>
 | 
			
		||||
                        <StyledTableRow>
 | 
			
		||||
                            <HeaderCell>Description</HeaderCell>
 | 
			
		||||
                            <HeaderCell />
 | 
			
		||||
                            <HeaderCell>Quantity</HeaderCell>
 | 
			
		||||
                            <HeaderCell>
 | 
			
		||||
                                <StyledAmountCell>Amount</StyledAmountCell>
 | 
			
		||||
                            </HeaderCell>
 | 
			
		||||
                        </StyledTableRow>
 | 
			
		||||
                        {mainLines.map((line) => (
 | 
			
		||||
                            <StyledTableRow key={line.description}>
 | 
			
		||||
                                <BillingInvoiceMainRow {...line} />
 | 
			
		||||
                            </StyledTableRow>
 | 
			
		||||
                        </TableBody>
 | 
			
		||||
                    ))}
 | 
			
		||||
                        ))}
 | 
			
		||||
                    </TableBody>
 | 
			
		||||
                    {usageLines.length ? (
 | 
			
		||||
                        <TableBody key='usage' title='Usage'>
 | 
			
		||||
                            <StyledTableRow>
 | 
			
		||||
                                <HeaderCell>Usage {monthText}</HeaderCell>
 | 
			
		||||
                                {hasLimitsColumn ? (
 | 
			
		||||
                                    <HeaderCell>Included</HeaderCell>
 | 
			
		||||
                                ) : (
 | 
			
		||||
                                    <HeaderCell />
 | 
			
		||||
                                )}
 | 
			
		||||
                                <HeaderCell>
 | 
			
		||||
                                    <StyledTableTitle>
 | 
			
		||||
                                        Usage – {monthText}
 | 
			
		||||
                                    </StyledTableTitle>
 | 
			
		||||
                                </HeaderCell>
 | 
			
		||||
                                <HeaderCell>Included</HeaderCell>
 | 
			
		||||
                                <HeaderCell>Overages</HeaderCell>
 | 
			
		||||
                                <HeaderCell>
 | 
			
		||||
                                    <StyledAmountCell>Amount</StyledAmountCell>
 | 
			
		||||
@ -191,44 +201,45 @@ export const BillingInvoice = ({
 | 
			
		||||
                            </StyledTableRow>
 | 
			
		||||
                            {usageLines.map((line) => (
 | 
			
		||||
                                <StyledTableRow key={line.description}>
 | 
			
		||||
                                    <BillingInvoiceUsageRow
 | 
			
		||||
                                        {...line}
 | 
			
		||||
                                        showLimits={hasLimitsColumn}
 | 
			
		||||
                                    />
 | 
			
		||||
                                    <BillingInvoiceUsageRow {...line} />
 | 
			
		||||
                                </StyledTableRow>
 | 
			
		||||
                            ))}
 | 
			
		||||
                        </TableBody>
 | 
			
		||||
                    ) : null}
 | 
			
		||||
                    ) : (
 | 
			
		||||
                        <StyledDivider />
 | 
			
		||||
                    )}
 | 
			
		||||
 | 
			
		||||
                    <BillingInvoiceFooter
 | 
			
		||||
                        totalAmount={totalAmount}
 | 
			
		||||
                        currency={currency}
 | 
			
		||||
                    />
 | 
			
		||||
                </StyledInvoiceGrid>
 | 
			
		||||
                <CardActions>
 | 
			
		||||
                    {invoiceURL ? (
 | 
			
		||||
                        <Button
 | 
			
		||||
                            variant='outlined'
 | 
			
		||||
                            href={invoiceURL}
 | 
			
		||||
                            target='_blank'
 | 
			
		||||
                            rel='noreferrer'
 | 
			
		||||
                            startIcon={<ReceiptLongOutlinedIcon />}
 | 
			
		||||
                        >
 | 
			
		||||
                            View invoice
 | 
			
		||||
                        </Button>
 | 
			
		||||
                    ) : null}
 | 
			
		||||
                    {invoicePDF ? (
 | 
			
		||||
                        <Button
 | 
			
		||||
                            variant='outlined'
 | 
			
		||||
                            href={invoicePDF}
 | 
			
		||||
                            target='_blank'
 | 
			
		||||
                            rel='noreferrer'
 | 
			
		||||
                            startIcon={<DownloadOutlinedIcon />}
 | 
			
		||||
                        >
 | 
			
		||||
                            Download PDF
 | 
			
		||||
                        </Button>
 | 
			
		||||
                    ) : null}
 | 
			
		||||
                </CardActions>
 | 
			
		||||
                {invoiceURL || invoicePDF ? (
 | 
			
		||||
                    <CardActions>
 | 
			
		||||
                        {invoiceURL ? (
 | 
			
		||||
                            <Button
 | 
			
		||||
                                variant='outlined'
 | 
			
		||||
                                href={invoiceURL}
 | 
			
		||||
                                target='_blank'
 | 
			
		||||
                                rel='noreferrer'
 | 
			
		||||
                                startIcon={<ReceiptLongOutlinedIcon />}
 | 
			
		||||
                            >
 | 
			
		||||
                                View invoice
 | 
			
		||||
                            </Button>
 | 
			
		||||
                        ) : null}
 | 
			
		||||
                        {invoicePDF ? (
 | 
			
		||||
                            <Button
 | 
			
		||||
                                variant='outlined'
 | 
			
		||||
                                href={invoicePDF}
 | 
			
		||||
                                target='_blank'
 | 
			
		||||
                                rel='noreferrer'
 | 
			
		||||
                                startIcon={<DownloadOutlinedIcon />}
 | 
			
		||||
                            >
 | 
			
		||||
                                Download PDF
 | 
			
		||||
                            </Button>
 | 
			
		||||
                        ) : null}
 | 
			
		||||
                    </CardActions>
 | 
			
		||||
                ) : null}
 | 
			
		||||
            </AccordionDetails>
 | 
			
		||||
        </StyledAccordion>
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
@ -6,6 +6,7 @@ import { StyledAmountCell, StyledSubgrid } from '../BillingInvoice.styles.tsx';
 | 
			
		||||
const StyledTableFooter = styled(StyledSubgrid)(({ theme }) => ({
 | 
			
		||||
    gridColumn: '3 / -1',
 | 
			
		||||
    padding: theme.spacing(1, 1, 0, 0),
 | 
			
		||||
    gap: 0,
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
const StyledTableFooterRow = styled('div')<{ last?: boolean }>(
 | 
			
		||||
@ -17,13 +18,14 @@ const StyledTableFooterRow = styled('div')<{ last?: boolean }>(
 | 
			
		||||
        ...(last
 | 
			
		||||
            ? { fontWeight: theme.typography.fontWeightBold }
 | 
			
		||||
            : { borderBottom: `1px solid ${theme.palette.divider}` }),
 | 
			
		||||
        padding: theme.spacing(1.25, 0),
 | 
			
		||||
    }),
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
const StyledTableFooterCell = styled('div', {
 | 
			
		||||
    shouldForwardProp: (prop) => prop !== 'colSpan',
 | 
			
		||||
})<{ colSpan?: number }>(({ theme, colSpan }) => ({
 | 
			
		||||
    padding: theme.spacing(1, 0, 1, 0.5),
 | 
			
		||||
    padding: theme.spacing(0, 0, 0, 0.5),
 | 
			
		||||
    ...(colSpan ? { gridColumn: `span ${colSpan}` } : {}),
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -2,17 +2,14 @@ import { formatLargeNumbers } from 'component/impact-metrics/metricsFormatters.t
 | 
			
		||||
import { formatCurrency } from '../formatCurrency.ts';
 | 
			
		||||
import { styled, Typography } from '@mui/material';
 | 
			
		||||
import type { DetailedInvoicesLineSchema } from 'openapi';
 | 
			
		||||
import { StyledAmountCell } from '../BillingInvoice.styles.tsx';
 | 
			
		||||
 | 
			
		||||
const StyledDescriptionCell = styled('div')(({ theme }) => ({
 | 
			
		||||
    display: 'flex',
 | 
			
		||||
    flexDirection: 'column',
 | 
			
		||||
    gap: theme.spacing(0.5),
 | 
			
		||||
}));
 | 
			
		||||
import {
 | 
			
		||||
    StyledAmountCell,
 | 
			
		||||
    StyledDescriptionCell,
 | 
			
		||||
} from '../BillingInvoice.styles.tsx';
 | 
			
		||||
 | 
			
		||||
const StyledSubText = styled(Typography)(({ theme }) => ({
 | 
			
		||||
    color: theme.palette.text.secondary,
 | 
			
		||||
    fontSize: theme.typography.body2.fontSize,
 | 
			
		||||
    fontSize: theme.typography.caption.fontSize,
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
export const BillingInvoiceMainRow = ({
 | 
			
		||||
@ -38,7 +35,7 @@ export const BillingInvoiceMainRow = ({
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <>
 | 
			
		||||
            <StyledDescriptionCell>
 | 
			
		||||
            <StyledDescriptionCell expand>
 | 
			
		||||
                <div>{description}</div>
 | 
			
		||||
                {formattedStart || formattedEnd ? (
 | 
			
		||||
                    <StyledSubText>
 | 
			
		||||
@ -46,7 +43,6 @@ export const BillingInvoiceMainRow = ({
 | 
			
		||||
                    </StyledSubText>
 | 
			
		||||
                ) : null}
 | 
			
		||||
            </StyledDescriptionCell>
 | 
			
		||||
            <div />
 | 
			
		||||
            <div>{quantity ? formatLargeNumbers(quantity) : '–'}</div>
 | 
			
		||||
            <StyledAmountCell>
 | 
			
		||||
                {formatCurrency(totalAmount || 0, currency)}
 | 
			
		||||
 | 
			
		||||
@ -3,18 +3,18 @@ import { formatCurrency } from '../formatCurrency.ts';
 | 
			
		||||
import { ConsumptionIndicator } from '../ConsumptionIndicator/ConsumptionIndicator.tsx';
 | 
			
		||||
import { styled } from '@mui/material';
 | 
			
		||||
import type { DetailedInvoicesLineSchema } from 'openapi';
 | 
			
		||||
import { StyledAmountCell } from '../BillingInvoice.styles.tsx';
 | 
			
		||||
import {
 | 
			
		||||
    StyledAmountCell,
 | 
			
		||||
    StyledDescriptionCell,
 | 
			
		||||
} from '../BillingInvoice.styles.tsx';
 | 
			
		||||
 | 
			
		||||
const StyledCellWithIndicator = styled('div')(({ theme }) => ({
 | 
			
		||||
    display: 'flex',
 | 
			
		||||
    alignItems: 'center',
 | 
			
		||||
    gap: theme.spacing(1),
 | 
			
		||||
    maxHeight: theme.spacing(2.5),
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
type BillingInvoiceRowProps = DetailedInvoicesLineSchema & {
 | 
			
		||||
    showLimits: boolean;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const BillingInvoiceUsageRow = ({
 | 
			
		||||
    quantity,
 | 
			
		||||
    consumption,
 | 
			
		||||
@ -22,8 +22,7 @@ export const BillingInvoiceUsageRow = ({
 | 
			
		||||
    description,
 | 
			
		||||
    currency,
 | 
			
		||||
    totalAmount,
 | 
			
		||||
    showLimits,
 | 
			
		||||
}: BillingInvoiceRowProps) => {
 | 
			
		||||
}: DetailedInvoicesLineSchema) => {
 | 
			
		||||
    const percentage =
 | 
			
		||||
        limit && limit > 0
 | 
			
		||||
            ? Math.min(100, Math.round(((consumption || 0) / limit) * 100))
 | 
			
		||||
@ -31,23 +30,19 @@ export const BillingInvoiceUsageRow = ({
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <>
 | 
			
		||||
            <div>{description}</div>
 | 
			
		||||
            {showLimits ? (
 | 
			
		||||
                <StyledCellWithIndicator>
 | 
			
		||||
                    <ConsumptionIndicator percentage={percentage || 0} />
 | 
			
		||||
                    <div>
 | 
			
		||||
                        {consumption !== undefined && limit !== undefined
 | 
			
		||||
                            ? `${formatLargeNumbers(consumption)}/${formatLargeNumbers(limit)}`
 | 
			
		||||
                            : consumption !== undefined
 | 
			
		||||
                              ? formatLargeNumbers(consumption)
 | 
			
		||||
                              : limit !== undefined
 | 
			
		||||
                                ? formatLargeNumbers(limit)
 | 
			
		||||
                                : '–'}
 | 
			
		||||
                    </div>
 | 
			
		||||
                </StyledCellWithIndicator>
 | 
			
		||||
            ) : (
 | 
			
		||||
                <StyledCellWithIndicator />
 | 
			
		||||
            )}
 | 
			
		||||
            <StyledDescriptionCell>{description}</StyledDescriptionCell>
 | 
			
		||||
            <StyledCellWithIndicator>
 | 
			
		||||
                <ConsumptionIndicator percentage={percentage || 0} />
 | 
			
		||||
                <div>
 | 
			
		||||
                    {consumption !== undefined && limit !== undefined
 | 
			
		||||
                        ? `${formatLargeNumbers(consumption)}/${formatLargeNumbers(limit)}`
 | 
			
		||||
                        : consumption !== undefined
 | 
			
		||||
                          ? formatLargeNumbers(consumption)
 | 
			
		||||
                          : limit !== undefined
 | 
			
		||||
                            ? formatLargeNumbers(limit)
 | 
			
		||||
                            : '–'}
 | 
			
		||||
                </div>
 | 
			
		||||
            </StyledCellWithIndicator>
 | 
			
		||||
            <div>{quantity ? formatLargeNumbers(quantity) : '–'}</div>
 | 
			
		||||
            <StyledAmountCell>
 | 
			
		||||
                {formatCurrency(totalAmount || 0, currency)}
 | 
			
		||||
 | 
			
		||||
@ -1,217 +0,0 @@
 | 
			
		||||
# Page & container
 | 
			
		||||
 | 
			
		||||
* **Canvas/background:** very light gray page (#EAEAED approx).
 | 
			
		||||
* **Main card:** centered, white background, full-width minus page padding, rounded corners (~12–16px), subtle shadow (1–2px blur, low opacity).
 | 
			
		||||
* **Outer padding:** ~24px around the card; ~16px vertical spacing between major blocks inside.
 | 
			
		||||
 | 
			
		||||
# Header row (card top bar)
 | 
			
		||||
 | 
			
		||||
* **Left:** page section title: **“October 15th”**.
 | 
			
		||||
 | 
			
		||||
  * Font: sans-serif UI (think Inter/ system).
 | 
			
		||||
  * Size/weight: ~20–22px, semibold.
 | 
			
		||||
  * Color: near-black (#202021).
 | 
			
		||||
* **Right:** total and controls, aligned on baseline with the title:
 | 
			
		||||
 | 
			
		||||
  * **Chip:** “Estimated”
 | 
			
		||||
 | 
			
		||||
    * Pill shape (full rounding ~9999px).
 | 
			
		||||
    * Height ~24px; horizontal padding ~10–12px.
 | 
			
		||||
    * Background: warm orange (#AD6321 to #BC8042 range). (warning)
 | 
			
		||||
    * Text: small/uppercase or all-caps feel (~12px, medium), light cream text (#FDF4E6).
 | 
			
		||||
  * **Grand total (top):** **$8,500** to the far right.
 | 
			
		||||
 | 
			
		||||
    * Size/weight: ~16px, bold/semibold; color dark gray (#4A4A4A).
 | 
			
		||||
  * **Caret icon:** small chevron pointing **up** (expanded state) near far/right edge with hit target ~24px square.
 | 
			
		||||
 | 
			
		||||
A thin divider isn’t drawn; the white card continues into the line-item table.
 | 
			
		||||
 | 
			
		||||
# Line-item table header
 | 
			
		||||
 | 
			
		||||
Four columns; left-aligned except Amount (right):
 | 
			
		||||
 | 
			
		||||
1. **Description** (left)
 | 
			
		||||
2. **Included**
 | 
			
		||||
3. **Quantity**
 | 
			
		||||
4. **Amount** (right)
 | 
			
		||||
 | 
			
		||||
* Header labels: small uppercase/medium weight (~12px), muted gray (#6E6E70).
 | 
			
		||||
* Column grid (desktop):
 | 
			
		||||
 | 
			
		||||
  * Description: ~40–45%
 | 
			
		||||
  * Included: ~20%
 | 
			
		||||
  * Quantity: ~15%
 | 
			
		||||
  * Amount: ~25% (right-aligned)
 | 
			
		||||
* Row height baseline: ~56–64px for the item row (before usage block).
 | 
			
		||||
 | 
			
		||||
# Line item: “Unleash PAYG Seat”
 | 
			
		||||
 | 
			
		||||
* **Description column:**
 | 
			
		||||
 | 
			
		||||
  * Primary: “**Unleash PAYG Seat**” (~14–15px, medium, #909090).
 | 
			
		||||
  * Secondary: “Sep 15 – Oct 15” (~12–13px, regular, #B6B6B7).
 | 
			
		||||
* **Included column:** empty for this item (the usage breakdown below covers entitlements).
 | 
			
		||||
* **Quantity:** **41** (center/left aligned to column).
 | 
			
		||||
* **Amount (right):** **$3,076** (right-aligned, ~14–15px, medium).
 | 
			
		||||
 | 
			
		||||
# Usage block (nested section)
 | 
			
		||||
 | 
			
		||||
A light panel under the line item spans full width of the card content.
 | 
			
		||||
 | 
			
		||||
* **Container:**
 | 
			
		||||
 | 
			
		||||
  * Background: light neutral/blue-gray (#F7F7FA).
 | 
			
		||||
  * Corner radius: matches card (~12px).
 | 
			
		||||
  * Inner padding: ~16–20px.
 | 
			
		||||
  * Top margin from the row above: ~12–16px.
 | 
			
		||||
* **Section title:** “**Usage September**”
 | 
			
		||||
 | 
			
		||||
  * Small, semibold (~13–14px), dark (#202021).
 | 
			
		||||
 | 
			
		||||
Each usage metric is a **row with 4 columns** mirroring the header: **Label | Included | Actual | Amount**.
 | 
			
		||||
 | 
			
		||||
### Metric rows (in order)
 | 
			
		||||
 | 
			
		||||
1. **Frontend traffic**
 | 
			
		||||
 | 
			
		||||
   * **Included:** `10/10M requests`
 | 
			
		||||
 | 
			
		||||
     * Preceded by a small **circular progress ring** (see component spec below) in **accent purple**.
 | 
			
		||||
   * **Actual:** `1,085M requests`
 | 
			
		||||
   * **Amount:** `$5,425`
 | 
			
		||||
 | 
			
		||||
2. **Service connections**
 | 
			
		||||
 | 
			
		||||
   * **Included:** `7/7 connections` (ring shows full/complete)
 | 
			
		||||
   * **Actual:** `20 connections`
 | 
			
		||||
   * **Amount:** `$0`
 | 
			
		||||
 | 
			
		||||
3. **Release templates**
 | 
			
		||||
 | 
			
		||||
   * **Included:** `3/5 templates` (ring partially filled)
 | 
			
		||||
   * **Actual:** *(empty / em dash not shown)*
 | 
			
		||||
   * **Amount:** `$0`
 | 
			
		||||
 | 
			
		||||
4. **Edge Frontend Traffic**
 | 
			
		||||
 | 
			
		||||
   * **Included:** `2/10M requests` (ring small partial)
 | 
			
		||||
   * **Actual:** *(empty)*
 | 
			
		||||
   * **Amount:** `$0`
 | 
			
		||||
 | 
			
		||||
5. **Edge Service Connections**
 | 
			
		||||
 | 
			
		||||
   * **Included:** `5/5 connections` (ring full)
 | 
			
		||||
   * **Actual:** *(empty)*
 | 
			
		||||
   * **Amount:** `$0`
 | 
			
		||||
 | 
			
		||||
* **Typography/colors inside usage rows:**
 | 
			
		||||
 | 
			
		||||
  * Labels (left): ~14–15px, dark (#202021).
 | 
			
		||||
  * Included & Actual values: ~14px, regular, dark (#202021).
 | 
			
		||||
  * Amounts on rows with $0: muted gray (~#818182) OR same dark but visual weight is from the value; non-zero amount ($5,425) uses dark color.
 | 
			
		||||
* **Row spacing:** ~12px vertical space between rows; no visible row borders.
 | 
			
		||||
 | 
			
		||||
### Circular progress ring (Included column)
 | 
			
		||||
 | 
			
		||||
* **Size:** ~18–20px outer diameter.
 | 
			
		||||
* **Stroke:** ~2–3px width.
 | 
			
		||||
* **Background track:** very light gray/lavender (#EAEAED to #F0F0F4).
 | 
			
		||||
* **Progress arc:** accent **purple/indigo** (appears around #6A5AE0 to #7B6EF6; treat as a single brand accent).
 | 
			
		||||
* **States:**
 | 
			
		||||
 | 
			
		||||
  * **Complete (7/7, 5/5, 10/10M):** arc forms a full ring; consider adding a subtle filled dot/gradient start (optional).
 | 
			
		||||
  * **Partial (3/5, 2/10M):** arc angle proportional to current / max.
 | 
			
		||||
  * **Empty:** (not shown here) would be track only.
 | 
			
		||||
* **Alignment:** the ring sits before the Included text with ~8px gap; ring and text are vertically centered.
 | 
			
		||||
 | 
			
		||||
# Subtotals & totals (footer of card)
 | 
			
		||||
 | 
			
		||||
* **Block container:** right side summary inside the same light usage panel’s parent (i.e., still white card).
 | 
			
		||||
* **Rows:**
 | 
			
		||||
 | 
			
		||||
  1. **Sub total** — value **$8,500**
 | 
			
		||||
 | 
			
		||||
     * Label: small gray (#9B9CA0 / #D5D5D5 seen on screen due to anti-aliasing), ~13–14px.
 | 
			
		||||
     * Value: right-aligned; medium weight; dark (#202021).
 | 
			
		||||
     * Divider line below (hairline, #EAEAED).
 | 
			
		||||
  2. **Customer tax is exempt**
 | 
			
		||||
 | 
			
		||||
     * Single line, gray text (~13px), no value column, sits aligned to the label column, no icon.
 | 
			
		||||
  3. **Total** — value **$8,500**
 | 
			
		||||
 | 
			
		||||
     * Label: small label “Total”.
 | 
			
		||||
     * Value: bold (~16px), right-aligned, dark (#202021).
 | 
			
		||||
* **Column behavior:** labels left, amounts right; the value column aligns with the table’s Amount column.
 | 
			
		||||
 | 
			
		||||
# Spacing & rhythm (approx)
 | 
			
		||||
 | 
			
		||||
* Title to table header: 16px.
 | 
			
		||||
* Table header to first row: 8–12px.
 | 
			
		||||
* Row vertical padding: 12–16px.
 | 
			
		||||
* Line item to usage panel: 12–16px.
 | 
			
		||||
* Inside usage panel: 14–16px around; 10–12px between rows.
 | 
			
		||||
* Usage panel to subtotal block: ~12–16px.
 | 
			
		||||
* Subtotal rows spacing: 8–12px; divider thickness 1px.
 | 
			
		||||
 | 
			
		||||
# Colors (usable palette approximations)
 | 
			
		||||
 | 
			
		||||
* **Text / primary:** #202021
 | 
			
		||||
* **Text / secondary:** #6E6E70, #818182, #909090, #B6B6B7
 | 
			
		||||
* **Background / page:** #EAEAED
 | 
			
		||||
* **Background / card:** #FFFFFF
 | 
			
		||||
* **Background / nested panel:** #F7F7FA
 | 
			
		||||
* **Border / hairline:** #EAEAED
 | 
			
		||||
* **Accent (progress rings):** purple/indigo ~#6A5AE0–#7B6EF6 (pick 1)
 | 
			
		||||
* **Chip (Estimated):** bg #AD6321–#BC8042 (warning), text #FDF4E6
 | 
			
		||||
 | 
			
		||||
# Alignment & responsiveness
 | 
			
		||||
 | 
			
		||||
* **Four-column grid** collapses on small screens. Suggested:
 | 
			
		||||
 | 
			
		||||
  * Tablet: Description 50%, Included 25%, Quantity 10–15%, Amount 15–20%.
 | 
			
		||||
  * Mobile: stack as: Description (with quantity & amount in a two-column subrow), then the usage block full-width below.
 | 
			
		||||
* **Amount column** is right-aligned everywhere.
 | 
			
		||||
* **Numbers** use thousands separators as shown: `1,085M`, currency with **$** and no decimals.
 | 
			
		||||
* **Date** uses en dash (–) between start and end.
 | 
			
		||||
 | 
			
		||||
# Interactions & states
 | 
			
		||||
 | 
			
		||||
* **Caret (collapse/expand):** toggles visibility of the line-item content (including the usage block and subtotal area). Rotates 180° to point down when collapsed.
 | 
			
		||||
* **Estimated chip:** non-interactive indicator; cursor default.
 | 
			
		||||
* **Usage rows:** non-interactive display; the rings are purely indicative (no hover shown).
 | 
			
		||||
* **Row hover (optional):** subtle background tint (#F9F9FB) or keep static.
 | 
			
		||||
 | 
			
		||||
# Accessibility
 | 
			
		||||
 | 
			
		||||
* **Color contrast:** ensure text vs. light panel meets WCAG AA (raise text color if needed).
 | 
			
		||||
* **Tab order:** title → chip → caret → table headers → row cells → usage rows → summary.
 | 
			
		||||
* **ARIA:**
 | 
			
		||||
 | 
			
		||||
  * Caret button: `aria-expanded` true/false.
 | 
			
		||||
  * Progress rings: use `role="img"` with `aria-label` like “3 of 5 templates”.
 | 
			
		||||
 | 
			
		||||
# Data in this example (verbatim)
 | 
			
		||||
 | 
			
		||||
* **Header:** October 15th · Chip: Estimated · Top-right total: $8,500.
 | 
			
		||||
* **Columns:** Description | Included | Quantity | Amount.
 | 
			
		||||
* **Row:** Unleash PAYG Seat · Sep 15 – Oct 15 · Quantity 41 · Amount $3,076.
 | 
			
		||||
* **Usage September** (rows):
 | 
			
		||||
 | 
			
		||||
  * Frontend traffic · 10/10M requests · 1,085M requests · $5,425
 | 
			
		||||
  * Service connections · 7/7 connections · 20 connections · $0
 | 
			
		||||
  * Release templates · 3/5 templates · — · $0
 | 
			
		||||
  * Edge Frontend Traffic · 2/10M requests · — · $0
 | 
			
		||||
  * Edge Service Connections · 5/5 connections · — · $0
 | 
			
		||||
* **Summary:** Sub total $8,500 · “Customer tax is exempt” · Total $8,500.
 | 
			
		||||
 | 
			
		||||
# Components to build
 | 
			
		||||
 | 
			
		||||
* **Card** (rounded, shadow).
 | 
			
		||||
* **Header bar** (title, chip, total, caret).
 | 
			
		||||
* **Four-column table** (responsive grid).
 | 
			
		||||
* **Usage panel** (light background, rounded).
 | 
			
		||||
* **Usage row** with **ProgressRing** + 3 texts + right amount.
 | 
			
		||||
* **Summary list** (two-column label/value with divider).
 | 
			
		||||
* **Pill/Chip** component for “Estimated”.
 | 
			
		||||
 | 
			
		||||
If you want, I can translate this into a component tree and props next.
 | 
			
		||||
@ -7,7 +7,7 @@ import { TablePlaceholder } from 'component/common/Table';
 | 
			
		||||
const StyledContainer = styled(Box)(({ theme }) => ({
 | 
			
		||||
    display: 'flex',
 | 
			
		||||
    flexDirection: 'column',
 | 
			
		||||
    gap: theme.spacing(3),
 | 
			
		||||
    gap: theme.spacing(2),
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
export const BillingInvoices: FC = () => {
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user