mirror of
https://github.com/christianvidalwolf-prog/CrazeAnalytix.git
synced 2026-08-03 13:45:23 +02:00
Fix Vendor filters: add year, date range, and ASIN search
- Add year filter with multi-select dropdown - Add date range pickers (from/to) - Fix tags filter using ilike for partial matching - Add clear filters button - Update supabase service with proper year filtering Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
co-authored by
Qwen-Coder
parent
ae6b8bfb9d
commit
a4919691b2
@@ -109,11 +109,15 @@ const VendorDataView: React.FC = () => {
|
|||||||
// Filter options
|
// Filter options
|
||||||
const [availableMarkets, setAvailableMarkets] = useState<string[]>([]);
|
const [availableMarkets, setAvailableMarkets] = useState<string[]>([]);
|
||||||
const [availableTags, setAvailableTags] = useState<string[]>([]);
|
const [availableTags, setAvailableTags] = useState<string[]>([]);
|
||||||
|
const [availableYears, setAvailableYears] = useState<number[]>([]);
|
||||||
|
|
||||||
// Selected filters
|
// Selected filters
|
||||||
const [selectedMarkets, setSelectedMarkets] = useState<string[]>([]);
|
const [selectedMarkets, setSelectedMarkets] = useState<string[]>([]);
|
||||||
const [selectedTags, setSelectedTags] = useState<string[]>([]);
|
const [selectedTags, setSelectedTags] = useState<string[]>([]);
|
||||||
|
const [selectedYears, setSelectedYears] = useState<number[]>([]);
|
||||||
const [asinSearch, setAsinSearch] = useState('');
|
const [asinSearch, setAsinSearch] = useState('');
|
||||||
|
const [dateFrom, setDateFrom] = useState('');
|
||||||
|
const [dateTo, setDateTo] = useState('');
|
||||||
|
|
||||||
// Load filter options on mount
|
// Load filter options on mount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -122,6 +126,14 @@ const VendorDataView: React.FC = () => {
|
|||||||
const opts = await fetchVendorFilterOptions();
|
const opts = await fetchVendorFilterOptions();
|
||||||
setAvailableMarkets(opts.markets);
|
setAvailableMarkets(opts.markets);
|
||||||
setAvailableTags(opts.tags);
|
setAvailableTags(opts.tags);
|
||||||
|
// Extract years from date range
|
||||||
|
if (opts.dateRange) {
|
||||||
|
const minYear = new Date(opts.dateRange.min).getFullYear();
|
||||||
|
const maxYear = new Date(opts.dateRange.max).getFullYear();
|
||||||
|
const years: number[] = [];
|
||||||
|
for (let y = minYear; y <= maxYear; y++) years.push(y);
|
||||||
|
setAvailableYears(years);
|
||||||
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error('Failed to load vendor filter options', e);
|
console.error('Failed to load vendor filter options', e);
|
||||||
}
|
}
|
||||||
@@ -138,6 +150,9 @@ const VendorDataView: React.FC = () => {
|
|||||||
markets: selectedMarkets.length > 0 ? selectedMarkets : undefined,
|
markets: selectedMarkets.length > 0 ? selectedMarkets : undefined,
|
||||||
tags: selectedTags.length > 0 ? selectedTags : undefined,
|
tags: selectedTags.length > 0 ? selectedTags : undefined,
|
||||||
asins: asinSearch.trim() ? asinSearch.split(',').map(a => a.trim()).filter(Boolean) : undefined,
|
asins: asinSearch.trim() ? asinSearch.split(',').map(a => a.trim()).filter(Boolean) : undefined,
|
||||||
|
dateFrom: dateFrom || undefined,
|
||||||
|
dateTo: dateTo || undefined,
|
||||||
|
years: selectedYears.length > 0 ? selectedYears : undefined,
|
||||||
});
|
});
|
||||||
setVendorRows(data);
|
setVendorRows(data);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
@@ -145,7 +160,7 @@ const VendorDataView: React.FC = () => {
|
|||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}, [selectedMarkets, selectedTags, asinSearch]);
|
}, [selectedMarkets, selectedTags, selectedYears, asinSearch, dateFrom, dateTo]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadData();
|
loadData();
|
||||||
@@ -197,6 +212,26 @@ const VendorDataView: React.FC = () => {
|
|||||||
selected={selectedTags}
|
selected={selectedTags}
|
||||||
onChange={setSelectedTags}
|
onChange={setSelectedTags}
|
||||||
/>
|
/>
|
||||||
|
<MultiSelectDropdown
|
||||||
|
label="Year"
|
||||||
|
options={availableYears.map(String)}
|
||||||
|
selected={selectedYears.map(String)}
|
||||||
|
onChange={(vals) => setSelectedYears(vals.map(v => parseInt(v)))}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="date"
|
||||||
|
value={dateFrom}
|
||||||
|
onChange={(e) => setDateFrom(e.target.value)}
|
||||||
|
className="bg-slate-900 border border-slate-700 rounded-lg px-3 py-2 text-sm text-slate-200 placeholder-slate-500 focus:outline-none focus:ring-1 focus:ring-indigo-500"
|
||||||
|
placeholder="From date"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="date"
|
||||||
|
value={dateTo}
|
||||||
|
onChange={(e) => setDateTo(e.target.value)}
|
||||||
|
className="bg-slate-900 border border-slate-700 rounded-lg px-3 py-2 text-sm text-slate-200 placeholder-slate-500 focus:outline-none focus:ring-1 focus:ring-indigo-500"
|
||||||
|
placeholder="To date"
|
||||||
|
/>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Search ASINs (comma-separated)"
|
placeholder="Search ASINs (comma-separated)"
|
||||||
@@ -204,6 +239,21 @@ const VendorDataView: React.FC = () => {
|
|||||||
onChange={e => setAsinSearch(e.target.value)}
|
onChange={e => setAsinSearch(e.target.value)}
|
||||||
className="bg-slate-900 border border-slate-700 rounded-lg px-3 py-2 text-sm text-slate-200 placeholder-slate-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 w-64"
|
className="bg-slate-900 border border-slate-700 rounded-lg px-3 py-2 text-sm text-slate-200 placeholder-slate-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 w-64"
|
||||||
/>
|
/>
|
||||||
|
{(selectedMarkets.length > 0 || selectedTags.length > 0 || selectedYears.length > 0 || dateFrom || dateTo || asinSearch) && (
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedMarkets([]);
|
||||||
|
setSelectedTags([]);
|
||||||
|
setSelectedYears([]);
|
||||||
|
setDateFrom('');
|
||||||
|
setDateTo('');
|
||||||
|
setAsinSearch('');
|
||||||
|
}}
|
||||||
|
className="text-xs text-slate-400 hover:text-white underline"
|
||||||
|
>
|
||||||
|
Clear filters
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
{loading && (
|
{loading && (
|
||||||
<div className="flex items-center gap-2 text-slate-400 text-sm">
|
<div className="flex items-center gap-2 text-slate-400 text-sm">
|
||||||
<div className="w-4 h-4 border-2 border-slate-600 border-t-indigo-400 rounded-full animate-spin"></div>
|
<div className="w-4 h-4 border-2 border-slate-600 border-t-indigo-400 rounded-full animate-spin"></div>
|
||||||
|
|||||||
+18
-1
@@ -23,6 +23,7 @@ export interface VendorFilters {
|
|||||||
asins?: string[];
|
asins?: string[];
|
||||||
dateFrom?: string;
|
dateFrom?: string;
|
||||||
dateTo?: string;
|
dateTo?: string;
|
||||||
|
years?: number[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const fetchVendorData = async (filters: VendorFilters): Promise<VendorDailyRow[]> => {
|
export const fetchVendorData = async (filters: VendorFilters): Promise<VendorDailyRow[]> => {
|
||||||
@@ -42,7 +43,8 @@ export const fetchVendorData = async (filters: VendorFilters): Promise<VendorDai
|
|||||||
query = query.in('market', filters.markets);
|
query = query.in('market', filters.markets);
|
||||||
}
|
}
|
||||||
if (filters.tags?.length) {
|
if (filters.tags?.length) {
|
||||||
query = query.in('tags', filters.tags);
|
// Use ilike for partial match on tags
|
||||||
|
query = query.or(filters.tags.map(t => `tags.ilike.%${t}%`).join(','));
|
||||||
}
|
}
|
||||||
if (filters.asins?.length) {
|
if (filters.asins?.length) {
|
||||||
query = query.in('asin', filters.asins);
|
query = query.in('asin', filters.asins);
|
||||||
@@ -53,6 +55,21 @@ export const fetchVendorData = async (filters: VendorFilters): Promise<VendorDai
|
|||||||
if (filters.dateTo) {
|
if (filters.dateTo) {
|
||||||
query = query.lte('date', filters.dateTo);
|
query = query.lte('date', filters.dateTo);
|
||||||
}
|
}
|
||||||
|
if (filters.years?.length) {
|
||||||
|
// Filter by year using date range
|
||||||
|
const yearFilters = filters.years.map(y => ({
|
||||||
|
from: `${y}-01-01`,
|
||||||
|
to: `${y}-12-31`
|
||||||
|
}));
|
||||||
|
const dateConditions = yearFilters.flatMap(f => [
|
||||||
|
`date.gte.${f.from}`,
|
||||||
|
`date.lte.${f.to}`
|
||||||
|
]);
|
||||||
|
// Build OR condition for multiple years
|
||||||
|
query = query.or(
|
||||||
|
filters.years.map(y => `and(date.gte.${y}-01-01,date.lte.${y}-12-31)`).join(',')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const { data, error } = await query;
|
const { data, error } = await query;
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
|
|||||||
Reference in New Issue
Block a user