From ed5b2b7348570b7fdb014e642607071163d46e2e Mon Sep 17 00:00:00 2001 From: seongminn Date: Sat, 7 Mar 2026 17:14:09 +0900 Subject: [PATCH 1/8] feat: add cn utils --- apps/spectator/package.json | 1 + apps/spectator/src/utils/cn.ts | 6 ++++++ pnpm-lock.yaml | 3 +++ 3 files changed, 10 insertions(+) create mode 100644 apps/spectator/src/utils/cn.ts diff --git a/apps/spectator/package.json b/apps/spectator/package.json index 8444550a..1544cde5 100644 --- a/apps/spectator/package.json +++ b/apps/spectator/package.json @@ -32,6 +32,7 @@ "@suspensive/react": "^3.10.1", "@vercel/analytics": "^1.5.0", "@vercel/speed-insights": "^1.2.0", + "clsx": "^2.1.1", "next": "^16.1.0", "nuqs": "^2.6.0", "react": "^19.1.1", diff --git a/apps/spectator/src/utils/cn.ts b/apps/spectator/src/utils/cn.ts new file mode 100644 index 00000000..9ad0df42 --- /dev/null +++ b/apps/spectator/src/utils/cn.ts @@ -0,0 +1,6 @@ +import { type ClassValue, clsx } from 'clsx'; +import { twMerge } from 'tailwind-merge'; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 964a9166..e496dc1a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -165,6 +165,9 @@ importers: '@vercel/speed-insights': specifier: ^1.2.0 version: 1.2.0(next@16.1.0(@babel/core@7.28.4)(@opentelemetry/api@1.9.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1) + clsx: + specifier: ^2.1.1 + version: 2.1.1 next: specifier: ^16.1.0 version: 16.1.0(@babel/core@7.28.4)(@opentelemetry/api@1.9.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) From dd5bcf2a245f9cc65ed0481bcdad8bdca2af1d1d Mon Sep 17 00:00:00 2001 From: seongminn Date: Sat, 7 Mar 2026 17:14:51 +0900 Subject: [PATCH 2/8] =?UTF-8?q?refactor:=20=EB=9D=BC=EC=9A=B0=ED=8C=85=20?= =?UTF-8?q?=EA=B5=AC=EC=A1=B0=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../_components/previous-tab/index.tsx | 64 --------------- .../_components/previous-tab/year-filter.tsx | 59 ------------- apps/spectator/src/app/(dashboard)/page.tsx | 77 ----------------- .../_components}/best-scorer.tsx | 2 +- .../_components/calendar-menu.tsx | 0 .../app/(home)/_components/error-message.tsx | 9 ++ .../_components/ranking-board/index.ts | 0 .../_components/ranking-board/list.tsx | 0 .../_components/ranking-board/root.tsx | 0 .../_components/ranking-board/title.tsx | 0 .../_components}/recent-records.tsx | 2 +- .../src/app/(home)/_components/tab-header.tsx | 30 +++++++ .../index.tsx => (home)/_components/tab.tsx} | 2 +- apps/spectator/src/app/(home)/layout.tsx | 20 +++++ apps/spectator/src/app/(home)/page.tsx | 17 ++++ .../previous/_components/league-card-list.tsx | 70 ++++++++++++++++ .../previous/_components}/league-card.tsx | 82 ++++++++----------- .../previous/_components/year-filter.tsx | 36 ++++++++ .../src/app/(home)/previous/page.tsx | 29 +++++++ .../teams/_components}/match-history.tsx | 0 .../teams/_components}/score-badge.tsx | 0 .../teams/_components}/score-list.tsx | 0 .../teams/_components}/score-modal.tsx | 0 .../teams/_components/tab.tsx} | 0 .../teams/_components}/team-card.tsx | 0 .../teams/_components}/team-filter/index.tsx | 0 .../_components}/team-filter/useTeamUnits.ts | 0 apps/spectator/src/app/(home)/teams/page.tsx | 16 ++++ apps/spectator/src/components/skeleton.tsx | 16 ++++ .../src/components/ui/filter-badge.tsx | 38 +++++---- 30 files changed, 301 insertions(+), 268 deletions(-) delete mode 100644 apps/spectator/src/app/(dashboard)/_components/previous-tab/index.tsx delete mode 100644 apps/spectator/src/app/(dashboard)/_components/previous-tab/year-filter.tsx delete mode 100644 apps/spectator/src/app/(dashboard)/page.tsx rename apps/spectator/src/app/{(dashboard)/_components/recent-tab => (home)/_components}/best-scorer.tsx (97%) rename apps/spectator/src/app/{(dashboard) => (home)}/_components/calendar-menu.tsx (100%) create mode 100644 apps/spectator/src/app/(home)/_components/error-message.tsx rename apps/spectator/src/app/{(dashboard) => (home)}/_components/ranking-board/index.ts (100%) rename apps/spectator/src/app/{(dashboard) => (home)}/_components/ranking-board/list.tsx (100%) rename apps/spectator/src/app/{(dashboard) => (home)}/_components/ranking-board/root.tsx (100%) rename apps/spectator/src/app/{(dashboard) => (home)}/_components/ranking-board/title.tsx (100%) rename apps/spectator/src/app/{(dashboard)/_components/recent-tab => (home)/_components}/recent-records.tsx (97%) create mode 100644 apps/spectator/src/app/(home)/_components/tab-header.tsx rename apps/spectator/src/app/{(dashboard)/_components/recent-tab/index.tsx => (home)/_components/tab.tsx} (98%) create mode 100644 apps/spectator/src/app/(home)/layout.tsx create mode 100644 apps/spectator/src/app/(home)/page.tsx create mode 100644 apps/spectator/src/app/(home)/previous/_components/league-card-list.tsx rename apps/spectator/src/app/{(dashboard)/_components/previous-tab => (home)/previous/_components}/league-card.tsx (80%) create mode 100644 apps/spectator/src/app/(home)/previous/_components/year-filter.tsx create mode 100644 apps/spectator/src/app/(home)/previous/page.tsx rename apps/spectator/src/app/{(dashboard)/_components/team-tab => (home)/teams/_components}/match-history.tsx (100%) rename apps/spectator/src/app/{(dashboard)/_components/team-tab => (home)/teams/_components}/score-badge.tsx (100%) rename apps/spectator/src/app/{(dashboard)/_components/team-tab => (home)/teams/_components}/score-list.tsx (100%) rename apps/spectator/src/app/{(dashboard)/_components/team-tab => (home)/teams/_components}/score-modal.tsx (100%) rename apps/spectator/src/app/{(dashboard)/_components/team-tab/index.tsx => (home)/teams/_components/tab.tsx} (100%) rename apps/spectator/src/app/{(dashboard)/_components/team-tab => (home)/teams/_components}/team-card.tsx (100%) rename apps/spectator/src/app/{(dashboard)/_components/team-tab => (home)/teams/_components}/team-filter/index.tsx (100%) rename apps/spectator/src/app/{(dashboard)/_components/team-tab => (home)/teams/_components}/team-filter/useTeamUnits.ts (100%) create mode 100644 apps/spectator/src/app/(home)/teams/page.tsx create mode 100644 apps/spectator/src/components/skeleton.tsx diff --git a/apps/spectator/src/app/(dashboard)/_components/previous-tab/index.tsx b/apps/spectator/src/app/(dashboard)/_components/previous-tab/index.tsx deleted file mode 100644 index b0c303e4..00000000 --- a/apps/spectator/src/app/(dashboard)/_components/previous-tab/index.tsx +++ /dev/null @@ -1,64 +0,0 @@ -'use client'; - -import { colors, Typography } from '@hcc/ui'; -import { ErrorBoundary, Suspense } from '@suspensive/react'; -import { useSuspenseLeagues } from '~/api'; -import { LeagueCard } from './league-card'; -import { YearFilter } from './year-filter'; - -interface Props { - year: number; -} - -export const PreviousTab = ({ year }: Props) => { - const { data } = useSuspenseLeagues({ year, size: 50 }); - - return ( -
- - -
- {data.map(league => ( - - - - - - - - - - - -
- - - - - - - - - 리그 통계 데이터가 집계되지 않았어요. - -
- } - > - - - - -
- - ))} -
- - ); -}; diff --git a/apps/spectator/src/app/(dashboard)/_components/previous-tab/year-filter.tsx b/apps/spectator/src/app/(dashboard)/_components/previous-tab/year-filter.tsx deleted file mode 100644 index b83c5be7..00000000 --- a/apps/spectator/src/app/(dashboard)/_components/previous-tab/year-filter.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import Conveyer from '@egjs/conveyer'; -import { useRouter } from 'next/navigation'; -import { useEffect, useRef } from 'react'; -import { FilterBadge } from '~/components/ui'; - -const SERVICE_START_YEAR = 2024; - -interface Props { - year: number; -} - -export const YearFilter = ({ year }: Props) => { - const router = useRouter(); - - const containerRef = useRef(null); - const conveyerRef = useRef(null); - const currentYear = new Date().getFullYear(); - - useEffect(() => { - if (!containerRef.current) return; - conveyerRef.current = new Conveyer(containerRef.current, { - horizontal: true, - useDrag: true, - useSideWheel: true, - preventClickOnDrag: true, - }); - return () => conveyerRef.current?.destroy(); - }, []); - - const years = Array.from( - { length: currentYear - SERVICE_START_YEAR + 1 }, - (_, i) => currentYear - i, - ); - - const handleSelectYear = (selectedYear: number) => { - const params = new URLSearchParams(window.location.search); - params.set('year', selectedYear.toString()); - router.push(`?${params.toString()}`); - }; - - return ( -
-
- {years.map(_year => { - return ( -
- handleSelectYear(_year)}> - {_year} - -
- ); - })} -
-
- ); -}; diff --git a/apps/spectator/src/app/(dashboard)/page.tsx b/apps/spectator/src/app/(dashboard)/page.tsx deleted file mode 100644 index 524e3ffa..00000000 --- a/apps/spectator/src/app/(dashboard)/page.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import { colors, Typography } from '@hcc/ui'; -import * as Tabs from '@radix-ui/react-tabs'; -import { ErrorBoundary, Suspense } from '@suspensive/react'; -import { redirect } from 'next/navigation'; -import { Header } from '~/components/layout'; -import { TabTrigger } from '~/components/ui'; -import { PreviousTab } from './_components/previous-tab'; -import { RecentTab } from './_components/recent-tab'; -import { TeamTab } from './_components/team-tab'; -import { CalendarMenu } from './_components/calendar-menu'; - -const validTabs = ['previous', 'recent', 'team']; - -interface Props { - searchParams: Promise<{ - tab?: string; - year: string; - }>; -} - -const Page = async ({ searchParams }: Props) => { - const { tab: _tab, year: _year } = await searchParams; - - const tab = validTabs.includes(_tab || '') ? _tab : 'recent'; - - if (_tab && !validTabs.includes(_tab)) { - redirect('?tab=recent'); - } - - const year = _year ? Number(_year) : new Date().getFullYear(); - - return ( - <> -
} /> - - - - 이전 대회 - 최근 경기 - 팀별 보기 - - - - }> - - - - - - - }> - - - - - - - }> - - - - - - - - ); -}; - -export default Page; - -const ErrorMessage = () => { - return ( - - 알 수 없는 오류가 발생했어요. 잠시 후 다시 시도해 주세요. - - ); -}; diff --git a/apps/spectator/src/app/(dashboard)/_components/recent-tab/best-scorer.tsx b/apps/spectator/src/app/(home)/_components/best-scorer.tsx similarity index 97% rename from apps/spectator/src/app/(dashboard)/_components/recent-tab/best-scorer.tsx rename to apps/spectator/src/app/(home)/_components/best-scorer.tsx index f0d081ed..6b1f91f1 100644 --- a/apps/spectator/src/app/(dashboard)/_components/recent-tab/best-scorer.tsx +++ b/apps/spectator/src/app/(home)/_components/best-scorer.tsx @@ -6,7 +6,7 @@ import { RankingBoardItem, RankingBoardList, RankingBoardTitle, -} from '../ranking-board'; +} from './ranking-board'; export const BestScorer = () => { const { data: leagueRecentSummary } = useSuspenseLeagueRecentSummary(); diff --git a/apps/spectator/src/app/(dashboard)/_components/calendar-menu.tsx b/apps/spectator/src/app/(home)/_components/calendar-menu.tsx similarity index 100% rename from apps/spectator/src/app/(dashboard)/_components/calendar-menu.tsx rename to apps/spectator/src/app/(home)/_components/calendar-menu.tsx diff --git a/apps/spectator/src/app/(home)/_components/error-message.tsx b/apps/spectator/src/app/(home)/_components/error-message.tsx new file mode 100644 index 00000000..fbe073a3 --- /dev/null +++ b/apps/spectator/src/app/(home)/_components/error-message.tsx @@ -0,0 +1,9 @@ +import { colors, Typography } from '@hcc/ui'; + +export const ErrorMessage = () => { + return ( + + 알 수 없는 오류가 발생했어요. 잠시 후 다시 시도해 주세요. + + ); +}; diff --git a/apps/spectator/src/app/(dashboard)/_components/ranking-board/index.ts b/apps/spectator/src/app/(home)/_components/ranking-board/index.ts similarity index 100% rename from apps/spectator/src/app/(dashboard)/_components/ranking-board/index.ts rename to apps/spectator/src/app/(home)/_components/ranking-board/index.ts diff --git a/apps/spectator/src/app/(dashboard)/_components/ranking-board/list.tsx b/apps/spectator/src/app/(home)/_components/ranking-board/list.tsx similarity index 100% rename from apps/spectator/src/app/(dashboard)/_components/ranking-board/list.tsx rename to apps/spectator/src/app/(home)/_components/ranking-board/list.tsx diff --git a/apps/spectator/src/app/(dashboard)/_components/ranking-board/root.tsx b/apps/spectator/src/app/(home)/_components/ranking-board/root.tsx similarity index 100% rename from apps/spectator/src/app/(dashboard)/_components/ranking-board/root.tsx rename to apps/spectator/src/app/(home)/_components/ranking-board/root.tsx diff --git a/apps/spectator/src/app/(dashboard)/_components/ranking-board/title.tsx b/apps/spectator/src/app/(home)/_components/ranking-board/title.tsx similarity index 100% rename from apps/spectator/src/app/(dashboard)/_components/ranking-board/title.tsx rename to apps/spectator/src/app/(home)/_components/ranking-board/title.tsx diff --git a/apps/spectator/src/app/(dashboard)/_components/recent-tab/recent-records.tsx b/apps/spectator/src/app/(home)/_components/recent-records.tsx similarity index 97% rename from apps/spectator/src/app/(dashboard)/_components/recent-tab/recent-records.tsx rename to apps/spectator/src/app/(home)/_components/recent-records.tsx index 63fd96a2..aa8eb60b 100644 --- a/apps/spectator/src/app/(dashboard)/_components/recent-tab/recent-records.tsx +++ b/apps/spectator/src/app/(home)/_components/recent-records.tsx @@ -5,7 +5,7 @@ import { RankingBoardItem, RankingBoardList, RankingBoardTitle, -} from '../ranking-board'; +} from './ranking-board'; export const RecentRecords = () => { const { data: leagueRecentSummary } = useSuspenseLeagueRecentSummary(); diff --git a/apps/spectator/src/app/(home)/_components/tab-header.tsx b/apps/spectator/src/app/(home)/_components/tab-header.tsx new file mode 100644 index 00000000..7bc32744 --- /dev/null +++ b/apps/spectator/src/app/(home)/_components/tab-header.tsx @@ -0,0 +1,30 @@ +'use client'; + +import { Tabs } from '@base-ui/react'; +import Link from 'next/link'; +import { useSelectedLayoutSegment } from 'next/navigation'; + +interface TabHeaderProps extends Tabs.Root.Props {} + +export const TabHeader = ({ children, ...props }: TabHeaderProps) => { + const segment = useSelectedLayoutSegment(); + const currentTab = segment === 'previous' || segment === 'teams' ? segment : 'recent'; + + return ( + + + 이전 대회} + nativeButton={false} + /> + 최근 경기} nativeButton={false} /> + 팀별 보기} nativeButton={false} /> + + + + + {children} + + ); +}; diff --git a/apps/spectator/src/app/(dashboard)/_components/recent-tab/index.tsx b/apps/spectator/src/app/(home)/_components/tab.tsx similarity index 98% rename from apps/spectator/src/app/(dashboard)/_components/recent-tab/index.tsx rename to apps/spectator/src/app/(home)/_components/tab.tsx index 88c69cf1..ab11b115 100644 --- a/apps/spectator/src/app/(dashboard)/_components/recent-tab/index.tsx +++ b/apps/spectator/src/app/(home)/_components/tab.tsx @@ -9,9 +9,9 @@ import { useSuspenseGames } from '~/api'; import { GameCard } from '~/components/ui'; import { routes } from '~/constants/routes'; +import { RankingBoard } from './ranking-board'; import { BestScorer } from './best-scorer'; import { RecentRecords } from './recent-records'; -import { RankingBoard } from '../ranking-board'; export const RecentTab = () => { const { data: scheduled } = useSuspenseGames({ state: 'SCHEDULED', size: 20 }); diff --git a/apps/spectator/src/app/(home)/layout.tsx b/apps/spectator/src/app/(home)/layout.tsx new file mode 100644 index 00000000..b6fae4ea --- /dev/null +++ b/apps/spectator/src/app/(home)/layout.tsx @@ -0,0 +1,20 @@ +import '@hcc/ui/styles.css'; +import '~/styles/globals.css'; + +import type { PropsWithChildren } from 'react'; + +import { Header } from '~/components/layout'; +import { CalendarMenu } from './_components/calendar-menu'; +import { TabHeader } from './_components/tab-header'; + +const RootLayout = ({ children }: PropsWithChildren) => { + return ( + <> +
} /> + + {children} + + ); +}; + +export default RootLayout; diff --git a/apps/spectator/src/app/(home)/page.tsx b/apps/spectator/src/app/(home)/page.tsx new file mode 100644 index 00000000..0589d2a3 --- /dev/null +++ b/apps/spectator/src/app/(home)/page.tsx @@ -0,0 +1,17 @@ +import { Tabs } from '@base-ui/react'; +import { ErrorBoundary, Suspense } from '@suspensive/react'; + +import { ErrorMessage } from './_components/error-message'; +import { RecentTab } from './_components/tab'; + +export default function Page() { + return ( + + }> + + + + + + ); +} diff --git a/apps/spectator/src/app/(home)/previous/_components/league-card-list.tsx b/apps/spectator/src/app/(home)/previous/_components/league-card-list.tsx new file mode 100644 index 00000000..331a0fe0 --- /dev/null +++ b/apps/spectator/src/app/(home)/previous/_components/league-card-list.tsx @@ -0,0 +1,70 @@ +'use client'; + +import { colors, Typography } from '@hcc/ui'; +import { ErrorBoundary, Suspense } from '@suspensive/react'; + +import { useSuspenseLeagues } from '~/api'; +import { Skeleton } from '~/components/skeleton'; + +import * as LeagueCard from './league-card'; +import { ChevronForwardIcon } from '@hcc/icons'; + +interface Props { + year: number; +} + +export const LeagueCardList = ({ year }: Props) => { + const { data } = useSuspenseLeagues({ year, size: 50 }); + + return ( +
+ {data.map(league => ( + + + + + + }> + } clientOnly> + + +
+ + +
+
+
+
+ ))} +
+ ); +}; + +const StatisticsSkeleton = () => { + return ( +
+ + +
+ ); +}; + +const StatisticsErrorFallback = ({ reset }: { reset: () => void }) => { + return ( +
+ + 리그 데이터를 불러오는 중에 오류가 발생했어요. + + + +
+ ); +}; diff --git a/apps/spectator/src/app/(dashboard)/_components/previous-tab/league-card.tsx b/apps/spectator/src/app/(home)/previous/_components/league-card.tsx similarity index 80% rename from apps/spectator/src/app/(dashboard)/_components/previous-tab/league-card.tsx rename to apps/spectator/src/app/(home)/previous/_components/league-card.tsx index 99e2ba78..ca68b683 100644 --- a/apps/spectator/src/app/(dashboard)/_components/previous-tab/league-card.tsx +++ b/apps/spectator/src/app/(home)/previous/_components/league-card.tsx @@ -1,24 +1,31 @@ +'use client'; + import { ChevronForwardIcon } from '@hcc/icons'; import { Badge, colors, Typography } from '@hcc/ui'; import Image from 'next/image'; import Link from 'next/link'; -import type { ComponentProps } from 'react'; +import { type ComponentProps, createContext, useContext } from 'react'; import { twMerge } from 'tailwind-merge'; -import { type LeagueType, useSuspenseLeagueStatistics, useSuspenseLeagueTopScorers } from '~/api'; + +import { useSuspenseLeagueStatistics, useSuspenseLeagueTopScorers, type LeagueType } from '~/api'; import { routes } from '~/constants/routes'; -/* ------------------------------------------------------------------------------------------------- - * LeagueCard - * -----------------------------------------------------------------------------------------------*/ +export const LeagueCardContext = createContext({} as LeagueType); -const LeagueCardRoot = ({ children, className, ...props }: ComponentProps<'div'>) => { +interface LeagueCardRootProps extends ComponentProps<'div'> { + league: LeagueType; +} + +export const Root = ({ league, className, children, ...props }: LeagueCardRootProps) => { return ( -
- {children} -
+ +
+ {children} +
+
); }; @@ -26,14 +33,14 @@ const LeagueCardRoot = ({ children, className, ...props }: ComponentProps<'div'> * LeagueCard.Header * -----------------------------------------------------------------------------------------------*/ -interface LeagueCardHeaderProps extends ComponentProps<'a'> { - league: LeagueType; -} +interface LeagueCardHeaderProps extends ComponentProps<'a'> {} + +export const Header = ({ className, ...props }: LeagueCardHeaderProps) => { + const { leagueId, name } = useContext(LeagueCardContext); -const LeagueCardHeader = ({ league, className, ...props }: LeagueCardHeaderProps) => { return ( @@ -41,7 +48,7 @@ const LeagueCardHeader = ({ league, className, ...props }: LeagueCardHeaderProps
- {league.name} + {name}
@@ -55,11 +62,10 @@ const LeagueCardHeader = ({ league, className, ...props }: LeagueCardHeaderProps * LeagueCard.Teams * -----------------------------------------------------------------------------------------------*/ -interface LeagueCardTeamsProps extends ComponentProps<'div'> { - leagueId: number; -} +interface LeagueCardTeamsProps extends ComponentProps<'div'> {} -const LeagueCardTeams = ({ leagueId, className, ...props }: LeagueCardTeamsProps) => { +export const Teams = ({ className, ...props }: LeagueCardTeamsProps) => { + const { leagueId } = useContext(LeagueCardContext); const { data } = useSuspenseLeagueStatistics({ leagueId }); return ( @@ -118,16 +124,11 @@ const LeagueCardTeams = ({ leagueId, className, ...props }: LeagueCardTeamsProps * -----------------------------------------------------------------------------------------------*/ interface LeagueCardScorersProps extends ComponentProps<'div'> { - leagueId: number; limit?: number; } -const LeagueCardScorers = ({ - leagueId, - limit = 3, - className, - ...props -}: LeagueCardScorersProps) => { +export const Scorers = ({ limit = 3, className, ...props }: LeagueCardScorersProps) => { + const { leagueId } = useContext(LeagueCardContext); const { data } = useSuspenseLeagueTopScorers({ leagueId }); return ( @@ -176,20 +177,15 @@ const LeagueCardScorers = ({ }; /* ------------------------------------------------------------------------------------------------- - * LeagueCard.Scorers + * LeagueCard.Statistics * -----------------------------------------------------------------------------------------------*/ interface LeagueCardStatisticsProps extends ComponentProps<'div'> { - leagueId: number; limit?: number; } -const LeagueCardStatistics = ({ - leagueId, - limit = 3, - className, - ...props -}: LeagueCardStatisticsProps) => { +export const Statistics = ({ limit = 3, className, ...props }: LeagueCardStatisticsProps) => { + const { leagueId } = useContext(LeagueCardContext); const { data } = useSuspenseLeagueStatistics({ leagueId }); return ( @@ -260,16 +256,6 @@ const LeagueCardStatistics = ({ * LeagueCard.Divider * -----------------------------------------------------------------------------------------------*/ -const LeagueCardDivider = ({ className, ...props }: ComponentProps<'hr'>) => { +export const Divider = ({ className, ...props }: ComponentProps<'hr'>) => { return
; }; - -/* -----------------------------------------------------------------------------------------------*/ - -export const LeagueCard = Object.assign(LeagueCardRoot, { - Header: LeagueCardHeader, - Teams: LeagueCardTeams, - Scorers: LeagueCardScorers, - Statistics: LeagueCardStatistics, - Divider: LeagueCardDivider, -}); diff --git a/apps/spectator/src/app/(home)/previous/_components/year-filter.tsx b/apps/spectator/src/app/(home)/previous/_components/year-filter.tsx new file mode 100644 index 00000000..d9e8ec64 --- /dev/null +++ b/apps/spectator/src/app/(home)/previous/_components/year-filter.tsx @@ -0,0 +1,36 @@ +import Link from 'next/link'; +import { FilterBadge } from '~/components/ui'; + +const SERVICE_START_YEAR = 2024; + +interface Props { + selectedYear: number; +} + +export const YearFilter = ({ selectedYear }: Props) => { + const currentYear = new Date().getFullYear(); + + const years = Array.from( + { length: currentYear - SERVICE_START_YEAR + 1 }, + (_, i) => currentYear - i, + ); + + return ( +
+
+ {years.map(_year => { + return ( +
+ } + isActive={selectedYear === _year} + > + {_year} + +
+ ); + })} +
+
+ ); +}; diff --git a/apps/spectator/src/app/(home)/previous/page.tsx b/apps/spectator/src/app/(home)/previous/page.tsx new file mode 100644 index 00000000..5cbcde2d --- /dev/null +++ b/apps/spectator/src/app/(home)/previous/page.tsx @@ -0,0 +1,29 @@ +import { Tabs } from '@base-ui/react'; +import { ErrorBoundary, Suspense } from '@suspensive/react'; + +import { ErrorMessage } from '../_components/error-message'; +import { LeagueCardList } from './_components/league-card-list'; +import { YearFilter } from './_components/year-filter'; + +interface PageProps { + searchParams: Promise<{ [key: string]: string | string[] | undefined }>; +} + +export default async function Page({ searchParams }: PageProps) { + const currentYear = new Date().getFullYear(); + const selectedYear = Number((await searchParams).year) || currentYear; + + return ( + + + +
+ }> + + + + +
+
+ ); +} diff --git a/apps/spectator/src/app/(dashboard)/_components/team-tab/match-history.tsx b/apps/spectator/src/app/(home)/teams/_components/match-history.tsx similarity index 100% rename from apps/spectator/src/app/(dashboard)/_components/team-tab/match-history.tsx rename to apps/spectator/src/app/(home)/teams/_components/match-history.tsx diff --git a/apps/spectator/src/app/(dashboard)/_components/team-tab/score-badge.tsx b/apps/spectator/src/app/(home)/teams/_components/score-badge.tsx similarity index 100% rename from apps/spectator/src/app/(dashboard)/_components/team-tab/score-badge.tsx rename to apps/spectator/src/app/(home)/teams/_components/score-badge.tsx diff --git a/apps/spectator/src/app/(dashboard)/_components/team-tab/score-list.tsx b/apps/spectator/src/app/(home)/teams/_components/score-list.tsx similarity index 100% rename from apps/spectator/src/app/(dashboard)/_components/team-tab/score-list.tsx rename to apps/spectator/src/app/(home)/teams/_components/score-list.tsx diff --git a/apps/spectator/src/app/(dashboard)/_components/team-tab/score-modal.tsx b/apps/spectator/src/app/(home)/teams/_components/score-modal.tsx similarity index 100% rename from apps/spectator/src/app/(dashboard)/_components/team-tab/score-modal.tsx rename to apps/spectator/src/app/(home)/teams/_components/score-modal.tsx diff --git a/apps/spectator/src/app/(dashboard)/_components/team-tab/index.tsx b/apps/spectator/src/app/(home)/teams/_components/tab.tsx similarity index 100% rename from apps/spectator/src/app/(dashboard)/_components/team-tab/index.tsx rename to apps/spectator/src/app/(home)/teams/_components/tab.tsx diff --git a/apps/spectator/src/app/(dashboard)/_components/team-tab/team-card.tsx b/apps/spectator/src/app/(home)/teams/_components/team-card.tsx similarity index 100% rename from apps/spectator/src/app/(dashboard)/_components/team-tab/team-card.tsx rename to apps/spectator/src/app/(home)/teams/_components/team-card.tsx diff --git a/apps/spectator/src/app/(dashboard)/_components/team-tab/team-filter/index.tsx b/apps/spectator/src/app/(home)/teams/_components/team-filter/index.tsx similarity index 100% rename from apps/spectator/src/app/(dashboard)/_components/team-tab/team-filter/index.tsx rename to apps/spectator/src/app/(home)/teams/_components/team-filter/index.tsx diff --git a/apps/spectator/src/app/(dashboard)/_components/team-tab/team-filter/useTeamUnits.ts b/apps/spectator/src/app/(home)/teams/_components/team-filter/useTeamUnits.ts similarity index 100% rename from apps/spectator/src/app/(dashboard)/_components/team-tab/team-filter/useTeamUnits.ts rename to apps/spectator/src/app/(home)/teams/_components/team-filter/useTeamUnits.ts diff --git a/apps/spectator/src/app/(home)/teams/page.tsx b/apps/spectator/src/app/(home)/teams/page.tsx new file mode 100644 index 00000000..82d85b7f --- /dev/null +++ b/apps/spectator/src/app/(home)/teams/page.tsx @@ -0,0 +1,16 @@ +import { Tabs } from '@base-ui/react'; +import { TeamTab } from './_components/tab'; +import { ErrorBoundary, Suspense } from '@suspensive/react'; +import { ErrorMessage } from '../_components/error-message'; + +export default function Page() { + return ( + + }> + + + + + + ); +} diff --git a/apps/spectator/src/components/skeleton.tsx b/apps/spectator/src/components/skeleton.tsx new file mode 100644 index 00000000..f22cb9e5 --- /dev/null +++ b/apps/spectator/src/components/skeleton.tsx @@ -0,0 +1,16 @@ +import { useRender } from '@base-ui/react'; +import { cn } from '~/utils/cn'; + +interface SkeletonProps extends useRender.ComponentProps<'div'> {} + +export const Skeleton = ({ ref, render, className, ...props }: SkeletonProps) => { + return useRender({ + ref, + render, + defaultTagName: 'div', + props: { + className: cn('animate-pulse rounded-md w-full bg-gray-100 p-3', className), + ...props, + }, + }); +}; diff --git a/apps/spectator/src/components/ui/filter-badge.tsx b/apps/spectator/src/components/ui/filter-badge.tsx index aa973cd9..9945bd66 100644 --- a/apps/spectator/src/components/ui/filter-badge.tsx +++ b/apps/spectator/src/components/ui/filter-badge.tsx @@ -1,34 +1,38 @@ +import { useRender } from '@base-ui/react'; import { CheckSmallIcon } from '@hcc/icons'; -import type { ComponentProps, ReactNode } from 'react'; import { twMerge } from 'tailwind-merge'; -interface FilterBadgeProps extends ComponentProps<'button'> { - children: ReactNode; +interface FilterBadgeProps extends useRender.ComponentProps<'button'> { isActive: boolean; } export const FilterBadge = ({ - children, + render, isActive, - onClick, className, + children, ...props }: FilterBadgeProps) => { - return ( - - ); + ), + + children: ( + <> + {isActive && } + {children} + + ), + + ...props, + }, + }); }; From 3948db92a53eee415e172237e7982617e88d7a1e Mon Sep 17 00:00:00 2001 From: seongminn Date: Sat, 7 Mar 2026 17:35:19 +0900 Subject: [PATCH 3/8] =?UTF-8?q?feat:=20tab-header=20=ED=83=AD=20=EC=8A=A4?= =?UTF-8?q?=ED=83=80=EC=9D=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/app/(home)/_components/tab-header.tsx | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/apps/spectator/src/app/(home)/_components/tab-header.tsx b/apps/spectator/src/app/(home)/_components/tab-header.tsx index 7bc32744..14e1c01b 100644 --- a/apps/spectator/src/app/(home)/_components/tab-header.tsx +++ b/apps/spectator/src/app/(home)/_components/tab-header.tsx @@ -13,13 +13,13 @@ export const TabHeader = ({ children, ...props }: TabHeaderProps) => { return ( - 이전 대회} nativeButton={false} /> - 최근 경기} nativeButton={false} /> - 팀별 보기} nativeButton={false} /> + 최근 경기} nativeButton={false} /> + 팀별 보기} nativeButton={false} /> @@ -28,3 +28,14 @@ export const TabHeader = ({ children, ...props }: TabHeaderProps) => { ); }; + +const Tab = ({ children, className, ...props }: Tabs.Tab.Props) => { + return ( + + {children} + + ); +}; From 1e1e535a20bd5bd6e82674b41e2509c3c845226c Mon Sep 17 00:00:00 2001 From: seongminn Date: Sat, 7 Mar 2026 17:45:58 +0900 Subject: [PATCH 4/8] =?UTF-8?q?refactor:=20useLeagueCardContext=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../previous/_components/league-card.tsx | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/apps/spectator/src/app/(home)/previous/_components/league-card.tsx b/apps/spectator/src/app/(home)/previous/_components/league-card.tsx index ca68b683..5d61efe1 100644 --- a/apps/spectator/src/app/(home)/previous/_components/league-card.tsx +++ b/apps/spectator/src/app/(home)/previous/_components/league-card.tsx @@ -12,6 +12,20 @@ import { routes } from '~/constants/routes'; export const LeagueCardContext = createContext({} as LeagueType); +const useLeagueCardContext = () => { + const context = useContext(LeagueCardContext); + + if (!context) { + throw new Error('LeagueCard compound components must be used within '); + } + + return context; +}; + +/* ------------------------------------------------------------------------------------------------- + * LeagueCard.Root + * -----------------------------------------------------------------------------------------------*/ + interface LeagueCardRootProps extends ComponentProps<'div'> { league: LeagueType; } @@ -36,7 +50,7 @@ export const Root = ({ league, className, children, ...props }: LeagueCardRootPr interface LeagueCardHeaderProps extends ComponentProps<'a'> {} export const Header = ({ className, ...props }: LeagueCardHeaderProps) => { - const { leagueId, name } = useContext(LeagueCardContext); + const { leagueId, name } = useLeagueCardContext(); return ( { interface LeagueCardTeamsProps extends ComponentProps<'div'> {} export const Teams = ({ className, ...props }: LeagueCardTeamsProps) => { - const { leagueId } = useContext(LeagueCardContext); + const { leagueId } = useLeagueCardContext(); const { data } = useSuspenseLeagueStatistics({ leagueId }); return ( @@ -128,7 +142,7 @@ interface LeagueCardScorersProps extends ComponentProps<'div'> { } export const Scorers = ({ limit = 3, className, ...props }: LeagueCardScorersProps) => { - const { leagueId } = useContext(LeagueCardContext); + const { leagueId } = useLeagueCardContext(); const { data } = useSuspenseLeagueTopScorers({ leagueId }); return ( @@ -185,7 +199,7 @@ interface LeagueCardStatisticsProps extends ComponentProps<'div'> { } export const Statistics = ({ limit = 3, className, ...props }: LeagueCardStatisticsProps) => { - const { leagueId } = useContext(LeagueCardContext); + const { leagueId } = useLeagueCardContext(); const { data } = useSuspenseLeagueStatistics({ leagueId }); return ( From 5e022ff3915ed4a011ff42a5c81e9d3ae6a0b89b Mon Sep 17 00:00:00 2001 From: seongminn Date: Sat, 7 Mar 2026 23:01:19 +0900 Subject: [PATCH 5/8] refactor: format & lint --- .github/labeler.yml | 22 +- .github/workflows/labeler.yml | 2 +- .oxfmtrc.json | 34 + .oxlintrc.json | 74 +++ apps/manager/package.json | 12 +- apps/manager/postcss.config.mjs | 2 +- .../(private)/_components/match-overview.tsx | 6 +- .../cheertalks/_components/cheertalk-list.tsx | 2 +- .../cheertalks/_components/cheertalk-tab.tsx | 4 +- .../leagues/[id]/[gameId]/form-section.tsx | 6 +- .../[id]/[gameId]/timeline/timelineClient.tsx | 2 +- .../game-form/game-basic-info-step.tsx | 26 +- .../game-form/game-lineup-step.tsx | 40 +- .../[id]/_components/game-form/index.tsx | 2 +- .../leagues/[id]/_components/game-list.tsx | 4 +- .../[id]/_components/league-form/index.tsx | 2 +- .../[id]/_components/timeline-tab/index.tsx | 4 +- .../timeline-tab/sheets/AddScoreSheet.tsx | 26 +- .../timeline-tab/sheets/StatusChangeSheet.tsx | 29 +- .../timeline-tab/sheets/SubstituteSheet.tsx | 34 +- .../timeline-tab/sheets/WarningSheet.tsx | 26 +- .../timeline-tab/timeline-delete.tsx | 4 +- .../leagues/[id]/_components/timeline.tsx | 4 +- .../leagues/[id]/create-game/form-section.tsx | 2 +- .../leagues/[id]/create-game/page.tsx | 2 +- .../[id]/manage/_components/form-section.tsx | 2 +- .../(private)/leagues/[id]/manage/page.tsx | 2 +- .../src/app/(private)/leagues/[id]/page.tsx | 2 +- .../leagues/_components/league-overview.tsx | 6 +- .../leagues/_components/select-team.tsx | 16 +- .../(private)/leagues/create/LeagueInfo.tsx | 10 +- .../leagues/create/LeagueRegister.tsx | 14 +- .../src/app/(private)/leagues/create/page.tsx | 4 +- .../src/app/(private)/players/[id]/page.tsx | 2 +- .../players/_components/player-form.tsx | 4 +- .../players/_components/player-list.tsx | 8 +- .../src/app/(private)/teams/[id]/page.tsx | 2 +- .../_components/player-append-dialog.tsx | 4 +- .../_components/team-basic-info-step.tsx | 10 +- .../(private)/teams/_components/team-form.tsx | 2 +- .../(private)/teams/_components/team-list.tsx | 2 +- .../teams/_components/team-players-step.tsx | 6 +- .../app/(public)/auth/login/login-form.tsx | 2 +- .../src/components/ui/input-select.tsx | 2 +- apps/manager/src/styles/globals.css | 6 +- apps/manager/src/utils/form-util.ts | 2 +- apps/manager/tsconfig.json | 3 +- apps/spectator/package.json | 11 +- apps/spectator/postcss.config.mjs | 2 +- apps/spectator/src/api/queryKey.ts | 4 +- .../app/(home)/_components/recent-records.tsx | 2 +- .../src/app/(home)/_components/tab-header.tsx | 6 +- .../src/app/(home)/_components/tab.tsx | 4 +- .../previous/_components/league-card-list.tsx | 4 +- .../previous/_components/league-card.tsx | 8 +- .../previous/_components/year-filter.tsx | 2 +- .../teams/_components/match-history.tsx | 2 +- .../(home)/teams/_components/score-list.tsx | 2 +- .../(home)/teams/_components/score-modal.tsx | 2 +- .../src/app/(home)/teams/_components/tab.tsx | 4 +- .../_components/team-filter/useTeamUnits.ts | 4 +- .../calendar/_components/CalendarOverview.tsx | 4 +- .../calendar/_components/calendar-grid.tsx | 19 +- .../cheer-talk/cheer-talk-form.tsx | 6 +- .../cheer-talk/cheer-talk-list.tsx | 9 +- .../[id]/_components/cheer-talk/index.tsx | 2 +- .../cheer-talk/useCheerTalkById.ts | 6 +- .../_components/cheer-talk/useGameTeamInfo.ts | 2 +- .../app/games/[id]/_components/cheer-vs.tsx | 4 +- .../_components/lineup-tab/candidate-list.tsx | 4 +- .../_components/lineup-tab/ground/index.tsx | 8 +- .../_components/lineup-tab/player-list.tsx | 4 +- .../[id]/_components/timeline-tab/index.tsx | 4 +- .../leagues/[id]/_components/game-list.tsx | 4 +- .../leagues/[id]/_components/round-filter.tsx | 2 +- .../leagues/[id]/_components/team-filter.tsx | 4 +- .../app/teams/[id]/_components/team-info.tsx | 2 +- .../src/app/teams/[id]/_components/trophy.tsx | 2 +- .../spectator/src/components/ui/game-card.tsx | 6 +- apps/spectator/src/hooks/useDebounce.ts | 1 - .../src/hooks/useIntersectionObserver.ts | 3 +- apps/spectator/src/hooks/useSocket.ts | 2 +- apps/spectator/src/styles/globals.css | 6 +- apps/spectator/tsconfig.json | 3 +- biome.json | 118 ---- package.json | 32 +- packages/api-base/package.json | 25 +- packages/api-base/rollup.config.cjs | 2 +- packages/icons/package.json | 31 +- packages/icons/rollup.config.cjs | 2 +- packages/style/css/theme.css | 62 +- packages/style/package.json | 10 +- packages/toolkit/package.json | 29 +- packages/toolkit/rollup.config.cjs | 2 +- .../src/utils/formatTime/formatTime.spec.ts | 1 + packages/ui/package.json | 25 +- packages/ui/rollup.config.cjs | 2 +- .../ui/src/accordion/Accordion.module.css | 6 +- packages/ui/src/button/Button.module.css | 6 +- packages/ui/src/input/Input.module.css | 10 +- packages/ui/src/modal/Modal.module.css | 20 +- packages/ui/src/select/Select.module.css | 5 +- packages/ui/src/select/Select.tsx | 3 - packages/ui/src/spinner/Spinner.module.css | 8 +- packages/ui/src/spinner/Spinner.tsx | 6 +- .../ui/src/typography/Typography.module.css | 5 +- pnpm-lock.yaml | 583 +++++++++++++++--- tooling/rollup-config/base.js | 2 +- tooling/rollup-config/package.json | 8 +- tooling/rollup-config/ui.js | 2 +- tooling/typescript-config/package.json | 2 +- turbo.json | 4 + 112 files changed, 1035 insertions(+), 622 deletions(-) create mode 100644 .oxfmtrc.json create mode 100644 .oxlintrc.json delete mode 100644 biome.json diff --git a/.github/labeler.yml b/.github/labeler.yml index 09d18937..a1d43d87 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -1,28 +1,28 @@ -"package: api": +'package: api': - any: - changed-files: - any-glob-to-any-file: - 'packages/api/**/*' -"package: icon": +'package: icon': - any: - changed-files: - any-glob-to-any-file: - 'packages/icon/**/*' -"package: style": +'package: style': - any: - changed-files: - any-glob-to-any-file: - 'packages/style/**/*' -"package: ui": +'package: ui': - any: - changed-files: - any-glob-to-any-file: - 'packages/ui/**/*' -"eslint": +'eslint': - any: - changed-files: - any-glob-to-any-file: @@ -30,21 +30,21 @@ - '.eslintrc*' - 'eslint.config.*' -"tsconfig": +'tsconfig': - any: - changed-files: - any-glob-to-any-file: - 'tooling/config-typescript/**/*' - 'tsconfig.json' -"documentation": +'documentation': - any: - changed-files: - any-glob-to-any-file: - 'docs/**/*' - 'README.md' -"build": +'build': - any: - changed-files: - any-glob-to-any-file: @@ -53,19 +53,19 @@ - 'packages/*/package.json' - 'apps/*/package.json' -"workflows": +'workflows': - any: - changed-files: - any-glob-to-any-file: - '.github/workflows/**/*' -"app: spectator": +'app: spectator': - any: - changed-files: - any-glob-to-any-file: - 'apps/spectator/**/*' -"app: manager": +'app: manager': - any: - changed-files: - any-glob-to-any-file: diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index f0886c26..2be97416 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -6,7 +6,7 @@ on: - main - wip/* - release/** - types: [ opened, synchronize, reopened ] + types: [opened, synchronize, reopened] concurrency: group: ${{ github.workflow }}-${{ github.ref }} diff --git a/.oxfmtrc.json b/.oxfmtrc.json new file mode 100644 index 00000000..4d2868c1 --- /dev/null +++ b/.oxfmtrc.json @@ -0,0 +1,34 @@ +{ + "$schema": "./node_modules/oxfmt/configuration_schema.json", + "singleQuote": true, + "jsxSingleQuote": false, + + // sort imports + "SortImports": { + "groups": [ + ["side-effect"], + ["builtin"], + ["external", "type-external"], + ["internal", "type-internal"], + ["parent", "type-parent"], + ["sibling", "type-sibling"], + ["index", "type-index"] + ], + "order": "asc" + }, + + // sort tailwindcss classes + "Tailwindcss": { + "stylesheet": "./path/to/stylesheet.css", + "attributes": ["class", "className"], + "functions": ["clsx", "cn"], + "preserveWhitespace": true + }, + + // sort package.json fields + "SortPackageJson": { + "sortScripts": true + }, + + "ignorePatterns": [] +} diff --git a/.oxlintrc.json b/.oxlintrc.json new file mode 100644 index 00000000..a5a42be7 --- /dev/null +++ b/.oxlintrc.json @@ -0,0 +1,74 @@ +{ + "$schema": "./node_modules/oxlint/configuration_schema.json", + "plugins": ["typescript", "react", "unicorn", "jsx-a11y"], + "categories": { + "correctness": "warn" + }, + "rules": { + "typescript/no-floating-promises": "error", + "typescript/no-unsafe-assignment": "warn", + "unicorn/prefer-node-protocol": "off", + "typescript/no-non-null-assertion": "warn", + "eslint/prefer-template": "warn", + "typescript/consistent-type-imports": "error", + "typescript/consistent-type-exports": "error", + "eslint/no-param-reassign": "error", + "typescript/prefer-as-const": "error", + "eslint/default-param-last": "error", + "typescript/prefer-enum-initializers": "error", + "react/self-closing-comp": "error", + "eslint/one-var": ["error", "never"], + "unicorn/prefer-number-properties": "error", + "typescript/no-inferrable-types": "error", + "eslint/no-else-return": "error", + "eslint/dot-notation": "off", + "typescript/ban-types": "off", + "unicorn/no-array-for-each": "off", + "react-hooks/exhaustive-deps": "warn", + "typescript/no-unused-vars": [ + "error", + { "argsIgnorePattern": "^_", "varsIgnorePattern": "^_", "caughtErrors": "none" } + ], + "react/no-danger": "warn", + "typescript/no-explicit-any": "warn", + "react/no-array-index-key": "warn", + "jsx-a11y/button-has-type": "off", + "jsx-a11y/click-events-have-key-events": "off", + "jsx-a11y/label-has-associated-control": "off", + "jsx-a11y/alt-text": "off" + }, + "settings": { + "jsx-a11y": { + "polymorphicPropName": null, + "components": {}, + "attributes": {} + }, + "next": { + "rootDir": [] + }, + "react": { + "formComponents": [], + "linkComponents": [], + "version": null, + "componentWrapperFunctions": [] + }, + "jsdoc": { + "ignorePrivate": false, + "ignoreInternal": false, + "ignoreReplacesDocs": true, + "overrideReplacesDocs": true, + "augmentsExtendsReplacesDocs": false, + "implementsReplacesDocs": false, + "exemptDestructuredRootsFromChecks": false, + "tagNamePreference": {} + }, + "vitest": { + "typecheck": false + } + }, + "env": { + "builtin": true + }, + "globals": {}, + "ignorePatterns": [] +} diff --git a/apps/manager/package.json b/apps/manager/package.json index 91b268d1..22b549b0 100644 --- a/apps/manager/package.json +++ b/apps/manager/package.json @@ -6,15 +6,9 @@ "dev": "next dev --turbopack --port 11114 --hostname 0.0.0.0", "build": "next build", "start": "next start", - "lint": "biome lint .", - "lint:fix": "biome lint --fix .", - "format": "biome format --write ." - }, - "lint-staged": { - "*.{js,jsx,ts,tsx}": [ - "biome lint --fix", - "biome format --write" - ] + "lint": "oxlint", + "lint:fix": "oxlint --fix", + "format": "oxfmt" }, "dependencies": { "@bprogress/next": "^3.2.12", diff --git a/apps/manager/postcss.config.mjs b/apps/manager/postcss.config.mjs index c7bcb4b1..ba720fe5 100644 --- a/apps/manager/postcss.config.mjs +++ b/apps/manager/postcss.config.mjs @@ -1,5 +1,5 @@ const config = { - plugins: ["@tailwindcss/postcss"], + plugins: ['@tailwindcss/postcss'], }; export default config; diff --git a/apps/manager/src/app/(private)/_components/match-overview.tsx b/apps/manager/src/app/(private)/_components/match-overview.tsx index 7e80c2f1..7359fb33 100644 --- a/apps/manager/src/app/(private)/_components/match-overview.tsx +++ b/apps/manager/src/app/(private)/_components/match-overview.tsx @@ -12,7 +12,7 @@ export const MatchOverview = () => { return (
- {data.map(league => ( + {data.map((league) => (
@@ -30,11 +30,11 @@ export const MatchOverview = () => {
- {league.inProgressGames.map(game => ( + {league.inProgressGames.map((game) => ( - {game.gameTeams.map(team => ( + {game.gameTeams.map((team) => ( ))} diff --git a/apps/manager/src/app/(private)/cheertalks/_components/cheertalk-list.tsx b/apps/manager/src/app/(private)/cheertalks/_components/cheertalk-list.tsx index 282da0ab..03ad19c4 100644 --- a/apps/manager/src/app/(private)/cheertalks/_components/cheertalk-list.tsx +++ b/apps/manager/src/app/(private)/cheertalks/_components/cheertalk-list.tsx @@ -115,7 +115,7 @@ export const CheerTalkList = ({ cheerTalks, status }: CheerTalkListProps) => { return (
- {cheerTalks.map(cheerTalk => ( + {cheerTalks.map((cheerTalk) => (
{renderActions(cheerTalk)}
diff --git a/apps/manager/src/app/(private)/cheertalks/_components/cheertalk-tab.tsx b/apps/manager/src/app/(private)/cheertalks/_components/cheertalk-tab.tsx index 38cbeca3..a763c963 100644 --- a/apps/manager/src/app/(private)/cheertalks/_components/cheertalk-tab.tsx +++ b/apps/manager/src/app/(private)/cheertalks/_components/cheertalk-tab.tsx @@ -30,7 +30,7 @@ export const CheerTalkTabs = () => {
- {TABS_CONFIG.map(tab => ( + {TABS_CONFIG.map((tab) => (
- {TABS_CONFIG.find(tab => tab.key === activeTab)?.renderer() || null} + {TABS_CONFIG.find((tab) => tab.key === activeTab)?.renderer() || null}
); diff --git a/apps/manager/src/app/(private)/leagues/[id]/[gameId]/form-section.tsx b/apps/manager/src/app/(private)/leagues/[id]/[gameId]/form-section.tsx index 3f14c700..c0f8dc4e 100644 --- a/apps/manager/src/app/(private)/leagues/[id]/[gameId]/form-section.tsx +++ b/apps/manager/src/app/(private)/leagues/[id]/[gameId]/form-section.tsx @@ -31,7 +31,7 @@ export const FormSection = ({ leagueId, gameId }: Props) => { toast.success('경기가 수정되었습니다.'); router.back(); }, - onError: error => { + onError: (error) => { console.error(`[manager/leagues/${leagueId}]`, error); toast.error('경기 수정에 실패했습니다. 잠시 후 다시 시도해주세요.'); }, @@ -61,8 +61,8 @@ export const FormSection = ({ leagueId, gameId }: Props) => { required > {roundOptions - .filter(item => league.maxRound >= item.round) - .map(item => ( + .filter((item) => league.maxRound >= item.round) + .map((item) => ( diff --git a/apps/manager/src/app/(private)/leagues/[id]/[gameId]/timeline/timelineClient.tsx b/apps/manager/src/app/(private)/leagues/[id]/[gameId]/timeline/timelineClient.tsx index ba0f506c..142f88dd 100644 --- a/apps/manager/src/app/(private)/leagues/[id]/[gameId]/timeline/timelineClient.tsx +++ b/apps/manager/src/app/(private)/leagues/[id]/[gameId]/timeline/timelineClient.tsx @@ -49,7 +49,7 @@ export default function TimelineClient({ gameId }: { gameId: number }) {
{ + onOpenChange={(isOpen) => { if (!isOpen) close(); }} > diff --git a/apps/manager/src/app/(private)/leagues/[id]/_components/game-form/game-basic-info-step.tsx b/apps/manager/src/app/(private)/leagues/[id]/_components/game-form/game-basic-info-step.tsx index bbc29aa4..bbb3a464 100644 --- a/apps/manager/src/app/(private)/leagues/[id]/_components/game-form/game-basic-info-step.tsx +++ b/apps/manager/src/app/(private)/leagues/[id]/_components/game-form/game-basic-info-step.tsx @@ -28,19 +28,19 @@ export const GameBasicInfoStep = ({ leagueId, onNext }: Props) => { const isValid = Boolean( name?.trim() && - round && - quarter && - state && - startTime && - team1Id && - team2Id && - team1Id !== team2Id, + round && + quarter && + state && + startTime && + team1Id && + team2Id && + team1Id !== team2Id, ); const roundFilteredOptions = useMemo( () => roundOptions - .filter(item => league.maxRound >= item.round) - .map(item => ({ value: item.value.toString(), label: item.label })), + .filter((item) => league.maxRound >= item.round) + .map((item) => ({ value: item.value.toString(), label: item.label })), [league.maxRound], ); const quarterListOptions = Object.entries(quarterOptions).map(([key, value]) => ({ @@ -53,13 +53,13 @@ export const GameBasicInfoStep = ({ leagueId, onNext }: Props) => { label: value, })); - const teamOptions = teams.map(team => ({ + const teamOptions = teams.map((team) => ({ value: team.leagueTeamId.toString(), label: team.teamName, })); const handleTeamChange = (teamNum: 1 | 2, leagueTeamId: string) => { - const selectedTeam = teams.find(team => team.leagueTeamId.toString() === leagueTeamId); + const selectedTeam = teams.find((team) => team.leagueTeamId.toString() === leagueTeamId); if (selectedTeam) { setValue(`team${teamNum}.teamId`, selectedTeam.teamId); setValue(`team${teamNum}.leagueTeamId`, selectedTeam.leagueTeamId); @@ -143,7 +143,7 @@ export const GameBasicInfoStep = ({ leagueId, onNext }: Props) => { label="팀 선택 1" options={teamOptions} value={field.value?.toString()} - onValueChange={val => { + onValueChange={(val) => { field.onChange(val); handleTeamChange(1, val); }} @@ -159,7 +159,7 @@ export const GameBasicInfoStep = ({ leagueId, onNext }: Props) => { label="팀 선택 2" options={teamOptions} value={field.value?.toString()} - onValueChange={val => { + onValueChange={(val) => { field.onChange(val); handleTeamChange(2, val); }} diff --git a/apps/manager/src/app/(private)/leagues/[id]/_components/game-form/game-lineup-step.tsx b/apps/manager/src/app/(private)/leagues/[id]/_components/game-form/game-lineup-step.tsx index 570a38c0..71d7c602 100644 --- a/apps/manager/src/app/(private)/leagues/[id]/_components/game-form/game-lineup-step.tsx +++ b/apps/manager/src/app/(private)/leagues/[id]/_components/game-form/game-lineup-step.tsx @@ -21,8 +21,8 @@ export const GameLineupStep = ({ leagueId, onNext, onPrevious }: Props) => { const [team1Id, team2Id] = watch(['team1.leagueTeamId', 'team2.leagueTeamId']); const { data: leagueTeams } = useSuspenseLeagueTeams({ leagueId }); - const team1 = leagueTeams.find(team => team.leagueTeamId === Number(team1Id)); - const team2 = leagueTeams.find(team => team.leagueTeamId === Number(team2Id)); + const team1 = leagueTeams.find((team) => team.leagueTeamId === Number(team1Id)); + const team2 = leagueTeams.find((team) => team.leagueTeamId === Number(team2Id)); const { data: team1Players } = useSuspenseLeagueTeamsPlayers({ leagueTeamId: team1?.leagueTeamId || 0, @@ -37,7 +37,7 @@ export const GameLineupStep = ({ leagueId, onNext, onPrevious }: Props) => { const getPlayerName = useCallback( (teamPlayerId: number) => { const allPlayers = [...team1Players, ...team2Players]; - const player = allPlayers.find(p => p.teamPlayerId === teamPlayerId); + const player = allPlayers.find((p) => p.teamPlayerId === teamPlayerId); return player?.name || `선수 ${teamPlayerId}`; }, [team1Players, team2Players], @@ -46,7 +46,7 @@ export const GameLineupStep = ({ leagueId, onNext, onPrevious }: Props) => { const [team1Selection, setTeam1Selection] = useState(() => { const existing = getValues('team1.lineupPlayers') || []; return existing.length > 0 - ? existing.map(p => ({ + ? existing.map((p) => ({ teamPlayerId: p.teamPlayerId, state: p.state, isCaptain: p.isCaptain, @@ -57,7 +57,7 @@ export const GameLineupStep = ({ leagueId, onNext, onPrevious }: Props) => { const [team2Selection, setTeam2Selection] = useState(() => { const existing = getValues('team2.lineupPlayers') || []; return existing.length > 0 - ? existing.map(p => ({ + ? existing.map((p) => ({ teamPlayerId: p.teamPlayerId, state: p.state, isCaptain: p.isCaptain, @@ -87,13 +87,13 @@ export const GameLineupStep = ({ leagueId, onNext, onPrevious }: Props) => { ) => { const setSelection = teamNumber === 1 ? setTeam1Selection : setTeam2Selection; - setSelection(prev => { - const existing = prev.find(p => p.teamPlayerId === teamPlayerId); + setSelection((prev) => { + const existing = prev.find((p) => p.teamPlayerId === teamPlayerId); if (existing) { if (existing.state === state) { - return prev.filter(p => p.teamPlayerId !== teamPlayerId); + return prev.filter((p) => p.teamPlayerId !== teamPlayerId); } - return prev.map(p => + return prev.map((p) => p.teamPlayerId === teamPlayerId ? { ...p, @@ -111,13 +111,13 @@ export const GameLineupStep = ({ leagueId, onNext, onPrevious }: Props) => { const setSelection = teamNumber === 1 ? setTeam1Selection : setTeam2Selection; const currentSelection = teamNumber === 1 ? team1Selection : team2Selection; - const playerInSelection = currentSelection.find(p => p.teamPlayerId === teamPlayerId); + const playerInSelection = currentSelection.find((p) => p.teamPlayerId === teamPlayerId); if (!playerInSelection || playerInSelection.state === 'CANDIDATE') { return; } - setSelection(prev => - prev.map(p => ({ + setSelection((prev) => + prev.map((p) => ({ ...p, isCaptain: p.teamPlayerId === teamPlayerId ? !p.isCaptain : false, })), @@ -126,7 +126,7 @@ export const GameLineupStep = ({ leagueId, onNext, onPrevious }: Props) => { const getPlayerState = (teamNumber: 1 | 2, playerId: number) => { const selection = teamNumber === 1 ? team1Selection : team2Selection; - return selection.find(p => p.teamPlayerId === playerId); + return selection.find((p) => p.teamPlayerId === playerId); }; const handleNext = () => { @@ -135,10 +135,10 @@ export const GameLineupStep = ({ leagueId, onNext, onPrevious }: Props) => { onNext(); }; - const team1Starters = team1Selection.filter(p => p.state === 'STARTER'); - const team2Starters = team2Selection.filter(p => p.state === 'STARTER'); - const team1Captain = team1Selection.find(p => p.isCaptain); - const team2Captain = team2Selection.find(p => p.isCaptain); + const team1Starters = team1Selection.filter((p) => p.state === 'STARTER'); + const team2Starters = team2Selection.filter((p) => p.state === 'STARTER'); + const team1Captain = team1Selection.find((p) => p.isCaptain); + const team2Captain = team2Selection.find((p) => p.isCaptain); const isValid = team1Starters.length > 0 && team2Starters.length > 0 && team1Captain && team2Captain; @@ -146,7 +146,7 @@ export const GameLineupStep = ({ leagueId, onNext, onPrevious }: Props) => { const currentPlayers = activeTab === 1 ? team1Players : team2Players; if (!currentPlayers) return []; - return currentPlayers.filter(player => { + return currentPlayers.filter((player) => { const playerName = getPlayerName(player.playerId).toLowerCase(); return ( playerName.includes(searchQuery.toLowerCase()) || @@ -161,7 +161,7 @@ export const GameLineupStep = ({ leagueId, onNext, onPrevious }: Props) => { return (
- {playersToShow.map(player => { + {playersToShow.map((player) => { const playerState = getPlayerState(teamNumber, player.teamPlayerId); return (
{ type="text" placeholder="선수 이름이나 등번호로 검색..." value={searchQuery} - onChange={e => setSearchQuery(e.target.value)} + onChange={(e) => setSearchQuery(e.target.value)} size="md" />
diff --git a/apps/manager/src/app/(private)/leagues/[id]/_components/game-form/index.tsx b/apps/manager/src/app/(private)/leagues/[id]/_components/game-form/index.tsx index 809e88cf..f65c83bc 100644 --- a/apps/manager/src/app/(private)/leagues/[id]/_components/game-form/index.tsx +++ b/apps/manager/src/app/(private)/leagues/[id]/_components/game-form/index.tsx @@ -49,7 +49,7 @@ export const GameForm = ({ leagueId, className, onSubmit, initialData, ...props step.title)} + steps={STEPS.map((step) => step.title)} />
diff --git a/apps/manager/src/app/(private)/leagues/[id]/_components/game-list.tsx b/apps/manager/src/app/(private)/leagues/[id]/_components/game-list.tsx index 51998650..b329a54c 100644 --- a/apps/manager/src/app/(private)/leagues/[id]/_components/game-list.tsx +++ b/apps/manager/src/app/(private)/leagues/[id]/_components/game-list.tsx @@ -21,11 +21,11 @@ export const GameList = ({ id, state }: Props) => {
{data.length > 0 && data[0]?.games?.length > 0 ? ( - data[0].games.map(game => ( + data[0].games.map((game) => ( - {game.gameTeams.map(team => ( + {game.gameTeams.map((team) => ( ))} diff --git a/apps/manager/src/app/(private)/leagues/[id]/_components/league-form/index.tsx b/apps/manager/src/app/(private)/leagues/[id]/_components/league-form/index.tsx index 55226b6d..d665a1f3 100644 --- a/apps/manager/src/app/(private)/leagues/[id]/_components/league-form/index.tsx +++ b/apps/manager/src/app/(private)/leagues/[id]/_components/league-form/index.tsx @@ -29,7 +29,7 @@ export const LeagueForm = ({ initialData, initialTeams, onSubmit }: Props) => { maxRound: initialData?.maxRound, }); const handleFormChange = (patch: Partial) => { - setFormData(prev => ({ + setFormData((prev) => ({ ...prev, ...patch, })); diff --git a/apps/manager/src/app/(private)/leagues/[id]/_components/timeline-tab/index.tsx b/apps/manager/src/app/(private)/leagues/[id]/_components/timeline-tab/index.tsx index e6fca7b5..a84c0cda 100644 --- a/apps/manager/src/app/(private)/leagues/[id]/_components/timeline-tab/index.tsx +++ b/apps/manager/src/app/(private)/leagues/[id]/_components/timeline-tab/index.tsx @@ -41,10 +41,10 @@ export const TimelineTab = ({ gameId }: Props) => { )} - {data.map(timeline => ( + {data.map((timeline) => (
- {timeline.records.map(record => { + {timeline.records.map((record) => { if (record.progressRecord?.gameProgressType) { if (timeline.gameQuarter === '경기 종료' || timeline.gameQuarter === '경기후') return null; diff --git a/apps/manager/src/app/(private)/leagues/[id]/_components/timeline-tab/sheets/AddScoreSheet.tsx b/apps/manager/src/app/(private)/leagues/[id]/_components/timeline-tab/sheets/AddScoreSheet.tsx index ef547563..47a43567 100644 --- a/apps/manager/src/app/(private)/leagues/[id]/_components/timeline-tab/sheets/AddScoreSheet.tsx +++ b/apps/manager/src/app/(private)/leagues/[id]/_components/timeline-tab/sheets/AddScoreSheet.tsx @@ -18,7 +18,7 @@ const QUARTER_LABELS = { }; const quarterOptions: SelectOption[] = ( Object.keys(QUARTER_LABELS) as Array -).map(key => ({ +).map((key) => ({ label: QUARTER_LABELS[key], value: QUARTER_TYPE[key], })); @@ -43,7 +43,7 @@ export default function AddScoreSheet({ const isPending = isScorePending || isPKPending; const { data: lineup } = useSuspenseGameLineupPlaying({ gameId }); const teamOptions: SelectOption[] = useMemo(() => { - return lineup.map(team => ({ + return lineup.map((team) => ({ label: team.teamName, value: String(team.gameTeamId), })); @@ -59,11 +59,11 @@ export default function AddScoreSheet({ if (!team) return []; const selectedTeamId = Number(team.value); - const selectedTeam = lineup.find(t => t.gameTeamId === selectedTeamId); + const selectedTeam = lineup.find((t) => t.gameTeamId === selectedTeamId); if (!selectedTeam) return []; - return selectedTeam.gameTeamPlayers.map(p => ({ + return selectedTeam.gameTeamPlayers.map((p) => ({ label: `${p.jerseyNumber} ${p.playerName}`, value: String(p.id), })); @@ -133,8 +133,8 @@ export default function AddScoreSheet({ label="쿼터" options={quarterOptions} value={quarter?.value} - onValueChange={value => { - setQuarter(quarterOptions.find(opt => opt.value === value) || null); + onValueChange={(value) => { + setQuarter(quarterOptions.find((opt) => opt.value === value) || null); setIsSuccess(null); // 쿼터 변경 시 성공 여부 초기화 }} /> @@ -143,8 +143,8 @@ export default function AddScoreSheet({ label="팀 명" options={teamOptions} value={team?.value} - onValueChange={value => { - setTeam(teamOptions.find(opt => opt.value === value) || null); + onValueChange={(value) => { + setTeam(teamOptions.find((opt) => opt.value === value) || null); setPlayer(null); // 팀이 바뀌면 선수 초기화 }} /> @@ -153,7 +153,9 @@ export default function AddScoreSheet({ label="선수" options={playerOptions} value={player?.value} - onValueChange={value => setPlayer(playerOptions.find(opt => opt.value === value) || null)} + onValueChange={(value) => + setPlayer(playerOptions.find((opt) => opt.value === value) || null) + } disabled={!team || playerOptions.length === 0} /> {isPK && ( @@ -161,8 +163,8 @@ export default function AddScoreSheet({ label="성공 여부" options={SUCCESS_OPTIONS} value={isSuccess?.value} - onValueChange={value => - setIsSuccess(SUCCESS_OPTIONS.find(opt => opt.value === value) || null) + onValueChange={(value) => + setIsSuccess(SUCCESS_OPTIONS.find((opt) => opt.value === value) || null) } /> )} @@ -170,7 +172,7 @@ export default function AddScoreSheet({ placeholder="시간(분)" type="number" value={minute} - onChange={e => setMinute(e.target.value)} + onChange={(e) => setMinute(e.target.value)} min={0} // 🚨 승부차기일 경우 시간 입력 필드 비활성화 disabled={isPK} diff --git a/apps/manager/src/app/(private)/leagues/[id]/_components/timeline-tab/sheets/StatusChangeSheet.tsx b/apps/manager/src/app/(private)/leagues/[id]/_components/timeline-tab/sheets/StatusChangeSheet.tsx index f84256b7..47228c2b 100644 --- a/apps/manager/src/app/(private)/leagues/[id]/_components/timeline-tab/sheets/StatusChangeSheet.tsx +++ b/apps/manager/src/app/(private)/leagues/[id]/_components/timeline-tab/sheets/StatusChangeSheet.tsx @@ -9,7 +9,12 @@ import { InputSelect } from '~/components/ui/input-select'; type SelectOption = { label: string; value: string }; -const QUARTER_LABELS: Partial> = { +type LabelType = Pick, K>; + +const QUARTER_LABELS: LabelType< + typeof QUARTER_TYPE, + Exclude +> = { PRE_GAME: '경기 시작', FIRST_HALF: '전반', SECOND_HALF: '후반', @@ -18,19 +23,19 @@ const QUARTER_LABELS: Partial> = { }; const quarterOptions: SelectOption[] = ( Object.keys(QUARTER_LABELS) as Array -).map(key => ({ - label: QUARTER_LABELS[key]!, +).map((key) => ({ + label: QUARTER_LABELS[key], value: QUARTER_TYPE[key], })); -const PROGRESS_LABELS: Partial> = { +const PROGRESS_LABELS: LabelType = { QUARTER_START: '쿼터 시작', GAME_END: '경기 종료', }; const progressOptions: SelectOption[] = ( Object.keys(PROGRESS_LABELS) as Array -).map(key => ({ - label: PROGRESS_LABELS[key]!, +).map((key) => ({ + label: PROGRESS_LABELS[key], value: PROGRESS_TYPE[key], })); @@ -68,7 +73,7 @@ export default function StatusChangeSheet({ toast.success('상태 변경이 등록되었습니다.'); onClose(); }, - onError: error => { + onError: (error) => { console.log(error); toast.error('상태 변경 등록에 실패했습니다. 다시 시도해주세요.'); }, @@ -83,14 +88,16 @@ export default function StatusChangeSheet({ label="쿼터" options={quarterOptions} value={quarter?.value} - onValueChange={value => setQuarter(quarterOptions.find(opt => opt.value === value) || null)} + onValueChange={(value) => + setQuarter(quarterOptions.find((opt) => opt.value === value) || null) + } /> - setProgress(progressOptions.find(opt => opt.value === value) || null) + onValueChange={(value) => + setProgress(progressOptions.find((opt) => opt.value === value) || null) } /> @@ -98,7 +105,7 @@ export default function StatusChangeSheet({ placeholder="시간(분)" type="number" value={minute} - onChange={e => setMinute(e.target.value)} + onChange={(e) => setMinute(e.target.value)} min={0} /> diff --git a/apps/manager/src/app/(private)/leagues/[id]/_components/timeline-tab/sheets/SubstituteSheet.tsx b/apps/manager/src/app/(private)/leagues/[id]/_components/timeline-tab/sheets/SubstituteSheet.tsx index a2efc05e..2ce4c37f 100644 --- a/apps/manager/src/app/(private)/leagues/[id]/_components/timeline-tab/sheets/SubstituteSheet.tsx +++ b/apps/manager/src/app/(private)/leagues/[id]/_components/timeline-tab/sheets/SubstituteSheet.tsx @@ -17,7 +17,7 @@ const QUARTER_LABELS = { }; const quarterOptions: SelectOption[] = ( Object.keys(QUARTER_LABELS) as Array -).map(key => ({ +).map((key) => ({ label: QUARTER_LABELS[key], value: QUARTER_TYPE[key], })); @@ -34,7 +34,7 @@ export default function SubstituteSheet({ }); const { data: lineup } = useSuspenseGameLineup({ gameId }); const teamOptions: SelectOption[] = useMemo(() => { - return lineup.map(team => ({ + return lineup.map((team) => ({ label: team.teamName, value: String(team.gameTeamId), })); @@ -49,12 +49,12 @@ export default function SubstituteSheet({ const playerInOptions: SelectOption[] = useMemo(() => { if (!team) return []; - const selectedTeam = lineup.find(t => String(t.gameTeamId) === team.value); + const selectedTeam = lineup.find((t) => String(t.gameTeamId) === team.value); if (!selectedTeam) return []; return selectedTeam.candidatePlayers - .filter(p => p.state === 'CANDIDATE') // 후보 선수만 필터링 - .map(p => ({ + .filter((p) => p.state === 'CANDIDATE') // 후보 선수만 필터링 + .map((p) => ({ label: `${p.jerseyNumber} ${p.playerName}`, value: String(p.id), })); @@ -62,12 +62,12 @@ export default function SubstituteSheet({ const playerOutOptions: SelectOption[] = useMemo(() => { if (!team) return []; - const selectedTeam = lineup.find(t => String(t.gameTeamId) === team.value); + const selectedTeam = lineup.find((t) => String(t.gameTeamId) === team.value); if (!selectedTeam) return []; return selectedTeam.starterPlayers - .filter(p => p.state === 'STARTER') // 주전 선수만 필터링 - .map(p => ({ + .filter((p) => p.state === 'STARTER') // 주전 선수만 필터링 + .map((p) => ({ label: `${p.jerseyNumber} ${p.playerName}`, value: String(p.id), })); @@ -106,15 +106,17 @@ export default function SubstituteSheet({ label="쿼터" options={quarterOptions} value={quarter?.value} - onValueChange={value => setQuarter(quarterOptions.find(opt => opt.value === value) || null)} + onValueChange={(value) => + setQuarter(quarterOptions.find((opt) => opt.value === value) || null) + } /> { - setTeam(teamOptions.find(opt => opt.value === value) || null); + onValueChange={(value) => { + setTeam(teamOptions.find((opt) => opt.value === value) || null); setPlayerIn(null); // 팀이 바뀌면 선수 초기화 }} /> @@ -125,8 +127,8 @@ export default function SubstituteSheet({ label="교체 투입 선수" options={playerInOptions} value={playerIn?.value} - onValueChange={value => - setPlayerIn(playerInOptions.find(opt => opt.value === value) || null) + onValueChange={(value) => + setPlayerIn(playerInOptions.find((opt) => opt.value === value) || null) } disabled={!team || playerInOptions.length === 0} /> @@ -134,8 +136,8 @@ export default function SubstituteSheet({ label="교체 아웃 선수" options={playerOutOptions} value={playerOut?.value} - onValueChange={value => - setPlayerOut(playerOutOptions.find(opt => opt.value === value) || null) + onValueChange={(value) => + setPlayerOut(playerOutOptions.find((opt) => opt.value === value) || null) } disabled={!team || playerOutOptions.length === 0} /> @@ -143,7 +145,7 @@ export default function SubstituteSheet({ placeholder="시간(분)" type="number" value={minute} - onChange={e => setMinute(e.target.value)} + onChange={(e) => setMinute(e.target.value)} min={0} /> diff --git a/apps/manager/src/app/(private)/leagues/[id]/_components/timeline-tab/sheets/WarningSheet.tsx b/apps/manager/src/app/(private)/leagues/[id]/_components/timeline-tab/sheets/WarningSheet.tsx index 709bfd45..c246152b 100644 --- a/apps/manager/src/app/(private)/leagues/[id]/_components/timeline-tab/sheets/WarningSheet.tsx +++ b/apps/manager/src/app/(private)/leagues/[id]/_components/timeline-tab/sheets/WarningSheet.tsx @@ -17,7 +17,7 @@ const QUARTER_LABELS = { }; const quarterOptions: SelectOption[] = ( Object.keys(QUARTER_LABELS) as Array -).map(key => ({ +).map((key) => ({ label: QUARTER_LABELS[key], value: QUARTER_TYPE[key], })); @@ -27,7 +27,7 @@ const CARD_LABELS = { } as const; const cardOptions: SelectOption[] = (Object.keys(CARD_TYPE) as Array).map( - key => ({ + (key) => ({ label: CARD_LABELS[key], value: CARD_TYPE[key], }), @@ -38,7 +38,7 @@ export default function WarningSheet({ gameId, onClose }: { gameId: number; onCl }); const { data: lineup } = useSuspenseGameLineupPlaying({ gameId }); const teamOptions: SelectOption[] = useMemo(() => { - return lineup.map(team => ({ + return lineup.map((team) => ({ label: team.teamName, value: String(team.gameTeamId), })); @@ -55,11 +55,11 @@ export default function WarningSheet({ gameId, onClose }: { gameId: number; onCl if (!team) return []; const selectedTeamId = Number(team.value); - const selectedTeam = lineup.find(t => t.gameTeamId === selectedTeamId); + const selectedTeam = lineup.find((t) => t.gameTeamId === selectedTeamId); if (!selectedTeam) return []; - return selectedTeam.gameTeamPlayers.map(p => ({ + return selectedTeam.gameTeamPlayers.map((p) => ({ label: `${p.jerseyNumber} ${p.playerName}`, value: String(p.id), })); @@ -98,15 +98,17 @@ export default function WarningSheet({ gameId, onClose }: { gameId: number; onCl label="쿼터" options={quarterOptions} value={quarter?.value} - onValueChange={value => setQuarter(quarterOptions.find(opt => opt.value === value) || null)} + onValueChange={(value) => + setQuarter(quarterOptions.find((opt) => opt.value === value) || null) + } /> { - setTeam(teamOptions.find(opt => opt.value === value) || null); + onValueChange={(value) => { + setTeam(teamOptions.find((opt) => opt.value === value) || null); setPlayer(null); // 팀이 바뀌면 선수 초기화 }} /> @@ -117,20 +119,22 @@ export default function WarningSheet({ gameId, onClose }: { gameId: number; onCl label="선수" options={playerOptions} value={player?.value} - onValueChange={value => setPlayer(playerOptions.find(opt => opt.value === value) || null)} + onValueChange={(value) => + setPlayer(playerOptions.find((opt) => opt.value === value) || null) + } disabled={!team || playerOptions.length === 0} /> setCard(cardOptions.find(opt => opt.value === value) || null)} + onValueChange={(value) => setCard(cardOptions.find((opt) => opt.value === value) || null)} /> setMinute(e.target.value)} + onChange={(e) => setMinute(e.target.value)} min={0} /> diff --git a/apps/manager/src/app/(private)/leagues/[id]/_components/timeline-tab/timeline-delete.tsx b/apps/manager/src/app/(private)/leagues/[id]/_components/timeline-tab/timeline-delete.tsx index a8af66bb..03bf5731 100644 --- a/apps/manager/src/app/(private)/leagues/[id]/_components/timeline-tab/timeline-delete.tsx +++ b/apps/manager/src/app/(private)/leagues/[id]/_components/timeline-tab/timeline-delete.tsx @@ -30,8 +30,8 @@ export const TimelineDeleteMenu = ({ gameId }: Props) => { }; const { latestRecord, latestRecordId } = useMemo(() => { - const all = (timelineData ?? []).flatMap(q => - (q.records ?? []).map(r => ({ ...r, __quarter: q.gameQuarter })), + const all = (timelineData ?? []).flatMap((q) => + (q.records ?? []).map((r) => ({ ...r, __quarter: q.gameQuarter })), ); const toNum = (v: unknown) => Number(v); diff --git a/apps/manager/src/app/(private)/leagues/[id]/_components/timeline.tsx b/apps/manager/src/app/(private)/leagues/[id]/_components/timeline.tsx index f407b78e..a82a8daa 100644 --- a/apps/manager/src/app/(private)/leagues/[id]/_components/timeline.tsx +++ b/apps/manager/src/app/(private)/leagues/[id]/_components/timeline.tsx @@ -40,10 +40,10 @@ export const Timeline = ({ gameId }: Props) => { )} - {data.map(timeline => ( + {data.map((timeline) => (
- {timeline.records.map(record => { + {timeline.records.map((record) => { if (record.progressRecord?.gameProgressType) { if (timeline.gameQuarter === '경기 종료' || timeline.gameQuarter === '경기후') return null; diff --git a/apps/manager/src/app/(private)/leagues/[id]/create-game/form-section.tsx b/apps/manager/src/app/(private)/leagues/[id]/create-game/form-section.tsx index 88e22013..c2de288c 100644 --- a/apps/manager/src/app/(private)/leagues/[id]/create-game/form-section.tsx +++ b/apps/manager/src/app/(private)/leagues/[id]/create-game/form-section.tsx @@ -21,7 +21,7 @@ export const FormSection = ({ leagueId }: Props) => { toast.success('경기가 생성되었습니다.'); router.back(); }, - onError: error => { + onError: (error) => { console.error(`[manager/leagues/${leagueId}/create-game]`, error); toast.error('경기 생성에 실패했습니다. 잠시 후 다시 시도해주세요.'); }, diff --git a/apps/manager/src/app/(private)/leagues/[id]/create-game/page.tsx b/apps/manager/src/app/(private)/leagues/[id]/create-game/page.tsx index c8a6d68b..de6e4ec0 100644 --- a/apps/manager/src/app/(private)/leagues/[id]/create-game/page.tsx +++ b/apps/manager/src/app/(private)/leagues/[id]/create-game/page.tsx @@ -10,7 +10,7 @@ const Page = async ({ params }: Props) => { const { id: _id } = await params; if (!_id || Number.isNaN(Number(_id))) notFound(); - const id: number = Number(_id); + const id = Number(_id); return ( <> diff --git a/apps/manager/src/app/(private)/leagues/[id]/manage/_components/form-section.tsx b/apps/manager/src/app/(private)/leagues/[id]/manage/_components/form-section.tsx index bf88c294..47dfa36b 100644 --- a/apps/manager/src/app/(private)/leagues/[id]/manage/_components/form-section.tsx +++ b/apps/manager/src/app/(private)/leagues/[id]/manage/_components/form-section.tsx @@ -41,7 +41,7 @@ export const LeagueEditContainer = ({ leagueId }: { leagueId: number }) => { return ( ({ + initialTeams={teams?.map((t) => ({ teamId: t.teamId, teamName: t.teamName, affiliationName: '', diff --git a/apps/manager/src/app/(private)/leagues/[id]/manage/page.tsx b/apps/manager/src/app/(private)/leagues/[id]/manage/page.tsx index 1a1b5a60..756e9e7e 100644 --- a/apps/manager/src/app/(private)/leagues/[id]/manage/page.tsx +++ b/apps/manager/src/app/(private)/leagues/[id]/manage/page.tsx @@ -11,7 +11,7 @@ const Page = async ({ params }: Props) => { const { id: _id } = await params; if (!_id || Number.isNaN(Number(_id))) notFound(); - const id: number = Number(_id); + const id = Number(_id); return ( <> diff --git a/apps/manager/src/app/(private)/leagues/[id]/page.tsx b/apps/manager/src/app/(private)/leagues/[id]/page.tsx index 20b48e86..3bc3e8f5 100644 --- a/apps/manager/src/app/(private)/leagues/[id]/page.tsx +++ b/apps/manager/src/app/(private)/leagues/[id]/page.tsx @@ -21,7 +21,7 @@ const Page = async ({ params }: Props) => { const { id: _id } = await params; if (!_id || Number.isNaN(Number(_id))) notFound(); - const id: number = Number(_id); + const id = Number(_id); return ( <> diff --git a/apps/manager/src/app/(private)/leagues/_components/league-overview.tsx b/apps/manager/src/app/(private)/leagues/_components/league-overview.tsx index 54e93d94..76873fcb 100644 --- a/apps/manager/src/app/(private)/leagues/_components/league-overview.tsx +++ b/apps/manager/src/app/(private)/leagues/_components/league-overview.tsx @@ -14,7 +14,7 @@ export const LeagueOverview = () => { const router = useRouter(); return ( - {data.map(league => ( + {data.map((league) => (
@@ -63,7 +63,7 @@ export const LeagueOverview = () => { size="sm" color="black" variant="subtle" - onClick={e => { + onClick={(e) => { e.stopPropagation(); router.push(`/${routes.teams}`); }} @@ -75,7 +75,7 @@ export const LeagueOverview = () => { size="sm" color="black" variant="subtle" - onClick={e => { + onClick={(e) => { e.stopPropagation(); router.push(`/${routes.cheertalks}`); }} diff --git a/apps/manager/src/app/(private)/leagues/_components/select-team.tsx b/apps/manager/src/app/(private)/leagues/_components/select-team.tsx index 32574434..9cfa7ebc 100644 --- a/apps/manager/src/app/(private)/leagues/_components/select-team.tsx +++ b/apps/manager/src/app/(private)/leagues/_components/select-team.tsx @@ -53,7 +53,7 @@ export const SelectTeam = ({ onClose, onRegister, maxSelectCount }: TeamCreation return Object.keys(units).map((unit, index) => ({ id: index + 1, name: unit, - teams: units[unit].map(team => ({ + teams: units[unit].map((team) => ({ id: team.id, name: team.name, })), @@ -65,17 +65,17 @@ export const SelectTeam = ({ onClose, onRegister, maxSelectCount }: TeamCreation ); const [selectedTeams, setSelectedTeams] = useState([]); const selectedAffiliation = useMemo( - () => affiliations.find(aff => aff.name === selectedAffiliationId), + () => affiliations.find((aff) => aff.name === selectedAffiliationId), [affiliations, selectedAffiliationId], ); const handleTeamClick = (team: Team) => { if (!selectedAffiliation) return; - const isAlreadySelected = selectedTeams.some(t => t.teamId === team.id); + const isAlreadySelected = selectedTeams.some((t) => t.teamId === team.id); if (isAlreadySelected) { // 이미 선택된 팀이면 제거 - setSelectedTeams(prev => prev.filter(t => t.teamId !== team.id)); + setSelectedTeams((prev) => prev.filter((t) => t.teamId !== team.id)); } else { // 새로운 팀 선택 시 최대 개수 제한 확인 if (selectedTeams.length >= maxSelectCount) { @@ -83,7 +83,7 @@ export const SelectTeam = ({ onClose, onRegister, maxSelectCount }: TeamCreation return; } // 선택 목록에 추가 - setSelectedTeams(prev => [ + setSelectedTeams((prev) => [ ...prev, { affiliationName: selectedAffiliation.name, @@ -112,7 +112,7 @@ export const SelectTeam = ({ onClose, onRegister, maxSelectCount }: TeamCreation
소속
- {affiliations.map(affiliation => ( + {affiliations.map((affiliation) => (
팀 이름
- {selectedAffiliation?.teams.map(team => ( + {selectedAffiliation?.teams.map((team) => ( t.teamId === team.id)} + isSelected={selectedTeams.some((t) => t.teamId === team.id)} onClick={() => handleTeamClick(team)} /> ))} diff --git a/apps/manager/src/app/(private)/leagues/create/LeagueInfo.tsx b/apps/manager/src/app/(private)/leagues/create/LeagueInfo.tsx index 44cbc4f1..0e68d127 100644 --- a/apps/manager/src/app/(private)/leagues/create/LeagueInfo.tsx +++ b/apps/manager/src/app/(private)/leagues/create/LeagueInfo.tsx @@ -13,7 +13,7 @@ export type LeagueInfoForm = { }; const ROUND_SIZES = [32, 16, 8, 4, 2]; -const ROUND_OPTIONS = ROUND_SIZES.map(n => ({ +const ROUND_OPTIONS = ROUND_SIZES.map((n) => ({ value: String(n), label: n === 2 ? '결승' : `${n}강`, })); @@ -39,26 +39,26 @@ const LeagueInfo = ({ form, onChange, onNext, isFormValid }: LeagueInfoProps) => placeholder="대회 이름" autoComplete="name" value={form.name} - onChange={e => onChange({ name: e.target.value })} + onChange={(e) => onChange({ name: e.target.value })} /> onChange({ startAt: d ?? undefined })} + onSelect={(d) => onChange({ startAt: d ?? undefined })} /> onChange({ endAt: d ?? undefined })} + onSelect={(d) => onChange({ endAt: d ?? undefined })} /> onChange({ maxRound: Number(v) })} + onValueChange={(v) => onChange({ maxRound: Number(v) })} />
diff --git a/apps/manager/src/app/(private)/leagues/create/LeagueRegister.tsx b/apps/manager/src/app/(private)/leagues/create/LeagueRegister.tsx index 08428b72..73d7892d 100644 --- a/apps/manager/src/app/(private)/leagues/create/LeagueRegister.tsx +++ b/apps/manager/src/app/(private)/leagues/create/LeagueRegister.tsx @@ -33,9 +33,11 @@ const LeagueRegister = ({ onPrev, round, leagueInfoForm, initialTeams = [], onSu const isEditMode = !!onSubmit; const handleRegisterTeam = (newTeams: RegisteredTeam[]) => { - setRegisteredTeams(prevTeams => { + setRegisteredTeams((prevTeams) => { // 기존에 없던 팀들만 필터링하여 합치기 (중복 방지) - const uniqueNewTeams = newTeams.filter(nt => !prevTeams.some(pt => pt.teamId === nt.teamId)); + const uniqueNewTeams = newTeams.filter( + (nt) => !prevTeams.some((pt) => pt.teamId === nt.teamId), + ); // 최대 참가 팀 수 제한 체크 const combined = [...prevTeams, ...uniqueNewTeams]; @@ -45,10 +47,10 @@ const LeagueRegister = ({ onPrev, round, leagueInfoForm, initialTeams = [], onSu }; const handleRemoveTeam = (teamId: number) => { - setRegisteredTeams(prevTeams => prevTeams.filter(team => team.teamId !== teamId)); + setRegisteredTeams((prevTeams) => prevTeams.filter((team) => team.teamId !== teamId)); }; const handleCreateLeague = () => { - const teamIds = registeredTeams.map(team => team.teamId); + const teamIds = registeredTeams.map((team) => team.teamId); if (isEditMode) { onSubmit(teamIds); @@ -63,7 +65,7 @@ const LeagueRegister = ({ onPrev, round, leagueInfoForm, initialTeams = [], onSu onSuccess: () => { router.push('/leagues'); }, - onError: error => { + onError: (error) => { alert(`대회 생성에 실패했습니다: ${error.message}`); }, }); @@ -95,7 +97,7 @@ const LeagueRegister = ({ onPrev, round, leagueInfoForm, initialTeams = [], onSu
- {registeredTeams.map(team => ( + {registeredTeams.map((team) => (
); diff --git a/apps/manager/src/app/(private)/teams/_components/team-form.tsx b/apps/manager/src/app/(private)/teams/_components/team-form.tsx index dbe8b8fb..381ee23e 100644 --- a/apps/manager/src/app/(private)/teams/_components/team-form.tsx +++ b/apps/manager/src/app/(private)/teams/_components/team-form.tsx @@ -52,7 +52,7 @@ export const TeamForm = ({ className, onSubmit, initialData, ...props }: Props) step.title)} + steps={STEPS.map((step) => step.title)} /> { return (
- {data.map(team => ( + {data.map((team) => (
diff --git a/apps/manager/src/app/(private)/teams/_components/team-players-step.tsx b/apps/manager/src/app/(private)/teams/_components/team-players-step.tsx index a7563da4..1c00b3bb 100644 --- a/apps/manager/src/app/(private)/teams/_components/team-players-step.tsx +++ b/apps/manager/src/app/(private)/teams/_components/team-players-step.tsx @@ -20,7 +20,7 @@ export const TeamPlayersStep = ({ onPrevious }: Props) => { const isValid = teamPlayers.length > 0; const handleAppendPlayer = (id: number) => { - if (teamPlayers.find(player => player.playerId === id)) { + if (teamPlayers.find((player) => player.playerId === id)) { toast.error('이 선수는 이미 추가되었어요.'); return; } @@ -51,7 +51,7 @@ export const TeamPlayersStep = ({ onPrevious }: Props) => { player.playerId === field.playerId)?.name ?? '-'} + value={data.find((player) => player.playerId === field.playerId)?.name ?? '-'} disabled readOnly /> @@ -60,7 +60,7 @@ export const TeamPlayersStep = ({ onPrevious }: Props) => { size="lg" placeholder="학번" value={ - data.find(player => player.playerId === field.playerId)?.studentNumber ?? '-' + data.find((player) => player.playerId === field.playerId)?.studentNumber ?? '-' } disabled readOnly diff --git a/apps/manager/src/app/(public)/auth/login/login-form.tsx b/apps/manager/src/app/(public)/auth/login/login-form.tsx index 7fde4c57..c4b0b076 100644 --- a/apps/manager/src/app/(public)/auth/login/login-form.tsx +++ b/apps/manager/src/app/(public)/auth/login/login-form.tsx @@ -22,7 +22,7 @@ export const LoginForm = () => { onSuccess: () => { router.push(`/${routes.home}`); }, - onError: error => { + onError: (error) => { console.error(`[hcc] ${error}`); toast.error('아이디 또는 비밀번호 오류'); }, diff --git a/apps/manager/src/components/ui/input-select.tsx b/apps/manager/src/components/ui/input-select.tsx index 06e46d39..5ae6b99b 100644 --- a/apps/manager/src/components/ui/input-select.tsx +++ b/apps/manager/src/components/ui/input-select.tsx @@ -47,7 +47,7 @@ export const InputSelect = ({ options, placeholder, label, ...props }: BoxSelect className="z-[80] w-[var(--radix-select-trigger-width)] overflow-hidden rounded-lg border border-gray-200 bg-white shadow-lg" > - {options.map(option => ( + {options.map((option) => ( { const messages = Object.values(errors) - .map(error => error?.message) + .map((error) => error?.message) .filter(Boolean); const [first, ...rest] = messages; diff --git a/apps/manager/tsconfig.json b/apps/manager/tsconfig.json index 01fc0125..300f41a7 100644 --- a/apps/manager/tsconfig.json +++ b/apps/manager/tsconfig.json @@ -2,8 +2,7 @@ "extends": "@hcc/typescript-config/nextjs.json", "compilerOptions": { "plugins": [{ "name": "next" }], - "baseUrl": ".", - "paths": { "~/*": ["src/*"] } + "paths": { "~/*": ["./src/*"] } }, "include": ["**/*.ts", "**/*.tsx", "next-env.d.ts", "next.config.ts", ".next/types/**/*.ts"], "exclude": ["node_modules"] diff --git a/apps/spectator/package.json b/apps/spectator/package.json index 1544cde5..1f80b400 100644 --- a/apps/spectator/package.json +++ b/apps/spectator/package.json @@ -6,14 +6,9 @@ "dev": "next dev --turbopack --port 11113 --hostname 0.0.0.0", "build": "next build", "start": "next start", - "lint": "biome lint .", - "format": "biome format --write ." - }, - "lint-staged": { - "*.{js,jsx,ts,tsx}": [ - "biome lint --fix", - "biome format --write" - ] + "lint": "oxlint", + "lint:fix": "oxlint --fix", + "format": "oxfmt" }, "dependencies": { "@base-ui/react": "^1.1.0", diff --git a/apps/spectator/postcss.config.mjs b/apps/spectator/postcss.config.mjs index c7bcb4b1..ba720fe5 100644 --- a/apps/spectator/postcss.config.mjs +++ b/apps/spectator/postcss.config.mjs @@ -1,5 +1,5 @@ const config = { - plugins: ["@tailwindcss/postcss"], + plugins: ['@tailwindcss/postcss'], }; export default config; diff --git a/apps/spectator/src/api/queryKey.ts b/apps/spectator/src/api/queryKey.ts index 2585adaa..c85c589f 100644 --- a/apps/spectator/src/api/queryKey.ts +++ b/apps/spectator/src/api/queryKey.ts @@ -92,7 +92,7 @@ const teamQueryKeys = createQueryKeys('teams', { if (payload.units) { const units = Array.isArray(payload.units) ? payload.units : [payload.units]; - units.forEach(u => params.append('units', u)); + units.forEach((u) => params.append('units', u)); } return fetcher.get('teams', { searchParams: params }); @@ -117,7 +117,7 @@ const teamQueryKeys = createQueryKeys('teams', { if (payload.units) { const units = Array.isArray(payload.units) ? payload.units : [payload.units]; - units.forEach(u => params.append('units', u)); + units.forEach((u) => params.append('units', u)); } return fetcher.get('teams/summary', { diff --git a/apps/spectator/src/app/(home)/_components/recent-records.tsx b/apps/spectator/src/app/(home)/_components/recent-records.tsx index aa8eb60b..02b8dc9a 100644 --- a/apps/spectator/src/app/(home)/_components/recent-records.tsx +++ b/apps/spectator/src/app/(home)/_components/recent-records.tsx @@ -19,7 +19,7 @@ export const RecentRecords = () => { 최근 대회 기록 - {leagueRecentSummary.records.map(record => ( + {leagueRecentSummary.records.map((record) => ( {record.name} diff --git a/apps/spectator/src/app/(home)/_components/tab-header.tsx b/apps/spectator/src/app/(home)/_components/tab-header.tsx index 14e1c01b..bd511dba 100644 --- a/apps/spectator/src/app/(home)/_components/tab-header.tsx +++ b/apps/spectator/src/app/(home)/_components/tab-header.tsx @@ -3,6 +3,7 @@ import { Tabs } from '@base-ui/react'; import Link from 'next/link'; import { useSelectedLayoutSegment } from 'next/navigation'; +import { cn } from '~/utils/cn'; interface TabHeaderProps extends Tabs.Root.Props {} @@ -32,7 +33,10 @@ export const TabHeader = ({ children, ...props }: TabHeaderProps) => { const Tab = ({ children, className, ...props }: Tabs.Tab.Props) => { return ( {children} diff --git a/apps/spectator/src/app/(home)/_components/tab.tsx b/apps/spectator/src/app/(home)/_components/tab.tsx index ab11b115..1fc6d605 100644 --- a/apps/spectator/src/app/(home)/_components/tab.tsx +++ b/apps/spectator/src/app/(home)/_components/tab.tsx @@ -22,7 +22,7 @@ export const RecentTab = () => { return (
- {playing.map(league => ( + {playing.map((league) => ( @@ -52,7 +52,7 @@ export const RecentTab = () => { })} ))} - {scheduled.map(league => ( + {scheduled.map((league) => ( diff --git a/apps/spectator/src/app/(home)/previous/_components/league-card-list.tsx b/apps/spectator/src/app/(home)/previous/_components/league-card-list.tsx index 331a0fe0..9a9335e6 100644 --- a/apps/spectator/src/app/(home)/previous/_components/league-card-list.tsx +++ b/apps/spectator/src/app/(home)/previous/_components/league-card-list.tsx @@ -18,7 +18,7 @@ export const LeagueCardList = ({ year }: Props) => { return (
- {data.map(league => ( + {data.map((league) => ( @@ -30,7 +30,7 @@ export const LeagueCardList = ({ year }: Props) => {
- +
diff --git a/apps/spectator/src/app/(home)/previous/_components/league-card.tsx b/apps/spectator/src/app/(home)/previous/_components/league-card.tsx index 5d61efe1..4b77d9fc 100644 --- a/apps/spectator/src/app/(home)/previous/_components/league-card.tsx +++ b/apps/spectator/src/app/(home)/previous/_components/league-card.tsx @@ -156,7 +156,7 @@ export const Scorers = ({ limit = 3, className, ...props }: LeagueCardScorersPro ) : (
    - {data.slice(0, limit).map(scorer => ( + {data.slice(0, limit).map((scorer) => (
  • { - limit?: number; -} +interface LeagueCardStatisticsProps extends ComponentProps<'div'> {} -export const Statistics = ({ limit = 3, className, ...props }: LeagueCardStatisticsProps) => { +export const Statistics = ({ className, ...props }: LeagueCardStatisticsProps) => { const { leagueId } = useLeagueCardContext(); const { data } = useSuspenseLeagueStatistics({ leagueId }); diff --git a/apps/spectator/src/app/(home)/previous/_components/year-filter.tsx b/apps/spectator/src/app/(home)/previous/_components/year-filter.tsx index d9e8ec64..92dbdd4b 100644 --- a/apps/spectator/src/app/(home)/previous/_components/year-filter.tsx +++ b/apps/spectator/src/app/(home)/previous/_components/year-filter.tsx @@ -18,7 +18,7 @@ export const YearFilter = ({ selectedYear }: Props) => { return (
    - {years.map(_year => { + {years.map((_year) => { return (
    { return (
    - {games.slice(0, limit).map(game => { + {games.slice(0, limit).map((game) => { if (game.gameTeams.length < 2) return null; const [home, away] = diff --git a/apps/spectator/src/app/(home)/teams/_components/score-list.tsx b/apps/spectator/src/app/(home)/teams/_components/score-list.tsx index 1ab0d5f2..9ad2b74c 100644 --- a/apps/spectator/src/app/(home)/teams/_components/score-list.tsx +++ b/apps/spectator/src/app/(home)/teams/_components/score-list.tsx @@ -16,7 +16,7 @@ export const ScoreList = ({ scorers, limit = 3 }: ScoreListProps) => { 아직 득점 기록이 없어요. )} - {scorers.slice(0, limit).map(player => ( + {scorers.slice(0, limit).map((player) => (
    {player.playerName} diff --git a/apps/spectator/src/app/(home)/teams/_components/score-modal.tsx b/apps/spectator/src/app/(home)/teams/_components/score-modal.tsx index 44984307..bc693828 100644 --- a/apps/spectator/src/app/(home)/teams/_components/score-modal.tsx +++ b/apps/spectator/src/app/(home)/teams/_components/score-modal.tsx @@ -80,7 +80,7 @@ export function ScorersModal({ open, onOpenChange, teamName, teamId }: TopScorer - {rows.map(r => { + {rows.map((r) => { const isTopThree = r.rank <= 3; const fontWeightClass = isTopThree ? 'font-semibold bg-gray-100' : 'font-medium'; diff --git a/apps/spectator/src/app/(home)/teams/_components/tab.tsx b/apps/spectator/src/app/(home)/teams/_components/tab.tsx index 1c23f0f6..de0b7f2e 100644 --- a/apps/spectator/src/app/(home)/teams/_components/tab.tsx +++ b/apps/spectator/src/app/(home)/teams/_components/tab.tsx @@ -26,7 +26,7 @@ export const TeamTab = () => {
    - {data.map(team => ( + {data.map((team) => ( @@ -68,7 +68,7 @@ export const TeamTab = () => { { + onOpenChange={(next) => { if (!next) setModal(null); }} teamId={modal.teamId} diff --git a/apps/spectator/src/app/(home)/teams/_components/team-filter/useTeamUnits.ts b/apps/spectator/src/app/(home)/teams/_components/team-filter/useTeamUnits.ts index 29154ece..a1f59660 100644 --- a/apps/spectator/src/app/(home)/teams/_components/team-filter/useTeamUnits.ts +++ b/apps/spectator/src/app/(home)/teams/_components/team-filter/useTeamUnits.ts @@ -6,7 +6,7 @@ export const useTeamUnits = () => { const [units, setUnits] = useQueryState('units', parseAsArrayOf(parseAsString).withDefault([])); const selected = useMemo( - () => units.filter(u => TEAM_UNIT_LIST.includes(u as TeamUnitType)) as TeamUnitType[], + () => units.filter((u) => TEAM_UNIT_LIST.includes(u as TeamUnitType)) as TeamUnitType[], [units], ); @@ -19,7 +19,7 @@ export const useTeamUnits = () => { } const isActive = selected.includes(unit); - const updated = isActive ? selected.filter(u => u !== unit) : [...selected, unit]; + const updated = isActive ? selected.filter((u) => u !== unit) : [...selected, unit]; startTransition(() => { setUnits(updated, { scroll: false, history: 'replace' }); diff --git a/apps/spectator/src/app/calendar/_components/CalendarOverview.tsx b/apps/spectator/src/app/calendar/_components/CalendarOverview.tsx index a666e81d..c2c4843d 100644 --- a/apps/spectator/src/app/calendar/_components/CalendarOverview.tsx +++ b/apps/spectator/src/app/calendar/_components/CalendarOverview.tsx @@ -40,7 +40,7 @@ export const CalendarOverview = () => { const { gamesByDate, gameDates } = useMemo(() => { const group: Record = {}; - games.forEach(game => { + games.forEach((game) => { const dateObj = new Date(game.startTime); if (dateObj.getFullYear() === year && dateObj.getMonth() === month) { const date = dateObj.getDate(); @@ -79,7 +79,7 @@ export const CalendarOverview = () => { {filteredGames.length > 0 ? ( <> - {filteredGames.map(game => ( + {filteredGames.map((game) => (
    - {['일', '월', '화', '수', '목', '금', '토'].map(d => ( + {['일', '월', '화', '수', '목', '금', '토'].map((d) => (
    {d}
    @@ -62,12 +62,15 @@ export const CalendarGrid = ({
    - {days.map((d, i) => { - const isSelected = d === selectedDay; - const hasGame = d !== null && gameDates.includes(d); + {days.map((day) => { + const isSelected = day === selectedDay; + const hasGame = day !== null && gameDates.includes(day); return ( -
    - {d ? ( +
    + {day ? (
    {hasGame && !isSelected && ( @@ -80,7 +83,7 @@ export const CalendarGrid = ({
    ) : ( diff --git a/apps/spectator/src/app/games/[id]/_components/cheer-talk/cheer-talk-form.tsx b/apps/spectator/src/app/games/[id]/_components/cheer-talk/cheer-talk-form.tsx index b2a0bfca..78f4a057 100644 --- a/apps/spectator/src/app/games/[id]/_components/cheer-talk/cheer-talk-form.tsx +++ b/apps/spectator/src/app/games/[id]/_components/cheer-talk/cheer-talk-form.tsx @@ -38,13 +38,13 @@ export const CheerTalkForm = ({ gameTeams, scrollToBottom, gameState }: CheerTal return (
    - {gameTeams.map(team => ( + {gameTeams.map((team) => (