/* 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 . For commercial licensing, please contact support@quantumnous.com */ import { flexRender, type Row } from '@tanstack/react-table' import { useTranslation } from 'react-i18next' import { cn } from '@/lib/utils' import { GroupBadge } from '@/components/group-badge' import { CHANNEL_STATUS } from '../constants' import { isTagAggregateRow, parseGroupsList } from '../lib' import type { Channel } from '../types' import { ChannelRowActionsLayoutContext } from './channel-row-actions-context' /** * Bespoke channel card for the card view. Reuses every column's existing cell * renderer via `flexRender`, so the table's information and interactions are * preserved: row selection, provider/multi-key/IO.NET type badge, id, * name/remark + warning icons, status (with tooltips), groups, inline * priority/weight spinners, balance refresh, response/test times, tag * expand-collapse, and the per-row (or per-tag) actions menu. */ export function ChannelCard({ row }: { row: Row }) { const { t } = useTranslation() const isTagRow = isTagAggregateRow(row.original) const cells = row.getAllCells() const renderCell = (id: string) => { const cell = cells.find((c) => c.column.id === id) if (!cell || !cell.column.columnDef.cell) { return null } return flexRender(cell.column.columnDef.cell, cell.getContext()) } const fieldLabels: Record = { balance: t('Used / Remaining'), response_time: t('Response'), test_time: t('Last Tested'), } const groups = parseGroupsList(row.original.group ?? '') const selectCell = renderCell('select') const typeCell = renderCell('type') const nameCell = renderCell('name') const statusCell = renderCell('status') const actionsCell = renderCell('actions') const priorityCell = renderCell('priority') const weightCell = renderCell('weight') const balanceCell = renderCell('balance') const responseCell = renderCell('response_time') const testCell = renderCell('test_time') const labelClass = 'text-muted-foreground text-[11px] font-medium select-none' // In card view the enable/disable state is already conveyed by the inline // power toggle, so the plain "Enabled"/"Disabled" badge is redundant. Keep // only the informative states (e.g. auto-disabled, unknown) and tag rows. const showStatusBadge = isTagRow || (row.original.status !== CHANNEL_STATUS.ENABLED && row.original.status !== CHANNEL_STATUS.MANUAL_DISABLED) return (
{/* Row 1: selection + type, with status badge + actions menu */}
{!isTagRow && selectCell && ( {selectCell} )}
{typeCell}
{showStatusBadge && statusCell} {actionsCell}
{/* Body: left column (id/name + balance) paired with a right-aligned column (priority/weight + response/test time). */}
{/* Left column */}
{!isTagRow &&
#{row.original.id}
} {nameCell}
{fieldLabels.balance}
{balanceCell ?? -}
{/* Right column (sits on the right, content left-aligned) */}
{t('Priority')} {t('Weight')}
{priorityCell}
{weightCell}
{fieldLabels.response_time}
{fieldLabels.test_time}
{responseCell ?? -}
{testCell ?? -}
{/* Last row: groups span the full width, showing every group (no label) */}
{groups.length > 0 ? (
{groups.map((g) => ( ))}
) : ( - )}
) }