Skip to content

Commit

Permalink
fixes some bugs with multiple filtering & keywords. Switched away fro…
Browse files Browse the repository at this point in the history
…m the search endpoint and added keyword searching to the list parts endpoint
  • Loading branch information
replaysMike committed May 3, 2023
1 parent e496c06 commit 15ffd31
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 70 deletions.
34 changes: 18 additions & 16 deletions Binner/Binner.Web/ClientApp/src/components/PartsGrid2Memoized.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,6 @@ export default function PartsGrid2Memoized(props) {
const navigate = useNavigate();
const location = useLocation();
const [searchParams] = useSearchParams();
var byParam = searchParams.get("by");
var valueParam = searchParams.get("value");
const by = byParam?.split(',') || [];
const byValue = valueParam?.split(',') || [];

const getViewPreference = (preferenceName) => {
return getLocalData(preferenceName, { settingsName: 'partsGridViewPreferences', location })
Expand Down Expand Up @@ -218,25 +214,25 @@ export default function PartsGrid2Memoized(props) {
};

const indexOfBy = (filterBy) => {
for(let i = 0; i < by.length; i++) {
if (by[i] === filterBy) {
for(let i = 0; i < props.by.length; i++) {
if (props.by[i] === filterBy) {
return i;
}
}
return -1;
};

const createFilterBy = (filterByToAdd) => {
const newFilterBy = [...by];
const newFilterBy = [...props.by];

if (!by.includes(filterByToAdd))
if (!newFilterBy.includes(filterByToAdd))
newFilterBy.push(filterByToAdd);
return newFilterBy.join(',');
};

const createFilterByValue = (filterByValueToAdd) => {
const newFilterByValue = [...byValue];
if (!by.includes(filterByValueToAdd))
const newFilterByValue = [...props.byValue];
if (!newFilterByValue.includes(filterByValueToAdd))
newFilterByValue.push(filterByValueToAdd);
return newFilterByValue.join(',');
};
Expand All @@ -246,7 +242,7 @@ export default function PartsGrid2Memoized(props) {
e.preventDefault();
e.stopPropagation();
if (part[propertyName]) {
const url = `${props.visitUrl}?by=${createFilterBy(propertyName)}&value=${createFilterByValue(part[propertyName])}`;
const url = `${props.visitUrl}?by=${createFilterBy(propertyName)}&value=${createFilterByValue(part[propertyName])}&keyword=${props.keyword}`;
navigate(url);
}
};
Expand Down Expand Up @@ -281,7 +277,7 @@ export default function PartsGrid2Memoized(props) {
<div onClick={e => handleSelfLink(e, row.original, columnName)}>
<div className="icon-container small">{getIconForPart(row.original)}
<div>
<Link style={{minWidth: '25px'}} to={`${props.visitUrl}?by=${createFilterBy(columnName)}&value=${createFilterByValue(row.original.partType)}`} onClick={e => handleSelfLink(e, row.original, columnName)}>
<Link style={{minWidth: '25px'}} to={`${props.visitUrl}?by=${createFilterBy(columnName)}&value=${createFilterByValue(row.original.partType)}&keyword=${props.keyword}`} onClick={e => handleSelfLink(e, row.original, columnName)}>
{row.original.partType}
</Link>
</div>
Expand All @@ -294,9 +290,9 @@ export default function PartsGrid2Memoized(props) {

return {...def, Cell: ({row}) => (
<div onClick={e => handleSelfLink(e, row.original, columnName)}>
{by.includes(columnName) && byValue[indexOfBy(columnName, row.original[columnName])]?.toString() === row.original[columnName]?.toString()
{props.by.includes(columnName) && props.byValue[indexOfBy(columnName, row.original[columnName])]?.toString() === row.original[columnName]?.toString()
? <span className='truncate'>{row.original[columnName]}</span>
: <Link style={{minWidth: '25px'}} to={`${props.visitUrl}?by=${createFilterBy(columnName)}&value=${createFilterByValue(row.original[columnName])}`} onClick={e => handleSelfLink(e, row.original, columnName)}>
: <Link style={{minWidth: '25px'}} to={`${props.visitUrl}?by=${createFilterBy(columnName)}&value=${createFilterByValue(row.original[columnName])}&keyword=${props.keyword}`} onClick={e => handleSelfLink(e, row.original, columnName)}>
<span className='truncate'>{row.original[columnName]}</span>
</Link>
}
Expand Down Expand Up @@ -482,7 +478,10 @@ PartsGrid2Memoized.propTypes = {
/** The link to use when clicking on items to filter by */
visitUrl: PropTypes.string,
/** Provides a function to get the default state */
onInit: PropTypes.func
onInit: PropTypes.func,
keyword: PropTypes.string,
by: PropTypes.array,
byValue: PropTypes.array
};

PartsGrid2Memoized.defaultProps = {
Expand All @@ -494,5 +493,8 @@ PartsGrid2Memoized.defaultProps = {
totalRecords: 0,
editable: true,
visitable: true,
visitUrl: '/inventory'
visitUrl: '/inventory',
keyword: '',
by: [],
byValue: []
};
33 changes: 23 additions & 10 deletions Binner/Binner.Web/ClientApp/src/pages/LowInventory.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,12 @@ export function LowInventory (props) {
const totalPages = Math.ceil(data.totalItems / results);
setTotalRecords(data.totalItems);
let newData = [];
if (reset)
newData = [...pageOfData];
else
newData = [...parts, ...pageOfData];
if (pageOfData) {
if (reset)
newData = [...pageOfData];
else
newData = [...parts, ...pageOfData];
}
setParts(newData);
setPage(page);
setTotalPages(totalPages);
Expand Down Expand Up @@ -74,6 +76,10 @@ export function LowInventory (props) {
};
}, [byParam, valueParam, initComplete]);

useEffect(() => {
loadParts(1, true, filterBy, filterByValue, pageSize, sortBy, sortDirection);
}, [filterBy, filterByValue]);

const handleNextPage = async (e, page) => {
await loadParts(page, true);
};
Expand All @@ -94,11 +100,16 @@ export function LowInventory (props) {
}
setFilterBy(newFilterBy);
setFilterByValue(newFilterByValue);
// go
if (newFilterBy.length > 0)
props.history(`/lowstock?by=${newFilterBy.join(',')}&value=${newFilterByValue.join(',')}`);
else
props.history('/lowstock');

// replace the browser url
let newBrowserUrl = '/lowstock';
if (newFilterBy.length > 0 || newFilterByValue.length > 0) {
newBrowserUrl += '?';
if (newFilterBy.length > 0)
newBrowserUrl += `by=${newFilterBy.join(',')}&value=${newFilterByValue.join(',')}`;
}
window.history.pushState(null, null, newBrowserUrl);
setRenderIsDirty(!renderIsDirty);
};

const handlePageSizeChange = async (e, pageSize) => {
Expand Down Expand Up @@ -128,10 +139,12 @@ export function LowInventory (props) {
onPageSizeChange={handlePageSizeChange}
onSortChange={handleSortChange}
onInit={handleInit}
by={filterBy}
byValue={filterByValue}
name='partsGrid'
visitUrl="/lowstock"
>{t('message.noMatchingResults', "No matching results.")}</PartsGrid2Memoized>);
}, [renderIsDirty, parts, page, totalPages, totalRecords, loading]);
}, [renderIsDirty, parts, page, totalPages, totalRecords, loading, filterBy, filterByValue]);

return (
<div>
Expand Down
85 changes: 41 additions & 44 deletions Binner/Binner.Web/ClientApp/src/pages/Search.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import PartsGrid2Memoized from "../components/PartsGrid2Memoized";
import { fetchApi } from "../common/fetchApi";
import { FormHeader } from "../components/FormHeader";
import { BarcodeScannerInput } from "../components/BarcodeScannerInput";
import { CatchingPokemonSharp } from "@mui/icons-material";

export function Search(props) {
const DebounceTimeMs = 400;
Expand Down Expand Up @@ -90,16 +89,20 @@ export function Search(props) {
}
};

const loadParts = async (page, reset = false, by = filterBy, byValue = filterByValue, results = pageSize, orderBy = sortBy, orderDirection = sortDirection) => {
const loadParts = async (page, reset = false, by = filterBy, byValue = filterByValue, results = pageSize, orderBy = sortBy, orderDirection = sortDirection, keyword = null) => {
const response = await fetchApi(
`api/part/list?orderBy=${orderBy || ""}&direction=${orderDirection || ""}&results=${results}&page=${page}&by=${by?.join(',')}&value=${byValue?.join(',')}`
`api/part/list?orderBy=${orderBy || ""}&direction=${orderDirection || ""}&results=${results}&page=${page}&keyword=${keyword || ""}&by=${by?.join(',')}&value=${byValue?.join(',')}`
);
const { data } = response;
const pageOfData = data.items;
const totalPages = Math.ceil(data.totalItems / results);
let newData = [];
if (reset) newData = [...pageOfData];
else newData = [...parts, ...pageOfData];
if (pageOfData) {
if (reset)
newData = [...pageOfData];
else
newData = [...parts, ...pageOfData];
}
setParts(newData);
setPage(page);
setTotalPages(totalPages);
Expand All @@ -109,7 +112,7 @@ export function Search(props) {
return response;
};

const search = useCallback(async (keyword) => {
const search = useCallback(async (keyword, by = filterBy, byValue = filterByValue) => {
Search.abortController.abort(); // Cancel the previous request
Search.abortController = new AbortController();
// if there's a keyword we should clear binning (because they use different endpoints)
Expand Down Expand Up @@ -151,52 +154,33 @@ export function Search(props) {
}, [renderIsDirty]);

const searchDebounced = useMemo(() => debounce(search, DebounceTimeMs), []);
const loadPartsDebounced = useMemo(() => debounce(loadParts, DebounceTimeMs), []);

useEffect(() => {
if (pageSize === -1) return;
if (keywordParam !== keyword) {
if (keywordParam && keywordParam.length > 0) {
// if there's a keyword we should clear binning (because they use different endpoints)
setKeyword(keywordParam);
setFilterBy([]);
setFilterByValue([]);
setPage(1);
search(keywordParam);
} else if (by && by.length > 0) {
// likewise, clear keyword if we're in a bin search
setFilterBy(by);
setFilterByValue(byValue);
setKeyword("");
setPage(1);
loadParts(page, true, by, byValue, pageSize);
} else {
setPage(1);
loadParts(page, true, [], [], pageSize);
}
} else {
if (keyword && keyword.length > 0) search(keyword);
else loadParts(page);
}

setPage(1);
setKeyword(keywordParam || "");
setFilterBy(byParam?.split(',') || []);
setFilterByValue(valueParam?.split(',') || []);
loadParts(1, true, by, byValue, pageSize, sortBy, sortDirection, keywordParam || "");
return () => {
Search.abortController.abort();
};
}, [byParam, valueParam, keywordParam, initComplete]);

useEffect(() => {
loadPartsDebounced(1, true, filterBy, filterByValue, pageSize, sortBy, sortDirection, keyword || "");
}, [filterBy, filterByValue, keyword]);

const handlePartClick = (e, part) => {
props.history(`/inventory/${encodeURIComponent(part.partNumber)}`);
};

const handleNextPage = async (e, page) => {
await loadParts(page, true);
await loadParts(page, true, filterBy, filterByValue, pageSize, sortBy, sortDirection, keyword || "");
};

const handleSearch = (e, control) => {
if (control.value && control.value.length > 0) {
searchDebounced(control.value);
} else {
loadParts(page, true);
}
setKeyword(control.value);
};

Expand All @@ -213,23 +197,33 @@ export function Search(props) {
setFilterBy(newFilterBy);
setFilterByValue(newFilterByValue);
// go
if (newFilterBy.length > 0)
props.history(`/inventory?by=${newFilterBy.join(',')}&value=${newFilterByValue.join(',')}`);
else
props.history('/inventory');

// replace the browser url
let newBrowserUrl = '/inventory';
if (newFilterBy.length > 0 || newFilterByValue.length > 0 || keyword.length > 0) {
newBrowserUrl += '?';
if (newFilterBy.length > 0)
newBrowserUrl += `by=${newFilterBy.join(',')}&value=${newFilterByValue.join(',')}`;
if (newFilterBy.length > 0 && keyword.length > 0)
newBrowserUrl += `&`;
if (keyword.length > 0)
newBrowserUrl += `keyword=${keyword}`;
}
window.history.pushState(null, null, newBrowserUrl);
setRenderIsDirty(!renderIsDirty);
};

const handlePageSizeChange = async (e, pageSize) => {
setPageSize(pageSize);
await loadParts(page, true, filterBy, filterByValue, pageSize);
await loadParts(page, true, filterBy, filterByValue, pageSize, sortBy, sortDirection, keyword || "");
};

const handleSortChange = async (sortBy, sortDirection) => {
const newSortBy = sortBy || "DateCreatedUtc";
const newSortDirection = sortDirection || "Descending";
setSortBy(newSortBy);
setSortDirection(newSortDirection);
return await loadParts(page, true, filterBy, filterByValue, pageSize, newSortBy, newSortDirection);
return await loadParts(page, true, filterBy, filterByValue, pageSize, newSortBy, newSortDirection, keyword || "");
};

const renderPartsTable = useMemo(() => {
Expand All @@ -244,9 +238,12 @@ export function Search(props) {
onPageSizeChange={handlePageSizeChange}
onSortChange={handleSortChange}
onInit={handleInit}
by={filterBy}
byValue={filterByValue}
keyword={keyword}
name="partsGrid"
>{t('message.noMatchingResults', "No matching results.")}</PartsGrid2Memoized>);
}, [renderIsDirty, parts, page, totalPages, totalRecords, loading]);
}, [renderIsDirty, parts, page, totalPages, totalRecords, loading, filterBy, filterByValue]);


return (
Expand All @@ -264,7 +261,7 @@ export function Search(props) {
focus
placeholder={t('page.search.search', "Search")}
icon="search"
value={keyword}
value={keyword || ""}
onChange={handleSearch}
id="keyword"
name="keyword"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1031,6 +1031,24 @@ public async Task<PartType> GetOrCreatePartTypeAsync(PartType partType, IUserCon
}
}

// finally, do any keyword filtering
if (!string.IsNullOrEmpty(request.Keyword))
{
entitiesQueryable = entitiesQueryable.Where(x =>
EF.Functions.Like(x.PartNumber, '%' + request.Keyword + '%')
|| EF.Functions.Like(x.ManufacturerPartNumber, '%' + request.Keyword + '%')
|| EF.Functions.Like(x.Description, '%' + request.Keyword + '%')
|| EF.Functions.Like(x.Manufacturer, '%' + request.Keyword + '%')
|| EF.Functions.Like(x.Keywords, '%' + request.Keyword + '%')
|| EF.Functions.Like(x.DigiKeyPartNumber, '%' + request.Keyword + '%')
|| EF.Functions.Like(x.MouserPartNumber, '%' + request.Keyword + '%')
|| EF.Functions.Like(x.ArrowPartNumber, '%' + request.Keyword + '%')
|| EF.Functions.Like(x.Location, '%' + request.Keyword + '%')
|| EF.Functions.Like(x.BinNumber, '%' + request.Keyword + '%')
|| EF.Functions.Like(x.BinNumber2, '%' + request.Keyword + '%')
);
}

if (additionalPredicate != null)
entitiesQueryable = entitiesQueryable.Where(additionalPredicate);

Expand Down
2 changes: 2 additions & 0 deletions Binner/Library/Binner.Common/Integrations/DigikeyApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -720,6 +720,8 @@ private string GetPower(string power)
private string GetResistance(string resistance)
{
var val = new String(resistance.Where(x => Char.IsDigit(x) || Char.IsPunctuation(x)).ToArray());
if (string.IsNullOrEmpty(val))
val = "0";
var unitsParsed = resistance.Replace(val, "").ToLower();
var units = "ohms";
switch (unitsParsed)
Expand Down
5 changes: 5 additions & 0 deletions Binner/Library/Binner.Model/PaginatedRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,10 @@ public class PaginatedRequest : ISortable, IPaginated
/// Property value to filter by
/// </summary>
public string? Value { get; set; }

/// <summary>
/// Keyword to filter by
/// </summary>
public string? Keyword { get; set; }
}
}

0 comments on commit 15ffd31

Please sign in to comment.