diff --git a/src/features/items/do-fetch-items.js b/src/features/items/do-fetch-items.js index 65b6c66..6c805d0 100644 --- a/src/features/items/do-fetch-items.js +++ b/src/features/items/do-fetch-items.js @@ -6,62 +6,155 @@ const NOTES = { '60a2828e8689911a226117f9': `Can't store Pillbox, Day Pack, LK 3F or MBSS inside`, }; -const QueryBody = JSON.stringify({ +const ItemPropertiesPart = ` +id +bsgCategoryId +name +shortName +basePrice +normalizedName +types +width +height +avg24hPrice +wikiLink +changeLast48h +low24hPrice +high24hPrice +lastLowPrice +gridImageLink +iconLink +updated +traderPrices { + price + trader { + name + } +} +sellFor { + source + price + requirements { + type + value + } + currency +} +buyFor { + source + price + currency + requirements { + type + value + } +} +containsItems { + count + item { + id + } +} +`; + +const AllItemsQueryBody = JSON.stringify({ query: `{ itemsByType(type:any){ - id - bsgCategoryId - name - shortName - basePrice - normalizedName - types - width - height - avg24hPrice - wikiLink - changeLast48h - low24hPrice - high24hPrice - lastLowPrice - gridImageLink - iconLink - updated - traderPrices { - price - trader { - name - } - } - sellFor { - source - price - requirements { - type - value - } - currency - } - buyFor { - source - price - currency - requirements { - type - value - } - } - containsItems { - count - item { - id - } - } + ${ItemPropertiesPart} } }`, }); -const doFetchItems = async (...a) => { +const getItemByNormalizedNameQueryBody = (normalizedName) => + JSON.stringify({ + query: `query ItemByNormalizedName($normalizedName: String!){ + itemByNormalizedName(normalizedName:$normalizedName){ + ${ItemPropertiesPart} + } + }`, + variables: { + normalizedName, + }, + }); + +const mapItem = (rawItem) => { + let item = { ...rawItem }; + + item.buyFor = item.buyFor.sort((a, b) => { + return ( + getRublePrice(a.price, a.currency) - + getRublePrice(b.price, b.currency) + ); + }); + + if (!Array.isArray(item.linkedItems)) { + item.linkedItems = []; + } + + item.sellFor = item.sellFor.map((sellPrice) => { + return { + ...sellPrice, + source: camelcaseToDashes(sellPrice.source), + }; + }); + + item.buyFor = item.buyFor.map((buyPrice) => { + return { + ...buyPrice, + source: camelcaseToDashes(buyPrice.source), + }; + }); + + item = { + ...item, + fee: calculateFee(item.avg24hPrice, item.basePrice), + fallbackImageLink: `${process.env.PUBLIC_URL}/images/unknown-item-icon.jpg`, + slots: item.width * item.height, + iconLink: item.iconLink, + grid: false, + notes: NOTES[item.id], + traderPrices: item.traderPrices.map((traderPrice) => { + return { + price: traderPrice.price, + trader: traderPrice.trader.name, + }; + }), + canHoldItems: undefined, + equipmentSlots: [], + }; + + const bestTraderPrice = item.traderPrices + .sort((a, b) => { + return b.price - a.price; + }) + .shift(); + + item.traderPrice = bestTraderPrice?.price || 0; + item.traderName = bestTraderPrice?.trader || '?'; + + return item; +}; + +export const doFetchItemByNormalizedName = async (normalizedName) => { + const response = await fetch('https://tarkov-tools.com/graphql', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json', + }, + body: getItemByNormalizedNameQueryBody(normalizedName), + }).then((response) => response.json()); + + if (!response?.data?.itemByNormalizedName) { + return null; + } + + const item = mapItem(response?.data?.itemByNormalizedName); + + return item; +}; + +const doFetchItems = async () => { const [itemData, itemGrids, itemProps] = await Promise.all([ fetch('https://tarkov-tools.com/graphql', { method: 'POST', @@ -69,7 +162,7 @@ const doFetchItems = async (...a) => { 'Content-Type': 'application/json', Accept: 'application/json', }, - body: QueryBody, + body: AllItemsQueryBody, }).then((response) => response.json()), fetch(`${process.env.PUBLIC_URL}/data/item-grids.min.json`).then( (response) => response.json(), @@ -78,9 +171,9 @@ const doFetchItems = async (...a) => { (response) => response.json(), ), ]); - - const allItems = itemData.data.itemsByType.map((rawItem) => { + const allItems = itemData.data.itemsByType.map((rawItemIn) => { let grid = false; + let rawItem = mapItem(rawItemIn); rawItem.itemProperties = itemProps[rawItem.id]?.itemProperties || {}; rawItem.linkedItems = itemProps[rawItem.id]?.linkedItems || {}; @@ -124,31 +217,6 @@ const doFetchItems = async (...a) => { } } - rawItem.buyFor = rawItem.buyFor.sort((a, b) => { - return ( - getRublePrice(a.price, a.currency) - - getRublePrice(b.price, b.currency) - ); - }); - - if (!Array.isArray(rawItem.linkedItems)) { - rawItem.linkedItems = []; - } - - rawItem.sellFor = rawItem.sellFor.map((sellPrice) => { - return { - ...sellPrice, - source: camelcaseToDashes(sellPrice.source), - }; - }); - - rawItem.buyFor = rawItem.buyFor.map((buyPrice) => { - return { - ...buyPrice, - source: camelcaseToDashes(buyPrice.source), - }; - }); - if (rawItem.itemProperties.defAmmo) { rawItem.defAmmo = rawItem.itemProperties.defAmmo; @@ -176,19 +244,7 @@ const doFetchItems = async (...a) => { return { ...rawItem, - fee: calculateFee(rawItem.avg24hPrice, rawItem.basePrice), - fallbackImageLink: `${process.env.PUBLIC_URL}/images/unknown-item-icon.jpg`, - slots: rawItem.width * rawItem.height, - // iconLink: `https://assets.tarkov-tools.com/${rawItem.id}-icon.jpg`, - iconLink: rawItem.iconLink, grid: grid, - notes: NOTES[rawItem.id], - traderPrices: rawItem.traderPrices.map((traderPrice) => { - return { - price: traderPrice.price, - trader: traderPrice.trader.name, - }; - }), canHoldItems: itemProps[rawItem.id]?.canHoldItems, equipmentSlots: itemProps[rawItem.id]?.slots || [], allowedAmmoIds: itemProps[rawItem.id]?.allowedAmmoIds, @@ -210,9 +266,9 @@ const doFetchItems = async (...a) => { localTraderPrice.price = item.containsItems.reduce( (previousValue, currentValue) => { - const partPrice = itemMap[ + const partPrice = itemMap?.[ currentValue.item.id - ].traderPrices.find( + ]?.traderPrices.find( (innerTraderPrice) => innerTraderPrice.name === localTraderPrice.name, ); @@ -236,9 +292,9 @@ const doFetchItems = async (...a) => { sellFor.price = item.containsItems.reduce( (previousValue, currentValue) => { - const partPrice = itemMap[ + const partPrice = itemMap?.[ currentValue.item.id - ].sellFor.find( + ]?.sellFor.find( (innerSellFor) => innerSellFor.source === sellFor.source, ); diff --git a/src/features/items/queries.js b/src/features/items/queries.js index 55f9632..918e88e 100644 --- a/src/features/items/queries.js +++ b/src/features/items/queries.js @@ -1,5 +1,5 @@ import { useQuery } from 'react-query'; -import doFetchItems from './do-fetch-items'; +import doFetchItems, { doFetchItemByNormalizedName } from './do-fetch-items'; export const useItemsQuery = (queryOptions) => { const itemsQuery = useQuery('items', () => doFetchItems(), { @@ -29,7 +29,21 @@ export const useItemByNameQuery = (itemName, queryOptions) => { ...queryOptions, }); - return itemQuery; + // Do a fast query to only fetch a basic version of the item we want + const singleItemQuery = useQuery( + ['items', itemName], + () => doFetchItemByNormalizedName(itemName), + { + enabled: !Boolean(itemQuery.data), + }, + ); + + // But when we have all data, use that + if (itemQuery.data) { + return itemQuery; + } + + return singleItemQuery; }; export const useItemsWithTypeQuery = (type, queryOptions) => { diff --git a/src/pages/item/index.js b/src/pages/item/index.js index 38ce524..bee694e 100644 --- a/src/pages/item/index.js +++ b/src/pages/item/index.js @@ -285,7 +285,6 @@ function Item() { {currentItemData.name} )} -

{t('Stats')}

- + {Boolean(currentItemData?.itemProperties) && ( + <> +

{t('Stats')}

+ + + )}