Files
CrazeAnalytix/services/supabase.ts
T
Christian Vidal WolfandClaude Opus 4.6 2bce0a839b feat: add Supabase vendor data integration (BSR, ratings, buy box)
- Create vendor_daily_data table schema and Supabase client service
- Add upload API route for Vendor Central CSV parsing and upsert
- Add VendorDataView with BSR trend, ratings, and buy box charts
- Integrate new Vendor tab into app navigation (desktop + mobile)
- Add vendor CSV upload card to FileUpload modal

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 13:19:25 +01:00

115 lines
2.8 KiB
TypeScript

import { createClient } from '@supabase/supabase-js';
const supabaseUrl = process.env.SUPABASE_URL || '';
const supabaseAnonKey = process.env.SUPABASE_ANON_KEY || '';
export const supabase = createClient(supabaseUrl, supabaseAnonKey);
export interface VendorDailyRow {
id?: number;
date: string;
market: string;
asin: string;
product_title: string | null;
tags: string | null;
bsr_top_rank: number | null;
bsr_top_category: string | null;
bsr_detail_rank: number | null;
bsr_detail_category: string | null;
avg_rating: number | null;
num_reviews: number | null;
buybox_owner: string | null;
buybox_price: number | null;
amazon_has_buybox: boolean | null;
glance_views: number | null;
}
export interface VendorFilters {
markets?: string[];
tags?: string[];
asins?: string[];
dateFrom?: string;
dateTo?: string;
}
export const fetchVendorData = async (filters: VendorFilters): Promise<VendorDailyRow[]> => {
const allRows: VendorDailyRow[] = [];
let offset = 0;
const pageSize = 1000;
let hasMore = true;
while (hasMore) {
let query = supabase
.from('vendor_daily_data')
.select('*')
.order('date', { ascending: true })
.range(offset, offset + pageSize - 1);
if (filters.markets?.length) {
query = query.in('market', filters.markets);
}
if (filters.tags?.length) {
query = query.in('tags', filters.tags);
}
if (filters.asins?.length) {
query = query.in('asin', filters.asins);
}
if (filters.dateFrom) {
query = query.gte('date', filters.dateFrom);
}
if (filters.dateTo) {
query = query.lte('date', filters.dateTo);
}
const { data, error } = await query;
if (error) throw error;
if (data) {
allRows.push(...data);
hasMore = data.length === pageSize;
offset += pageSize;
} else {
hasMore = false;
}
}
return allRows;
};
export const fetchVendorFilterOptions = async (): Promise<{
markets: string[];
tags: string[];
dateRange: { min: string; max: string } | null;
}> => {
const { data: marketData } = await supabase
.from('vendor_daily_data')
.select('market');
const { data: tagData } = await supabase
.from('vendor_daily_data')
.select('tags');
const { data: dateMin } = await supabase
.from('vendor_daily_data')
.select('date')
.order('date', { ascending: true })
.limit(1);
const { data: dateMax } = await supabase
.from('vendor_daily_data')
.select('date')
.order('date', { ascending: false })
.limit(1);
const markets = [...new Set((marketData || []).map(r => r.market))].sort();
const tags = [...new Set((tagData || []).map(r => r.tags).filter(Boolean))].sort();
return {
markets,
tags,
dateRange: dateMin?.[0] && dateMax?.[0]
? { min: dateMin[0].date, max: dateMax[0].date }
: null,
};
};