fix: align dynamic pricing style with log details dialog sections
This commit is contained in:
+80
-35
@@ -59,6 +59,11 @@ type DynamicPricingBreakdownProps = {
|
||||
* call they are inspecting. Defaults to false (show all configured prices).
|
||||
*/
|
||||
hideCacheColumns?: boolean
|
||||
/**
|
||||
* Dense rendering for the usage-log details dialog: drops the colored
|
||||
* icon header and uses the dialog's small text sizes. Defaults to false.
|
||||
*/
|
||||
compact?: boolean
|
||||
}
|
||||
|
||||
const VAR_LABELS: Record<string, string> = {
|
||||
@@ -152,6 +157,7 @@ export function DynamicPricingBreakdown({
|
||||
billingExpr,
|
||||
matchedTierLabel,
|
||||
hideCacheColumns = false,
|
||||
compact = false,
|
||||
}: DynamicPricingBreakdownProps) {
|
||||
const { t } = useTranslation()
|
||||
const expr = billingExpr || ''
|
||||
@@ -190,20 +196,22 @@ export function DynamicPricingBreakdown({
|
||||
|
||||
if (!hasTiers) {
|
||||
return (
|
||||
<section className='min-w-0 py-4'>
|
||||
<div className='mb-3 flex items-center gap-2'>
|
||||
<span className='inline-flex size-6 items-center justify-center rounded-lg bg-amber-100 text-amber-700 shadow-sm dark:bg-amber-500/20 dark:text-amber-300'>
|
||||
<TagIcon className='size-3.5' />
|
||||
</span>
|
||||
<div>
|
||||
<div className='text-foreground text-base font-medium'>
|
||||
{t('Special billing expression')}
|
||||
</div>
|
||||
<div className='text-muted-foreground text-xs'>
|
||||
{t('Unable to parse structured pricing')}
|
||||
<section className={cn('min-w-0', !compact && 'py-4')}>
|
||||
{!compact && (
|
||||
<div className='mb-3 flex items-center gap-2'>
|
||||
<span className='inline-flex size-6 items-center justify-center rounded-lg bg-amber-100 text-amber-700 shadow-sm dark:bg-amber-500/20 dark:text-amber-300'>
|
||||
<TagIcon className='size-3.5' />
|
||||
</span>
|
||||
<div>
|
||||
<div className='text-foreground text-base font-medium'>
|
||||
{t('Special billing expression')}
|
||||
</div>
|
||||
<div className='text-muted-foreground text-xs'>
|
||||
{t('Unable to parse structured pricing')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className='text-muted-foreground mb-1 text-[10px] font-medium tracking-wider uppercase'>
|
||||
{t('Raw expression')}
|
||||
</div>
|
||||
@@ -223,24 +231,32 @@ export function DynamicPricingBreakdown({
|
||||
})
|
||||
|
||||
return (
|
||||
<section className='min-w-0 py-3 sm:py-4'>
|
||||
<div className='mb-3 flex items-start gap-2 sm:mb-4'>
|
||||
<span className='mt-0.5 inline-flex size-6 items-center justify-center rounded-lg bg-amber-100 text-amber-700 shadow-sm dark:bg-amber-500/20 dark:text-amber-300'>
|
||||
<TagIcon className='size-3.5' />
|
||||
</span>
|
||||
<div>
|
||||
<div className='text-foreground text-base font-medium'>
|
||||
{t('Dynamic Pricing')}
|
||||
</div>
|
||||
<div className='text-muted-foreground text-xs'>
|
||||
{t('Prices vary by usage tier and request conditions')}
|
||||
<section className={cn('min-w-0', !compact && 'py-3 sm:py-4')}>
|
||||
{!compact && (
|
||||
<div className='mb-3 flex items-start gap-2 sm:mb-4'>
|
||||
<span className='mt-0.5 inline-flex size-6 items-center justify-center rounded-lg bg-amber-100 text-amber-700 shadow-sm dark:bg-amber-500/20 dark:text-amber-300'>
|
||||
<TagIcon className='size-3.5' />
|
||||
</span>
|
||||
<div>
|
||||
<div className='text-foreground text-base font-medium'>
|
||||
{t('Dynamic Pricing')}
|
||||
</div>
|
||||
<div className='text-muted-foreground text-xs'>
|
||||
{t('Prices vary by usage tier and request conditions')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{hasTiers && (
|
||||
<div className='mb-3 sm:mb-4'>
|
||||
<div className='text-foreground mb-2 text-sm font-semibold'>
|
||||
<div className={cn(compact ? cn(hasRules && 'mb-2') : 'mb-3 sm:mb-4')}>
|
||||
<div
|
||||
className={
|
||||
compact
|
||||
? 'text-muted-foreground mb-1.5 text-xs font-medium'
|
||||
: 'text-foreground mb-2 text-sm font-semibold'
|
||||
}
|
||||
>
|
||||
{t('Tiered price table')}
|
||||
</div>
|
||||
<div className='space-y-1.5 sm:hidden'>
|
||||
@@ -289,7 +305,12 @@ export function DynamicPricingBreakdown({
|
||||
<div className='text-muted-foreground truncate text-[10px] font-medium tracking-wider uppercase'>
|
||||
{t(v.shortLabel)}
|
||||
</div>
|
||||
<div className='truncate font-mono text-sm font-semibold'>
|
||||
<div
|
||||
className={cn(
|
||||
'truncate font-mono',
|
||||
compact ? 'text-xs' : 'text-sm font-semibold'
|
||||
)}
|
||||
>
|
||||
{value > 0
|
||||
? `${symbol}${(value * rate).toFixed(4)}`
|
||||
: '-'}
|
||||
@@ -304,7 +325,11 @@ export function DynamicPricingBreakdown({
|
||||
</div>
|
||||
<StaticDataTable
|
||||
className='hidden rounded-none border-0 sm:block'
|
||||
tableClassName='text-sm'
|
||||
tableClassName={
|
||||
compact
|
||||
? '[&_td]:text-xs [&_td_*]:text-xs [&_th]:text-xs [&_th_*]:text-xs'
|
||||
: 'text-sm'
|
||||
}
|
||||
headerRowClassName='hover:bg-transparent'
|
||||
data={tiers}
|
||||
getRowKey={(_tier, index) => `tier-${index}`}
|
||||
@@ -321,8 +346,11 @@ export function DynamicPricingBreakdown({
|
||||
{
|
||||
id: 'tier',
|
||||
header: t('Tier'),
|
||||
className: 'text-muted-foreground py-2 font-medium',
|
||||
cellClassName: 'py-2.5 align-top',
|
||||
className: cn(
|
||||
'text-muted-foreground py-2 font-medium',
|
||||
compact && 'h-8'
|
||||
),
|
||||
cellClassName: cn('align-top', compact ? 'py-2' : 'py-2.5'),
|
||||
cell: (tier) => {
|
||||
const condSummary = formatConditionSummary(tier.conditions, t)
|
||||
const isMatched =
|
||||
@@ -359,14 +387,20 @@ export function DynamicPricingBreakdown({
|
||||
...visiblePriceFields.map((v, index) => ({
|
||||
id: v.field ?? `price-${index}`,
|
||||
header: t(v.shortLabel),
|
||||
className: 'text-muted-foreground py-2 text-right font-medium',
|
||||
cellClassName: 'py-2.5 text-right align-top font-mono',
|
||||
className: cn(
|
||||
'text-muted-foreground py-2 text-right font-medium',
|
||||
compact && 'h-8'
|
||||
),
|
||||
cellClassName: cn(
|
||||
'text-right align-top font-mono',
|
||||
compact ? 'py-2' : 'py-2.5'
|
||||
),
|
||||
cell: (tier: ParsedTier) => {
|
||||
const value = Number(
|
||||
tier[v.field as string as keyof ParsedTier] || 0
|
||||
)
|
||||
return value > 0 ? (
|
||||
<span className='font-semibold'>
|
||||
<span className={cn(!compact && 'font-semibold')}>
|
||||
{`${symbol}${(value * rate).toFixed(4)}`}
|
||||
</span>
|
||||
) : (
|
||||
@@ -381,7 +415,13 @@ export function DynamicPricingBreakdown({
|
||||
|
||||
{hasRules && (
|
||||
<div>
|
||||
<div className='text-foreground mb-2 text-sm font-semibold'>
|
||||
<div
|
||||
className={
|
||||
compact
|
||||
? 'text-muted-foreground mb-1.5 text-xs font-medium'
|
||||
: 'text-foreground mb-2 text-sm font-semibold'
|
||||
}
|
||||
>
|
||||
{t('Conditional multipliers')}
|
||||
</div>
|
||||
<ul className='space-y-1.5'>
|
||||
@@ -390,7 +430,12 @@ export function DynamicPricingBreakdown({
|
||||
key={`group-${gi}`}
|
||||
className='bg-muted/50 flex items-center justify-between gap-3 rounded-md px-3 py-2'
|
||||
>
|
||||
<span className='text-foreground text-sm break-all'>
|
||||
<span
|
||||
className={cn(
|
||||
'text-foreground break-all',
|
||||
compact ? 'text-xs' : 'text-sm'
|
||||
)}
|
||||
>
|
||||
{describeGroup(group, t)}
|
||||
</span>
|
||||
<Badge
|
||||
|
||||
@@ -966,13 +966,14 @@ export function DetailsDialog(props: DetailsDialogProps) {
|
||||
|
||||
{/* Tiered pricing breakdown (when billing_mode is tiered_expr) */}
|
||||
{isTieredBilling && other?.expr_b64 && (
|
||||
<div className='bg-muted/30 min-w-0 overflow-hidden rounded-md border px-3 max-sm:px-2'>
|
||||
<DetailSection label={t('Dynamic Pricing')}>
|
||||
<DynamicPricingBreakdown
|
||||
compact
|
||||
billingExpr={decodeBillingExprB64(other.expr_b64)}
|
||||
matchedTierLabel={other.matched_tier}
|
||||
hideCacheColumns={!hasAnyCacheTokens(other)}
|
||||
/>
|
||||
</div>
|
||||
</DetailSection>
|
||||
)}
|
||||
|
||||
{/* Admin billing mode indicator for non-consume */}
|
||||
|
||||
Reference in New Issue
Block a user