2424import React , { useEffect , useState , useCallback } from 'react' ;
2525import type { ObjectGridSchema , DataSource , ListColumn , ViewData } from '@object-ui/types' ;
2626import { SchemaRenderer , useDataScope , useNavigationOverlay , useAction } from '@object-ui/react' ;
27- import { getCellRenderer , formatCurrency , formatDate } from '@object-ui/fields' ;
27+ import { getCellRenderer , formatCurrency , formatCompactCurrency , formatDate , formatPercent } from '@object-ui/fields' ;
2828import {
2929 Badge , Button , NavigationOverlay ,
3030 Popover , PopoverContent , PopoverTrigger ,
@@ -859,6 +859,7 @@ export const ObjectGrid: React.FC<ObjectGridProps> = ({
859859 const amountKeys = [ 'amount' , 'price' , 'total' , 'revenue' , 'cost' , 'value' , 'budget' , 'salary' ] ;
860860 const stageKeys = [ 'stage' , 'status' , 'priority' , 'category' , 'severity' , 'level' ] ;
861861 const dateKeys = [ 'date' , 'due' , 'created' , 'updated' , 'deadline' , 'start' , 'end' , 'expires' ] ;
862+ const percentKeys = [ 'probability' , 'percent' , 'rate' , 'ratio' , 'confidence' , 'score' ] ;
862863
863864 // Stage badge color mapping for common pipeline stages
864865 const stageBadgeColor = ( value : string ) : string => {
@@ -878,11 +879,30 @@ export const ObjectGrid: React.FC<ObjectGridProps> = ({
878879 return 'bg-gray-100 text-gray-800 border-gray-300' ;
879880 } ;
880881
881- const classify = ( key : string ) : 'amount' | 'stage' | 'date' | 'other' => {
882+ // Left border color for card accent based on stage
883+ const stageBorderLeft = ( value : string ) : string => {
884+ const v = ( value || '' ) . toLowerCase ( ) ;
885+ if ( v . includes ( 'won' ) || v . includes ( 'completed' ) || v . includes ( 'done' ) || v . includes ( 'active' ) )
886+ return 'border-l-green-500' ;
887+ if ( v . includes ( 'lost' ) || v . includes ( 'cancelled' ) || v . includes ( 'rejected' ) )
888+ return 'border-l-red-500' ;
889+ if ( v . includes ( 'negotiation' ) || v . includes ( 'review' ) || v . includes ( 'in progress' ) )
890+ return 'border-l-yellow-500' ;
891+ if ( v . includes ( 'proposal' ) || v . includes ( 'pending' ) )
892+ return 'border-l-blue-500' ;
893+ if ( v . includes ( 'qualification' ) || v . includes ( 'qualified' ) )
894+ return 'border-l-indigo-500' ;
895+ if ( v . includes ( 'prospecting' ) || v . includes ( 'new' ) || v . includes ( 'open' ) )
896+ return 'border-l-purple-500' ;
897+ return 'border-l-gray-300' ;
898+ } ;
899+
900+ const classify = ( key : string ) : 'amount' | 'stage' | 'date' | 'percent' | 'other' => {
882901 const k = key . toLowerCase ( ) ;
883902 if ( amountKeys . some ( p => k . includes ( p ) ) ) return 'amount' ;
884903 if ( stageKeys . some ( p => k . includes ( p ) ) ) return 'stage' ;
885904 if ( dateKeys . some ( p => k . includes ( p ) ) ) return 'date' ;
905+ if ( percentKeys . some ( p => k . includes ( p ) ) ) return 'percent' ;
886906 return 'other' ;
887907 } ;
888908
@@ -895,63 +915,94 @@ export const ObjectGrid: React.FC<ObjectGridProps> = ({
895915 const amountCol = secondaryCols . find ( ( c : any ) => classify ( c . accessorKey ) === 'amount' ) ;
896916 const stageCol = secondaryCols . find ( ( c : any ) => classify ( c . accessorKey ) === 'stage' ) ;
897917 const dateCols = secondaryCols . filter ( ( c : any ) => classify ( c . accessorKey ) === 'date' ) ;
918+ const percentCols = secondaryCols . filter ( ( c : any ) => classify ( c . accessorKey ) === 'percent' ) ;
898919 const otherCols = secondaryCols . filter (
899- ( c : any ) => c !== amountCol && c !== stageCol && ! dateCols . includes ( c )
920+ ( c : any ) => c !== amountCol && c !== stageCol && ! dateCols . includes ( c ) && ! percentCols . includes ( c )
900921 ) ;
901922
923+ // Determine left border accent color from stage value
924+ const stageValue = stageCol ? String ( row [ stageCol . accessorKey ] ?? '' ) : '' ;
925+ const leftBorderClass = stageValue ? stageBorderLeft ( stageValue ) : '' ;
926+ const cardClassName = [
927+ 'border rounded-lg p-2.5 bg-card hover:bg-accent/50 cursor-pointer transition-colors touch-manipulation' ,
928+ leftBorderClass ? `border-l-[3px] ${ leftBorderClass } ` : '' ,
929+ ] . filter ( Boolean ) . join ( ' ' ) ;
930+
902931 return (
903932 < div
904933 key = { row . id || row . _id || idx }
905- className = "border rounded-lg p-3 bg-card hover:bg-accent/50 cursor-pointer transition-colors touch-manipulation"
934+ className = { cardClassName }
906935 onClick = { ( ) => navigation . handleClick ( row ) }
907936 >
908937 { /* Title row - Name as bold prominent title */ }
909938 { titleCol && (
910- < div className = "font-semibold text-sm truncate mb-1.5 " >
939+ < div className = "font-semibold text-sm truncate mb-1" >
911940 { row [ titleCol . accessorKey ] ?? '—' }
912941 </ div >
913942 ) }
914943
915944 { /* Amount + Stage row - side by side for compact display */ }
916945 { ( amountCol || stageCol ) && (
917- < div className = "flex items-center justify-between gap-2 mb-1.5 " >
946+ < div className = "flex items-center justify-between gap-2 mb-1" >
918947 { amountCol && (
919948 < span className = "text-sm tabular-nums font-medium" >
920949 { typeof row [ amountCol . accessorKey ] === 'number'
921- ? formatCurrency ( row [ amountCol . accessorKey ] )
950+ ? formatCompactCurrency ( row [ amountCol . accessorKey ] )
922951 : row [ amountCol . accessorKey ] ?? '—' }
923952 </ span >
924953 ) }
925954 { stageCol && row [ stageCol . accessorKey ] && (
926955 < Badge
927956 variant = "outline"
928- className = { `text-xs ${ stageBadgeColor ( String ( row [ stageCol . accessorKey ] ) ) } ` }
957+ className = { `text-xs shrink-0 max-w-[140px] truncate ${ stageBadgeColor ( String ( row [ stageCol . accessorKey ] ) ) } ` }
929958 >
930959 { row [ stageCol . accessorKey ] }
931960 </ Badge >
932961 ) }
933962 </ div >
934963 ) }
935964
936- { /* Date fields - formatted short date */ }
937- { dateCols . map ( ( col : any ) => (
938- < div key = { col . accessorKey } className = "flex justify-between items-center py-0.5" >
939- < span className = "text-xs text-muted-foreground" > { col . header } </ span >
940- < span className = "text-xs text-muted-foreground tabular-nums" >
941- { row [ col . accessorKey ] ? formatDate ( row [ col . accessorKey ] ) : '—' }
942- </ span >
965+ { /* Date + Percent combined row for density */ }
966+ { ( dateCols . length > 0 || percentCols . length > 0 ) && (
967+ < div className = "flex items-center justify-between py-0.5 text-xs text-muted-foreground" >
968+ { dateCols [ 0 ] && (
969+ < span className = "tabular-nums" >
970+ { row [ dateCols [ 0 ] . accessorKey ]
971+ ? formatDate ( row [ dateCols [ 0 ] . accessorKey ] , 'short' )
972+ : '—' }
973+ </ span >
974+ ) }
975+ { percentCols [ 0 ] && row [ percentCols [ 0 ] . accessorKey ] != null && (
976+ < span className = "tabular-nums" >
977+ { formatPercent ( Number ( row [ percentCols [ 0 ] . accessorKey ] ) ) }
978+ </ span >
979+ ) }
943980 </ div >
944- ) ) }
981+ ) }
945982
946- { /* Other fields - standard key-value rows */ }
947- { otherCols . map ( ( col : any ) => (
983+ { /* Additional date fields beyond the first */ }
984+ { dateCols . slice ( 1 ) . map ( ( col : any ) => (
948985 < div key = { col . accessorKey } className = "flex justify-between items-center py-0.5" >
949986 < span className = "text-xs text-muted-foreground" > { col . header } </ span >
950- < span className = "text-xs font-medium truncate ml-2 text-right " >
951- { col . cell ? col . cell ( row [ col . accessorKey ] , row ) : String ( row [ col . accessorKey ] ?? '—' ) }
987+ < span className = "text-xs text-muted-foreground tabular-nums " >
988+ { row [ col . accessorKey ] ? formatDate ( row [ col . accessorKey ] , 'short' ) : '—' }
952989 </ span >
953990 </ div >
954991 ) ) }
992+
993+ { /* Other fields - hide empty values on mobile */ }
994+ { otherCols . map ( ( col : any ) => {
995+ const val = row [ col . accessorKey ] ;
996+ if ( val == null || val === '' ) return null ;
997+ return (
998+ < div key = { col . accessorKey } className = "flex justify-between items-center py-0.5" >
999+ < span className = "text-xs text-muted-foreground" > { col . header } </ span >
1000+ < span className = "text-xs font-medium truncate ml-2 text-right" >
1001+ { col . cell ? col . cell ( val , row ) : String ( val ) }
1002+ </ span >
1003+ </ div >
1004+ ) ;
1005+ } ) }
9551006 </ div >
9561007 ) ;
9571008 } ) }
0 commit comments