Skip to content

[feat/MAT-614] 개념태그 그래프 시스템 관리자 페이지 적용#317

Open
anjm1020 wants to merge 11 commits intodevelopfrom
feat/MAT-614
Open

[feat/MAT-614] 개념태그 그래프 시스템 관리자 페이지 적용#317
anjm1020 wants to merge 11 commits intodevelopfrom
feat/MAT-614

Conversation

@anjm1020
Copy link
Copy Markdown
Contributor

@anjm1020 anjm1020 commented May 8, 2026

Summary

신규 BE API (/api/admin/concept/graph/*) 위에 시트형 관리자 페이지를 추가합니다. 기존 /concept-tags 레거시 페이지는 그대로 유지되며, GNB 에 "개념 그래프" 가 문제관리·Q&A 와 동등한 최상위 섹션으로 추가되어 개념 노드 / 개념 그래프 / 액션 그래프 / 타입 관리 4 페이지로 분리됩니다.

Linear

Changes

시트 페이지 (4개 라우트)

  • 개념 노드 시트 (/concept-graph/node): 이름/설명/타입 필터, 3축 정렬(NAME/NODE_TYPE/DESCRIPTION), TipTap 에디터 기반 description, payload(JSON) 미리보기, 행 추가/수정/삭제
  • 개념 그래프 시트 (/concept-graph/edge — 기존 "개념 엣지" 라벨에서 변경): from/관계/to 필터·정렬, NodeSearchSelect (검색·드롭다운 + initialNode fallback + excludeIds + nodeTypeId 필터), from ≠ to 검증
  • 액션 그래프 pivot 시트 (/concept-graph/action-edge): BE 가 보내준 columns(code 오름차순) 기준 동적 컬럼, 셀 클릭 → 우측 사이드 패널(CellEditPanel)에서 다중 노드 검색·선택 후 PUT /sheet/action-edge/cell 일괄 교체, 미저장 변경 시 TwoButtonModalTemplate 닫힘 confirm, 행 삭제는 모든 셀 비우기로 처리
  • 타입 관리 (/concept-graph/types): 노드/엣지/액션엣지 타입 3종 시트형 CRUD, react-hook-form + zod 검증

Description / Payload 에디터 통합

  • 노드 description 을 TipTap 기반 에디터(@repo/pointer-editor-v2) 로 전환 — problem.content 와 동일한 react-hook-form Controller 패턴 (EditorField 재사용). 빈 doc 은 BE 로 undefined 전송, plain-text 레거시 데이터는 parseEditorContent fallback
  • 시트 description 컬럼은 InlineProblemViewer (maxLine=2) 로 렌더 — KaTeX 자동 + 라인 클램핑
  • Action 타입 노드payload 는 raw JSON textarea 대신 두 개의 에디터 필드 (예시, 포인팅 예시) 로 입력 → payload.example / payload.pointingExample 키에 매핑되어 저장·로드. 비-Action 타입은 payload 입력 영역 자체를 노출하지 않음
  • Action 행의 시트 payload 셀은 InlineProblemViewer (maxLine=1) 로 두 키 모두 인라인 렌더 (mono key chip + value 형태로 시각 분리)

액션 노드 행 추가 (AddActionRowModal)

  • getNodeType() 결과에서 code === 'Action' 매칭으로 nodeType 을 자동 결정 → NodeSearchSelectnodeTypeId 로 전달해 후보를 액션 타입으로만 한정
  • Action 코드의 nodeType 이 BE 에 없으면 amber 경고 배너 + 등록 버튼 disabled (운영자는 "타입 관리" 탭에서 먼저 생성해야 함)
  • conceptNode 다중 선택은 type 무관 (의도 — 셀에는 어떤 type 이든 들어갈 수 있어야 함)

GNB / 페이지 layout

  • "개념 그래프" 를 최상위 섹션 (SectionTitle) 으로 분리, 4 NavItem 으로 펼침 (Circle / Network / Activity / Settings 아이콘)
  • "개념 엣지" → "개념 그래프" 라벨 변경 (개념 노드 간 관계가 곧 개념 그래프이므로 도메인 명칭에 부합)
  • 4개 라우트 모두 다른 어드민 페이지와 동일한 <Header> + mx-auto max-w-7xl px-8 py-8 레이아웃 적용. 페이지 내부의 inline "행 추가" 버튼은 Header.Button 으로 이동
  • 라우트 내부의 ConceptGraphTabs 제거 — GNB sub-menu 와 역할 중복
  • 시트 컬럼에서 ID 노출 제거 (운영자에게 불필요), 엣지 삭제 모달 본문도 ID 표기 제거
  • 노드 시트 "타입" 컬럼 폭 120px 로 고정 + text-xs inline pill (Tag 컴포넌트 대신)

모달 / 컴포넌트 폭

  • EditConceptNodeModal560px860px (에디터 두 필드 공간 확보)

인프라

  • API 레이어: apps/admin/src/apis/controller/conceptGraph/ 에 24개 openapi-react-query wrapper (sheet 조회 3종, 노드/엣지/액션엣지 단건 CRUD, 타입 코드 CRUD 3종, 셀 일괄 교체 PUT)
  • 공통 컴포넌트: SheetTable, PaginationControls, SearchFilterBar, RowActions, NodeSearchSelect, CellEditPanel 외 모달 4종 (apps/admin/src/components/conceptGraph/)
  • OpenAPI 스키마 재생성: pnpm openapiapps/admin/src/types/api/schema.d.ts 갱신
  • useInvalidate: invalidateConceptGraphSheets/Nodes/Edges/ActionEdges/Types 5개 메서드 추가
  • 쿼리 파라미터 별칭: ConceptNodeSheetSearchOptions, ConceptEdgeSheetSearchOptions, ActionGraphSheetSearchOptions (paths[...] 추출)

Testing

  • pnpm ci:lint — 0 errors / 0 warnings
  • pnpm format:check — All matched files use Prettier code style
  • pnpm ci:typecheck — 5/5 tasks successful
  • pnpm ci:build — admin 빌드 성공
  • ✅ GitHub Actions ci / claude-review / Vercel 모두 green
  • ⚠️ 수동 QA 필요 — 본 코드베이스에 자동 테스트 인프라가 없어 dev/qa 환경에서 다음 시나리오 수동 확인 필요:
    • GNB "개념 그래프" 섹션 4 NavItem 진입 / 활성 표시
    • 각 시트의 필터·정렬·페이지네이션 (page-size 변경 시 page=0 리셋 포함)
    • 노드 행 추가·수정·삭제, description 에디터 입력·저장·재로드
    • 시트 description 컬럼이 TipTap → 텍스트 + KaTeX 로 인라인 렌더, 빈 값/레거시 plain-text 도 깨지지 않음
    • Action 타입 선택 시 모달의 payload 입력이 예시 + 포인팅 예시 두 에디터로 전환, 저장 후 시트 payload 셀에 인라인 렌더
    • 비-Action 타입 선택 시 payload 입력 영역이 사라짐 (raw JSON textarea 미노출)
    • 액션 그래프 셀 클릭 → 사이드 패널 검색·다중선택·저장 (단일 PUT)
    • 미저장 변경 상태에서 ESC/백드롭/X 클릭 시 confirm 모달 노출
    • "액션 노드 행 추가" 모달에서 Action 타입 노드만 후보로 표시 + Action 코드 nodeType 없을 때 disabled 동작
    • 행 삭제 시 모든 셀이 비워지면서 행이 시트에서 사라지는지
    • 타입 코드 삭제 시 BE 가 거부(4xx) 하면 토스트로 사용자 안내

Risk / Impact

  • 영향 범위: admin 앱 한정. 신규 라우트 / 신규 컴포넌트 위주이고 기존 /concept-tags 페이지·API·컴포넌트는 손대지 않음.
  • 확인이 필요한 부분:
    • 액션 노드 타입 자동 매칭은 code === 'Action' (대소문자 정확히 일치) 가정 — BE 데이터에서 코드가 변경되면 amber 경고 배너로 fallback 되며 사용자가 진행 불가. 운영 정책상 코드가 다른 값이면 FE 상수도 함께 수정 필요.
    • payloadexample / pointingExample 키 컨벤션도 BE 와 함께 합의된 명명 — 변경 시 FE 상수 동기화 필요.
    • 노드 description 이 BE 컨벤션상 TipTap JSON 문자열로 전환됨 — 레거시 plain text 데이터가 있다면 BE 마이그레이션 또는 FE fallback (parseContent) 의 모니터링이 필요.
  • 배포 시 유의사항:
    • BE PR #81 (MAT-613) + description / payload 에디터 컨벤션 변경 배포가 선행되어야 함. dev/qa 에는 이미 반영됨, prod 배포 시 순서 주의.
    • 빌드 결과물의 admin js bundle size 가 2.8MB(gzip 829kB) 수준 — 본 변경분이 유의미하게 키운 것은 아니나 프로젝트 차원의 code-splitting 검토 여지.

Screenshots / Video

수동 QA 시 첨부 예정 — GNB "개념 그래프" 섹션 / 노드 시트 (description 인라인, payload key chip) / Action 타입 모달 (예시/포인팅 예시 에디터) / 액션 그래프 pivot + 사이드 패널 / 타입 관리 캡처.

🤖 Generated with Claude Code

MAT-614. 신규 BE API (/api/admin/concept/graph/*) 위에 시트형 관리자
페이지 /concept-graph 를 추가한다. 기존 /concept-tags 레거시 페이지는
그대로 유지.

- /concept-graph/node — 개념 노드 시트 (필터·정렬·페이지네이션 + CRUD,
  payload JSON 검증)
- /concept-graph/edge — 개념 엣지 시트 (NodeSearchSelect 신설로
  from/to 노드 검색 선택)
- /concept-graph/action-edge — 액션 그래프 pivot 시트, 셀 클릭 시 우측
  사이드 패널에서 다중 선택 후 일괄 PUT
- /concept-graph/types — 노드/엣지/액션엣지 타입 코드 CRUD
- 공통 시트 컴포넌트(SheetTable, PaginationControls, SearchFilterBar,
  RowActions) + ConceptGraphTabs
- 24개 openapi-react-query wrapper, useInvalidate 5개 메서드 확장,
  GNB "개념 그래프" 메뉴 추가

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@linear
Copy link
Copy Markdown

linear Bot commented May 8, 2026

MAT-614

@vercel
Copy link
Copy Markdown

vercel Bot commented May 8, 2026

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

Project Deployment Actions Updated (UTC)
pointer-admin Ready Ready Preview, Comment May 9, 2026 11:16am

AddActionRowModal 의 액션 노드 선택을 nodeType 으로 제한할 수 있도록 한다.

- NodeSearchSelect 에 nodeTypeId / disabled prop 추가 → getSheetNode 의
  nodeTypeId 쿼리로 후보를 좁힘
- AddActionRowModal 에 "액션 노드 타입" 드롭다운 추가, code === 'ACTION'
  매칭이 있으면 자동 prefill, 없으면 사용자가 직접 선택
- 타입 변경 시 기존 액션 노드 선택 초기화

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
액션 그래프 맥락에서 actionNode 는 항상 ACTION 타입이어야 하므로 사용자가
타입을 직접 선택할 필요가 없다. 이전 커밋의 dropdown 을 제거하고 ACTION
코드의 nodeType 을 NodeSearchSelect 에 자동 전달한다.

- ACTION nodeType 이 없으면 amber 안내 배너 + 등록 버튼 disabled
- 노드 후보는 항상 ACTION 타입으로만 필터링

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
anjm1020 and others added 2 commits May 9, 2026 18:56
운영자에게 ID 노출이 불필요하다는 피드백을 반영해 모든 시트의 ID 컬럼을
숨기고, 삭제 확인 모달의 ID 표기도 함께 제거한다.

- /concept-graph/node, edge, types 의 SheetTable 컬럼 정의에서 id 항목 삭제
- /concept-graph/edge 삭제 확인 본문에서 "#{id}" 표기 제거
  (from → to 노드 이름만으로 식별 가능)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
BE 데이터의 실제 코드 값이 'Action' (capitalized) 이라 'ACTION' (all caps)
으로 검색하면 매칭되지 않아 amber 경고 배너가 노출되던 이슈 수정.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
BE 가 ConceptNode.description 을 plain text 에서 TipTap JSON 에디터 콘텐츠
로 전환함에 따라 FE 도 problem.content 와 동일한 패턴을 적용한다.

- EditConceptNodeModal: 설명 입력을 Input 에서 EditorField 로 교체
  (problemContent 와 동일한 react-hook-form Controller 기반)
- 빈 doc 저장 시 BE 로 undefined 전송, plain text 레거시 데이터는
  parseEditorContent 가 fallback 처리
- /concept-graph/node 시트 description 컬럼은 InlineProblemViewer
  (maxLine=2) 로 인라인 렌더, KaTeX 수식 자동 렌더 + 라인 클램핑

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GNB 에서 "개념 그래프" 를 문제관리/Q&A 와 동등한 최상위 섹션으로
승격하고 4개 NavItem (개념 노드 / 개념 그래프 / 액션 그래프 /
타입 관리) 으로 분리한다. 라우트 내부의 ConceptGraphTabs 는 GNB
sub-menu 와 역할이 중복되어 제거.

- "개념 엣지" → "개념 그래프" 라벨 변경 (개념 노드 간 관계가
  곧 개념 그래프이므로 도메인 명칭에 부합)
- 노드 시트 "타입" 컬럼 폭 120px 로 고정 + 폰트 text-xs 로 축소,
  Tag 컴포넌트 대신 가벼운 inline pill 사용
- ConceptGraphTabs 컴포넌트 / 4개 라우트의 import + 렌더 + 배럴
  export 모두 정리

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Action 코드의 nodeType 이 선택되면 raw JSON textarea 대신
"Payload — Example" / "Payload — Pointing Example" 두 개의
TipTap 에디터 필드를 노출한다. 저장 시 두 값은 payload 객체의
같은 이름 키로 들어가고, 불러올 때도 동일한 키에서 읽어온다.

- 빈 doc 인 필드는 payload 에서 제외 (둘 다 비어있으면 payload
  자체를 undefined 로 전송)
- 다른 nodeType 은 기존 raw JSON textarea 그대로 유지
- 타입을 Action 으로 바꾸는 즉시 UI 가 두 에디터로 전환

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
에디터 필드가 추가되면서 560px 폭은 비좁아 860px 로 확장한다. 또한
Action 타입 payload 라벨을 도메인 용어에 맞게 한국어로 표시한다.

- w-[560px] → w-[860px]
- "Payload — Example" → "예시"
- "Payload — Pointing Example" → "포인팅 예시"

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- 시트 payload 컬럼에서 nodeType.code === 'Action' 인 행은 raw JSON
  truncate 대신 InlineProblemViewer 로 예시/포인팅 예시를 한 줄씩
  렌더 (KaTeX 자동 렌더 + 라인 클램핑)
- Action 이 아닌 행은 기존 JSON truncate + 클릭으로 모달 미리보기
  유지
- 모달에서 Action 이 아닌 타입의 raw JSON payload 입력 영역을 완전
  제거 — 사용 안 되는 payload zod schema, defaultPayload, register,
  onSubmit 의 JSON.parse 분기까지 정리

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
다른 어드민 페이지(개념 태그, 문제 등) 들의 표준 레이아웃에 맞춰
4개 라우트 모두 Header + max-w-7xl 컨테이너를 적용한다.

- min-h-screen bg-gray-50 outer + Header(title + primary action)
  + mx-auto max-w-7xl px-8 py-8 wrapper 패턴 적용
- 페이지 내부의 inline "행 추가" Button 은 Header.Button 으로 이동
  (개념 노드 / 개념 그래프 / 액션 그래프). 타입 관리는 단일 primary
  action 이 없어 Header children 만 비워서 호출.
- 노드 시트의 payload 셀 예시/포인팅 라벨을 monospace 칩 (border +
  bg-gray-100) 로 명확한 key 시각 분리, max-w 360px 로 조정

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@anjm1020 anjm1020 marked this pull request as ready for review May 9, 2026 11:31
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a7408fd580

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +69 to +70
return target?.description && target.description.length > 0
? target.description
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Convert legacy node descriptions to editor JSON before load

This passes target.description straight into EditorField, but EditorField initializes via parseEditorContent (JSON parse) and falls back to an empty doc on invalid JSON. Any existing node description stored as plain text (legacy format) will render as empty in the modal and can be silently erased on save. Please normalize legacy text into a valid TipTap JSON string before setting the form default.

Useful? React with 👍 / 👎.

type FormValues = z.infer<typeof formSchema>;

const extractPayloadEditorString = (raw: unknown): string => {
if (typeof raw === 'string' && raw.length > 0) return raw;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Normalize legacy payload strings before initializing editors

When payload.example / payload.pointingExample are plain strings, this helper returns them unchanged. Those values are then fed into EditorField, which expects serialized editor JSON and falls back to an empty doc for non-JSON strings. Editing an action node with legacy plain-text payload values will therefore open blank fields and overwrite existing data on save.

Useful? React with 👍 / 👎.

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.

1 participant