@@ -64,10 +64,8 @@ import type { ChatStompClient } from "../api/stomp";
6464import { getAccessToken } from "../auth" ;
6565import { useProfile } from "../contexts/ProfileContext" ;
6666import { fetchMyWorkspaces , getWorkspaceMembers , updatePresence , type WorkspaceDto , type WorkspaceMember } from "../api/workspace" ;
67- import { type WorkspaceEventDto } from "../api/events" ;
6867import { useWorkspace } from "../contexts/WorkspaceContext" ;
6968import { ApiClientError } from "../api/client" ;
70- import { connectWorkspaceRepository , fetchWorkspaceRepositories , syncRepositoryIssueStatuses } from "../api/github" ;
7169
7270const APISpecPage = lazy ( ( ) => import ( "./APISpecPage" ) . then ( ( module ) => ( { default : module . APISpecPage } ) ) ) ;
7371const ERDPage = lazy ( ( ) => import ( "./ERDPage" ) . then ( ( module ) => ( { default : module . ERDPage } ) ) ) ;
@@ -135,8 +133,6 @@ interface RepositoryItem {
135133 connected : boolean ;
136134 membersOnline : number ;
137135 workspaceId ?: string ;
138- channelId ?: number ; // 백엔드 repository channel DB id
139- dbRepoId ?: string ; // github_repositories DB id
140136}
141137
142138interface WorkspaceItem {
@@ -573,29 +569,6 @@ function mapChannelMessageToWorkspaceMessage(message: ChannelMessage) {
573569 // The backend marks soft-deleted messages by replacing the content with a sentinel string.
574570 // Detect it here so a page refresh keeps the message in the "deleted" state (no edit/delete buttons).
575571 const isDeleted = message . isDeleted === true || message . content === DELETED_MESSAGE_CONTENT ;
576- const replyTo = message . replyTo
577- ? { user : message . replyTo . senderName ?? "" , text : message . replyTo . content }
578- : undefined ;
579-
580- // GitHub bot issue notification — parse meta JSON from the issue attachment
581- const issueAttachment = ( message . attachments ?? [ ] ) . find (
582- ( a ) => a . attachmentType === "issue" || a . type === "issue"
583- ) ;
584- let issueFields : Record < string , unknown > = { } ;
585- if ( issueAttachment ?. meta ) {
586- try {
587- const parsed = JSON . parse ( issueAttachment . meta ) ;
588- // 우선순위 미설정 시 기본값 'medium'
589- if ( ! parsed . issuePriority ) parsed . issuePriority = 'medium' ;
590- // 담당자 미설정 시 작성자로 fallback
591- if ( ! parsed . issueAssignees || ( parsed . issueAssignees as unknown [ ] ) . length === 0 ) {
592- parsed . issueAssignees = parsed . issueAuthor ? [ parsed . issueAuthor ] : [ ] ;
593- }
594- issueFields = { type : "issue" , ...parsed } ;
595- } catch {
596- // meta가 JSON이 아닌 경우 무시
597- }
598- }
599572
600573 return {
601574 id : message . id ,
@@ -609,14 +582,11 @@ function mapChannelMessageToWorkspaceMessage(message: ChannelMessage) {
609582 time : formatApiDateTime ( message . createdAt ) ,
610583 replies : 0 ,
611584 attachments,
612- ...( replyTo ? { replyTo } : { } ) ,
613- ...( isDeleted ? { deleted : true } : { } ) ,
614- ...issueFields ,
585+ ...( isDeleted ? { deleted : true } : { } )
615586 } ;
616587}
617588
618589function mapThreadReplyToWorkspaceMessage ( reply : ThreadReply ) {
619- const isDeleted = reply . isDeleted === true || reply . content === DELETED_MESSAGE_CONTENT ;
620590 return {
621591 id : reply . id ,
622592 backendReplyId : reply . id ,
@@ -626,8 +596,7 @@ function mapThreadReplyToWorkspaceMessage(reply: ThreadReply) {
626596 avatar : reply . senderName ?. charAt ( 0 ) . toUpperCase ( ) || "U" ,
627597 text : reply . content ,
628598 message : reply . content ,
629- time : formatApiDateTime ( reply . createdAt ) ,
630- ...( isDeleted ? { deleted : true } : { } )
599+ time : formatApiDateTime ( reply . createdAt )
631600 } ;
632601}
633602
@@ -1095,15 +1064,11 @@ export function ChatPage() {
10951064 const [ workspaceMembers , setWorkspaceMembers ] = useState < WorkspaceMember [ ] > ( [ ] ) ;
10961065
10971066 useEffect ( ( ) => {
1098- const state = location . state as { workspaceId ?: string | number ; pendingEvent ?: WorkspaceEventDto } | null ;
1099- const pendingEvent = state ?. pendingEvent ?? null ;
1100- if ( pendingEvent ) pendingEventRef . current = pendingEvent ;
1101-
11021067 fetchMyWorkspaces ( )
11031068 . then ( ( workspaces ) => {
11041069 setApiWorkspaces ( workspaces ) ;
11051070 if ( workspaces . length > 0 ) {
1106- const incomingId = pendingEvent ?. workspaceId ?? state ?. workspaceId ;
1071+ const incomingId = ( location . state as { workspaceId ?: string | number } | null ) ?. workspaceId ;
11071072 const incomingApiId = parseWorkspaceApiId ( incomingId ) ;
11081073 const target = incomingApiId !== null
11091074 ? workspaces . find ( ( w ) => w . id === incomingApiId )
@@ -1179,7 +1144,6 @@ export function ChatPage() {
11791144 ) ;
11801145 const [ isMainExpanded , setIsMainExpanded ] = useState ( false ) ;
11811146 const prevMainExpanded = useRef ( false ) ;
1182- const pendingEventRef = useRef < WorkspaceEventDto | null > ( null ) ;
11831147 const [ teamInviteOpen , setTeamInviteOpen ] = useState ( false ) ;
11841148 const [ expandedSidebarGroups , setExpandedSidebarGroups ] = useState < Record < SidebarGroupId , boolean > > ( {
11851149 documentation : true
@@ -1258,6 +1222,7 @@ export function ChatPage() {
12581222
12591223
12601224 const hasRepositories = repositoriesImported && repositories . length > 0 ;
1225+ const currentRepo = repositories . find ( repo => repo . id === selectedRepository ) ;
12611226 const currentWorkspace = workspaceList . find ( ws => ws . id === selectedWorkspace ) ?? workspaceList [ 0 ] ;
12621227 const canManageWorkspaceChannels = canManageWorkspaceChannel ( currentWorkspace ?. myRole ) ;
12631228 const currentWorkspaceApiId = getWorkspaceApiId ( selectedWorkspace , currentWorkspace ) ;
@@ -1350,41 +1315,6 @@ export function ChatPage() {
13501315 return channels ;
13511316 } ) ;
13521317 } , [ currentWorkspaceApiId ] ) ;
1353-
1354- // 백엔드 레포 목록으로 channelId 동기화 (기존 localStorage 항목 업데이트)
1355- useEffect ( ( ) => {
1356- if ( ! currentWorkspaceApiId || currentWorkspaceApiId <= 0 ) return ;
1357- fetchWorkspaceRepositories ( currentWorkspaceApiId ) . then ( ( list ) => {
1358- if ( ! list . length ) return ;
1359-
1360- // 기존 이슈 상태 동기화 (DB meta와 실제 GitHub 이슈 상태 일치)
1361- list . forEach ( ( repo ) => {
1362- syncRepositoryIssueStatuses ( String ( repo . id ) ) . catch ( ( ) => { /* ignore */ } ) ;
1363- } ) ;
1364-
1365- const raw = window . localStorage . getItem ( WORKSPACE_REPOS_KEY ) ;
1366- if ( ! raw ) return ;
1367- try {
1368- const parsed = JSON . parse ( raw ) ;
1369- if ( ! Array . isArray ( parsed ) ) return ;
1370- const wsId = String ( currentWorkspaceApiId ) ;
1371- let changed = false ;
1372- const updated = parsed . map ( ( r ) => {
1373- if ( r . workspaceId !== wsId ) return r ;
1374- const match = list . find (
1375- ( b ) => r . dbRepoId === String ( b . id ) || r . name === b . name
1376- ) ;
1377- if ( match && ( r . channelId !== match . channelId || r . dbRepoId !== String ( match . id ) ) ) {
1378- changed = true ;
1379- return { ...r , channelId : match . channelId , dbRepoId : String ( match . id ) } ;
1380- }
1381- return r ;
1382- } ) ;
1383- if ( changed ) window . localStorage . setItem ( WORKSPACE_REPOS_KEY , JSON . stringify ( updated ) ) ;
1384- } catch { /* ignore */ }
1385- } ) . catch ( ( ) => { /* ignore */ } ) ;
1386- } , [ currentWorkspaceApiId ] ) ;
1387-
13881318 const visibleRepositories = useMemo ( ( ) => {
13891319 try {
13901320 const raw = window . localStorage . getItem ( WORKSPACE_REPOS_KEY ) ;
@@ -1405,8 +1335,6 @@ export function ChatPage() {
14051335 connected : true ,
14061336 membersOnline : 0 ,
14071337 workspaceId : r . workspaceId ,
1408- channelId : r . channelId ,
1409- dbRepoId : r . dbRepoId ,
14101338 } ) ) ;
14111339 }
14121340 }
@@ -1416,8 +1344,6 @@ export function ChatPage() {
14161344 }
14171345 return [ ] ;
14181346 } , [ currentWorkspaceApiId ] ) ;
1419- const currentRepo = visibleRepositories . find ( repo => repo . id === selectedRepository )
1420- ?? repositories . find ( repo => repo . id === selectedRepository ) ;
14211347 const firstVisibleRepositoryId = visibleRepositories [ 0 ] ?. id ?? null ;
14221348 const apiChannelIdByUiChannel = useMemo ( ( ) => {
14231349 return apiChannels . reduce < Record < string , number > > ( ( acc , channel ) => {
@@ -1431,11 +1357,7 @@ export function ChatPage() {
14311357 return acc ;
14321358 } , { } ) ;
14331359 } , [ apiChannels ] ) ;
1434- // 'issues' 탭은 현재 레포의 repository channel(DB id)로 매핑
1435- const activeApiChannelId =
1436- selectedChannel === 'issues' && currentRepo ?. channelId
1437- ? currentRepo . channelId
1438- : apiChannelIdByUiChannel [ selectedChannel ] ;
1360+ const activeApiChannelId = apiChannelIdByUiChannel [ selectedChannel ] ;
14391361 const hasActiveApiChatChannel = activeApiChannelId !== undefined ;
14401362 const hasChatAccessToken = Boolean ( getAccessToken ( ) ) ;
14411363 const realtimeConnectionBlockReason = useMemo < RealtimeConnectionReason | null > ( ( ) => {
@@ -1454,7 +1376,7 @@ export function ChatPage() {
14541376 ] ) ;
14551377 const apiCustomChannels = useMemo < CustomChannelItem [ ] > ( ( ) => {
14561378 return apiChannels
1457- . filter ( ( channel ) => getApiChannelUiId ( channel ) !== "general" && channel . channelType !== "repository" )
1379+ . filter ( ( channel ) => getApiChannelUiId ( channel ) !== "general" )
14581380 . map ( ( channel ) => ( {
14591381 id : getApiChannelUiId ( channel ) ,
14601382 label : cleanChannelLabel ( channel . name ) ,
@@ -2538,7 +2460,6 @@ export function ChatPage() {
25382460 setChatStompReadyKey ( ( key ) => key + 1 ) ;
25392461
25402462 eventSubscriptions = apiChannels . map ( ( channel ) => {
2541- // Repository channels map to the 'issues' UI tab, not to the generic api-ch-{id} key
25422463 const uiChannelId = String ( channel . channelType ?? "" ) . toLowerCase ( ) === "repository"
25432464 ? "issues"
25442465 : getApiChannelUiId ( channel ) ;
@@ -2798,50 +2719,24 @@ export function ChatPage() {
27982719 setRepoUrlInput ( '' ) ;
27992720 } ;
28002721
2801- const handleSubmitRepoForm = async ( ) => {
2802- const trimmed = repoUrlInput . trim ( ) . replace ( / \. g i t $ / , '' ) ;
2803- const parts = trimmed . split ( '/' ) . filter ( Boolean ) ;
2804- const repoName = parts [ parts . length - 1 ] ;
2805- const owner = parts [ parts . length - 2 ] ;
2806- if ( ! repoName || ! owner ) return ;
2807-
2808- try {
2809- const res = await connectWorkspaceRepository ( currentWorkspaceApiId , owner , repoName ) ;
2810- const nextRepository : RepositoryItem = {
2811- id : `repo-${ res . id } ` ,
2812- name : res . name ,
2813- openPRs : 0 ,
2814- highRisk : 0 ,
2815- activeIssues : 0 ,
2816- connected : true ,
2817- membersOnline : 1 ,
2818- workspaceId : selectedWorkspace ,
2819- channelId : res . channelId ,
2820- dbRepoId : String ( res . id ) ,
2821- } ;
2822- setRepositories ( prev => [ nextRepository , ...prev ] ) ;
2823- setRepositoriesImported ( true ) ;
2824- setSelectedRepository ( nextRepository . id ) ;
2825- setSelectedChannel ( 'overview' ) ;
2826- handleCloseRepoForm ( ) ;
2827- } catch {
2828- // 백엔드 연동 실패 시 로컬 목데이터로 폴백
2829- const nextRepository : RepositoryItem = {
2830- id : `repo-${ Date . now ( ) } ` ,
2831- name : repoName ,
2832- openPRs : 0 ,
2833- highRisk : 0 ,
2834- activeIssues : 0 ,
2835- connected : true ,
2836- membersOnline : 1 ,
2837- workspaceId : selectedWorkspace ,
2838- } ;
2839- setRepositories ( prev => [ nextRepository , ...prev ] ) ;
2840- setRepositoriesImported ( true ) ;
2841- setSelectedRepository ( nextRepository . id ) ;
2842- setSelectedChannel ( 'overview' ) ;
2843- handleCloseRepoForm ( ) ;
2844- }
2722+ const handleSubmitRepoForm = ( ) => {
2723+ const repoName = parseRepoNameFromUrl ( repoUrlInput ) ;
2724+ if ( ! repoName ) return ;
2725+ const nextRepository : RepositoryItem = {
2726+ id : `repo-${ Date . now ( ) } ` ,
2727+ name : repoName ,
2728+ openPRs : 0 ,
2729+ highRisk : 0 ,
2730+ activeIssues : 0 ,
2731+ connected : true ,
2732+ membersOnline : 1 ,
2733+ workspaceId : selectedWorkspace
2734+ } ;
2735+ setRepositories ( prev => [ nextRepository , ...prev ] ) ;
2736+ setRepositoriesImported ( true ) ;
2737+ setSelectedRepository ( nextRepository . id ) ;
2738+ setSelectedChannel ( 'overview' ) ;
2739+ handleCloseRepoForm ( ) ;
28452740 } ;
28462741
28472742 const handleDeleteRepository = ( repositoryId : string ) => {
@@ -3590,56 +3485,6 @@ export function ChatPage() {
35903485 setSelectedThread ( null ) ;
35913486 } ;
35923487
3593- // pendingEvent: 레포 목록 변경 시 레포·채널 선택
3594- useEffect ( ( ) => {
3595- const pending = pendingEventRef . current ;
3596- if ( ! pending ) return ;
3597- if ( pending . type === "MENTION" || pending . type === "REPLY" ) return ; // 채널 처리는 아래에서
3598- if ( ! pending . repositoryName ) return ;
3599- const repo = repositories . find ( ( r ) => r . name === pending . repositoryName ) ;
3600- if ( ! repo ) return ;
3601- setSelectedRepository ( repo . id ) ;
3602- if ( pending . type === "PR_CREATED" || pending . type === "PR_REVIEW" ) {
3603- setSelectedChannel ( "pull-requests" ) ;
3604- } else if ( pending . type === "ISSUE_CREATED" ) {
3605- setSelectedChannel ( "issues" ) ;
3606- }
3607- } , [ repositories ] ) ;
3608-
3609- // pendingEvent: apiChannels 변경 시 MENTION·REPLY 채널 선택
3610- useEffect ( ( ) => {
3611- const pending = pendingEventRef . current ;
3612- if ( ! pending || ( pending . type !== "MENTION" && pending . type !== "REPLY" ) ) return ;
3613- if ( ! pending . channelId ) return ;
3614- const uiChannelId = apiChannelUiById [ pending . channelId ] ;
3615- if ( uiChannelId ) setSelectedChannel ( uiChannelId ) ;
3616- } , [ apiChannelUiById ] ) ;
3617-
3618- // pendingEvent: 메시지 로드 후 PR·이슈·스레드 패널 열기
3619- useEffect ( ( ) => {
3620- const pending = pendingEventRef . current ;
3621- if ( ! pending ) return ;
3622- if ( pending . type === "PR_CREATED" || pending . type === "PR_REVIEW" ) {
3623- if ( ! pending . prNumber ) return ;
3624- const msg = currentMessages . find ( ( m : any ) => m . type === "pr" && m . prNumber === pending . prNumber ) ;
3625- if ( ! msg ) return ;
3626- handleReviewPR ( msg ) ;
3627- pendingEventRef . current = null ;
3628- } else if ( pending . type === "ISSUE_CREATED" ) {
3629- if ( ! pending . issueNumber ) return ;
3630- const msg = currentMessages . find ( ( m : any ) => m . type === "issue" && m . issueNumber === pending . issueNumber ) ;
3631- if ( ! msg ) return ;
3632- handleViewIssue ( msg ) ;
3633- pendingEventRef . current = null ;
3634- } else if ( pending . type === "MENTION" || pending . type === "REPLY" ) {
3635- if ( ! pending . threadId ) return ;
3636- const msg = currentMessages . find ( ( m : any ) => Number ( m . backendMessageId ?? m . id ) === pending . threadId ) ;
3637- if ( ! msg ) return ;
3638- handleOpenThread ( msg ) ;
3639- pendingEventRef . current = null ;
3640- }
3641- } , [ currentMessages ] ) ;
3642-
36433488 const getReactionTarget = ( reactionKey : string ) => {
36443489 if ( reactionKey . endsWith ( ":original" ) && selectedThread ) {
36453490 return {
@@ -3746,7 +3591,7 @@ export function ChatPage() {
37463591 } ) ;
37473592 } ;
37483593
3749- const handleSendMessage = ( text : string , attachments : MessageAttachment [ ] = [ ] , replyTo ?: { user : string ; text : string ; messageId ?: number } , metadata ?: MessageMetadata ) => {
3594+ const handleSendMessage = ( text : string , attachments : MessageAttachment [ ] = [ ] , replyTo ?: { user : string ; text : string } , metadata ?: MessageMetadata ) => {
37503595 const trimmedText = text . trim ( ) ;
37513596 if ( ! trimmedText && attachments . length === 0 ) return ;
37523597 if ( attachments . length > 10 ) return ;
@@ -3755,7 +3600,6 @@ export function ChatPage() {
37553600 const pendingMessageId = Date . now ( ) ;
37563601 const attachmentPayload = attachments . map ( toMessageAttachmentRequest ) ;
37573602 const messageText = trimmedText || `${ attachments . length } 개 항목을 공유합니다.` ;
3758- const replyToMessageId = replyTo ?. messageId ;
37593603
37603604 const nextMessage : any = {
37613605 id : pendingMessageId ,
@@ -3790,8 +3634,7 @@ export function ChatPage() {
37903634 stompClient . send (
37913635 chatWebSocketDestinations . sendChannelMessage ( activeApiChannelId ) ,
37923636 {
3793- content : messageText ,
3794- ...( replyToMessageId ? { replyToMessageId } : { } )
3637+ content : messageText
37953638 }
37963639 ) ;
37973640 return ;
@@ -3802,8 +3645,7 @@ export function ChatPage() {
38023645
38033646 createChannelMessage ( activeApiChannelId , {
38043647 content : messageText ,
3805- ...( attachmentPayload . length > 0 ? { attachments : attachmentPayload } : { } ) ,
3806- ...( replyToMessageId ? { replyToMessageId } : { } )
3648+ ...( attachmentPayload . length > 0 ? { attachments : attachmentPayload } : { } )
38073649 } )
38083650 . then ( ( serverMessage ) => appendServerMessage ( selectedChannel , serverMessage ) )
38093651 . catch ( ( error ) => {
0 commit comments