mirror of
https://github.com/QuantumNous/new-api.git
synced 2026-09-07 01:56:53 +00:00
refactor(model-pricing): split visual pricing editor modules
- extract pricing form primitives, snapshot helpers, and table column setup to keep the editor components smaller. - remove draft comparison UI now that switching models discards unsaved edits. - refine the model list with a fixed actions column and tighter mode and price summary display.
This commit is contained in:
+160
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
Copyright (C) 2023-2026 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { type ColumnDef } from '@tanstack/react-table'
|
||||
import { Pencil, Trash2 } from 'lucide-react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Checkbox } from '@/components/ui/checkbox'
|
||||
import { DataTableColumnHeader } from '@/components/data-table'
|
||||
import { StatusBadge } from '@/components/status-badge'
|
||||
import {
|
||||
getModeLabel,
|
||||
getModeVariant,
|
||||
getPriceDetail,
|
||||
getPriceSummary,
|
||||
type ModelRow,
|
||||
} from './model-pricing-snapshots'
|
||||
|
||||
const filterBySelectedValues = (
|
||||
rowValue: unknown,
|
||||
filterValue: unknown
|
||||
): boolean => {
|
||||
if (!Array.isArray(filterValue) || filterValue.length === 0) return true
|
||||
return filterValue.includes(String(rowValue))
|
||||
}
|
||||
|
||||
type BuildModelRatioColumnsOptions = {
|
||||
onDelete: (name: string) => void
|
||||
onEdit: (model: ModelRow) => void
|
||||
t: (key: string) => string
|
||||
}
|
||||
|
||||
export function buildModelRatioColumns({
|
||||
onDelete,
|
||||
onEdit,
|
||||
t,
|
||||
}: BuildModelRatioColumnsOptions): ColumnDef<ModelRow>[] {
|
||||
return [
|
||||
{
|
||||
id: 'select',
|
||||
header: ({ table }) => (
|
||||
<Checkbox
|
||||
checked={table.getIsAllPageRowsSelected()}
|
||||
indeterminate={table.getIsSomePageRowsSelected()}
|
||||
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
|
||||
aria-label={t('Select all')}
|
||||
className='translate-y-[2px]'
|
||||
/>
|
||||
),
|
||||
cell: ({ row }) => (
|
||||
<Checkbox
|
||||
checked={row.getIsSelected()}
|
||||
onCheckedChange={(value) => row.toggleSelected(!!value)}
|
||||
aria-label={t('Select row')}
|
||||
className='translate-y-[2px]'
|
||||
/>
|
||||
),
|
||||
enableSorting: false,
|
||||
enableHiding: false,
|
||||
meta: { label: t('Select') },
|
||||
},
|
||||
{
|
||||
accessorKey: 'name',
|
||||
header: ({ column }) => (
|
||||
<DataTableColumnHeader column={column} title={t('Model name')} />
|
||||
),
|
||||
cell: ({ row }) => (
|
||||
<div className='flex items-center gap-2 font-medium'>
|
||||
{row.getValue('name')}
|
||||
{row.original.billingMode === 'tiered_expr' && (
|
||||
<StatusBadge label={t('Tiered')} variant='info' copyable={false} />
|
||||
)}
|
||||
{row.original.hasConflict && (
|
||||
<StatusBadge
|
||||
label={t('Conflict')}
|
||||
variant='danger'
|
||||
copyable={false}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
enableHiding: false,
|
||||
},
|
||||
{
|
||||
accessorKey: 'billingMode',
|
||||
header: ({ column }) => (
|
||||
<DataTableColumnHeader column={column} title={t('Mode')} />
|
||||
),
|
||||
cell: ({ row }) => (
|
||||
<StatusBadge
|
||||
label={t(getModeLabel(row.original.billingMode))}
|
||||
variant={getModeVariant(row.original.billingMode)}
|
||||
copyable={false}
|
||||
showDot={false}
|
||||
className='px-0'
|
||||
/>
|
||||
),
|
||||
filterFn: (row, id, value) =>
|
||||
filterBySelectedValues(row.getValue(id), value),
|
||||
meta: { label: t('Mode') },
|
||||
},
|
||||
{
|
||||
id: 'priceSummary',
|
||||
header: ({ column }) => (
|
||||
<DataTableColumnHeader column={column} title={t('Price summary')} />
|
||||
),
|
||||
cell: ({ row }) => (
|
||||
<div className='flex min-w-[180px] flex-col gap-1'>
|
||||
<span className='font-medium'>
|
||||
{getPriceSummary(row.original, t)}
|
||||
</span>
|
||||
<span className='text-muted-foreground max-w-[320px] truncate text-xs'>
|
||||
{getPriceDetail(row.original, t)}
|
||||
</span>
|
||||
</div>
|
||||
),
|
||||
sortingFn: (rowA, rowB) =>
|
||||
getPriceSummary(rowA.original, t).localeCompare(
|
||||
getPriceSummary(rowB.original, t)
|
||||
),
|
||||
meta: { label: t('Price summary') },
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
cell: ({ row }) => (
|
||||
<div className='flex justify-end gap-2'>
|
||||
<Button
|
||||
variant='ghost'
|
||||
size='sm'
|
||||
onClick={() => onEdit(row.original)}
|
||||
>
|
||||
<Pencil />
|
||||
</Button>
|
||||
<Button
|
||||
variant='ghost'
|
||||
size='sm'
|
||||
onClick={() => onDelete(row.original.name)}
|
||||
>
|
||||
<Trash2 />
|
||||
</Button>
|
||||
</div>
|
||||
),
|
||||
enableHiding: false,
|
||||
},
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user