/**
* aPaaS Custom Component ViewModel
*
* This file implements the data handling and business logic
* for the TikTok Analytics Dashboard component
*/
import { TikTokVideoRecord, DashboardData } from '../../src/vchart-component/types';
import { TikTokDataTransformer } from '../../src/vchart-component/utils';
export interface BitableConfig {
appToken: string;
tableId: string;
accessToken?: string;
}
export interface DataFilter {
startDate?: string;
endDate?: string;
minViews?: number;
videoIds?: string[];
}
export class TikTokDashboardModel {
private config: BitableConfig;
private records: TikTokVideoRecord[] = [];
private filter: DataFilter = {};
private isLoading = false;
private error: Error | null = null;
constructor(config: BitableConfig) {
this.config = config;
}
/**
* Fetch data from Bitable
*/
async fetchData(): Promise<TikTokVideoRecord[]> {
this.isLoading = true;
this.error = null;
try {
// In actual implementation, use Lark Open API to fetch Bitable records
// Example using fetch:
const response = await fetch(
`https://open.feishu.cn/open-apis/bitable/v1/apps/${this.config.appToken}/tables/${this.config.tableId}/records`,
{
method: 'GET',
headers: {
'Authorization': `Bearer ${this.config.accessToken}`,
'Content-Type': 'application/json',
},
}
);
if (!response.ok) {
throw new Error(`Failed to fetch data: ${response.statusText}`);
}
const data = await response.json();
// Transform Bitable records to TikTokVideoRecord format
this.records = this.transformBitableRecords(data.data.items);
return this.getFilteredRecords();
} catch (error) {
this.error = error instanceof Error ? error : new Error('Unknown error');
throw this.error;
} finally {
this.isLoading = false;
}
}
/**
* Transform Bitable API response to TikTokVideoRecord
*/
private transformBitableRecords(items: any[]): TikTokVideoRecord[] {
return items.map((item) => {
const fields = item.fields;
return {
video_id: fields.video_id || '',
video_title: fields.video_title || '',
publish_date: fields.publish_date || '',
views: Number(fields.views) || 0,
likes: Number(fields.likes) || 0,
comments: Number(fields.comments) || 0,
shares: Number(fields.shares) || 0,
saves: Number(fields.saves) || 0,
duration_seconds: Number(fields.duration_seconds) || 0,
video_url: fields.video_url || '',
};
});
}
/**
* Get filtered records based on current filter
*/
getFilteredRecords(): TikTokVideoRecord[] {
let filtered = [...this.records];
// Apply date filter
if (this.filter.startDate || this.filter.endDate) {
filtered = filtered.filter((record) => {
const date = new Date(record.publish_date);
if (this.filter.startDate && date < new Date(this.filter.startDate)) {
return false;
}
if (this.filter.endDate && date > new Date(this.filter.endDate)) {
return false;
}
return true;
});
}
// Apply min views filter
if (this.filter.minViews) {
filtered = filtered.filter((record) => record.views >= this.filter.minViews!);
}
// Apply video IDs filter
if (this.filter.videoIds && this.filter.videoIds.length > 0) {
filtered = filtered.filter((record) => this.filter.videoIds!.includes(record.video_id));
}
return filtered;
}
/**
* Update filter and return filtered records
*/
updateFilter(filter: DataFilter): TikTokVideoRecord[] {
this.filter = { ...this.filter, ...filter };
return this.getFilteredRecords();
}
/**
* Clear all filters
*/
clearFilter(): TikTokVideoRecord[] {
this.filter = {};
return this.getFilteredRecords();
}
/**
* Get dashboard data (transformed for charts)
*/
getDashboardData(): DashboardData {
const filtered = this.getFilteredRecords();
return TikTokDataTransformer.toDashboardData(filtered);
}
/**
* Get data for specific chart type
*/
getChartData(chartType: 'line' | 'bar' | 'pie') {
const filtered = this.getFilteredRecords();
switch (chartType) {
case 'line':
return TikTokDataTransformer.toViewsOverTime(filtered);
case 'bar':
return TikTokDataTransformer.toTopVideos(filtered);
case 'pie':
return TikTokDataTransformer.toEngagementBreakdown(filtered);
default:
throw new Error(`Unknown chart type: ${chartType}`);
}
}
/**
* Get aggregated metrics
*/
getMetrics() {
const filtered = this.getFilteredRecords();
return TikTokDataTransformer.calculateMetrics(filtered);
}
/**
* Get loading state
*/
getIsLoading(): boolean {
return this.isLoading;
}
/**
* Get error state
*/
getError(): Error | null {
return this.error;
}
/**
* Get raw records
*/
getRecords(): TikTokVideoRecord[] {
return this.records;
}
/**
* Update configuration
*/
updateConfig(config: Partial<BitableConfig>): void {
this.config = { ...this.config, ...config };
}
}
/**
* Create model instance with automatic data fetching
*/
export async function createTikTokDashboardModel(
config: BitableConfig
): Promise<TikTokDashboardModel> {
const model = new TikTokDashboardModel(config);
await model.fetchData();
return model;
}