import { useState, useEffect, useCallback } from 'react';
import type { TikTokVideoData } from '../../index';
import type { TikTokVideoRecord } from './meta';
import { FIELD_MAPPING } from './meta';
/**
* ViewModel for fetching and transforming TikTok data from Bitable
*/
export function useTikTokData(tableId: string, refreshInterval: number = 0) {
const [data, setData] = useState<TikTokVideoData[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
const fetchData = useCallback(async () => {
try {
setLoading(true);
setError(null);
// Get app token from current context
const appToken = await getAppToken();
// Fetch records from Bitable
const records = await fetchBitableRecords(appToken, tableId);
// Transform records to TikTokVideoData format
const transformedData = transformRecords(records);
setData(transformedData);
setLoading(false);
return transformedData;
} catch (err) {
const error = err instanceof Error ? err : new Error('Failed to fetch data');
setError(error);
setLoading(false);
throw error;
}
}, [tableId]);
useEffect(() => {
fetchData();
// Set up auto-refresh if interval is specified
if (refreshInterval > 0) {
const intervalId = setInterval(() => {
fetchData();
}, refreshInterval * 1000);
return () => clearInterval(intervalId);
}
}, [fetchData, refreshInterval]);
return {
data,
loading,
error,
refresh: fetchData
};
}
/**
* Get app token from Lark aPaaS context
*/
async function getAppToken(): Promise<string> {
// In aPaaS environment, use bitable SDK
if (typeof window !== 'undefined' && (window as any).bitable) {
const { bitable } = (window as any);
const selection = await bitable.base.getSelection();
return selection.baseId;
}
throw new Error('Not running in Lark aPaaS environment');
}
/**
* Fetch records from Bitable table
*/
async function fetchBitableRecords(appToken: string, tableId: string): Promise<TikTokVideoRecord[]> {
if (typeof window !== 'undefined' && (window as any).bitable) {
const { bitable } = (window as any);
// Get table instance
const table = await bitable.base.getTable(tableId);
// Fetch all records
const recordList = await table.getRecords({
pageSize: 500 // Adjust as needed
});
return recordList.records || [];
}
throw new Error('Bitable SDK not available');
}
/**
* Transform Bitable records to TikTokVideoData format
*/
function transformRecords(records: TikTokVideoRecord[]): TikTokVideoData[] {
return records
.map(record => {
const fields = record.fields;
// Skip records with missing required fields
if (!fields[FIELD_MAPPING.videoId] || !fields[FIELD_MAPPING.title]) {
return null;
}
return {
videoId: String(fields[FIELD_MAPPING.videoId] || ''),
title: String(fields[FIELD_MAPPING.title] || 'Untitled'),
views: Number(fields[FIELD_MAPPING.views] || 0),
likes: Number(fields[FIELD_MAPPING.likes] || 0),
comments: Number(fields[FIELD_MAPPING.comments] || 0),
shares: Number(fields[FIELD_MAPPING.shares] || 0),
watchPercent: Number(fields[FIELD_MAPPING.watchPercent] || 0),
datePublished: fields[FIELD_MAPPING.datePublished]
? new Date(fields[FIELD_MAPPING.datePublished]).toISOString()
: new Date().toISOString(),
duration: Number(fields[FIELD_MAPPING.duration] || 0)
};
})
.filter((item): item is TikTokVideoData => item !== null);
}
/**
* Export chart as image
*/
export async function exportChartImage(
chartInstance: any,
format: 'png' | 'svg' | 'jpeg' = 'png'
): Promise<string> {
if (!chartInstance) {
throw new Error('Chart instance not available');
}
try {
const imageData = await chartInstance.exportImg(format);
return imageData;
} catch (err) {
throw new Error('Failed to export chart image: ' + (err as Error).message);
}
}