Skip to content

[refactor/native/scrap-#190] scrap qa 수정#192

Open
b0nsu wants to merge 46 commits intodevelopfrom
refactor/native/scrap-#190
Open

[refactor/native/scrap-#190] scrap qa 수정#192
b0nsu wants to merge 46 commits intodevelopfrom
refactor/native/scrap-#190

Conversation

@b0nsu
Copy link
Collaborator

@b0nsu b0nsu commented Feb 5, 2026

✅ Key Changes

이번 PR에서 작업한 내용을 간략히 설명해주세요

1. 낙관적 업데이트 로직 수정

  • optimisticMoveScrap 폴더 이동시, 이동된 항목을 현재 폴더에서 제거 ❌ 이동된 스크랩의 folderId 필드 변경 ✅
     queryClient.setQueriesData<ScrapSearchResponse>(filters, (old) => {
             // 원래 폴더별 제거 개수 계산
             const sourceFolderCounts = new Map<number, number>();
             old.scraps?.forEach((scrap) => {
               if (movedIds.has(`SCRAP-${scrap.id}`) && scrap.folderId != null) {
                 sourceFolderCounts.set(scrap.folderId, (sourceFolderCounts.get(scrap.folderId) ?? 0) + 1);
               }
             });
           
             return {
               folders: old.folders?.map((folder) => ({
                 ...folder,
                 scrapCount: (folder.scrapCount ?? 0)
                   - (sourceFolderCounts.get(folder.id) ?? 0)  // 원래 폴더 -1
                   + (folder.id === targetFolderId ? items.length : 0)  // 대상 폴더 +1
               })),
               scraps: old.scraps?.map((scrap) =>
                 movedIds.has(`SCRAP-${scrap.id}`)
                   ? { ...scrap, folderId: targetFolderId }  // folderId만 변경!
                   : scrap
               )
             };
           });
  • optimisticDeleteScrap 폴더 내 스크랩 삭제 시, 삭제된 항목을 즉시 제거 및 폴더 정보 업데이트 추가
      queryClient.setQueriesData<ScrapSearchResponse>(filters, (old) => {
        // 삭제되는 스크랩들의 folderId별 개수 계산
        const folderCountDeltas = new Map<number, number>();
        old.scraps?.forEach((scrap) => {
          if (deletedIds.has(`SCRAP-${scrap.id}`) && scrap.folderId != null) {
            folderCountDeltas.set(scrap.folderId, (folderCountDeltas.get(scrap.folderId) ?? 0) + 1);
          }
        });
      
        return {
          folders: old.folders
            ?.filter((folder) => !deletedIds.has(`FOLDER-${folder.id}`))
            .map((folder) => ({
              ...folder,
              scrapCount: (folder.scrapCount ?? 0) - (folderCountDeltas.get(folder.id) ?? 0)  // 카운트 감소!
            })),
          scraps: old.scraps?.filter((scrap) => !deletedIds.has(`SCRAP-${scrap.id}`))
        };
      });
  • optimisticHelpers.ts
      // 삭제된 항목을 Set으로 관리하여 조회
      const deletedIds = createDeletedIdsSet(items); // "SCRAP-123", "FOLDER-456" 형태
      
      // 폴더별 스크랩 개수 증감 계산
      const folderCountDeltas = new Map<number, number>();
      // ... 삭제/이동되는 스크랩의 folderId별 개수 집계
      
      // React Query 캐시 직접 업데이트
      queryClient.setQueriesData<ScrapSearchResponse>(filters, (old) => ({
      folders: old.folders.map(folder => ({
        ...folder,
        scrapCount: folder.scrapCount - folderCountDeltas.get(folder.id)
      })),
      scraps: old.scraps.filter(scrap => !deletedIds.has(`SCRAP-${scrap.id}`))
      }));

2. useScrapStoreSync 추가 및 RecentScrapStore 객체 단순화

  • 낙관적 업데이트나 서버 응답 후 실제 유효한 스크랩 ID 목록을 통해 recentScrapStore, scrapNoteStore 관리

useScrapStoreSync.ts

    // 초기 마운트 시에는 스킵 (불필요한 정리 방지)
    if (isInitialMount.current) return;
    
    // undefined면 로딩 중 → 스킵
    if (validScrapIds === undefined) return;
    
    // 유효한 ID Set과 비교하여 무효한 항목만 정리
    const invalidRecentIds = recentScrapIds.filter(id => !validSet.has(id));
    if (invalidRecentIds.length > 0) {
      removeScrapIds(invalidRecentIds);
    }

RecentScrapStore.ts

    interface RecentScrapStore {
    -  scraps: RecentScrapItem[];  // { scrapDetail: ScrapDetailResp }
    -  addScrap: (scrapDetail: ScrapDetailResp) => void;
    +  scrapIds: number[];
    +  addScrapId: (scrapId: number) => void;
    }

3. 정렬 로직 리팩토링

  • 타임스탬프 함수, ScrapItem은 updatedAt 사용, TrashItem은 createdAt에 deletedAt 매핑, 분기 처리 통일
    sortScrap.ts
    function getSortTimestamp(item: ScrapItem | TrashItem): string {
      if ('updatedAt' in item && item.updatedAt) return item.updatedAt;
      return item.createdAt; // TrashItem은 createdAt에 deletedAt이 매핑됨
    }
  • 정렬 키 변경
    types.ts
   - case 'TITLE':
   + case 'NAME':
       return a.name.localeCompare(b.name, 'ko', { numeric: true }) * mul;
  - export type UISortKey = 'TYPE' | 'TITLE' | 'DATE';  // 타입 불일치 (`TITLE` → `NAME`)
  + export type UISortKey = 'TYPE' | 'NAME' | 'DATE';
  
  - export type ApiSortKey = 'CREATED_AT' | 'NAME';
  + export type ApiSortKey = 'CREATED_AT' | 'NAME' | 'TYPE' | 'SIMILARITY';
  • SortDropdown 수정
    정렬 옵션 순서 변경, 정렬 순서 토글 개선:
    - { label: '유형순', value: 'TYPE' },
    - { label: '이름순', value: 'TITLE' },
      { label: '최신순', value: 'DATE' },
    + { label: '이름순', value: 'NAME' },
    + { label: '유형순', value: 'TYPE' },
    - // 오른쪽 아이콘 버튼 클릭 시 토글
    - renderRightIcon={() => (
    -   <Pressable onPress={() => setSortOrder(...)}>
    -     <ChevronIcon />
    -   </Pressable>
    - )}
    
    + // 같은 항목 다시 선택 시 ASC/DESC 토글
      onChange={(item) => {
    +   if (item.value === orderValue) {
    +     setSortOrder((prev) => (prev === 'ASC' ? 'DESC' : 'ASC'));
    +   } else {
          setOrderValue(item.value);
    +   }
      }}
  • Screen별 클라이언트 정렬 복원
    ScrapScreen.ts & FolderScrapScreen.ts
    const sortedData = useMemo(() => {
      return sortScrapData(data, sortKey, sortOrder);
    }, [data, sortKey, sortOrder]);
    
    <ScrapGrid data={sortedData} />
  1. 스크랩 상세보기 -> 포인팅 전체보기 수정 (문제에 대한 모든 포인팅 display 및 실전문제, 연습문제 포함한 display)
  • useGetEntireProblem: 전체 문제 데이터 조회 (MAIN_PROBLEM + CHILD_PROBLEM) 추가

  • useGetEntireProblemPointing: 문제의 모든 포인팅 데이터 조회 추가

  • convertScrapToGroup 함수 개선, MAIN_PROBLEM과 CHILD_PROBLEM 구분 및 포인팅 매칭

    function convertScrapToGroup(
      entireProblem: ProblemEntireResp[],
      entirePointing: PointingEntireResp[]
    ): PublishProblemGroupResp | null
    const problem: ProblemWithStudyInfoResp = {
      ...mainProblem,
      pointings: // mainProblem의 포인팅들,
      childProblems: transformedChildProblems, //  childProblem + childPointings
    };

ScrapDetailScreen.tsx

    const { data: entireProblem } = useGetEntireProblem(problemId);
    const { data: entireProblemPointing } = useGetEntireProblemPointing(problemId);
    
    const handleViewAllPointings = () => {
      const group = convertScrapToGroup(
        entireProblem?.data || [],
        entireProblemPointing?.data || []
      );
      // ...
    };

4. 스크랩 상세보기 AnalysisSection 추가

    {hasReadingTip && (
      <AnalysisSection
        label='문제를 읽어내려갈 때'
        content={scrapDetail.problem?.readingTipContent || ''}
        isScraped={scrapDetail.isReadingTipScrapped}
      />
    )}
    {hasOneStepMore && (
      <AnalysisSection
        label='한 걸음 더'
        content={scrapDetail.problem?.oneStepMoreContent || ''}
        isScraped={scrapDetail.isOneStepMoreScrapped}
      />
    )}

scrapFilters.ts 필터 추가

    if (scrapDetail.isReadingTipScrapped) {
      options.push('문제를 읽어내려갈 때');
    }
    if (scrapDetail.isOneStepMoreScrapped) {
      options.push('한 걸음 더');
}

5. ScrapCard, TrashCard, ScrapFolderDefaultIcon, ImageWithSkeleton 호버 상태 추가

6. ScrapCardGrid, itemHeight 제거 및 ScrapCard, TrashCard, SearchResultCard, ScrapAddCard 레이아웃 수정

7. ScrapHeader, DeletedScrapHeader 레이아웃 수정 및 SearchScrapHeader text input 레이아웃 버그 수정

8. LoadQnaImageModal ScrapModalsContext에 추가, CreateFolderModal 폴더 추가 시 Toast 메시지 안 보이는 버그 수정

@b0nsu b0nsu linked an issue Feb 5, 2026 that may be closed by this pull request
@vercel
Copy link

vercel bot commented Feb 5, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
pointer-admin Ready Ready Preview, Comment Feb 11, 2026 1:40pm

@b0nsu b0nsu changed the base branch from main to develop February 5, 2026 15:27
… improved SVG rendering and size consistency
…onsistency in DeletedScrap and Scrap headers
… and update ScrapHeadCard for improved modal handling
@b0nsu b0nsu changed the title [refactor/native/scrap-#190] scrap qa 수정 중 (pr xxx) [refactor/native/scrap-#190] scrap qa 수정 Feb 11, 2026
@b0nsu b0nsu requested a review from sterdsterd February 11, 2026 13:04
@sterdsterd sterdsterd requested a review from Copilot February 11, 2026 13:18
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors the native “scrap” feature to improve optimistic cache updates, simplify recent-scrap state management, unify sorting behavior, and enhance the scrap detail experience (entire pointings + new analysis sections), alongside several UI/layout adjustments and modal integrations.

Changes:

  • Fix/expand optimistic cache updates for scrap move/delete and improve store synchronization via a new useScrapStoreSync hook.
  • Refactor sorting keys/logic (TITLENAME, unified timestamp handling) and restore client-side sorting where needed.
  • Update scrap detail to support “전체 포인팅 보기” using newly added “entire problem/pointing” queries, and add AnalysisSection rendering/filtering.

Reviewed changes

Copilot reviewed 40 out of 40 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
apps/native/src/features/student/scrap/utils/types.ts Updates sort key unions (UISortKey, ApiSortKey) to align UI/API sorting changes.
apps/native/src/features/student/scrap/utils/scrapTransformers.ts Refactors conversion logic for AllPointings, now based on “entire” problem/pointing inputs.
apps/native/src/features/student/scrap/utils/scrapFilters.ts Adds analysis-section filter generation and visibility logic.
apps/native/src/features/student/scrap/utils/layout/gridLayout.ts Removes fixed item height from grid layout calculation.
apps/native/src/features/student/scrap/utils/formatters/sortScrap.ts Unifies sorting timestamp logic and updates sort key from TITLE→NAME.
apps/native/src/features/student/scrap/utils/formatters/formatToMinute.ts Adds custom ko-KR date formatting path.
apps/native/src/features/student/scrap/stores/recentScrapStore.ts Simplifies recent store from “detail objects” to “scrapIds”.
apps/native/src/features/student/scrap/screens/ScrapScreen.tsx Restores client-side sorting, recent list derived from search results, and adds store sync hook.
apps/native/src/features/student/scrap/screens/ScrapDetailScreen.tsx Adds “entire problem/pointing” queries, AnalysisSection rendering, and view-all navigation changes.
apps/native/src/features/student/scrap/screens/FolderScrapScreen.tsx Restores client-side sorting and adjusts folder lookup/refetch wiring.
apps/native/src/features/student/scrap/hooks/useScrapStoreSync.ts New hook to prune recent IDs and close notes based on valid scrap IDs.
apps/native/src/features/student/scrap/hooks/index.ts Exports useScrapStoreSync.
apps/native/src/features/student/scrap/hoc/withScrapModals.tsx Injects LoadQnaImageModal globally via the scrap modals provider.
apps/native/src/features/student/scrap/contexts/ScrapModalsContext.tsx Adds LoadQnaImageModal visibility state and open/close actions.
apps/native/src/features/student/scrap/components/scrap/PointingsList.tsx Minor padding change.
apps/native/src/features/student/scrap/components/scrap/AnalysisSection.tsx New component to render “reading tip / one step more” analysis content.
apps/native/src/features/student/scrap/components/Tooltip/TooltipPopover.tsx Adds onOpenChange callback for tooltip open/close state.
apps/native/src/features/student/scrap/components/Tooltip/ScrapItemTooltip.tsx Simplifies state sync for rename input and removes now-unneeded cleanup paths.
apps/native/src/features/student/scrap/components/Tooltip/AddScrapTooltip.tsx Refetches scraps after creating a scrap from an image.
apps/native/src/features/student/scrap/components/Modal/LoadQnaImageModal.tsx Converts modal to context-driven visibility + triggers refetch/toast behavior.
apps/native/src/features/student/scrap/components/Modal/CreateFolderModal.tsx Adjusts toast timing and text input layout; changes image upload error handling flow.
apps/native/src/features/student/scrap/components/Header/SearchScrapHeader.tsx Layout changes and icon swap for clear button.
apps/native/src/features/student/scrap/components/Header/ScrapHeader.tsx Selection header layout/text updates.
apps/native/src/features/student/scrap/components/Header/DeletedScrapHeader.tsx Trash selection header layout/text updates.
apps/native/src/features/student/scrap/components/Dropdown/SortDropdown.tsx Reorders options and changes ASC/DESC toggling behavior.
apps/native/src/features/student/scrap/components/Card/cards/TrashCard.tsx Adds hover/tooltip-open driven styling and adjusts selection UI.
apps/native/src/features/student/scrap/components/Card/cards/SearchResultCard.tsx Minor layout tweaks.
apps/native/src/features/student/scrap/components/Card/cards/ScrapHeadCard.tsx Uses context-driven LoadQnaImageModal instead of local state.
apps/native/src/features/student/scrap/components/Card/cards/ScrapCard.tsx Layout refactor + hover styling wiring to ImageWithSkeleton.
apps/native/src/features/student/scrap/components/Card/cards/RecentScrapCard.tsx Adjusts prop typing to match new “recent by ID” approach.
apps/native/src/features/student/scrap/components/Card/ScrapCardGrid.tsx Removes fixed itemHeight usage and adds max height constraint.
apps/native/src/components/system/icons/ScrapFolderDefalutIcon.tsx Adds hover styling support to default folder icon.
apps/native/src/components/system/icons/ScrapDefalutIcon.tsx Adjusts default scrap icon sizing behavior to square.
apps/native/src/components/system/icons/ChevronUpFilledIcon.tsx Updates icon path geometry.
apps/native/src/components/common/ImageWithSkeleton.tsx Adds isHovered support and adjusts border/overflow styling.
apps/native/src/apis/controller/student/study/useGetEntireProblemPointing.ts Adds new query hook for entire problem pointings.
apps/native/src/apis/controller/student/study/useGetEntireProblem.ts Adds new query hook for entire problems.
apps/native/src/apis/controller/student/study/index.ts Exports newly added “entire” study hooks.
apps/native/src/apis/controller/student/scrap/utils/optimisticHelpers.ts Updates optimistic move/delete to adjust folder counts and folderId updates.
apps/native/src/apis/controller/student/scrap/putMoveScraps.ts Passes targetFolderId into optimistic move for correct cache updates.
Comments suppressed due to low confidence (3)

apps/native/src/features/student/scrap/components/Header/SearchScrapHeader.tsx:6

  • 현재 파일에서 ChevronLeft, CircleX, X(lucide-react-native)가 사용되지 않습니다. ESLint no-unused-vars 경고를 줄이기 위해 미사용 import를 제거해 주세요.
import { CircleXFilledIcon } from '@/components/system/icons';
import { StudentRootStackParamList } from '@/navigation/student/types';
import { colors } from '@/theme/tokens';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { ChevronLeft, CircleX, X } from 'lucide-react-native';
import { useEffect, useRef } from 'react';

apps/native/src/features/student/scrap/utils/formatters/sortScrap.ts:25

  • 정렬 키가 TITLENAME으로 변경됐는데, JSDoc이 아직 TITLE을 참조하고 있어 코드와 문서가 불일치합니다. 주석의 정렬 키 설명/매핑 예시를 NAME 기준으로 업데이트해 주세요.
    apps/native/src/features/student/scrap/stores/recentScrapStore.ts:52
  • recent-scrap-store의 persist 구조가 {scraps: ...}에서 {scrapIds: ...}로 변경되었는데 persistversion/migrate가 없어 기존 저장 데이터를 가진 사용자는 최근 본 목록이 모두 초기화됩니다. 기존 persisted state의 scraps[].scrapDetail.idscrapIds로 변환하는 마이그레이션을 추가하거나, 의도된 초기화라면 버전 업과 함께 명시적으로 처리해 주세요.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 57 to +61
return {
no: 1,
problemId: scrapDetail.problem.id,
progress: 'DONE',
problemId: mainProblem.problemId,
problem,
childProblems: [],
};
childProblems: transformedChildProblems,
} as PublishProblemGroupResp;
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

convertScrapToGroupPublishProblemGroupResp를 리턴한다고 하면서 no, progress 필드를 채우지 않고 as PublishProblemGroupResp로 캐스팅하고 있습니다. AllPointingsScreen에서 group.no를 사용해 헤더/설명을 만들기 때문에 현재는 undefined번으로 표시되거나 런타임 오류로 이어질 수 있어, 최소한 no/progress를 올바른 값으로 설정해 반환해야 합니다.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[refactor/native/scrap-#190] scrap QA 반영 사항

1 participant