11
2- import React , { useState , useEffect , useContext } from 'react' ;
2+ import React , { useState , useEffect , useContext , useCallback } from 'react' ;
33import { useDataScope , SchemaRendererContext } from '@object-ui/react' ;
44import { ChartRenderer } from './ChartRenderer' ;
55import { ComponentRegistry , extractRecords } from '@object-ui/core' ;
6+ import { AlertCircle } from 'lucide-react' ;
67
78/**
89 * Client-side aggregation for fetched records.
@@ -60,56 +61,64 @@ export const ObjectChart = (props: any) => {
6061
6162 const [ fetchedData , setFetchedData ] = useState < any [ ] > ( [ ] ) ;
6263 const [ loading , setLoading ] = useState ( false ) ;
64+ const [ error , setError ] = useState < string | null > ( null ) ;
65+
66+ const fetchData = useCallback ( async ( ds : any , mounted : { current : boolean } ) => {
67+ if ( ! ds || ! schema . objectName ) return ;
68+ if ( mounted . current ) {
69+ setLoading ( true ) ;
70+ setError ( null ) ;
71+ }
72+ try {
73+ let data : any [ ] ;
74+
75+ // Prefer server-side aggregation when aggregate config is provided
76+ // and dataSource supports the aggregate() method.
77+ if ( schema . aggregate && typeof ds . aggregate === 'function' ) {
78+ const results = await ds . aggregate ( schema . objectName , {
79+ field : schema . aggregate . field ,
80+ function : schema . aggregate . function ,
81+ groupBy : schema . aggregate . groupBy ,
82+ filter : schema . filter ,
83+ } ) ;
84+ data = Array . isArray ( results ) ? results : [ ] ;
85+ } else if ( typeof ds . find === 'function' ) {
86+ // Fallback: fetch all records and aggregate client-side
87+ const results = await ds . find ( schema . objectName , {
88+ $filter : schema . filter
89+ } ) ;
90+
91+ data = extractRecords ( results ) ;
92+
93+ // Apply client-side aggregation when aggregate config is provided
94+ if ( schema . aggregate && data . length > 0 ) {
95+ data = aggregateRecords ( data , schema . aggregate ) ;
96+ }
97+ } else {
98+ return ;
99+ }
100+
101+ if ( mounted . current ) {
102+ setFetchedData ( data ) ;
103+ }
104+ } catch ( e ) {
105+ console . error ( '[ObjectChart] Fetch error:' , e ) ;
106+ if ( mounted . current ) {
107+ setError ( e instanceof Error ? e . message : 'Failed to load chart data' ) ;
108+ }
109+ } finally {
110+ if ( mounted . current ) setLoading ( false ) ;
111+ }
112+ } , [ schema . objectName , schema . aggregate , schema . filter ] ) ;
63113
64114 useEffect ( ( ) => {
65- let isMounted = true ;
66- const fetchData = async ( ) => {
67- if ( ! dataSource || ! schema . objectName ) return ;
68- if ( isMounted ) setLoading ( true ) ;
69- try {
70- let data : any [ ] ;
71-
72- // Prefer server-side aggregation when aggregate config is provided
73- // and dataSource supports the aggregate() method.
74- if ( schema . aggregate && typeof dataSource . aggregate === 'function' ) {
75- const results = await dataSource . aggregate ( schema . objectName , {
76- field : schema . aggregate . field ,
77- function : schema . aggregate . function ,
78- groupBy : schema . aggregate . groupBy ,
79- filter : schema . filter ,
80- } ) ;
81- data = Array . isArray ( results ) ? results : [ ] ;
82- } else if ( typeof dataSource . find === 'function' ) {
83- // Fallback: fetch all records and aggregate client-side
84- const results = await dataSource . find ( schema . objectName , {
85- $filter : schema . filter
86- } ) ;
87-
88- data = extractRecords ( results ) ;
89-
90- // Apply client-side aggregation when aggregate config is provided
91- if ( schema . aggregate && data . length > 0 ) {
92- data = aggregateRecords ( data , schema . aggregate ) ;
93- }
94- } else {
95- return ;
96- }
97-
98- if ( isMounted ) {
99- setFetchedData ( data ) ;
100- }
101- } catch ( e ) {
102- console . error ( '[ObjectChart] Fetch error:' , e ) ;
103- } finally {
104- if ( isMounted ) setLoading ( false ) ;
105- }
106- } ;
115+ const mounted = { current : true } ;
107116
108117 if ( schema . objectName && ! boundData && ! schema . data ) {
109- fetchData ( ) ;
118+ fetchData ( dataSource , mounted ) ;
110119 }
111- return ( ) => { isMounted = false ; } ;
112- } , [ schema . objectName , dataSource , boundData , schema . data , schema . filter , schema . aggregate ] ) ;
120+ return ( ) => { mounted . current = false ; } ;
121+ } , [ schema . objectName , dataSource , boundData , schema . data , schema . filter , schema . aggregate , fetchData ] ) ;
113122
114123 const rawData = boundData || schema . data || fetchedData ;
115124 const finalData = Array . isArray ( rawData ) ? rawData : [ ] ;
@@ -121,11 +130,22 @@ export const ObjectChart = (props: any) => {
121130 } ;
122131
123132 if ( loading && finalData . length === 0 ) {
124- return < div className = { "flex items-center justify-center text-muted-foreground text-sm p-4 " + ( schema . className || '' ) } > Loading chart data…</ div > ;
133+ return < div className = { "flex items-center justify-center text-muted-foreground text-sm p-4 " + ( schema . className || '' ) } data-testid = "chart-loading" > Loading chart data…</ div > ;
134+ }
135+
136+ // Error state — show the error prominently so issues are not hidden
137+ if ( error ) {
138+ return (
139+ < div className = { "flex flex-col items-center justify-center gap-2 p-4 " + ( schema . className || '' ) } data-testid = "chart-error" role = "alert" >
140+ < AlertCircle className = "h-6 w-6 text-destructive opacity-60" />
141+ < p className = "text-xs text-destructive font-medium" > Failed to load chart data</ p >
142+ < p className = "text-xs text-muted-foreground max-w-xs text-center" > { error } </ p >
143+ </ div >
144+ ) ;
125145 }
126146
127147 if ( ! dataSource && schema . objectName && finalData . length === 0 ) {
128- return < div className = { "flex items-center justify-center text-muted-foreground text-sm p-4 " + ( schema . className || '' ) } > No data source available for " { schema . objectName } " </ div > ;
148+ return < div className = { "flex items-center justify-center text-muted-foreground text-sm p-4 " + ( schema . className || '' ) } data-testid = "chart-no-datasource" > No data source available for “ { schema . objectName } ” </ div > ;
129149 }
130150
131151 return < ChartRenderer { ...props } schema = { finalSchema } /> ;
0 commit comments