@@ -11,6 +11,7 @@ import { assessTextPaste, formatPasteLimit, PASTE_LIMITS } from '@sim/utils/past
1111import { useVirtualizer } from '@tanstack/react-virtual'
1212import { useParams , useRouter } from 'next/navigation'
1313import { usePostHog } from 'posthog-js/react'
14+ import { extractValidationIssues , isValidationError } from '@/lib/api/client/errors'
1415import type { RunLimit , RunMode , TableFindMatch } from '@/lib/api/contracts/tables'
1516import { attachSelectionContextToClipboard } from '@/lib/copilot/chat/selection-clipboard'
1617import { captureEvent } from '@/lib/posthog/client'
@@ -78,6 +79,7 @@ import {
7879 chipRowCount ,
7980 classifyExecStatusMix ,
8081 collectRowSnapshots ,
82+ columnNameIssue ,
8183 computeNormalizedSelection ,
8284 drainTargetForChip ,
8385 type ExecStatusMix ,
@@ -1502,16 +1504,60 @@ export function TableGrid({
15021504 const handleFindCloseRef = useRef ( handleFindClose )
15031505 handleFindCloseRef . current = handleFindClose
15041506
1507+ const [ renameError , setRenameError ] = useState ( false )
1508+
15051509 const columnRename = useInlineRename ( {
15061510 // `columnName` is the column id; record the prior display name + id so undo
15071511 // restores the label (not the id) and targets the right column.
15081512 onSave : ( columnName , newName ) => {
15091513 const oldName = columnsRef . current . find ( ( c ) => c . key === columnName ) ?. name ?? columnName
15101514 pushUndoRef . current ( { type : 'rename-column' , oldName, newName, columnId : columnName } )
15111515 handleColumnRename ( columnName , newName )
1512- return updateColumnMutation . mutateAsync ( { columnName, updates : { name : newName } } )
1516+ return updateColumnMutation
1517+ . mutateAsync ( { columnName, updates : { name : newName } } )
1518+ . catch ( ( error : unknown ) => {
1519+ if ( isValidationError ( error ) ) {
1520+ toast . error ( extractValidationIssues ( error ) [ 0 ] ?. message ?? getErrorMessage ( error ) )
1521+ }
1522+ setRenameError ( true )
1523+ throw error
1524+ } )
15131525 } ,
15141526 } )
1527+ const columnRenameRef = useRef ( columnRename )
1528+ columnRenameRef . current = columnRename
1529+
1530+ const handleRenameValueChange = useCallback ( ( value : string ) => {
1531+ setRenameError ( false )
1532+ columnRenameRef . current . setEditValue ( value )
1533+ } , [ ] )
1534+
1535+ /** Keeps invalid names in the header so the user can correct them in place. */
1536+ const handleRenameSubmit = useCallback ( ( ) => {
1537+ const { editingId, editValue, submitRename } = columnRenameRef . current
1538+ const trimmedName = editValue . trim ( )
1539+ const currentColumn = columnsRef . current . find ( ( column ) => column . key === editingId )
1540+ if ( trimmedName && currentColumn && trimmedName !== currentColumn . name ) {
1541+ const issue = columnNameIssue (
1542+ trimmedName ,
1543+ schemaColumnsRef . current
1544+ . filter ( ( column ) => getColumnId ( column ) !== editingId )
1545+ . map ( ( column ) => column . name )
1546+ )
1547+ if ( issue ) {
1548+ toast . error ( issue )
1549+ setRenameError ( true )
1550+ return
1551+ }
1552+ }
1553+ setRenameError ( false )
1554+ void submitRename ( )
1555+ } , [ ] )
1556+
1557+ const handleRenameCancel = useCallback ( ( ) => {
1558+ setRenameError ( false )
1559+ columnRenameRef . current . cancelRename ( )
1560+ } , [ ] )
15151561
15161562 const toggleBooleanCell = useCallback (
15171563 ( rowId : string , columnName : string , currentValue : unknown ) => {
@@ -3987,6 +4033,15 @@ export function TableGrid({
39874033 [ onOpenColumnConfig , onOpenWorkflowConfig , workflowGroupById ]
39884034 )
39894035
4036+ const handleRenameColumn = useCallback (
4037+ ( columnName : string ) => {
4038+ setRenameError ( false )
4039+ const column = columnsRef . current . find ( ( candidate ) => candidate . key === columnName )
4040+ columnRename . startRename ( columnName , column ?. name ?? columnName )
4041+ } ,
4042+ [ columnRename . startRename ]
4043+ )
4044+
39904045 const handleConfigureWorkflowGroup = useCallback (
39914046 ( groupId : string ) => {
39924047 const group = workflowGroupById . get ( groupId )
@@ -4877,9 +4932,10 @@ export function TableGrid({
48774932 renameValue = {
48784933 columnRename . editingId === column . key ? columnRename . editValue : ''
48794934 }
4880- onRenameValueChange = { columnRename . setEditValue }
4881- onRenameSubmit = { columnRename . submitRename }
4882- onRenameCancel = { columnRename . cancelRename }
4935+ renameError = { renameError && columnRename . editingId === column . key }
4936+ onRenameValueChange = { handleRenameValueChange }
4937+ onRenameSubmit = { handleRenameSubmit }
4938+ onRenameCancel = { handleRenameCancel }
48834939 onColumnSelect = { handleColumnSelect }
48844940 // Required props here, and the menu is already
48854941 // suppressed for non-editors by `readOnly`.
@@ -4904,6 +4960,9 @@ export function TableGrid({
49044960 workflowGroups = { tableWorkflowGroups }
49054961 sourceInfo = { columnSourceInfo . get ( column . key ) }
49064962 onOpenConfig = { handleConfigureColumn }
4963+ onRenameColumn = {
4964+ userPermissions . canEdit ? handleRenameColumn : undefined
4965+ }
49074966 onGoToReferenceTable = { handleGoToReferenceTable }
49084967 onViewWorkflow = { handleViewWorkflow }
49094968 onSortColumn = { onSortColumn }
0 commit comments