@@ -180,6 +180,7 @@ function TransactionsPage() {
180180 const [ detailTransaction , setDetailTransaction ] = useState < Transaction | null > ( null )
181181 const [ swipedTxId , setSwipedTxId ] = useState < number | null > ( null )
182182 const touchStartX = useRef < number | null > ( null )
183+ const recentMemosRequestId = useRef ( 0 )
183184
184185 const [ searchOpen , setSearchOpen ] = useState ( false )
185186 const [ searchKeyword , setSearchKeyword ] = useState ( '' )
@@ -195,6 +196,7 @@ function TransactionsPage() {
195196 const [ schedules , setSchedules ] = useState < Schedule [ ] > ( [ ] )
196197 const [ installments , setInstallments ] = useState < Installment [ ] > ( [ ] )
197198 const [ recentCategories , setRecentCategories ] = useState < Category [ ] > ( [ ] )
199+ const [ recentMemos , setRecentMemos ] = useState < string [ ] > ( [ ] )
198200
199201 const goPrevMonth = ( ) => {
200202 const current = new Date ( selectedYear , selectedMonth - 1 , 1 )
@@ -296,6 +298,25 @@ function TransactionsPage() {
296298 setRecentCategories ( Array . isArray ( payload ) ? payload : ( payload . data ?? [ ] ) )
297299 } , [ ledgerId , tab ] )
298300
301+ const fetchRecentMemos = useCallback ( async ( ) => {
302+ const requestId = recentMemosRequestId . current + 1
303+ recentMemosRequestId . current = requestId
304+ setRecentMemos ( [ ] )
305+
306+ if ( ! ledgerId || tab === 'TRANSFER' || ! categoryId ) {
307+ return
308+ }
309+
310+ const response = await apiFetch (
311+ `/api/ledgers/${ ledgerId } /transactions/recent-memos?categoryId=${ encodeURIComponent ( categoryId ) } ` ,
312+ )
313+ if ( ! response . ok ) return
314+
315+ const payload = ( await response . json ( ) ) as { data ?: string [ ] } | string [ ]
316+ if ( requestId !== recentMemosRequestId . current ) return
317+ setRecentMemos ( Array . isArray ( payload ) ? payload : ( payload . data ?? [ ] ) )
318+ } , [ categoryId , ledgerId , tab ] )
319+
299320 useEffect ( ( ) => {
300321 void fetchTransactions ( )
301322 void fetchMetadata ( )
@@ -311,6 +332,11 @@ function TransactionsPage() {
311332 void fetchRecentCategories ( )
312333 } , [ entryOpen , fetchRecentCategories , tab ] )
313334
335+ useEffect ( ( ) => {
336+ if ( ! entryOpen ) return
337+ void fetchRecentMemos ( )
338+ } , [ entryOpen , fetchRecentMemos ] )
339+
314340 useEffect ( ( ) => {
315341 setCalendarDateSheetOpen ( false )
316342 setSelectedCalendarDate ( null )
@@ -361,6 +387,7 @@ function TransactionsPage() {
361387 setIsFixed ( false )
362388 setTab ( 'EXPENSE' )
363389 setEditingTransactionId ( null )
390+ setRecentMemos ( [ ] )
364391 }
365392
366393 const openCreate = ( ) => {
@@ -550,6 +577,13 @@ function TransactionsPage() {
550577
551578 const monthLabel = `${ selectedYear } 년 ${ selectedMonth } 월`
552579 const categoryOptions = categories . filter ( ( item ) => item . type === tab )
580+ const memoSuggestions = useMemo ( ( ) => {
581+ const keyword = memo . trim ( ) . toLowerCase ( )
582+ if ( ! keyword || ! categoryId || tab === 'TRANSFER' ) return [ ]
583+ return recentMemos
584+ . filter ( ( item ) => item . toLowerCase ( ) . includes ( keyword ) && item . toLowerCase ( ) !== keyword )
585+ . slice ( 0 , 5 )
586+ } , [ categoryId , memo , recentMemos , tab ] )
553587 const accountNameById = useMemo ( ( ) => new Map ( accounts . map ( ( a ) => [ a . id , a . name ] ) ) , [ accounts ] )
554588 const installmentById = useMemo ( ( ) => new Map ( installments . map ( ( item ) => [ item . id , item ] ) ) , [ installments ] )
555589 const categoryNameById = useMemo (
@@ -1930,13 +1964,30 @@ function TransactionsPage() {
19301964 </ select >
19311965 ) : null }
19321966
1933- < input
1934- value = { memo }
1935- onChange = { ( e ) => setMemo ( e . target . value ) }
1936- placeholder = "메모 (선택)"
1937- className = "rounded-md border px-3 py-2 text-sm"
1938- aria-label = "메모"
1939- />
1967+ < div className = "relative" >
1968+ < input
1969+ value = { memo }
1970+ onChange = { ( e ) => setMemo ( e . target . value ) }
1971+ placeholder = "메모 (선택)"
1972+ className = "w-full rounded-md border px-3 py-2 text-sm"
1973+ aria-label = "메모"
1974+ autoComplete = "off"
1975+ />
1976+ { memoSuggestions . length > 0 ? (
1977+ < div className = "absolute left-0 right-0 top-[calc(100%+4px)] z-10 rounded-md border bg-popover shadow-sm" >
1978+ { memoSuggestions . map ( ( suggestion ) => (
1979+ < button
1980+ key = { suggestion }
1981+ type = "button"
1982+ onClick = { ( ) => setMemo ( suggestion ) }
1983+ className = "w-full px-3 py-2 text-left text-sm hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background"
1984+ >
1985+ { suggestion }
1986+ </ button >
1987+ ) ) }
1988+ </ div >
1989+ ) : null }
1990+ </ div >
19401991
19411992 < label className = "flex items-center gap-2 text-sm" >
19421993 < input type = "checkbox" checked = { isFixed } onChange = { ( e ) => setIsFixed ( e . target . checked ) } />
0 commit comments