Skip to content

Commit

Permalink
feat: entity sort and search (#731)
Browse files Browse the repository at this point in the history
  • Loading branch information
gabehamilton authored Mar 29, 2024
1 parent 40d62be commit 94206c3
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 62 deletions.
38 changes: 19 additions & 19 deletions src/Entities/Examples/ComponentsPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,25 +22,21 @@ const entityTable = "metadata";
const user = "dataplatform_near";
const collection = `${user}_${entityIndexer}_${entityTable}`;

const sortTypes = [
{ text: "Most Stars", value: "{ star_count: desc }, { block_height: desc }" },
{ text: "Least Stars", value: "{ star_count: asc }, { block_height: desc }" },
{ text: "Name A-Z", value: "{ component_name: asc }" },
{ text: "Name Z-A", value: "{ component_name: desc }" },
{ text: "Newest", value: "{ block_height: desc }" },
{ text: "Oldest", value: "{ block_height: asc }" },
];
const buildQueries = (searchKey, sort) => {
const queryFilter = searchKey ? `name: {_ilike: "%${searchKey}%"}` : "";
let querySortOption = "";
switch (sort) {
case "z-a":
querySortOption = `{ component_name: desc },`;
break;
case "a-z":
querySortOption = `{ component_name: asc },`;
break;
default:
querySortOption = "{ block_height: desc },";
}

const queryFilter = searchKey ? `component_name: {_ilike: "%${searchKey}%"}` : "";
return `
query ListQuery($offset: Int, $limit: Int) {
${collection}(
where: {${queryFilter}}
order_by: [${querySortOption} { block_height: desc }],
order_by: [${sort} { block_height: desc }],
offset: $offset, limit: $limit) {
block_height
block_timestamp_ms
Expand All @@ -58,7 +54,9 @@ query ListQuery($offset: Int, $limit: Int) {
tags
website
}
${collection}_aggregate {
${collection}_aggregate(
where: {${queryFilter}}
) {
aggregate {
count
}
Expand All @@ -80,8 +78,10 @@ const renderItem = (item, editFunction) => {
);
};
return (
<Widget
src="${REPL_ACCOUNT}/widget/Entities.Template.EntityList"
props={{ loadItems, buildQueries, queryName, collection, renderItem, createWidget, schema }}
/>
<div className="gateway-page-container">
<Widget
src="${REPL_ACCOUNT}/widget/Entities.Template.EntityList"
props={{ loadItems, buildQueries, queryName, collection, renderItem, createWidget, schema, sortTypes }}
/>
</div>
);
4 changes: 2 additions & 2 deletions src/Entities/Examples/FavoriteCars.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
return (
<>
<div className="gateway-page-container">
<p>A custom message</p>
<Widget
src="${REPL_ACCOUNT}/widget/Entities.Template.GenericEntityConfig"
Expand All @@ -11,5 +11,5 @@ return (
//todo defaultImage: "some car photo",
}}
/>
</>
</div>
);
10 changes: 6 additions & 4 deletions src/Entities/Examples/FavoritePlaces.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
return (
<Widget
src="${REPL_ACCOUNT}/widget/Entities.Template.GenericEntityConfig"
props={{ namespace: "examples", entityType: "favoritePlace", title: "Favorite Place" }}
/>
<div className="gateway-page-container">
<Widget
src="${REPL_ACCOUNT}/widget/Entities.Template.GenericEntityConfig"
props={{ namespace: "examples", entityType: "favoritePlace", title: "Favorite Place" }}
/>
</div>
);
132 changes: 115 additions & 17 deletions src/Entities/Template/EntityList.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,25 @@ const { schema, description, buildQueries, queryName, collection, renderItem, cr

const finalCreateWidget = createWidget ?? `${REPL_ACCOUNT}/widget/Entities.Template.EntityCreate`;

const sortTypes = props.sortTypes ?? [
{ text: "Most Stars", value: "{ stars: desc }, { id: desc }" },
{ text: "Least Stars", value: "{ stars: asc }, { id: desc }" },
{ text: "Name A-Z", value: "{ display_name: asc }" },
{ text: "Name Z-A", value: "{ display_name: desc }" },
{ text: "Newest", value: "{ created_at: desc }" },
{ text: "Oldest", value: "{ created_at: asc }" },
{ text: "Newest Updates", value: "{ updated_at: desc }" },
{ text: "Oldest Updates", value: "{ updated_at: asc }" },
];

const [searchKey, setSearchKey] = useState("");
const [sort, setSort] = useState("");
const [items, setItems] = useState([]);
const [totalItems, setTotalItems] = useState(0);
const [sort, setSort] = useState(sortTypes[0].value);
const [items, setItems] = useState({ list: [], total: 0 });
const [showCreateModal, setShowCreateModal] = useState(false);
const [activeItem, setActiveItem] = useState(null);

const [debounceTimer, setDebounceTimer] = useState(null);

const closeModal = () => {
setActiveItem(null);
setShowCreateModal(false);
Expand All @@ -28,26 +40,46 @@ const editFunction = (item) => {
setShowCreateModal(true);
};
const onLoad = (newItems, totalItems) => {
setItems([...items, ...newItems]);
setTotalItems(totalItems);
setItems({ list: [...items.list, ...newItems], total: totalItems });
};
const onLoadReset = (newItems, totalItems) => {
setItems({ list: newItems, total: totalItems });
};
const loadItemsUseState = (isResetOrPageNumber) => {
const loader = isResetOrPageNumber === true ? onLoadReset : onLoad;
const offset = isResetOrPageNumber === true ? 0 : items.list.length;
return loadItems(buildQueries(searchKey, sort), queryName, offset, collection, loader);
};
const loadItemsUseState = () =>
loadItems(buildQueries(searchKey, sort), queryName, items.length ?? 0, collection, onLoad);
useEffect(() => {
setItems([]);
loadItemsUseState();
}, [sort, searchKey]);
if (debounceTimer) clearTimeout(debounceTimer);
const search = (searchKey ?? "").toLowerCase();
setItems({
list: items.list.filter((item) => (item.display_name ?? "").toLowerCase().includes(search)),
total: items.total,
});
setDebounceTimer(
setTimeout(() => {
loadItemsUseState(true);
}, 500),
);
return () => clearTimeout(debounceTimer);
}, [searchKey]);
useEffect(() => {
loadItemsUseState(true);
}, [sort]);

const Wrapper = styled.div`
display: flex;
flex-direction: column;
gap: 48px;
padding-left: 24px;
`;

const Header = styled.div`
display: flex;
flex-direction: column;
gap: 12px;
padding: 24px 0 12px 0px;
`;

const Search = styled.div`
Expand All @@ -57,7 +89,40 @@ const Search = styled.div`
width: 100%;
}
`;
const SortContainer = styled.div`
display: flex;
justify-content: flex-end;
align-items: flex-end;
@media (max-width: 1200px) {
padding: 12px;
}
`;
const Sort = styled.div`
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-end;
gap: 16px;
& > span.label {
font-family: "Inter";
font-style: normal;
font-weight: 600;
font-size: 14px;
line-height: 18px;
color: #11181c;
white-space: nowrap;
}
&:last-child {
width: 40%;
@media screen and (max-width: 768px) {
width: 85%;
}
}
`;
const H1 = styled.h1`
font-weight: 600;
font-size: 24px;
Expand Down Expand Up @@ -116,17 +181,17 @@ const ScrollBox = styled.div`
`;

return (
<Wrapper className="gateway-page-container">
<Wrapper>
<Header>
<div className="row">
<div className="col">
<H2>
{totalItems} {schema.entityTitle + (totalItems !== 1 ? "s" : "")}
{items.total} {schema.entityTitle + (items.total !== 1 ? "s" : "")}
</H2>
{description && <Text>{description}</Text>}
</div>
{context.accountId && (
<div className="col-3">
<div className="col-3" style={{ textAlign: "right" }}>
<Widget
src="${REPL_ACCOUNT}/widget/DIG.Button"
props={{
Expand Down Expand Up @@ -159,14 +224,45 @@ return (
</div>
)}
</div>
<div className="row">
<div className="col">
<Search>
<Widget
src="${REPL_ACCOUNT}/widget/DIG.Input"
props={{
label: "",
placeholder: "Search by name",
value: searchKey,
onChange: (e) => setSearchKey(e.target.value),
}}
/>
</Search>
</div>
<div className="col">
<SortContainer>
<Sort>
<Widget
src={`${REPL_ACCOUNT}/widget/Select`}
props={{
noLabel: true,
value: { text: sortTypes.find((it) => it.value === sort), value: sort },
onChange: ({ value }) => setSort(value),
options: sortTypes,
border: "none",
}}
/>
</Sort>
</SortContainer>
</div>
</div>
</Header>

{items.length > 0 && (
{items.total > 0 && (
<InfiniteScroll
className="mb-5"
pageStart={0}
loadMore={loadItemsUseState}
hasMore={totalItems !== items.length}
hasMore={items.total > items.list.length}
initialLoad={false}
loader={
<div className="loader">
Expand All @@ -176,10 +272,12 @@ return (
}
>
{props.table ? (
items.map((item) => <div key={`${item.accountId}-${item.widgetName}`}>{renderItem(item, editFunction)}</div>)
items.list.map((item) => (
<div key={`${item.accountId}-${item.widgetName}`}>{renderItem(item, editFunction)}</div>
))
) : (
<Items>
{items.map((item) => (
{items.list.map((item) => (
<Item key={`${item.accountId}-${item.widgetName}`}>{renderItem(item, editFunction)}</Item>
))}
</Items>
Expand Down
32 changes: 12 additions & 20 deletions src/Entities/Template/GenericEntityConfig.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,23 +22,12 @@ const standardFields = ["id", "accountId", "name", "displayName", "logoUrl", "at
const attributeFields = dataFields.filter((key) => !standardFields.includes(key));
const ns = namespace ? namespace : "default";
const buildQueries = (searchKey, sort) => {
const queryFilter = searchKey ? `name: {_ilike: "%${searchKey}%"}` : "";
let querySortOption;
switch (sort) {
case "z-a":
querySortOption = `{ name: desc },`;
break;
case "a-z":
querySortOption = `{ name: asc },`;
break;
default:
querySortOption = "{ stars: desc }, { id: desc }";
}
const queryFilter = searchKey ? `display_name: {_ilike: "%${searchKey}%"}` : "";
return `
query ListQuery($offset: Int, $limit: Int) {
${collection}(
where: {namespace: {_eq: "${ns}"}, entity_type: {_eq: "${entityType}"}, ${queryFilter}}
order_by: [${querySortOption} ],
order_by: [${sort} ],
offset: $offset, limit: $limit) {
id
account_id
Expand Down Expand Up @@ -132,7 +121,7 @@ const Button = styled.button`
background: ${(p) => (p.primary ? "rgb(112 242 164)" : "#ECEDEE")};
}
`;
const renderItem = (rawItem, editFunction) => {
const defaultRenderTableItem = (rawItem, editFunction) => {
const item = convertSnakeToPascal(rawItem);
const { accountId, name, displayName, logoUrl, attributes } = item;
const itemComponent = item.component ? item.component : `${REPL_ACCOUNT}/widget/Entities.Template.EntityDetails`;
Expand Down Expand Up @@ -161,9 +150,11 @@ const renderItem = (rawItem, editFunction) => {
<div className="col-2">
<Link to={detailsLink}>{displayName}</Link>
</div>
{attributeFields.map((key) => (
<div className="col-1">{attributes[key]}</div>
))}
{attributeFields.map((key) => {
const value = attributes[key];
const formattedValue = value?.length > 50 ? value.substring(0, 50) + "..." : value;
return <div className="col-1">{formattedValue}</div>;
})}
<div className="col-2">
<Actions>
<Widget
Expand Down Expand Up @@ -205,6 +196,7 @@ const renderItem = (rawItem, editFunction) => {
/>
<Widget
src="${REPL_ACCOUNT}/widget/SocialIndexActionButton"
key={name}
props={{
actionName: "star",
actionUndoName: "unstar",
Expand Down Expand Up @@ -252,13 +244,13 @@ const renderItem = (rawItem, editFunction) => {
};

const createWidget = "${REPL_ACCOUNT}/widget/Entities.Template.EntityCreate";

const renderItem = props.renderItem ? props.renderItem : defaultRenderTableItem;
const table = props.renderItem ? false : true;
return (
<div>
<h4>{title}</h4>
<Widget
src="${REPL_ACCOUNT}/widget/Entities.Template.EntityList"
props={{ table: true, buildQueries, queryName, collection, renderItem, createWidget, schema }}
props={{ table, buildQueries, queryName, collection, renderItem, createWidget, schema }}
/>
</div>
);

0 comments on commit 94206c3

Please sign in to comment.