import { ABILITIES } from '@smogon/calc'; import React, { useCallback, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { toast } from 'react-toastify'; import Button from 'semantic-ui-react/dist/commonjs/elements/Button'; import Icon from 'semantic-ui-react/dist/commonjs/elements/Icon'; import Input from 'semantic-ui-react/dist/commonjs/elements/Input'; import Checkbox from 'semantic-ui-react/dist/commonjs/modules/Checkbox'; import Dropdown from 'semantic-ui-react/dist/commonjs/modules/Dropdown'; import Modal from 'semantic-ui-react/dist/commonjs/modules/Modal'; import { ItemSelector, PkmImage, PokemonSelector } from 'common'; import { MoveSelector, Natures, PokemonType } from 'components'; import { Abilities, RangeSelector } from 'components/Tracker/elements'; import { GAME_GENERATION, GENDERS } from 'constants/constant'; import NATURES from 'constants/natures'; import { POKEMAP } from 'constants/pokemon'; import type { Gender, TEncounter } from 'constants/types'; import { selectItemGeneration, selectNAGeneration } from 'selectors'; import useStore from 'store'; import dropdownStyles from 'assets/styles/Dropdown.module.scss'; import styles from './Detail.module.scss'; interface DetailProps { encounter?: TEncounter; } function Detail({ encounter }: DetailProps): JSX.Element { const { t } = useTranslation('tracker'); const darkMode = useStore(useCallback((state) => state.darkMode, [])); const changeDetails = useStore(useCallback((state) => state.changeDetails, [])); const exportTeamMember = useStore(useCallback((state) => state.exportTeamMember, [])); const exportToGame = useStore(useCallback((state) => state.exportToGame, [])); const selectedGame = useStore(useCallback((state) => state.selectedGame, [])); const gamesList = useStore(useCallback((state) => state.gamesList, [])); const soulink = useStore(useCallback((state) => state.soulink, [])); const isNatureAbilityGen = useStore(selectNAGeneration); const isItemGenderGen = useStore(selectItemGeneration); const foundPokemon = POKEMAP.get(encounter.pokemon); const [show, setShow] = useState(false); const [level, setLevel] = useState(encounter?.details?.level); const [metLevel, setMetLevel] = useState(encounter?.details?.metLevel); const [gender, setGender] = useState(encounter?.details?.gender); const [ability, setAbility] = useState(encounter?.details?.ability); const [nature, setNature] = useState(encounter?.details?.nature); const [item, setItem] = useState(encounter?.details?.item); const [faint, setFaint] = useState(encounter?.details?.faint); const [moveOne, setMoveOne] = useState(encounter?.details?.moves[0]); const [moveTwo, setMoveTwo] = useState(encounter?.details?.moves[1]); const [moveThree, setMoveThree] = useState(encounter?.details?.moves[2]); const [moveFour, setMoveFour] = useState(encounter?.details?.moves[3]); const [shiny, setShiny] = useState(encounter?.details?.shiny); const [ivhp, setIvhp] = useState(encounter?.details?.ivhp); const [ivatk, setIvatk] = useState(encounter?.details?.ivatk); const [ivdef, setIvdef] = useState(encounter?.details?.ivdef); const [ivspatk, setIvspatk] = useState(encounter?.details?.ivspatk); const [ivspeed, setIvspeed] = useState(encounter?.details?.ivspeed); const [ivspdef, setIvspdef] = useState(encounter?.details?.ivspdef); const [evhp, setEvhp] = useState(encounter?.details?.evhp); const [evatk, setEvatk] = useState(encounter?.details?.evatk); const [evdef, setEvdef] = useState(encounter?.details?.evdef); const [evspatk, setEvspatk] = useState(encounter?.details?.evspatk); const [evspeed, setEvspeed] = useState(encounter?.details?.evspeed); const [evspdef, setEvspdef] = useState(encounter?.details?.evspdef); const [soulLink, setSoulLink] = useState(encounter?.details?.soulink); const limitGen = GAME_GENERATION[selectedGame?.value] || undefined; const foundSoulLink = POKEMAP.get(soulLink); const handleClose = () => { setShow(false); setLevel(encounter?.details?.level); setMetLevel(encounter?.details?.metLevel); setGender(encounter?.details?.gender); setAbility(encounter?.details?.ability); setNature(encounter?.details?.nature); setItem(encounter?.details?.item); setFaint(encounter?.details?.faint); setMoveOne(encounter?.details?.moves[0]); setMoveTwo(encounter?.details?.moves[1]); setMoveThree(encounter?.details?.moves[2]); setMoveFour(encounter?.details?.moves[3]); setShiny(encounter?.details?.shiny); setSoulLink(encounter?.details?.soulink); }; const handleSave = () => { changeDetails( encounter.id, level, metLevel, gender, ability, nature, item, faint, moveOne, moveTwo, moveThree, moveFour, shiny, ivhp, ivatk, ivdef, ivspatk, ivspdef, ivspeed, evhp, evatk, evdef, evspatk, evspdef, evspeed, soulLink ); setShow(false); }; const handleExport = () => { exportTeamMember({ ability, id: encounter.pokemon, level, item, nature, moves: [moveOne, moveTwo, moveThree, moveFour], }); toast.success(t('pokemon_export')); }; const handleGameExport = (game: string) => { if (game !== selectedGame?.value) { exportToGame(encounter, game, `From ${selectedGame?.text} - ${new Date().toLocaleString()}`); handleClose(); toast.success(t('pokemon_export')); } }; const handleDeleteSoulLink = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => { e.preventDefault(); e.stopPropagation(); setSoulLink(null); }; return ( <Modal open={show} trigger={ <Button aria-label="edit encounter" basic compact data-testid={`edit-encounter-${encounter.id}`} icon inverted={darkMode} onClick={() => setShow(true)} type="button" > <Icon name="pencil" /> </Button> } > <Modal.Content className={styles.content} scrolling> <div className={styles.header}> <div className={styles.headerLeft}> <div className={styles.image}> <PkmImage name={foundPokemon?.text} shiny={shiny} /> </div> <span className={styles.name}>{foundPokemon.text}</span> </div> <PokemonType pokemon={foundPokemon} /> </div> {soulink && ( <div className={styles.soulink}> <span>Soul Link Pokémon:</span> <PokemonSelector handlePokemon={(pokemonId) => setSoulLink(pokemonId)}> {soulLink ? ( <div className={styles.selector} data-testid={`soullink-${encounter.id}`}> <div className={styles.image}> <PkmImage name={foundSoulLink.text} /> </div> <span className={styles.soulLinkName}>{foundSoulLink.text}</span> <Button aria-label="delete-soullink" className={styles.deleteSoullink} data-testid={`delete-soullink-${encounter.id}`} icon onClick={handleDeleteSoulLink} style={{ boxShadow: 'none' }} > <Icon name="trash" /> </Button> </div> ) : ( <div className={styles.selector} data-testid={`soullink-${encounter.id}`}> <span data-testid={`soullink-empty-${encounter.id}`} className={styles.placeholder} > Pokémon... </span> </div> )} </PokemonSelector> </div> )} <details open> <summary data-testid="detail-summary">{t('details', { ns: 'badges' })}</summary> <div className={styles.expandable}> <Input className={styles.input} data-testid="level" maxLength={3} label={t('level', { ns: 'rules' })} onChange={(e, data) => setLevel(Number(data.value))} value={Number.isNaN(level) ? '' : level} /> <Input className={styles.input} data-testid="metlevel" label={t('met_level')} maxLength={3} onChange={(e, data) => setMetLevel(Number(data.value))} value={Number.isNaN(metLevel) ? '' : metLevel} /> {isItemGenderGen && ( <Dropdown aria-label="gender-selector" className={dropdownStyles.dropdown} clearable data-testid="gender" inline lazyLoad onChange={(e, data) => setGender(data.value as unknown as Gender)} options={GENDERS} placeholder={t('gender', { ns: 'calculator' })} selection value={gender ?? ''} /> )} {isNatureAbilityGen && ( <div className={styles.natureContainer}> <Dropdown aria-label="nature-selector" className={dropdownStyles.dropdown} clearable data-testid="nature" inline lazyLoad onChange={(e, data) => setNature(data.value as unknown as string)} options={NATURES} placeholder={t('select_nature', { ns: 'calculator' })} search selection value={nature ?? ''} /> <Natures /> </div> )} {isNatureAbilityGen && ( <div className={styles.natureContainer}> <Dropdown aria-label="ability" className={dropdownStyles.dropdown} clearable data-testid="ability" inline lazyLoad onChange={(e, data) => setAbility(data.value as unknown as string)} options={[...new Set(ABILITIES[8])].map((smogonAbility) => { return { text: smogonAbility, value: smogonAbility }; })} placeholder={t('select_ability', { ns: 'calculator' })} search selection value={ability ?? ''} /> <Abilities text={ability ?? ''} /> </div> )} {isItemGenderGen && ( <ItemSelector item={item} onChange={(newItem) => setItem(newItem)} /> )} <Checkbox checked={shiny} className={styles.checkbox} data-testid="shiny" label="Shiny" onChange={(e, data) => setShiny(data.checked)} /> </div> </details> <details className={styles.expandable}> <summary data-testid="move-summary">Moves</summary> <div className={styles.expandable}> <div data-testid="move-1"> <span>Move 1:</span> <MoveSelector currentMoveId={moveOne} handleMove={(moveId: number) => setMoveOne(moveId)} limitGen={limitGen} /> </div> <div data-testid="move-2"> <span>Move 2:</span> <MoveSelector currentMoveId={moveTwo} handleMove={(moveId: number) => setMoveTwo(moveId)} limitGen={limitGen} /> </div> <div data-testid="move-3"> <span>Move 3:</span> <MoveSelector currentMoveId={moveThree} handleMove={(moveId: number) => setMoveThree(moveId)} limitGen={limitGen} /> </div> <div data-testid="move-4"> <span>Move 4:</span> <MoveSelector currentMoveId={moveFour} handleMove={(moveId: number) => setMoveFour(moveId)} limitGen={limitGen} /> </div> </div> </details> <details className={styles.expandable} style={{ display: limitGen > 2 ? undefined : 'none' }} > <summary data-testid="stats-summary">Stats</summary> <div className={styles.expandable}> HP: <fieldset className={styles.fieldset}> <RangeSelector name="ivhp" value={ivhp ?? 0} onChange={(newValue: number) => setIvhp(newValue)} /> <RangeSelector name="evhp" value={evhp ?? 0} onChange={(newValue: number) => setEvhp(newValue)} /> </fieldset> ATK: <fieldset className={styles.fieldset}> <RangeSelector name="ivatk" value={ivatk ?? 0} onChange={(newValue: number) => setIvatk(newValue)} /> <RangeSelector name="evatk" value={evatk ?? 0} onChange={(newValue: number) => setEvatk(newValue)} /> </fieldset> DEF: <fieldset className={styles.fieldset}> <RangeSelector name="ivdef" value={ivdef ?? 0} onChange={(newValue: number) => setIvdef(newValue)} /> <RangeSelector name="evdef" value={evdef ?? 0} onChange={(newValue: number) => setEvdef(newValue)} /> </fieldset> SPATK: <fieldset className={styles.fieldset}> <RangeSelector name="ivspatk" value={ivspatk ?? 0} onChange={(newValue: number) => setIvspatk(newValue)} /> <RangeSelector name="evspatk" value={evspatk ?? 0} onChange={(newValue: number) => setEvspatk(newValue)} /> </fieldset> SPDEF: <fieldset className={styles.fieldset}> <RangeSelector name="ivspdef" value={ivspdef ?? 0} onChange={(newValue: number) => setIvspdef(newValue)} /> <RangeSelector name="evspdef" value={evspdef ?? 0} onChange={(newValue: number) => setEvspdef(newValue)} /> </fieldset> SPEED: <fieldset className={styles.fieldset}> <RangeSelector name="ivspeed" value={ivspeed ?? 0} onChange={(newValue: number) => setIvspeed(newValue)} /> <RangeSelector name="evspeed" value={evspeed ?? 0} onChange={(newValue: number) => setEvspeed(newValue)} /> </fieldset> </div> </details> {encounter?.status?.value === 2 && ( <label> <div>{t('cause_of_fainting')}:</div> <textarea className={styles.textarea} data-testid="cause of fainting" maxLength={250} onChange={(e) => setFaint(e.target.value)} rows={5} value={faint} /> </label> )} </Modal.Content> <Modal.Actions> <Button onClick={handleClose}>{t('cancel', { ns: 'common' })}</Button> <Button onClick={handleExport}>{t('export_to_builder')}</Button> <Dropdown button data-testid="export-to-game" onChange={(e, data) => handleGameExport(data.value as string)} options={gamesList} text={t('export_to_game')} value={selectedGame?.value} /> <Button onClick={handleSave} primary> {t('save', { ns: 'common' })} </Button> </Modal.Actions> </Modal> ); } export default Detail;