From 034c64060186729600f18ec8cdcf7cf0b2a6f7b1 Mon Sep 17 00:00:00 2001 From: "IGNY8 VPS (Salman)" Date: Sat, 27 Dec 2025 08:00:09 +0000 Subject: [PATCH] mpre ui fixes --- .../components/common/StatusMetricsCard.tsx | 183 ++++++++++++++++++ frontend/src/config/pages/approved.config.tsx | 4 +- frontend/src/config/pages/clusters.config.tsx | 18 +- frontend/src/config/pages/content.config.tsx | 3 +- frontend/src/config/pages/ideas.config.tsx | 6 +- frontend/src/config/pages/keywords.config.tsx | 47 +---- frontend/src/config/pages/review.config.tsx | 2 +- frontend/src/config/pages/tasks.config.tsx | 3 +- .../src/config/snippets/columns.snippets.ts | 16 +- frontend/src/layout/AppHeader.tsx | 18 ++ frontend/src/layout/AppSidebar.tsx | 29 +-- frontend/src/pages/Writer/Approved.tsx | 2 +- frontend/src/pages/Writer/Content.tsx | 55 ++++-- frontend/src/pages/Writer/Images.tsx | 56 ++++-- frontend/src/pages/Writer/Review.tsx | 19 +- frontend/src/pages/Writer/Tasks.tsx | 45 ++++- frontend/src/templates/TablePageTemplate.tsx | 4 +- 17 files changed, 352 insertions(+), 158 deletions(-) create mode 100644 frontend/src/components/common/StatusMetricsCard.tsx diff --git a/frontend/src/components/common/StatusMetricsCard.tsx b/frontend/src/components/common/StatusMetricsCard.tsx new file mode 100644 index 00000000..854ea62c --- /dev/null +++ b/frontend/src/components/common/StatusMetricsCard.tsx @@ -0,0 +1,183 @@ +/** + * StatusMetricsCard Component + * Displays status metrics in a card format with colored left border + * Used in table action rows to show page-specific status metrics + */ + +import React from 'react'; +import { Link } from 'react-router-dom'; + +export interface StatusMetricItem { + label: string; + value: string | number; +} + +export interface StatusMetricsCardProps { + /** Title for the card */ + title: string; + /** Subtitle text shown below the main count */ + subtitle?: string; + /** Icon to display */ + icon?: React.ReactNode; + /** Color variant - matches page badge colors */ + color: 'blue' | 'orange' | 'pink' | 'emerald' | 'green' | 'purple' | 'amber' | 'red' | 'indigo' | 'cyan' | 'teal'; + /** Main count value */ + count: number; + /** Array of metric items for 2-column layout */ + metrics?: StatusMetricItem[]; + /** Review count to display */ + reviewCount?: number; + /** Link to review page */ + reviewLink?: string; + /** Custom action button */ + actionButton?: { + label: string; + href: string; + }; +} + +const colorClasses: Record = { + blue: { + border: 'border-l-blue-500', + bg: 'bg-blue-50 dark:bg-blue-500/10', + text: 'text-blue-700 dark:text-blue-300', + iconBg: 'bg-blue-100 dark:bg-blue-500/20' + }, + orange: { + border: 'border-l-orange-500', + bg: 'bg-orange-50 dark:bg-orange-500/10', + text: 'text-orange-700 dark:text-orange-300', + iconBg: 'bg-orange-100 dark:bg-orange-500/20' + }, + pink: { + border: 'border-l-pink-500', + bg: 'bg-pink-50 dark:bg-pink-500/10', + text: 'text-pink-700 dark:text-pink-300', + iconBg: 'bg-pink-100 dark:bg-pink-500/20' + }, + emerald: { + border: 'border-l-emerald-500', + bg: 'bg-emerald-50 dark:bg-emerald-500/10', + text: 'text-emerald-700 dark:text-emerald-300', + iconBg: 'bg-emerald-100 dark:bg-emerald-500/20' + }, + green: { + border: 'border-l-green-500', + bg: 'bg-green-50 dark:bg-green-500/10', + text: 'text-green-700 dark:text-green-300', + iconBg: 'bg-green-100 dark:bg-green-500/20' + }, + purple: { + border: 'border-l-purple-500', + bg: 'bg-purple-50 dark:bg-purple-500/10', + text: 'text-purple-700 dark:text-purple-300', + iconBg: 'bg-purple-100 dark:bg-purple-500/20' + }, + amber: { + border: 'border-l-amber-500', + bg: 'bg-amber-50 dark:bg-amber-500/10', + text: 'text-amber-700 dark:text-amber-300', + iconBg: 'bg-amber-100 dark:bg-amber-500/20' + }, + red: { + border: 'border-l-red-500', + bg: 'bg-red-50 dark:bg-red-500/10', + text: 'text-red-700 dark:text-red-300', + iconBg: 'bg-red-100 dark:bg-red-500/20' + }, + indigo: { + border: 'border-l-indigo-500', + bg: 'bg-indigo-50 dark:bg-indigo-500/10', + text: 'text-indigo-700 dark:text-indigo-300', + iconBg: 'bg-indigo-100 dark:bg-indigo-500/20' + }, + cyan: { + border: 'border-l-cyan-500', + bg: 'bg-cyan-50 dark:bg-cyan-500/10', + text: 'text-cyan-700 dark:text-cyan-300', + iconBg: 'bg-cyan-100 dark:bg-cyan-500/20' + }, + teal: { + border: 'border-l-teal-500', + bg: 'bg-teal-50 dark:bg-teal-500/10', + text: 'text-teal-700 dark:text-teal-300', + iconBg: 'bg-teal-100 dark:bg-teal-500/20' + }, +}; + +export default function StatusMetricsCard({ + title, + subtitle, + icon, + color, + count, + metrics, + reviewCount, + reviewLink = '/writer/review', + actionButton, +}: StatusMetricsCardProps) { + const colors = colorClasses[color] || colorClasses.blue; + + return ( +
+
+ {/* Icon */} + {icon && ( +
+
+ {icon} +
+
+ )} + + {/* Content */} +
+ {/* Title and Count Row */} +
+

{title}

+ {count} +
+ + {/* Subtitle */} + {subtitle && ( +

{subtitle}

+ )} + + {/* 2-column metrics */} + {metrics && metrics.length > 0 && ( +
+ {metrics.map((metric, index) => ( +
+ {metric.label} + {metric.value} +
+ ))} +
+ )} + + {/* Review link/button */} + {(reviewCount !== undefined || actionButton) && ( +
+ {reviewCount !== undefined && ( + + {reviewCount} awaiting review + + )} + {actionButton && ( + + {actionButton.label} + + + + + )} +
+ )} +
+
+
+ ); +} diff --git a/frontend/src/config/pages/approved.config.tsx b/frontend/src/config/pages/approved.config.tsx index f092770a..be15ba76 100644 --- a/frontend/src/config/pages/approved.config.tsx +++ b/frontend/src/config/pages/approved.config.tsx @@ -59,7 +59,7 @@ export function createApprovedPageConfig(params: { const columns: ColumnConfig[] = [ { key: 'title', - label: 'Title', + label: 'Content Idea Title', sortable: true, sortField: 'title', render: (value: string, row: Content) => ( @@ -92,7 +92,7 @@ export function createApprovedPageConfig(params: { }, { key: 'wordpress_status', - label: 'Site Status', + label: 'Site Content Status', sortable: false, width: '120px', render: (_value: any, row: Content) => { diff --git a/frontend/src/config/pages/clusters.config.tsx b/frontend/src/config/pages/clusters.config.tsx index cc00b416..45b9fe22 100644 --- a/frontend/src/config/pages/clusters.config.tsx +++ b/frontend/src/config/pages/clusters.config.tsx @@ -127,7 +127,7 @@ export const createClustersPageConfig = ( }] : []), { key: 'keywords_count', - label: 'Keywords', + label: 'KW Count', sortable: true, sortField: 'keywords_count', width: '120px', @@ -141,17 +141,7 @@ export const createClustersPageConfig = ( sortField: 'ideas_count', width: '120px', align: 'center' as const, - render: (value: number) => ( - 0 ? 'success' : 'light'} - size="xs" - variant="soft" - > - - {value > 0 ? `${value.toLocaleString()} ideas` : 'No ideas'} - - - ), + render: (value: number) => value.toLocaleString(), }, { key: 'volume', @@ -244,7 +234,7 @@ export const createClustersPageConfig = ( }, { key: 'updated_at', - label: 'Updated', + label: 'Modified', sortable: false, // Backend doesn't support sorting by updated_at sortField: 'updated_at', defaultVisible: false, @@ -253,7 +243,7 @@ export const createClustersPageConfig = ( // Generate Ideas action column - only shows button for status = 'new' { key: 'generate_action', - label: 'Actions', + label: 'Generate Ideas', sortable: false, width: '120px', render: (_value: any, row: Cluster) => { diff --git a/frontend/src/config/pages/content.config.tsx b/frontend/src/config/pages/content.config.tsx index 3dab87f3..7fa560a3 100644 --- a/frontend/src/config/pages/content.config.tsx +++ b/frontend/src/config/pages/content.config.tsx @@ -97,6 +97,7 @@ export const createContentPageConfig = ( columns: [ { ...titleColumn, + label: 'Content Idea Title', sortable: true, sortField: 'title', render: (value: string, row: Content) => ( @@ -385,7 +386,7 @@ export const createContentPageConfig = ( // Optional columns - hidden by default { key: 'updated_at', - label: 'Updated', + label: 'Modified', sortable: true, sortField: 'updated_at', defaultVisible: false, diff --git a/frontend/src/config/pages/ideas.config.tsx b/frontend/src/config/pages/ideas.config.tsx index fcf4a272..e5f1ea10 100644 --- a/frontend/src/config/pages/ideas.config.tsx +++ b/frontend/src/config/pages/ideas.config.tsx @@ -95,7 +95,7 @@ export const createIdeasPageConfig = ( { ...titleColumn, key: 'idea_title', - label: 'Title', + label: 'Content Idea Title', sortable: true, sortField: 'idea_title', toggleable: true, // Enable toggle for this column @@ -150,7 +150,7 @@ export const createIdeasPageConfig = ( }, { key: 'target_keywords', - label: 'Target Keywords', + label: 'Keywords', sortable: false, width: '250px', render: (value: string) => ( @@ -215,7 +215,7 @@ export const createIdeasPageConfig = ( // Optional columns - hidden by default { key: 'updated_at', - label: 'Updated', + label: 'Modified', sortable: false, // Backend doesn't support sorting by updated_at sortField: 'updated_at', defaultVisible: false, diff --git a/frontend/src/config/pages/keywords.config.tsx b/frontend/src/config/pages/keywords.config.tsx index 014b7afb..0a4a20b4 100644 --- a/frontend/src/config/pages/keywords.config.tsx +++ b/frontend/src/config/pages/keywords.config.tsx @@ -245,56 +245,11 @@ export const createKeywordsPageConfig = ( }, { ...createdColumn, + label: 'Added', sortable: true, sortField: 'created_at', render: (value: string) => formatRelativeDate(value), }, - // Optional columns - hidden by default - { - key: 'updated_at', - label: 'Updated', - sortable: true, - sortField: 'updated_at', - defaultVisible: false, - render: (value: string) => formatRelativeDate(value), - }, - { - key: 'volume_override', - label: 'Volume Override', - sortable: true, - sortField: 'volume_override', - defaultVisible: false, - render: (value: number | null) => value ? value.toLocaleString() : '-', - }, - { - key: 'difficulty_override', - label: 'Difficulty Override', - sortable: true, - sortField: 'difficulty_override', - defaultVisible: false, - align: 'center' as const, - render: (value: number | null) => { - if (value === null || value === undefined) return '-'; - const difficultyNum = getDifficultyNumber(value); - return typeof difficultyNum === 'number' ? ( - - {difficultyNum} - - ) : ( - difficultyNum - ); - }, - }, ], filters: [ { diff --git a/frontend/src/config/pages/review.config.tsx b/frontend/src/config/pages/review.config.tsx index 1cc7e7a9..0020f3a6 100644 --- a/frontend/src/config/pages/review.config.tsx +++ b/frontend/src/config/pages/review.config.tsx @@ -59,7 +59,7 @@ export function createReviewPageConfig(params: { const columns: ColumnConfig[] = [ { key: 'title', - label: 'Title', + label: 'Content Idea Title', sortable: true, sortField: 'title', render: (value: string, row: Content) => ( diff --git a/frontend/src/config/pages/tasks.config.tsx b/frontend/src/config/pages/tasks.config.tsx index b21c6310..26fa74e0 100644 --- a/frontend/src/config/pages/tasks.config.tsx +++ b/frontend/src/config/pages/tasks.config.tsx @@ -101,6 +101,7 @@ export const createTasksPageConfig = ( columns: [ { ...titleColumn, + label: 'Content Idea Title', sortable: true, sortField: 'title', toggleable: true, @@ -295,7 +296,7 @@ export const createTasksPageConfig = ( }, { key: 'updated_at', - label: 'Updated', + label: 'Modified', sortable: true, sortField: 'updated_at', defaultVisible: false, diff --git a/frontend/src/config/snippets/columns.snippets.ts b/frontend/src/config/snippets/columns.snippets.ts index 5826d066..74fd0c3e 100644 --- a/frontend/src/config/snippets/columns.snippets.ts +++ b/frontend/src/config/snippets/columns.snippets.ts @@ -12,7 +12,7 @@ export const titleColumn = { export const keywordColumn = { key: 'keyword', - label: 'Search Term', + label: 'Keyword', sortable: true, width: 'auto', }; @@ -27,7 +27,7 @@ export const statusColumn = { export const volumeColumn = { key: 'volume', - label: 'Monthly Searches', + label: 'Volume', sortable: true, numeric: true, width: '100px', @@ -35,7 +35,7 @@ export const volumeColumn = { export const difficultyColumn = { key: 'difficulty', - label: 'Competition Level', + label: 'Difficulty', sortable: true, badge: true, width: '120px', @@ -43,7 +43,7 @@ export const difficultyColumn = { export const countryColumn = { key: 'country', - label: 'Target Location', + label: 'Country', sortable: true, badge: true, width: '120px', @@ -51,7 +51,7 @@ export const countryColumn = { export const clusterColumn = { key: 'cluster', - label: 'Topic Group', + label: 'Cluster', sortable: true, width: '200px', }; @@ -66,7 +66,7 @@ export const createdColumn = { export const updatedColumn = { key: 'updated_at', - label: 'Updated', + label: 'Modified', sortable: true, date: true, width: '150px', @@ -82,7 +82,7 @@ export const actionsColumn = { export const wordCountColumn = { key: 'word_count', - label: 'Word Count', + label: 'Words', sortable: true, numeric: true, width: '120px', @@ -90,7 +90,7 @@ export const wordCountColumn = { export const sectorColumn = { key: 'sector_name', - label: 'Topic Area', + label: 'Sector', sortable: false, width: '150px', }; diff --git a/frontend/src/layout/AppHeader.tsx b/frontend/src/layout/AppHeader.tsx index 92493e3d..19e07519 100644 --- a/frontend/src/layout/AppHeader.tsx +++ b/frontend/src/layout/AppHeader.tsx @@ -1,6 +1,7 @@ import { useEffect, useState } from "react"; import { Link } from "react-router-dom"; import { usePageContext } from "../context/PageContext"; +import { useSidebar } from "../context/SidebarContext"; import { ThemeToggleButton } from "../components/common/ThemeToggleButton"; import NotificationDropdown from "../components/header/NotificationDropdown"; import UserDropdown from "../components/header/UserDropdown"; @@ -29,6 +30,7 @@ const AppHeader: React.FC = () => { const [isApplicationMenuOpen, setApplicationMenuOpen] = useState(false); const [isSearchOpen, setIsSearchOpen] = useState(false); const { pageInfo } = usePageContext(); + const { isExpanded, toggleSidebar } = useSidebar(); const toggleApplicationMenu = () => { setApplicationMenuOpen(!isApplicationMenuOpen); @@ -71,6 +73,22 @@ const AppHeader: React.FC = () => { {/* Page Title with Badge - Desktop */} {pageInfo && (
+ {/* Sidebar Toggle Button - Always visible on desktop */} + + {pageInfo.badge && (
{pageInfo.badge.icon && typeof pageInfo.badge.icon === 'object' && 'type' in pageInfo.badge.icon diff --git a/frontend/src/layout/AppSidebar.tsx b/frontend/src/layout/AppSidebar.tsx index de7e0927..f78ed047 100644 --- a/frontend/src/layout/AppSidebar.tsx +++ b/frontend/src/layout/AppSidebar.tsx @@ -131,11 +131,11 @@ const AppSidebar: React.FC = () => { icon: , name: "Writer", subItems: [ - { name: "Queue", path: "/writer/tasks" }, - { name: "Drafts", path: "/writer/content" }, - { name: "Images", path: "/writer/images" }, - { name: "Review", path: "/writer/review" }, - { name: "Approved", path: "/writer/approved" }, + { name: "Content Queue", path: "/writer/tasks" }, + { name: "Content Drafts", path: "/writer/content" }, + { name: "Content Images", path: "/writer/images" }, + { name: "Content Review", path: "/writer/review" }, + { name: "Content Approved", path: "/writer/approved" }, ], }); } @@ -452,25 +452,6 @@ const AppSidebar: React.FC = () => { onMouseEnter={() => !isExpanded && setIsHovered(true)} onMouseLeave={() => setIsHovered(false)} > - {/* Collapse/Expand Toggle Button - Fixed 5px from sidebar edge, does not move with hover */} - -
{isExpanded || isHovered || isMobileOpen ? ( diff --git a/frontend/src/pages/Writer/Approved.tsx b/frontend/src/pages/Writer/Approved.tsx index 320fec77..d1aaf2ad 100644 --- a/frontend/src/pages/Writer/Approved.tsx +++ b/frontend/src/pages/Writer/Approved.tsx @@ -305,7 +305,7 @@ export default function Approved() { return ( <> , color: 'green' }} parent="Writer" /> diff --git a/frontend/src/pages/Writer/Content.tsx b/frontend/src/pages/Writer/Content.tsx index 59787389..f0f2f22d 100644 --- a/frontend/src/pages/Writer/Content.tsx +++ b/frontend/src/pages/Writer/Content.tsx @@ -25,6 +25,7 @@ import { useProgressModal } from '../../hooks/useProgressModal'; import PageHeader from '../../components/common/PageHeader'; import ModuleMetricsFooter, { MetricItem, ProgressMetric } from '../../components/dashboard/ModuleMetricsFooter'; import { PencilSquareIcon } from '@heroicons/react/24/outline'; +import StatusMetricsCard from '../../components/common/StatusMetricsCard'; export default function Content() { const toast = useToast(); @@ -55,6 +56,22 @@ export default function Content() { const progressModal = useProgressModal(); const hasReloadedRef = useRef(false); + // Review count state + const [reviewCount, setReviewCount] = useState(0); + + // Load review count + useEffect(() => { + const loadReviewCount = async () => { + try { + const data = await fetchContent({ status: 'review', page_size: 1 }); + setReviewCount(data.count || 0); + } catch (error) { + console.error('Error fetching review count:', error); + } + }; + loadReviewCount(); + }, []); + // Load content - wrapped in useCallback const loadContent = useCallback(async () => { setLoading(true); @@ -227,7 +244,7 @@ export default function Content() { return ( <> , color: 'orange' }} parent="Writer" /> @@ -274,26 +291,22 @@ export default function Content() { onBulkDelete={handleBulkDelete} getItemDisplayName={(row: ContentType) => row.title || `Content #${row.id}`} statusExplainer={ -
-
- Currently Generated (Draft): {content.filter(c => c.status === 'draft').length} -
-
- Image Prompts: {content.filter(c => c.has_image_prompts).length}/{content.length} -
-
- Images Generated: {content.filter(c => c.has_generated_images).length}/{content.length} -
-
- - Review ({content.filter(c => c.status === 'review').length}) - - -
-
+ } + count={totalCount} + subtitle="draft content items" + metrics={[ + { label: 'Image Prompts', value: `${content.filter(c => c.has_image_prompts).length}/${content.length}` }, + { label: 'Images Generated', value: `${content.filter(c => c.has_generated_images).length}/${content.length}` }, + ]} + reviewCount={reviewCount} + actionButton={{ + label: 'Review', + href: '/writer/review', + }} + /> } /> diff --git a/frontend/src/pages/Writer/Images.tsx b/frontend/src/pages/Writer/Images.tsx index b64eba89..90e793ba 100644 --- a/frontend/src/pages/Writer/Images.tsx +++ b/frontend/src/pages/Writer/Images.tsx @@ -17,6 +17,7 @@ import { fetchAPI, deleteContent, bulkDeleteContent, + fetchContent, } from '../../services/api'; import { useToast } from '../../components/ui/toast/ToastContainer'; import { FileIcon, DownloadIcon, ArrowRightIcon } from '../../icons'; @@ -26,6 +27,7 @@ import ImageQueueModal, { ImageQueueItem } from '../../components/common/ImageQu import SingleRecordStatusUpdateModal from '../../components/common/SingleRecordStatusUpdateModal'; import PageHeader from '../../components/common/PageHeader'; import { Modal } from '../../components/ui/modal'; +import StatusMetricsCard from '../../components/common/StatusMetricsCard'; export default function Images() { const toast = useToast(); @@ -68,6 +70,22 @@ export default function Images() { const [isImageModalOpen, setIsImageModalOpen] = useState(false); const [modalImageUrl, setModalImageUrl] = useState(null); + // Review count state + const [reviewCount, setReviewCount] = useState(0); + + // Load review count + useEffect(() => { + const loadReviewCount = async () => { + try { + const data = await fetchContent({ status: 'review', page_size: 1 }); + setReviewCount(data.count || 0); + } catch (error) { + console.error('Error fetching review count:', error); + } + }; + loadReviewCount(); + }, []); + // Load images - wrapped in useCallback const loadImages = useCallback(async () => { setLoading(true); @@ -451,7 +469,7 @@ export default function Images() { return ( <> , color: 'pink' }} parent="Writer" /> @@ -516,26 +534,22 @@ export default function Images() { }} onRowAction={handleRowAction} statusExplainer={ -
-
- Content Images Status -
-
- Need Images: {images.filter(i => i.overall_status === 'pending').length} -
-
- Images Complete: {images.filter(i => i.overall_status === 'complete').length} -
-
- - Go to Review - - -
-
+ } + count={totalCount} + subtitle="content items with images" + metrics={[ + { label: 'Need Images', value: images.filter(i => i.overall_status === 'pending').length }, + { label: 'Images Complete', value: images.filter(i => i.overall_status === 'complete').length }, + ]} + reviewCount={reviewCount} + actionButton={{ + label: 'Review', + href: '/writer/review', + }} + /> } /> , color: 'emerald' }} parent="Writer" /> @@ -454,11 +455,17 @@ export default function Review() { }} onRowAction={handleRowAction} statusExplainer={ -
-
- Approve {totalCount} content pages/articles awaiting approval -
-
+ } + count={totalCount} + subtitle="awaiting approval" + actionButton={{ + label: 'Approved', + href: '/writer/approved', + }} + /> } /> (false); + // Load review count + useEffect(() => { + const loadReviewCount = async () => { + try { + const data = await fetchContent({ status: 'review', page_size: 1 }); + setReviewCount(data.count || 0); + } catch (error) { + console.error('Error fetching review count:', error); + } + }; + loadReviewCount(); + }, []); + // Load clusters for filter dropdown @@ -367,7 +386,7 @@ export default function Tasks() { return ( <> , color: 'blue' }} parent="Writer" /> @@ -417,12 +436,6 @@ export default function Tasks() { setIsEditMode(true); setIsModalOpen(true); }} - onCreate={() => { - resetForm(); - setIsModalOpen(true); - }} - createLabel="Add Task" - onCreateIcon={} onDelete={async (id: number) => { await deleteTask(id); loadTasks(); @@ -473,6 +486,24 @@ export default function Tasks() { setTypeFilter(''); setCurrentPage(1); }} + statusExplainer={ + } + count={totalCount} + subtitle="content items queued" + metrics={[ + { label: 'Queued', value: tasks.filter(t => t.status === 'queued').length }, + { label: 'Processing', value: tasks.filter(t => t.status === 'in_progress').length }, + ]} + reviewCount={reviewCount} + actionButton={{ + label: 'Review', + href: '/writer/review', + }} + /> + } /> {/* Module Metrics Footer - Pipeline Style with Cross-Module Links */} diff --git a/frontend/src/templates/TablePageTemplate.tsx b/frontend/src/templates/TablePageTemplate.tsx index 2d0956b0..3cd91bac 100644 --- a/frontend/src/templates/TablePageTemplate.tsx +++ b/frontend/src/templates/TablePageTemplate.tsx @@ -828,7 +828,7 @@ export default function TablePageTemplate({ )} {visibleColumnsList.map((column, colIndex) => { const isLastColumn = colIndex === visibleColumnsList.length - 1; - const displayName = formatColumnKey(column.key); + const displayName = column.label || formatColumnKey(column.key); return ( ({ key: col.key, - label: formatColumnKey(col.key), + label: col.label || formatColumnKey(col.key), defaultVisible: col.defaultVisible !== false, }))} visibleColumns={visibleColumns}