feat: implement multi-word AND search in all views and initialize RuFlo V3

This commit is contained in:
Christian Vidal Wolf
2026-04-24 12:20:14 +02:00
parent b662acddd7
commit 97fe06eedd
11 changed files with 639 additions and 37 deletions
+9 -4
View File
@@ -148,10 +148,15 @@ export function DimensionsView({ data, headers, onEdit, onSaveRow, onCaptureStat
if (search || lineFilter.length > 0 || classFilter.length > 0) {
const s = search.toLowerCase();
result = result.filter(g => {
const matchesSearch = !search || g.rows.some(({ row }) =>
String(row[COLUMNS.ARTICLE_NO] || '').toLowerCase().includes(s) ||
String(row[COLUMNS.ARTICLE_NAME] || '').toLowerCase().includes(s)
);
const matchesSearch = !search || (() => {
const terms = search.toLowerCase().split(/\s+/).filter(Boolean);
if (terms.length === 0) return true;
return g.rows.some(({ row }) => {
const articleNo = String(row[COLUMNS.ARTICLE_NO] || '').toLowerCase();
const articleName = String(row[COLUMNS.ARTICLE_NAME] || '').toLowerCase();
return terms.every(term => articleNo.includes(term) || articleName.includes(term));
});
})();
const matchesLine = lineFilter.length === 0 || g.rows.some(({ row }) => lineFilter.includes(String(row[COLUMNS.LINE] || '')));
const matchesClass = classFilter.length === 0 || g.rows.some(({ row }) => classFilter.includes(String(row[COLUMNS.CLASSIFICATION] || '')));
+7 -5
View File
@@ -78,11 +78,13 @@ export function HistoryView({ headers, data, onRevert, onEdit }: HistoryViewProp
}).format(date);
};
const filteredHistory = history.filter(entry =>
entry.article_name?.toLowerCase().includes(search.toLowerCase()) ||
entry.product_id.toLowerCase().includes(search.toLowerCase()) ||
entry.changed_by?.toLowerCase().includes(search.toLowerCase())
);
const filteredHistory = history.filter(entry => {
const terms = search.toLowerCase().split(/\s+/).filter(Boolean);
if (terms.length === 0) return true;
const searchableText = `${entry.article_name} ${entry.product_id} ${entry.changed_by}`.toLowerCase();
return terms.every(term => searchableText.includes(term));
});
if (loading) {
return (
+9 -5
View File
@@ -203,12 +203,16 @@ export function MissingDataView({ data, headers, onSaveRow, onCaptureState }: Mi
}
}
// Global search (SKU + Name) - Multi-word AND support
if (search) {
const s = search.toLowerCase();
result = result.filter(r =>
String(r.row[COLUMNS.ARTICLE_NO] || '').toLowerCase().includes(s) ||
String(r.row[COLUMNS.ARTICLE_NAME] || '').toLowerCase().includes(s)
);
const terms = search.toLowerCase().split(/\s+/).filter(Boolean);
if (terms.length > 0) {
result = result.filter(r => {
const articleNo = String(r.row[COLUMNS.ARTICLE_NO] || '').toLowerCase();
const articleName = String(r.row[COLUMNS.ARTICLE_NAME] || '').toLowerCase();
return terms.every(term => articleNo.includes(term) || articleName.includes(term));
});
}
}
(Object.entries(columnFilters) as [string, string[]][]).forEach(([colIdx, filterValues]) => {
+9 -6
View File
@@ -19,12 +19,15 @@ export function PendingValidationView({ data, pendingRows, rowStatuses, onRevert
const pendingEntries = Object.entries(pendingRows);
const filteredEntries = search
? pendingEntries.filter(([articleNo, { articleName }]) =>
articleNo.toLowerCase().includes(search.toLowerCase()) ||
articleName.toLowerCase().includes(search.toLowerCase())
)
: pendingEntries;
const filteredEntries = (() => {
const terms = search.toLowerCase().split(/\s+/).filter(Boolean);
if (terms.length === 0) return pendingEntries;
return pendingEntries.filter(([articleNo, { articleName }]) => {
const searchableText = `${articleNo} ${articleName}`.toLowerCase();
return terms.every(term => searchableText.includes(term));
});
})();
const getFieldDiff = (original: ExcelRow, updated: ExcelRow, colIndex: number) => {
const orig = original[colIndex];
+19 -12
View File
@@ -114,12 +114,16 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
const searchSuggestions = useMemo(() => {
if (!search && selectedSearchItems.size === 0) return data.slice(0, 50);
if (!search) return [];
const s = search.toLowerCase();
return data.filter(r =>
String(r[COLUMNS.ARTICLE_NO] || '').toLowerCase().includes(s) ||
String(r[COLUMNS.ARTICLE_NAME] || '').toLowerCase().includes(s)
).slice(0, 50);
}, [data, search, selectedSearchItems]);
const terms = search.toLowerCase().split(/\s+/).filter(Boolean);
if (terms.length === 0) return data.slice(0, 50);
return data.filter(r => {
const articleNo = String(r[COLUMNS.ARTICLE_NO] || '').toLowerCase();
const articleName = String(r[COLUMNS.ARTICLE_NAME] || '').toLowerCase();
return terms.every(term => articleNo.includes(term) || articleName.includes(term));
}).slice(0, 50);
}, [data, search, selectedSearchItems, COLUMNS]);
const handleSearchItemClick = (index: number) => {
const newSelected = new Set(selectedSearchItems);
@@ -310,13 +314,16 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
case 'units_errors': result = analyzedRows.filter(r => r.unitErrors.length > 0); break;
}
// Global search (SKU + Name)
// Global search (SKU + Name) - Multi-word AND support
if (search) {
const s = search.toLowerCase();
result = result.filter(r =>
String(r.row[COLUMNS.ARTICLE_NO] || '').toLowerCase().includes(s) ||
String(r.row[COLUMNS.ARTICLE_NAME] || '').toLowerCase().includes(s)
);
const terms = search.toLowerCase().split(/\s+/).filter(Boolean);
if (terms.length > 0) {
result = result.filter(r => {
const articleNo = String(r.row[COLUMNS.ARTICLE_NO] || '').toLowerCase();
const articleName = String(r.row[COLUMNS.ARTICLE_NAME] || '').toLowerCase();
return terms.every(term => articleNo.includes(term) || articleName.includes(term));
});
}
}
// Selected items from search dropdown
+8 -5
View File
@@ -65,11 +65,14 @@ export function ProductDescriptions({ data, headers, asinColumnIndex, onEdit, ro
// Search filter
if (search) {
const s = search.toLowerCase();
result = result.filter(r =>
String(r.row[COLUMNS.ARTICLE_NO] || '').toLowerCase().includes(s) ||
String(r.row[COLUMNS.ARTICLE_NAME] || '').toLowerCase().includes(s)
);
const terms = search.toLowerCase().split(/\s+/).filter(Boolean);
if (terms.length > 0) {
result = result.filter(r => {
const articleNo = String(r.row[COLUMNS.ARTICLE_NO] || '').toLowerCase();
const articleName = String(r.row[COLUMNS.ARTICLE_NAME] || '').toLowerCase();
return terms.every(term => articleNo.includes(term) || articleName.includes(term));
});
}
}
// Dropdown filters