add real offering/seeking counts to item page
This commit is contained in:
parent
90233c0a03
commit
68e6e9e243
3 changed files with 112 additions and 10 deletions
|
@ -552,22 +552,54 @@ function ItemPageWantButton({ itemId, isChecked }) {
|
|||
}
|
||||
|
||||
function ItemPageTradeLinks({ itemId }) {
|
||||
const { data, loading, error } = useQuery(
|
||||
gql`
|
||||
query ItemPageTradeLinks($itemId: ID!) {
|
||||
item(id: $itemId) {
|
||||
id
|
||||
numUsersOfferingThis
|
||||
numUsersSeekingThis
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ variables: { itemId } }
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return <Box color="red.400">{error.message}</Box>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Tooltip label="These are placeholders—real trade lists coming soon!">
|
||||
<HStack spacing="2">
|
||||
<Box as="header" fontSize="sm" fontWeight="bold">
|
||||
Trading:
|
||||
</Box>
|
||||
<ItemPageTradeLink count={8} label="offering" colorScheme="green" />
|
||||
<ItemPageTradeLink count={12} label="seeking" colorScheme="blue" />
|
||||
</HStack>
|
||||
</Tooltip>
|
||||
<HStack spacing="2">
|
||||
<Box as="header" fontSize="sm" fontWeight="bold">
|
||||
Trading:
|
||||
</Box>
|
||||
<SubtleSkeleton isLoaded={!loading}>
|
||||
<ItemPageTradeLink
|
||||
itemId={itemId}
|
||||
count={data?.item?.numUsersOfferingThis || 0}
|
||||
label="offering"
|
||||
colorScheme="green"
|
||||
/>
|
||||
</SubtleSkeleton>
|
||||
<SubtleSkeleton isLoaded={!loading}>
|
||||
<ItemPageTradeLink
|
||||
itemId={itemId}
|
||||
count={data?.item?.numUsersSeekingThis || 0}
|
||||
label="seeking"
|
||||
colorScheme="blue"
|
||||
/>
|
||||
</SubtleSkeleton>
|
||||
</HStack>
|
||||
);
|
||||
}
|
||||
|
||||
function ItemPageTradeLink({ count, label, colorScheme }) {
|
||||
function ItemPageTradeLink({ itemId, count, label, colorScheme }) {
|
||||
return (
|
||||
<Button
|
||||
as="a"
|
||||
// TODO: Link to a new Impress 2020 page instead!
|
||||
href={`https://impress.openneo.net/items/${itemId}`}
|
||||
size="xs"
|
||||
variant="outline"
|
||||
colorScheme={colorScheme}
|
||||
|
|
|
@ -388,6 +388,56 @@ const buildItemAllOccupiedZonesLoader = (db) =>
|
|||
);
|
||||
});
|
||||
|
||||
const buildItemTradeCountsLoader = (db) =>
|
||||
new DataLoader(
|
||||
async (itemIdOwnedPairs) => {
|
||||
const qs = itemIdOwnedPairs
|
||||
.map((_) => "(closet_hangers.item_id = ? AND closet_hangers.owned = ?)")
|
||||
.join(" OR ");
|
||||
const values = itemIdOwnedPairs
|
||||
.map(({ itemId, isOwned }) => [itemId, isOwned])
|
||||
.flat();
|
||||
const [rows, _] = await db.execute(
|
||||
`
|
||||
SELECT
|
||||
items.id AS item_id, closet_hangers.owned AS is_owned,
|
||||
count(DISTINCT closet_hangers.user_id) AS users_count
|
||||
FROM items
|
||||
INNER JOIN closet_hangers ON closet_hangers.item_id = items.id
|
||||
INNER JOIN users ON users.id = closet_hangers.user_id
|
||||
LEFT JOIN closet_lists ON closet_lists.id = closet_hangers.list_id
|
||||
WHERE (
|
||||
(${qs})
|
||||
AND (
|
||||
(closet_hangers.list_id IS NOT NULL AND closet_lists.visibility >= 2)
|
||||
OR (
|
||||
closet_hangers.list_id IS NULL AND closet_hangers.owned = 1
|
||||
AND users.owned_closet_hangers_visibility >= 2
|
||||
)
|
||||
OR (
|
||||
closet_hangers.list_id IS NULL AND closet_hangers.owned = 0
|
||||
AND users.wanted_closet_hangers_visibility >= 2
|
||||
)
|
||||
)
|
||||
)
|
||||
GROUP BY items.id, closet_hangers.owned;
|
||||
`,
|
||||
values
|
||||
);
|
||||
|
||||
const entities = rows.map(normalizeRow);
|
||||
|
||||
return itemIdOwnedPairs.map(({ itemId, isOwned }) => {
|
||||
// NOTE: There may be no matching row, if there are 0 such trades.
|
||||
const entity = entities.find(
|
||||
(e) => e.itemId === itemId && Boolean(e.isOwned) === isOwned
|
||||
);
|
||||
return entity ? entity.usersCount : 0;
|
||||
});
|
||||
},
|
||||
{ cacheKeyFn: ({ itemId, isOwned }) => `${itemId}-${isOwned}` }
|
||||
);
|
||||
|
||||
const buildPetTypeLoader = (db, loaders) =>
|
||||
new DataLoader(async (petTypeIds) => {
|
||||
const qs = petTypeIds.map((_) => "?").join(",");
|
||||
|
@ -811,6 +861,7 @@ function buildLoaders(db) {
|
|||
db
|
||||
);
|
||||
loaders.itemAllOccupiedZonesLoader = buildItemAllOccupiedZonesLoader(db);
|
||||
loaders.itemTradeCountsLoader = buildItemTradeCountsLoader(db);
|
||||
loaders.petTypeLoader = buildPetTypeLoader(db, loaders);
|
||||
loaders.petTypeBySpeciesAndColorLoader = buildPetTypeBySpeciesAndColorLoader(
|
||||
db,
|
||||
|
|
|
@ -22,6 +22,10 @@ const typeDefs = gql`
|
|||
currentUserOwnsThis: Boolean!
|
||||
currentUserWantsThis: Boolean!
|
||||
|
||||
# How many users are offering/seeking this in their public trade lists.
|
||||
numUsersOfferingThis: Int!
|
||||
numUsersSeekingThis: Int!
|
||||
|
||||
# How this item appears on the given species/color combo. If it does not
|
||||
# fit the pet, we'll return an empty ItemAppearance with no layers.
|
||||
appearanceOn(speciesId: ID!, colorId: ID!): ItemAppearance!
|
||||
|
@ -185,6 +189,21 @@ const resolvers = {
|
|||
return closetHangers.some((h) => h.itemId === id && !h.owned);
|
||||
},
|
||||
|
||||
numUsersOfferingThis: async ({ id }, _, { itemTradeCountsLoader }) => {
|
||||
const count = await itemTradeCountsLoader.load({
|
||||
itemId: id,
|
||||
isOwned: true,
|
||||
});
|
||||
return count;
|
||||
},
|
||||
numUsersSeekingThis: async ({ id }, _, { itemTradeCountsLoader }) => {
|
||||
const count = await itemTradeCountsLoader.load({
|
||||
itemId: id,
|
||||
isOwned: false,
|
||||
});
|
||||
return count;
|
||||
},
|
||||
|
||||
appearanceOn: async (
|
||||
{ id },
|
||||
{ speciesId, colorId },
|
||||
|
|
Loading…
Reference in a new issue