/**
* TikTok Analytics Dashboard - aPaaS Custom Component Entry
*
* This is the main entry point for the custom component that integrates
* with Feishu/Lark aPaaS platform
*/
import React, { useState, useEffect, useCallback, useRef } from 'react';
import TikTokAnalyticsChart from '../../src/vchart-component/index';
import { TikTokDashboardModel, createTikTokDashboardModel } from './model';
import { TikTokDashboardProps, defaultProps } from './meta';
import type { DashboardData } from '../../src/vchart-component/types';
export const TikTokAnalyticsDashboard: React.FC<TikTokDashboardProps> = (props) => {
const {
chartType = defaultProps.chartType!,
width = defaultProps.width!,
height = defaultProps.height!,
bitableTableId,
bitableAppToken,
topVideosMetric = defaultProps.topVideosMetric!,
showViewsOverTime = defaultProps.showViewsOverTime!,
showTopVideos = defaultProps.showTopVideos!,
showEngagement = defaultProps.showEngagement!,
autoRefresh = defaultProps.autoRefresh!,
refreshInterval = defaultProps.refreshInterval!,
onChartReady,
onChartClick,
onDataUpdate,
onError,
} = props;
const [model, setModel] = useState<TikTokDashboardModel | null>(null);
const [data, setData] = useState<DashboardData | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
const refreshTimerRef = useRef<NodeJS.Timeout>();
/**
* Initialize model and fetch data
*/
const initializeModel = useCallback(async () => {
try {
setLoading(true);
setError(null);
// Get access token from Lark SDK
// In actual implementation, use Lark SDK to get current user's access token
const accessToken = await getAccessToken();
const newModel = await createTikTokDashboardModel({
appToken: bitableAppToken,
tableId: bitableTableId,
accessToken,
});
setModel(newModel);
// Get dashboard data
const dashboardData = newModel.getDashboardData();
setData(dashboardData);
// Notify parent
if (onDataUpdate) {
onDataUpdate(dashboardData);
}
setLoading(false);
} catch (err) {
const error = err instanceof Error ? err : new Error('Failed to load data');
setError(error);
setLoading(false);
if (onError) {
onError(error);
}
}
}, [bitableTableId, bitableAppToken, onDataUpdate, onError]);
/**
* Refresh data
*/
const refresh = useCallback(async () => {
if (!model) return;
try {
await model.fetchData();
const dashboardData = model.getDashboardData();
setData(dashboardData);
if (onDataUpdate) {
onDataUpdate(dashboardData);
}
} catch (err) {
const error = err instanceof Error ? err : new Error('Failed to refresh data');
setError(error);
if (onError) {
onError(error);
}
}
}, [model, onDataUpdate, onError]);
/**
* Update filter
*/
const updateFilter = useCallback(
async (startDate: string, endDate: string) => {
if (!model) return;
model.updateFilter({ startDate, endDate });
const dashboardData = model.getDashboardData();
setData(dashboardData);
if (onDataUpdate) {
onDataUpdate(dashboardData);
}
},
[model, onDataUpdate]
);
/**
* Export chart as image
*/
const exportImage = useCallback(async (): Promise<string> => {
// This would be implemented using VChart's export functionality
// Return base64 encoded image data
return '';
}, []);
/**
* Initialize on mount
*/
useEffect(() => {
initializeModel();
}, [initializeModel]);
/**
* Setup auto-refresh
*/
useEffect(() => {
if (autoRefresh && refreshInterval > 0) {
refreshTimerRef.current = setInterval(refresh, refreshInterval);
return () => {
if (refreshTimerRef.current) {
clearInterval(refreshTimerRef.current);
}
};
}
}, [autoRefresh, refreshInterval, refresh]);
/**
* Expose methods to parent (for aPaaS API)
*/
useEffect(() => {
// Register component methods
// In actual aPaaS implementation, expose these via component API
(window as any).tiktokDashboardMethods = {
refresh,
exportImage,
updateFilter,
};
}, [refresh, exportImage, updateFilter]);
if (loading) {
return (
<div
style={{
width,
height,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: 14,
color: '#646A73',
}}
>
Loading TikTok Analytics...
</div>
);
}
if (error) {
return (
<div
style={{
width,
height,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
fontSize: 14,
color: '#F54A45',
padding: 20,
textAlign: 'center',
}}
>
<div style={{ fontWeight: 500, marginBottom: 8 }}>Error Loading Dashboard</div>
<div>{error.message}</div>
</div>
);
}
if (!data) {
return (
<div
style={{
width,
height,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: 14,
color: '#8F959E',
}}
>
No data available
</div>
);
}
// Render appropriate chart based on type
if (chartType === 'dashboard') {
return (
<div style={{ width, height, padding: 20, backgroundColor: '#F7F8FA' }}>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 20, height: '100%' }}>
{showViewsOverTime && (
<div style={{ gridColumn: '1 / -1', backgroundColor: 'white', borderRadius: 8, padding: 16 }}>
<h3 style={{ margin: '0 0 16px 0', fontSize: 16, color: '#1F2329' }}>Views Over Time</h3>
<TikTokAnalyticsChart
chartType="line"
viewsOverTimeData={data.viewsOverTime}
height={300}
onChartReady={onChartReady}
onChartClick={onChartClick}
/>
</div>
)}
{showTopVideos && (
<div style={{ backgroundColor: 'white', borderRadius: 8, padding: 16 }}>
<h3 style={{ margin: '0 0 16px 0', fontSize: 16, color: '#1F2329' }}>
Top Videos by {topVideosMetric.charAt(0).toUpperCase() + topVideosMetric.slice(1)}
</h3>
<TikTokAnalyticsChart
chartType="bar"
topVideosData={data.topVideos}
topVideosMetric={topVideosMetric}
height={400}
onChartReady={onChartReady}
onChartClick={onChartClick}
/>
</div>
)}
{showEngagement && (
<div style={{ backgroundColor: 'white', borderRadius: 8, padding: 16 }}>
<h3 style={{ margin: '0 0 16px 0', fontSize: 16, color: '#1F2329' }}>Engagement Breakdown</h3>
<TikTokAnalyticsChart
chartType="pie"
engagementData={data.engagement}
height={400}
onChartReady={onChartReady}
onChartClick={onChartClick}
/>
</div>
)}
</div>
</div>
);
}
// Single chart rendering
return (
<TikTokAnalyticsChart
chartType={chartType}
viewsOverTimeData={chartType === 'line' ? data.viewsOverTime : undefined}
topVideosData={chartType === 'bar' ? data.topVideos : undefined}
engagementData={chartType === 'pie' ? data.engagement : undefined}
width={width}
height={height}
topVideosMetric={topVideosMetric}
onChartReady={onChartReady}
onChartClick={onChartClick}
/>
);
};
/**
* Get access token from Lark SDK
* In actual implementation, use Lark's SDK methods
*/
async function getAccessToken(): Promise<string> {
// Example using Lark SDK
// return await lark.bitable.getTableAccessToken();
// For now, return empty string (should be replaced with actual implementation)
return '';
}
export default TikTokAnalyticsDashboard;