mirror of
https://github.com/christianvidalwolf-prog/CrazeAnalytix.git
synced 2026-08-03 14:45:24 +02:00
feat: implement bulk search for ASINs/SKUs in DataGrid
This commit is contained in:
@@ -69,6 +69,7 @@ const App: React.FC = () => {
|
|||||||
stock: [],
|
stock: [],
|
||||||
vendorStock: [],
|
vendorStock: [],
|
||||||
woc: [],
|
woc: [],
|
||||||
|
bulkSearch: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
const top50Mode = useMemo(() => {
|
const top50Mode = useMemo(() => {
|
||||||
|
|||||||
+76
-29
@@ -249,6 +249,7 @@ const DataGrid: React.FC<DataGridProps> = ({ data, hasCustomerFilter, adsData, s
|
|||||||
const [visibleMetrics, setVisibleMetrics] = useState<('sellOut' | 'units')[]>(['sellOut', 'units']);
|
const [visibleMetrics, setVisibleMetrics] = useState<('sellOut' | 'units')[]>(['sellOut', 'units']);
|
||||||
const [showAdsMetrics, setShowAdsMetrics] = useState(true);
|
const [showAdsMetrics, setShowAdsMetrics] = useState(true);
|
||||||
const [showAttributedSales, setShowAttributedSales] = useState(false);
|
const [showAttributedSales, setShowAttributedSales] = useState(false);
|
||||||
|
const [showBulkSearch, setShowBulkSearch] = useState(false);
|
||||||
const [showYTD, setShowYTD] = useState(false);
|
const [showYTD, setShowYTD] = useState(false);
|
||||||
const [showOnlyTop50, setShowOnlyTop50] = useState(false);
|
const [showOnlyTop50, setShowOnlyTop50] = useState(false);
|
||||||
const [showDimensionMenu, setShowDimensionMenu] = useState(false);
|
const [showDimensionMenu, setShowDimensionMenu] = useState(false);
|
||||||
@@ -413,16 +414,40 @@ const DataGrid: React.FC<DataGridProps> = ({ data, hasCustomerFilter, adsData, s
|
|||||||
|
|
||||||
// 0. Global Search Filter
|
// 0. Global Search Filter
|
||||||
if (searchTerm) {
|
if (searchTerm) {
|
||||||
const term = searchTerm.toLowerCase().trim();
|
const rawTerm = searchTerm.trim();
|
||||||
result = result.filter(row => {
|
// Check if it looks like a bulk search (contains newlines or commas)
|
||||||
return (
|
const isBulk = rawTerm.includes('\n') || rawTerm.includes(',') || rawTerm.includes(' ');
|
||||||
(row.sku?.toLowerCase().includes(term)) ||
|
|
||||||
(row.asin?.toLowerCase().includes(term)) ||
|
if (isBulk) {
|
||||||
(row.title?.toLowerCase().includes(term)) ||
|
const searchTerms = rawTerm
|
||||||
(row.line?.toLowerCase().includes(term)) ||
|
.split(/[\s,\n]+/)
|
||||||
(row.customer?.toLowerCase().includes(term))
|
.map(t => t.trim().toLowerCase())
|
||||||
);
|
.filter(t => t.length > 0);
|
||||||
});
|
|
||||||
|
if (searchTerms.length > 0) {
|
||||||
|
result = result.filter(row => {
|
||||||
|
const rowSku = row.sku?.toLowerCase() || '';
|
||||||
|
const rowAsin = row.asin?.toLowerCase() || '';
|
||||||
|
const rowTitle = row.title?.toLowerCase() || '';
|
||||||
|
return searchTerms.some(term =>
|
||||||
|
rowSku.includes(term) ||
|
||||||
|
rowAsin.includes(term) ||
|
||||||
|
rowTitle.includes(term)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const term = rawTerm.toLowerCase();
|
||||||
|
result = result.filter(row => {
|
||||||
|
return (
|
||||||
|
(row.sku?.toLowerCase().includes(term)) ||
|
||||||
|
(row.asin?.toLowerCase().includes(term)) ||
|
||||||
|
(row.title?.toLowerCase().includes(term)) ||
|
||||||
|
(row.line?.toLowerCase().includes(term)) ||
|
||||||
|
(row.customer?.toLowerCase().includes(term))
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. Filter
|
// 1. Filter
|
||||||
@@ -1329,25 +1354,47 @@ const DataGrid: React.FC<DataGridProps> = ({ data, hasCustomerFilter, adsData, s
|
|||||||
<div className="flex flex-col md:flex-row justify-between items-start md:items-center gap-4 bg-slate-900/50 p-4 rounded-xl border border-white/5 shadow-lg">
|
<div className="flex flex-col md:flex-row justify-between items-start md:items-center gap-4 bg-slate-900/50 p-4 rounded-xl border border-white/5 shadow-lg">
|
||||||
<div className="flex flex-wrap items-center gap-4 w-full md:w-auto">
|
<div className="flex flex-wrap items-center gap-4 w-full md:w-auto">
|
||||||
{/* Search Bar */}
|
{/* Search Bar */}
|
||||||
<div className="relative w-full md:w-80">
|
<div className="flex flex-col gap-2 w-full md:w-auto">
|
||||||
<input
|
<div className="relative w-full md:w-80">
|
||||||
type="text"
|
{showBulkSearch ? (
|
||||||
placeholder="Search SKU, Title, or ASIN..."
|
<textarea
|
||||||
value={searchTerm}
|
placeholder="Paste ASINs or SKUs (separated by commas or newlines)..."
|
||||||
onChange={(e) => setSearchTerm(e.target.value)}
|
value={searchTerm}
|
||||||
className="w-full bg-slate-950 border border-white/10 rounded-xl px-4 py-2 text-sm text-white focus:outline-none focus:ring-2 focus:ring-indigo-500 transition-all pl-10"
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
/>
|
className="w-full bg-slate-950 border border-white/10 rounded-xl px-4 py-2 text-sm text-white focus:outline-none focus:ring-2 focus:ring-indigo-500 transition-all min-h-[100px] font-mono"
|
||||||
<svg className="absolute left-3 top-2.5 w-4 h-4 text-slate-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
/>
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
) : (
|
||||||
</svg>
|
<>
|
||||||
{searchTerm && (
|
<input
|
||||||
<button
|
type="text"
|
||||||
onClick={() => setSearchTerm('')}
|
placeholder="Search SKU, Title, or ASIN..."
|
||||||
className="absolute right-3 top-2.5 text-slate-500 hover:text-white"
|
value={searchTerm}
|
||||||
>
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
<CloseIcon />
|
className="w-full bg-slate-950 border border-white/10 rounded-xl px-4 py-2 text-sm text-white focus:outline-none focus:ring-2 focus:ring-indigo-500 transition-all pl-10"
|
||||||
</button>
|
/>
|
||||||
)}
|
<svg className="absolute left-3 top-2.5 w-4 h-4 text-slate-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||||
|
</svg>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{searchTerm && (
|
||||||
|
<button
|
||||||
|
onClick={() => setSearchTerm('')}
|
||||||
|
className={`absolute right-3 ${showBulkSearch ? 'top-2' : 'top-2.5'} text-slate-500 hover:text-white`}
|
||||||
|
>
|
||||||
|
<CloseIcon />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={() => setShowBulkSearch(!showBulkSearch)}
|
||||||
|
className={`text-[10px] uppercase tracking-wider font-bold px-3 py-1 rounded-lg border transition-all self-start ${showBulkSearch
|
||||||
|
? 'bg-indigo-500/20 border-indigo-500/50 text-indigo-400 shadow-[0_0_10px_rgba(99,102,241,0.2)]'
|
||||||
|
: 'bg-slate-800/50 border-white/5 text-slate-400 hover:text-slate-200 hover:border-white/10'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{showBulkSearch ? '← Back to Single Search' : 'Bulk Search Mode'}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="h-8 w-px bg-white/10 hidden md:block" />
|
<div className="h-8 w-px bg-white/10 hidden md:block" />
|
||||||
|
|||||||
@@ -843,7 +843,24 @@ export const filterAdsData = (
|
|||||||
const stockMatch = checkStockFilter(meta?.sku || '', filters.stock, stockMap);
|
const stockMatch = checkStockFilter(meta?.sku || '', filters.stock, stockMap);
|
||||||
const vendorStockMatch = checkVendorStockFilter(asin, filters.vendorStock, vendorStockMap, top50Mode);
|
const vendorStockMatch = checkVendorStockFilter(asin, filters.vendorStock, vendorStockMap, top50Mode);
|
||||||
|
|
||||||
return countryMatch && yearMatch && weekMatch && asinMatch && skuMatch && lineMatch && stockMatch && vendorStockMatch;
|
// Bulk Search Logic
|
||||||
|
let bulkMatch = true;
|
||||||
|
if (filters.bulkSearch && filters.bulkSearch.trim()) {
|
||||||
|
const searchTerms = filters.bulkSearch
|
||||||
|
.split(/[\s,\n]+/)
|
||||||
|
.map(t => t.trim().toUpperCase())
|
||||||
|
.filter(t => t.length > 0);
|
||||||
|
|
||||||
|
if (searchTerms.length > 0) {
|
||||||
|
const itemAsin = (asin || '').toUpperCase();
|
||||||
|
const itemSku = (meta?.sku || '').toUpperCase();
|
||||||
|
bulkMatch = searchTerms.some(term =>
|
||||||
|
itemAsin.includes(term) || itemSku.includes(term)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return countryMatch && yearMatch && weekMatch && asinMatch && skuMatch && lineMatch && stockMatch && vendorStockMatch && bulkMatch;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -873,6 +890,23 @@ export const filterData = (
|
|||||||
const skuMatch = filters.sku.length === 0 || filters.sku.includes(item.sku);
|
const skuMatch = filters.sku.length === 0 || filters.sku.includes(item.sku);
|
||||||
const titleMatch = filters.title.length === 0 || filters.title.includes(item.title);
|
const titleMatch = filters.title.length === 0 || filters.title.includes(item.title);
|
||||||
|
|
||||||
|
// Bulk Search Logic
|
||||||
|
let bulkMatch = true;
|
||||||
|
if (filters.bulkSearch && filters.bulkSearch.trim()) {
|
||||||
|
const searchTerms = filters.bulkSearch
|
||||||
|
.split(/[\s,\n]+/)
|
||||||
|
.map(t => t.trim().toUpperCase())
|
||||||
|
.filter(t => t.length > 0);
|
||||||
|
|
||||||
|
if (searchTerms.length > 0) {
|
||||||
|
const itemAsin = (item.asin || '').toUpperCase();
|
||||||
|
const itemSku = (item.sku || '').toUpperCase();
|
||||||
|
bulkMatch = searchTerms.some(term =>
|
||||||
|
itemAsin.includes(term) || itemSku.includes(term)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Week Logic: Match "W1", "W2" etc.
|
// Week Logic: Match "W1", "W2" etc.
|
||||||
// item.week is a number (e.g. 1), filter uses strings "W1"
|
// item.week is a number (e.g. 1), filter uses strings "W1"
|
||||||
const weekStr = item.week ? `W${item.week}` : '';
|
const weekStr = item.week ? `W${item.week}` : '';
|
||||||
@@ -882,7 +916,7 @@ export const filterData = (
|
|||||||
const stockMatch = checkStockFilter(item.sku, filters.stock, stockMap);
|
const stockMatch = checkStockFilter(item.sku, filters.stock, stockMap);
|
||||||
const vendorStockMatch = checkVendorStockFilter(item.asin, filters.vendorStock, vendorStockMap, top50Mode);
|
const vendorStockMatch = checkVendorStockFilter(item.asin, filters.vendorStock, vendorStockMap, top50Mode);
|
||||||
|
|
||||||
return customerMatch && yearMatch && monthMatch && lineMatch && asinMatch && skuMatch && titleMatch && weekMatch && stockMatch && vendorStockMatch;
|
return customerMatch && yearMatch && monthMatch && lineMatch && asinMatch && skuMatch && titleMatch && bulkMatch && weekMatch && stockMatch && vendorStockMatch;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ export interface FilterState {
|
|||||||
stock: string[]; // Added Stock filter
|
stock: string[]; // Added Stock filter
|
||||||
vendorStock: string[]; // Added Vendor Stock filter
|
vendorStock: string[]; // Added Vendor Stock filter
|
||||||
woc: string[]; // Added Weeks of Coverage filter
|
woc: string[]; // Added Weeks of Coverage filter
|
||||||
|
bulkSearch: string; // Added Bulk Search support
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GrowthMetric {
|
export interface GrowthMetric {
|
||||||
|
|||||||
Reference in New Issue
Block a user