From 228a90009db6788ea192624236988f99392bf5eb Mon Sep 17 00:00:00 2001 From: Hanbin Cho Date: Wed, 22 Apr 2026 09:00:06 +0900 Subject: [PATCH 1/3] Fix margin --- apps/landing/src/app/test-case/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/landing/src/app/test-case/page.tsx b/apps/landing/src/app/test-case/page.tsx index 94c50d5..9c1c437 100644 --- a/apps/landing/src/app/test-case/page.tsx +++ b/apps/landing/src/app/test-case/page.tsx @@ -260,7 +260,7 @@ export default async function TestCasePage() { - + {cases} From 482acc4d205dcd997feadf64c79e1f77444304a8 Mon Sep 17 00:00:00 2001 From: Hanbin Cho Date: Wed, 22 Apr 2026 09:18:06 +0900 Subject: [PATCH 2/3] Fix sidesheet close button --- apps/landing/src/app/test-case/page.tsx | 133 +++++++++--------- .../test-case/TestCaseFilterValue.tsx | 13 ++ .../test-case/TestCaseTypeToggle.tsx | 4 + 3 files changed, 81 insertions(+), 69 deletions(-) create mode 100644 apps/landing/src/components/test-case/TestCaseFilterValue.tsx diff --git a/apps/landing/src/app/test-case/page.tsx b/apps/landing/src/app/test-case/page.tsx index 9c1c437..3cfa290 100644 --- a/apps/landing/src/app/test-case/page.tsx +++ b/apps/landing/src/app/test-case/page.tsx @@ -17,6 +17,7 @@ import { TestCaseList } from '@/components/test-case/list/TestCaseList' import { TestCaseTable } from '@/components/test-case/table/TestCaseTable' import { TestCaseDisplayBoundary } from '@/components/test-case/TestCaseDisplayBoundary' import { TestCaseFilterContainer } from '@/components/test-case/TestCaseFilterContainer' +import { TestCaseFilterValue } from '@/components/test-case/TestCaseFilterValue' import { TestCaseProvider } from '@/components/test-case/TestCaseProvider' import { TestCaseRuleContainer } from '@/components/test-case/TestCaseRuleContainer' import { TestCaseStat } from '@/components/test-case/TestCaseStat' @@ -334,44 +335,41 @@ export default async function TestCasePage() { })} position="bottom" > - -
- + + - 접기 + + 목차 -
-
- - - 한글 목차 - + +
+ +
+
+ - -
- + + - 접기 + + 목차 -
-
- - - 한글 목차 - + +
+ +
+
+ +}) { + const { options } = useTestCase() + const selected = options.filters[0] + return map[selected] +} diff --git a/apps/landing/src/components/test-case/TestCaseTypeToggle.tsx b/apps/landing/src/components/test-case/TestCaseTypeToggle.tsx index d454991..35fbd20 100644 --- a/apps/landing/src/components/test-case/TestCaseTypeToggle.tsx +++ b/apps/landing/src/components/test-case/TestCaseTypeToggle.tsx @@ -1,6 +1,7 @@ 'use client' import { Toggle } from '@devup-ui/components' +import { css } from '@devup-ui/react' import { ComponentProps } from 'react' import { useTestCase } from './TestCaseProvider' @@ -9,6 +10,9 @@ export function TestCaseTypeToggle(props: ComponentProps) { const { options, onChangeOptions } = useTestCase() return ( onChangeOptions({ ...options, type: value ? 'table' : 'list' }) } From f8670092cae4639101f996eeddd5217a68a011e8 Mon Sep 17 00:00:00 2001 From: Hanbin Cho Date: Wed, 22 Apr 2026 10:49:43 +0900 Subject: [PATCH 3/3] Feat stats in each test case category --- apps/landing/src/app/test-case/page.tsx | 99 +++++++++++++------ .../components/test-case/TestCaseProvider.tsx | 13 +++ .../test-case/TestCaseRuleContainer.tsx | 7 +- .../src/components/test-case/TestCaseStat.tsx | 14 ++- .../test-case/TestCaseStatFiltered.tsx | 24 +++++ apps/landing/src/constants/index.ts | 6 +- 6 files changed, 127 insertions(+), 36 deletions(-) create mode 100644 apps/landing/src/components/test-case/TestCaseStatFiltered.tsx diff --git a/apps/landing/src/app/test-case/page.tsx b/apps/landing/src/app/test-case/page.tsx index 3cfa290..26dfb7b 100644 --- a/apps/landing/src/app/test-case/page.tsx +++ b/apps/landing/src/app/test-case/page.tsx @@ -18,12 +18,22 @@ import { TestCaseTable } from '@/components/test-case/table/TestCaseTable' import { TestCaseDisplayBoundary } from '@/components/test-case/TestCaseDisplayBoundary' import { TestCaseFilterContainer } from '@/components/test-case/TestCaseFilterContainer' import { TestCaseFilterValue } from '@/components/test-case/TestCaseFilterValue' -import { TestCaseProvider } from '@/components/test-case/TestCaseProvider' +import { + FilterTotalMap, + TestCaseFilter as TestCaseFilterType, + TestCaseProvider, +} from '@/components/test-case/TestCaseProvider' import { TestCaseRuleContainer } from '@/components/test-case/TestCaseRuleContainer' import { TestCaseStat } from '@/components/test-case/TestCaseStat' +import { TestCaseStatFiltered } from '@/components/test-case/TestCaseStatFiltered' import { TestCaseTotalBoundary } from '@/components/test-case/TestCaseTotalBoundary' import { TestCaseTypeToggle } from '@/components/test-case/TestCaseTypeToggle' -import { createFilterMap, TEST_CASE_FILTERS } from '@/constants' +import { + CATEGORY_PREFIX_MAP, + createFilterMap, + TEST_CASE_FILTERS, + TEST_CASE_FILTERS_MAP, +} from '@/constants' import { TestStatusMap } from '@/types' export const metadata: Metadata = { @@ -44,13 +54,37 @@ export default async function TestCasePage() { // Dynamically create filter map based on rule_map keys const filterMap = createFilterMap(Object.keys(ruleMap)) + + const filterTotalMap = Object.fromEntries( + Object.entries(filterMap).map(([key]) => [ + key, + { + braillify: { total: 0, fail: 0 }, + world: { total: 0, fail: 0 }, + jeomsarang: { total: 0, fail: 0 }, + }, + ]), + ) as FilterTotalMap + let totalTest = 0 let totalFail = 0 let totalWorldTest = 0 let totalWorldFail = 0 let totalJeomsarangTest = 0 let totalJeomsarangFail = 0 - const cases = Object.entries(ruleMap).map(([key, value]) => { + const cases = Object.entries(ruleMap).map(([key, value], index, self) => { + const category = Object.entries(CATEGORY_PREFIX_MAP).find(([prefix]) => + key.startsWith(prefix), + )?.[1] as TestCaseFilterType | undefined + if (category) { + filterTotalMap[category].braillify.total += testStatus[key][0] + filterTotalMap[category].braillify.fail += testStatus[key][1] + filterTotalMap[category].world.total += testStatus[key][2] + filterTotalMap[category].world.fail += testStatus[key][3] + filterTotalMap[category].jeomsarang.total += testStatus[key][4] + filterTotalMap[category].jeomsarang.fail += testStatus[key][5] + } + totalTest += testStatus[key][0] totalFail += testStatus[key][1] totalWorldTest += testStatus[key][2] @@ -59,6 +93,8 @@ export default async function TestCasePage() { totalJeomsarangFail += testStatus[key][5] const isBut = value.title.includes('다만') + const currentClause = key.match(/\d+/)?.[0] + const nextClause = self[index + 1]?.[0]?.match(/\d+/)?.[0] return ( + {currentClause !== nextClause && ( + + )} ) }) return ( - + - {cases} + + + + + + + + + + {cases} +
- - 목차 + 목차
@@ -454,18 +504,7 @@ export default async function TestCasePage() { typography="featureTitle" wordBreak="keep-all" > - + 목차 diff --git a/apps/landing/src/components/test-case/TestCaseProvider.tsx b/apps/landing/src/components/test-case/TestCaseProvider.tsx index ec87fa7..c43c007 100644 --- a/apps/landing/src/components/test-case/TestCaseProvider.tsx +++ b/apps/landing/src/components/test-case/TestCaseProvider.tsx @@ -22,9 +22,19 @@ export type TestCaseOptions = { export type FilterMap = Record +export type FilterTotalMap = Record< + TestCaseFilter, + { + braillify: { total: number; fail: number } + world: { total: number; fail: number } + jeomsarang: { total: number; fail: number } + } +> + const TestCaseContext = createContext<{ testStatusMap: TestStatusMap filterMap: FilterMap + filterTotalMap: FilterTotalMap options: TestCaseOptions onChangeOptions: (options: Partial) => void } | null>(null) @@ -40,10 +50,12 @@ export function useTestCase() { export function TestCaseProvider({ testStatusMap, filterMap, + filterTotalMap, children, }: { testStatusMap: TestStatusMap filterMap: FilterMap + filterTotalMap: FilterTotalMap children: React.ReactNode }) { const [options, setOptions] = useState({ @@ -59,6 +71,7 @@ export function TestCaseProvider({ >) { const { options } = useTestCase() const isList = options.type === 'list' return ( @@ -20,6 +21,8 @@ export function TestCaseRuleContainer({ pb={[isList ? '30px' : '40px', null, null, '40px']} pt={exception ? null : [isList ? '30px' : '40px', null, null, '40px']} px={['16px', null, null, '60px']} + styleOrder={1} + {...props} > {children} diff --git a/apps/landing/src/components/test-case/TestCaseStat.tsx b/apps/landing/src/components/test-case/TestCaseStat.tsx index f66f4f3..0afa0e6 100644 --- a/apps/landing/src/components/test-case/TestCaseStat.tsx +++ b/apps/landing/src/components/test-case/TestCaseStat.tsx @@ -33,6 +33,7 @@ function CompetitorStat({ label, total, fail }: CompetitorStatProps) { interface TestCaseStatProps extends ComponentProps> { showTotal?: boolean + colorPercentage?: boolean total: number success: number fail: number @@ -44,6 +45,7 @@ interface TestCaseStatProps extends ComponentProps> { export function TestCaseStat({ showTotal = false, + colorPercentage = true, total, success, fail, @@ -60,7 +62,12 @@ export function TestCaseStat({ const hasJeomsarang = jeomsarangTotal != null && jeomsarangTotal > 0 return ( - +
({braillifyPercent}%) @@ -110,8 +117,9 @@ export function TestCaseStat({ px: '16px', bottom: '0', left: '0', + borderRadius: '10px', })} - translateY="100%" + translateY="calc(100% + 4px)" > {hasWorld && ( diff --git a/apps/landing/src/components/test-case/TestCaseStatFiltered.tsx b/apps/landing/src/components/test-case/TestCaseStatFiltered.tsx new file mode 100644 index 0000000..142b092 --- /dev/null +++ b/apps/landing/src/components/test-case/TestCaseStatFiltered.tsx @@ -0,0 +1,24 @@ +'use client' + +import { useTestCase } from './TestCaseProvider' +import { TestCaseStat } from './TestCaseStat' + +export function TestCaseStatFiltered() { + const { options, filterTotalMap } = useTestCase() + const selected = options.filters[0] + return ( + + ) +} diff --git a/apps/landing/src/constants/index.ts b/apps/landing/src/constants/index.ts index 4ece1ab..e99db92 100644 --- a/apps/landing/src/constants/index.ts +++ b/apps/landing/src/constants/index.ts @@ -29,7 +29,11 @@ export const TEST_CASE_FILTERS: { label: string; value: TestCaseFilter }[] = [ }, ] -const CATEGORY_PREFIX_MAP: Record = { +export const TEST_CASE_FILTERS_MAP = Object.fromEntries( + TEST_CASE_FILTERS.map((filter) => [filter.value, filter.label]), +) as Record + +export const CATEGORY_PREFIX_MAP: Record = { 'korean/': 'korean', 'math/': 'math', 'science/': 'science',