diff --git a/package.json b/package.json index 5bfb3f1..0a737e9 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "react-router-dom": "^6.6.2", "react-virtualized": "git+https://git@github.com/remorses/react-virtualized-fixed-import.git#9.22.3", "recoil": "^0.7.6", + "string-similarity": "^4.0.4", "styled-components": "^5.3.6", "swr": "^2.0.0", "vite-electron-plugin": "^0.8.2" @@ -50,6 +51,7 @@ "@types/node": "^18.11.18", "@types/react": "^18.0.26", "@types/react-dom": "^18.0.10", + "@types/string-similarity": "^4.0.0", "@types/styled-components": "^5.1.26", "@vitejs/plugin-react": "^3.0.1", "cross-env": "^7.0.3", diff --git a/src/features/tier/TierTable/TierTable.tsx b/src/features/tier/TierTable/TierTable.tsx index a44a3f8..211f8e9 100644 --- a/src/features/tier/TierTable/TierTable.tsx +++ b/src/features/tier/TierTable/TierTable.tsx @@ -1,15 +1,20 @@ +import { useState } from 'react'; import { Controller } from 'react-hook-form'; import { useNavigate } from 'react-router-dom'; import { Table, Tooltip } from 'antd'; +import Search from 'antd/es/input/Search'; import clsx from 'clsx'; import dayjs from 'dayjs'; +import { uniqBy } from 'lodash'; import RankingVariation from '~/components/RankingVariation'; import ChampionProfileSmall from '~/features/asset/ChampionProfileSmall'; import LaneSelect from '~/features/lane/LaneSelect'; -import useAPI from '~/hooks/useAPI'; import { useCustomForm } from '~/hooks/useCustomForm'; +import { useLaneTiers } from '~/repositories/tier/useLaneTiers'; +import { useTiers } from '~/repositories/tier/useTiers'; +import { compareChampionName, isEqualChampionName } from '~/utils/champion'; import { TierTableStyled } from './styled'; @@ -29,12 +34,22 @@ const TierTable = ({ className }: TierTableProps) => { const lane = form.watch('lane'); - const { data = [], isLoading } = useAPI('ps', `/tiers/${lane}`, { - dedupingInterval: 1000 * 60 * 5, - }); + const { data, isLoading } = useLaneTiers(lane); + const { data: tiers } = useTiers(); + + const [inputtedChampionName, setInputtedChampionName] = useState(''); + + const searchedTiers = uniqBy( + tiers + .filter(tier => isEqualChampionName(tier.championInfo.nameKr, inputtedChampionName)) + .sort(tier => compareChampionName(tier.championInfo.nameKr, inputtedChampionName)), + tier => tier.championId, + ); const updatedAt = data[0]?.updatedAt; + const viewingData = inputtedChampionName === '' ? data : searchedTiers; + return (
@@ -55,7 +70,27 @@ const TierTable = ({ className }: TierTableProps) => { } + render={({ field }) => ( +
+ +
+ { + setInputtedChampionName(e.target.value); + }} + onSearch={_ => { + const highScoreTier = searchedTiers[0]; + + if (highScoreTier === undefined) { + return; + } + + navigate(`/champ/${highScoreTier.championId}`); + }} + /> +
+
+ )} />
@@ -160,8 +195,8 @@ const TierTable = ({ className }: TierTableProps) => { width: 100, }, ]} - dataSource={data} - rowKey={record => record.ranking} + dataSource={viewingData} + rowKey={record => String(record.championId + record.laneId + record.championInfo.nameKr)} loading={isLoading} pagination={false} scroll={{ y: 600 }} diff --git a/src/features/tier/TierTable/styled.ts b/src/features/tier/TierTable/styled.ts index 81630bd..02d24c2 100644 --- a/src/features/tier/TierTable/styled.ts +++ b/src/features/tier/TierTable/styled.ts @@ -74,4 +74,17 @@ export const TierTableStyled = styled.div` } } } + + .lane-selection { + display: flex; + justify-content: space-between; + align-items: center; + } + + .champion-search { + max-width: 400px; + } + .champion-search .ant-input { + padding: 4px 12px; + } `; diff --git a/src/repositories/tier/types.ts b/src/repositories/tier/types.ts new file mode 100644 index 0000000..694c59a --- /dev/null +++ b/src/repositories/tier/types.ts @@ -0,0 +1,23 @@ +export interface ChampionStats { + count: number; + pickRate: number; + banRate: number; + winRate: number; + opScore: number; + opTier: number; + isHoney: boolean; + isOp: boolean; + ranking: number; + rankingVariation: number; + updatedAt: string; + championId: number; + laneId: number; + honeyScore: number; + overallRanking: number; + overallRankingVariation: number; + championInfo: { + nameKr: string; + nameUs: string; + nameCn: string; + }; +} diff --git a/src/repositories/tier/useLaneTiers.ts b/src/repositories/tier/useLaneTiers.ts new file mode 100644 index 0000000..50d7910 --- /dev/null +++ b/src/repositories/tier/useLaneTiers.ts @@ -0,0 +1,14 @@ +import useAPI from '~/hooks/useAPI'; + +import { ChampionStats } from './types'; + +export const useLaneTiers = (lane: number) => { + const { data = [], isLoading } = useAPI('ps', `/tiers/${lane}`, { + dedupingInterval: 1000 * 60 * 5, + }); + + return { + data, + isLoading, + }; +}; diff --git a/src/repositories/tier/useTiers.ts b/src/repositories/tier/useTiers.ts new file mode 100644 index 0000000..af628cc --- /dev/null +++ b/src/repositories/tier/useTiers.ts @@ -0,0 +1,21 @@ +import { LANE } from '~/types/lane'; + +import { useLaneTiers } from './useLaneTiers'; + +export const useTiers = () => { + const { data: topLaneTiers } = useLaneTiers(LANE.TOP); + const { data: jungleLaneTiers } = useLaneTiers(LANE.JUNGLE); + const { data: midLaneTiers } = useLaneTiers(LANE.MID); + const { data: ADLaneTiers } = useLaneTiers(LANE.ADC); + const { data: supporterLaneTiers } = useLaneTiers(LANE.SUPPORT); + + const allLaneTiers = [ + ...topLaneTiers, + ...jungleLaneTiers, + ...midLaneTiers, + ...ADLaneTiers, + ...supporterLaneTiers, + ]; + + return { data: allLaneTiers }; +}; diff --git a/src/types/lane.ts b/src/types/lane.ts new file mode 100644 index 0000000..37b3a1e --- /dev/null +++ b/src/types/lane.ts @@ -0,0 +1,7 @@ +export enum LANE { + TOP = 0, + JUNGLE = 1, + MID = 2, + ADC = 3, + SUPPORT = 4, +} diff --git a/src/utils/champion.ts b/src/utils/champion.ts new file mode 100644 index 0000000..130902f --- /dev/null +++ b/src/utils/champion.ts @@ -0,0 +1,15 @@ +import { compareTwoStrings } from 'string-similarity'; + +// TODO: 초성검색 +export const isEqualChampionName = (target: string, reference: string) => { + return target.toLowerCase().includes(reference.toLowerCase()); +}; + +// TODO: 한글 +const THRESHOLD = 0.8; +export const compareChampionName = (a: string, b: string, threshold = THRESHOLD) => { + if (a === b) { + return 0; + } + return compareTwoStrings(a, b) > threshold ? 1 : -1; +}; diff --git a/yarn.lock b/yarn.lock index e77400f..8d8af86 100644 --- a/yarn.lock +++ b/yarn.lock @@ -866,6 +866,11 @@ resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.13.tgz#da4bfd73f49bd541d28920ab0e2bf0ee80f71c91" integrity sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw== +"@types/string-similarity@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@types/string-similarity/-/string-similarity-4.0.0.tgz#8cc03d5d1baad2b74530fe6c7d849d5768d391ad" + integrity sha512-dMS4S07fbtY1AILG/RhuwmptmzK1Ql8scmAebOTJ/8iBtK/KI17NwGwKzu1uipjj8Kk+3mfPxum56kKZE93mzQ== + "@types/styled-components@^5.1.26": version "5.1.26" resolved "https://registry.yarnpkg.com/@types/styled-components/-/styled-components-5.1.26.tgz#5627e6812ee96d755028a98dae61d28e57c233af" @@ -4563,6 +4568,11 @@ string-convert@^0.2.0: resolved "https://registry.yarnpkg.com/string-convert/-/string-convert-0.2.1.tgz#6982cc3049fbb4cd85f8b24568b9d9bf39eeff97" integrity sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A== +string-similarity@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/string-similarity/-/string-similarity-4.0.4.tgz#42d01ab0b34660ea8a018da8f56a3309bb8b2a5b" + integrity sha512-/q/8Q4Bl4ZKAPjj8WerIBJWALKkaPRfrvhfF8k/B23i4nzrlRj2/go1m90In7nG/3XDSbOo0+pu6RvCTM9RGMQ== + string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"