import { createRef, useCallback, useEffect, useRef, useState } from 'react' import { useRouter } from 'next/router' import { useTranslation } from 'react-i18next' import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, } from 'cmdk' import Popover from '~components/common/Popover' import SegmentedControl from '~components/common/SegmentedControl' import Segment from '~components/common/Segment' import RaidItem from '~components/raids/RaidItem' import Tooltip from '~components/common/Tooltip' import api from '~utils/api' interface Props { showAllRaidsOption: boolean currentRaid?: Raid currentRaidSlug?: string defaultRaid?: Raid minimal?: boolean onChange?: (raid?: Raid) => void onBlur?: (event: React.ChangeEvent) => void } import Button from '~components/common/Button' import ArrowIcon from '~public/icons/Arrow.svg' import CrossIcon from '~public/icons/Cross.svg' import './index.scss' import classNames from 'classnames' import { appState } from '~utils/appState' const NUM_SECTIONS = 3 enum Sort { ASCENDING, DESCENDING, } const RaidCombobox = (props: Props) => { // Set up router for locale const router = useRouter() const locale = router.locale || 'en' // Set up translations const { t } = useTranslation('common') // Component state const [open, setOpen] = useState(false) const [sort, setSort] = useState(Sort.DESCENDING) const [scrolled, setScrolled] = useState(false) // Data state const [currentSection, setCurrentSection] = useState(1) const [query, setQuery] = useState('') const [sections, setSections] = useState() const [currentRaid, setCurrentRaid] = useState() // Refs const listRef = createRef() function slugToRaid(slug: string) { return appState.raidGroups .filter((group) => group.section > 0) .flatMap((group) => group.raids) .find((raid) => raid.slug === slug) } useEffect(() => { if (appState.party.raid) { setCurrentRaid(appState.party.raid) setCurrentSection(appState.party.raid.group.section) } }, []) useEffect(() => { if (props.currentRaidSlug) { setCurrentRaid(slugToRaid(props.currentRaidSlug)) } }) // Scroll to the top of the list when the user switches tabs useEffect(() => { if (listRef.current) listRef.current.scrollTop = 0 }, [currentSection]) const scrollToItem = useCallback( (node) => { if (!scrolled && open && currentRaid && listRef.current && node) { const { top: listTop } = listRef.current.getBoundingClientRect() const { top: itemTop } = node.getBoundingClientRect() listRef.current.scrollTop = itemTop - listTop setScrolled(true) } }, [scrolled, open, currentRaid, listRef] ) // Methods: Convenience methods function reverseSort() { if (sort === Sort.ASCENDING) setSort(Sort.DESCENDING) else setSort(Sort.ASCENDING) } // Sort raids into defined groups const sortGroups = useCallback( (groups: RaidGroup[]) => { const sections: [RaidGroup[], RaidGroup[], RaidGroup[]] = [[], [], []] groups.forEach((group) => { if (group.section > 0) sections[group.section - 1].push(group) }) setSections(sections) }, [setSections] ) function handleValueChange(raid: Raid) { setCurrentRaid(raid) setOpen(false) setScrolled(false) if (props.onChange) props.onChange(raid) } function toggleOpen() { if (open) { if (currentRaid) setCurrentSection(currentRaid.group.section) setScrolled(false) } setOpen(!open) } function clearSearch() { setQuery('') } const linkClass = classNames({ wind: currentRaid && currentRaid.element == 1, fire: currentRaid && currentRaid.element == 2, water: currentRaid && currentRaid.element == 3, earth: currentRaid && currentRaid.element == 4, dark: currentRaid && currentRaid.element == 5, light: currentRaid && currentRaid.element == 6, }) // Fetch all raids on mount useEffect(() => { api.raidGroups().then((response) => sortGroups(response.data)) }, [sortGroups]) // Methods: Rendering function renderRaidSections() { // Renders each raid section return Array.from({ length: NUM_SECTIONS }, (_, i) => renderRaidSection(i)) } function renderRaidSection(section: number) { // Renders the specified raid section const currentSection = sections?.[section] if (!currentSection) return const sortedGroups = currentSection.sort((a, b) => { return sort === Sort.ASCENDING ? a.order - b.order : b.order - a.order }) return sortedGroups.map((group, i) => renderRaidGroup(section, i)) } function renderRaidGroup(section: number, index: number) { // Renders the specified raid group if (!sections?.[section]?.[index]) return const group = sections[section][index] const options = generateRaidItems(group.raids) const groupClassName = classNames({ CommandGroup: true, Hidden: group.section !== currentSection, }) const heading = (
{group.name[locale]}
) return ( {options} ) } function generateRaidItems(raids: Raid[]) { // Generates a list of RaidItem components from the specified raids return raids .sort((a, b) => { if (a.element > 0 && b.element > 0) return a.element - b.element if (a.name.en.includes('NM') && b.name.en.includes('NM')) return a.level - b.level return a.name.en.localeCompare(b.name.en) }) .map((item, i) => renderRaidItem(item, i)) } function renderRaidItem(raid: Raid, key: number) { // Renders a RaidItem component for the specified raid const isSelected = currentRaid?.id === raid.id const isRef = isSelected ? scrollToItem : undefined const imageUrl = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/raids/${raid.slug}.png` return ( handleValueChange(raid)} > {raid.name[locale]} ) } function renderSegmentedControl() { // Renders a SegmentedControl component for selecting raid sections. return ( setCurrentSection(2)} > {t('raids.sections.events')} setCurrentSection(1)} > {t('raids.sections.raids')} setCurrentSection(3)} > {t('raids.sections.solo')} ) } function renderSortButton() { // Renders a Button for sorting raids and a Tooltip for explaining what it does. return (