mirror of
https://github.com/christianvidalwolf-prog/CrazeAnalytix.git
synced 2026-08-03 13:35:24 +02:00
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>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
fed39b18be
commit
2bce0a839b
@@ -0,0 +1,114 @@
|
||||
|
||||
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,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user