11'use client' ;
2- import React , { useState } from 'react' ;
2+ import React , { useEffect , useMemo , useState } from 'react' ;
33import Button from '@/app/_components/common/Button' ;
44import Arrow from '@/assets/icons/arrow_up.svg' ;
5+ import { useSpellCheckStore } from '@/store/spellcheck.store' ;
6+ import { useEditorStore } from '@/store/editor.store' ;
7+ import { applyAllCorrections , type CorrectionPair } from '@/util/spellReplace' ;
8+ import { applySpellHighlights } from '@/util/spellMark' ;
9+ import type { Editor } from '@tiptap/core' ;
10+
11+ type UIResult = {
12+ id : number ;
13+ original : string ;
14+ corrected : string ;
15+ open : boolean ;
16+ custom : string ;
17+ } ;
518
619const SpellCheck = ( ) => {
20+ const { items, loading } = useSpellCheckStore ( ) ;
721 const [ isOpen , setIsOpen ] = useState ( false ) ;
22+ const [ results , setResults ] = useState < UIResult [ ] > ( [ ] ) ;
823
9- const [ results , setResults ] = useState ( [
10- {
11- id : 1 ,
12- original : '안농하세요' ,
13- corrected : '안녕하세요' ,
14- open : false ,
15- custom : '' ,
16- } ,
17- {
18- id : 2 ,
19- original : '안농하세요' ,
20- corrected : '안녕하세요' ,
21- open : false ,
22- custom : '' ,
23- } ,
24- {
25- id : 3 ,
26- original : '안농하세요' ,
27- corrected : '안녕하세요' ,
28- open : false ,
29- custom : '' ,
30- } ,
31- {
32- id : 4 ,
33- original : '안농하세요' ,
34- corrected : '안녕하세요 ',
35- open : false ,
24+ const { features , skills , goals , sectionNumber } = useEditorStore ( ) ;
25+ const editors = useMemo (
26+ ( ) =>
27+ ( sectionNumber === '0' ? [ features , skills , goals ] : [ features ] ) . filter (
28+ ( e ) : e is Editor => ! ! e && ! e . isDestroyed
29+ ) ,
30+ [ features , skills , goals , sectionNumber ]
31+ ) ;
32+
33+ const [ pendingPairs , setPendingPairs ] = useState < CorrectionPair [ ] | null > (
34+ null
35+ ) ;
36+
37+ useEffect ( ( ) => {
38+ if ( loading ) setIsOpen ( true ) ;
39+ } , [ loading ] ) ;
40+
41+ useEffect ( ( ) => {
42+ const next = ( items ?? [ ] ) . map ( ( item ) => ( {
43+ id : item . id ,
44+ original : item . original ?? '' ,
45+ corrected :
46+ ( Array . isArray ( item . suggestions ) && item . suggestions [ 0 ] ) ||
47+ item . corrected ||
48+ item . original ||
49+ ' ',
50+ open : Boolean ( item . open ) ,
3651 custom : '' ,
37- } ,
38- ] ) ;
52+ } ) ) ;
53+ setResults ( next ) ;
54+ } , [ items ] ) ;
55+
56+ useEffect ( ( ) => {
57+ if ( ! pendingPairs || editors . length === 0 ) return ;
58+ const raf = requestAnimationFrame ( ( ) => {
59+ applyAllCorrections ( editors , pendingPairs ) ;
60+ applySpellHighlights ( editors , items ) ;
61+ setPendingPairs ( null ) ;
62+ } ) ;
63+ return ( ) => cancelAnimationFrame ( raf ) ;
64+ } , [ pendingPairs , editors , items ] ) ;
3965
4066 const toggleItem = ( id : number ) => {
4167 setResults ( ( prev ) =>
@@ -52,21 +78,49 @@ const SpellCheck = () => {
5278 } ;
5379
5480 const handleApply = ( id : number ) => {
55- setResults ( ( prev ) =>
56- prev . map ( ( item ) =>
81+ setResults ( ( prev ) => {
82+ const target = prev . find ( ( p ) => p . id === id ) ;
83+ if ( target ) {
84+ const replacement =
85+ target . custom || target . corrected || target . original ;
86+ setPendingPairs ( [
87+ { original : target . original , corrected : replacement } ,
88+ ] ) ;
89+ }
90+ return prev . map ( ( item ) =>
5791 item . id === id
58- ? { ...item , corrected : item . custom || item . corrected , open : false }
92+ ? {
93+ ...item ,
94+ corrected : item . custom || item . corrected ,
95+ open : false ,
96+ custom : '' ,
97+ }
5998 : item
60- )
99+ ) ;
100+ } ) ;
101+ } ;
102+
103+ const handleApplyAll = ( ) => {
104+ const pairs : CorrectionPair [ ] = results . map ( ( r ) => ( {
105+ original : r . original ,
106+ corrected : r . custom || r . corrected || r . original ,
107+ } ) ) ;
108+ setPendingPairs ( pairs ) ;
109+ setResults ( ( prev ) =>
110+ prev . map ( ( it ) => ( { ...it , open : false , custom : '' } ) )
61111 ) ;
62112 } ;
63113
64114 return (
65115 < div
66- className = { `flex w-full flex-col rounded-[12px] bg-white ${ isOpen ? 'h-[297px]' : '' } ` }
116+ className = { `flex w-full flex-col rounded-[12px] bg-white ${
117+ isOpen ? 'h-[297px]' : ''
118+ } `}
67119 >
68120 < div
69- className = { `flex w-full items-center justify-between border-b border-gray-200 px-6 ${ isOpen ? 'pt-4 pb-[10px]' : 'py-4' } ` }
121+ className = { `flex w-full items-center justify-between border-b border-gray-200 px-6 ${
122+ isOpen ? 'pt-4 pb-[10px]' : 'py-4'
123+ } `}
70124 >
71125 < span className = "ds-subtitle font-semibold text-gray-900" >
72126 맞춤법 검사
@@ -75,6 +129,8 @@ const SpellCheck = () => {
75129 type = "button"
76130 aria-label = "맞춤법검사 토글"
77131 onClick = { ( ) => setIsOpen ( ( prev ) => ! prev ) }
132+ disabled = { loading }
133+ className = { loading ? 'cursor-not-allowed opacity-60' : '' }
78134 >
79135 < Arrow
80136 className = { `cursor-pointer ${ isOpen ? 'rotate-180' : 'rotate-0' } ` }
@@ -135,7 +191,11 @@ const SpellCheck = () => {
135191 </ div >
136192
137193 < div className = "px-6 py-4" >
138- < Button text = "모두 수정하기" className = "w-full rounded-[8px]" />
194+ < Button
195+ text = "모두 수정하기"
196+ className = "w-full rounded-[8px]"
197+ onClick = { handleApplyAll }
198+ />
139199 </ div >
140200 </ >
141201 ) }
0 commit comments