@@ -345,6 +345,7 @@ <h3>선택된 게시글 편집</h3>
345345 < div class ="promotion-actions ">
346346 < button type ="button " id ="btnSavePromotion "> 저장</ button >
347347 < button type ="button " id ="btnResetPromotion "> 원본으로 되돌리기</ button >
348+ < button type ="button " id ="btnDeletePromotion "> 삭제</ button >
348349 < button type ="button " id ="btnClearPromotionSelection "> 선택 해제</ button >
349350 </ div >
350351 < p id ="promotionHelpText " class ="promotion-help "> 기존 게시글의 `clubId`는 읽기 전용입니다. 저장 후 목록을 다시 조회해 서버 기준 최신값으로 동기화합니다.</ p >
@@ -517,6 +518,7 @@ <h2>이미지 변환 배치 (전체 동아리)</h2>
517518 let promotionIsLoading = false ;
518519 let promotionIsSaving = false ;
519520 let promotionIsUploading = false ;
521+ let promotionIsDeleting = false ;
520522
521523 function getToken ( ) { return sessionStorage . getItem ( 'devPortalToken' ) || '' ; }
522524 function setToken ( t ) { sessionStorage . setItem ( 'devPortalToken' , t ) ; }
@@ -1084,7 +1086,7 @@ <h2>이미지 변환 배치 (전체 동아리)</h2>
10841086 const hasSelection = ! ! promotionSelectedArticleId ;
10851087 const isCreateMode = isPromotionCreateMode ( ) ;
10861088 const hasEditorTarget = hasSelection || isCreateMode ;
1087- const busy = promotionIsLoading || promotionIsSaving || promotionIsUploading ;
1089+ const busy = promotionIsLoading || promotionIsSaving || promotionIsUploading || promotionIsDeleting ;
10881090 const dirty = isPromotionDirty ( ) ;
10891091 const badge = document . getElementById ( 'promotionEditingBadge' ) ;
10901092 const summary = document . getElementById ( 'promotionSelectionSummary' ) ;
@@ -1097,6 +1099,7 @@ <h2>이미지 변환 배치 (전체 동아리)</h2>
10971099 const btnCreate = document . getElementById ( 'btnCreatePromotion' ) ;
10981100 const btnSave = document . getElementById ( 'btnSavePromotion' ) ;
10991101 const btnReset = document . getElementById ( 'btnResetPromotion' ) ;
1102+ const btnDelete = document . getElementById ( 'btnDeletePromotion' ) ;
11001103 const btnClear = document . getElementById ( 'btnClearPromotionSelection' ) ;
11011104
11021105 PROMOTION_FORM_IDS . forEach ( ( id ) => {
@@ -1111,10 +1114,12 @@ <h2>이미지 변환 배치 (전체 동아리)</h2>
11111114 btnCreate . disabled = busy ;
11121115 btnSave . disabled = busy || ! hasEditorTarget ;
11131116 btnReset . disabled = busy || ! hasEditorTarget || ! dirty ;
1117+ btnDelete . disabled = busy || ! hasSelection || isCreateMode ;
11141118 btnClear . disabled = busy || ! hasEditorTarget ;
11151119 btnSave . textContent = promotionIsSaving
11161120 ? ( isCreateMode ? '생성 중...' : '저장 중...' )
11171121 : ( isCreateMode ? '생성' : '저장' ) ;
1122+ btnDelete . textContent = promotionIsDeleting ? '삭제 중...' : '삭제' ;
11181123
11191124 if ( isCreateMode ) {
11201125 badge . textContent = dirty ? '새 게시글 작성 중 · 저장되지 않은 변경 있음' : '새 게시글 작성 중' ;
@@ -1326,7 +1331,7 @@ <h2>이미지 변환 배치 (전체 동아리)</h2>
13261331 const removeBtn = document . createElement ( 'button' ) ;
13271332 removeBtn . type = 'button' ;
13281333 removeBtn . textContent = '제거' ;
1329- removeBtn . disabled = promotionIsLoading || promotionIsSaving || promotionIsUploading || ( ! promotionSelectedArticleId && ! isPromotionCreateMode ( ) ) ;
1334+ removeBtn . disabled = promotionIsLoading || promotionIsSaving || promotionIsUploading || promotionIsDeleting || ( ! promotionSelectedArticleId && ! isPromotionCreateMode ( ) ) ;
13301335 removeBtn . onclick = ( ) => {
13311336 removePromotionImageAt ( index ) ;
13321337 } ;
@@ -1463,6 +1468,15 @@ <h2>이미지 변환 배치 (전체 동아리)</h2>
14631468 return { res, data } ;
14641469 }
14651470
1471+ async function deletePromotionArticleRequest ( articleId ) {
1472+ const res = await fetch ( API_BASE + '/api/promotion/' + encodeURIComponent ( articleId ) , {
1473+ method : 'DELETE' ,
1474+ headers : headers ( )
1475+ } ) ;
1476+ const data = await readJsonOrEmpty ( res ) ;
1477+ return { res, data } ;
1478+ }
1479+
14661480 function clearPromotionState ( ) {
14671481 promotionArticles = [ ] ;
14681482 promotionEditorMode = 'edit' ;
@@ -1472,6 +1486,7 @@ <h2>이미지 변환 배치 (전체 동아리)</h2>
14721486 promotionIsLoading = false ;
14731487 promotionIsSaving = false ;
14741488 promotionIsUploading = false ;
1489+ promotionIsDeleting = false ;
14751490 document . getElementById ( 'promotionListLoading' ) . classList . add ( 'hidden' ) ;
14761491 document . getElementById ( 'promotionImageUploadFile' ) . value = '' ;
14771492 clearPromotionBanner ( ) ;
@@ -1537,6 +1552,67 @@ <h2>이미지 변환 배치 (전체 동아리)</h2>
15371552 clearPromotionSelection ( ) ;
15381553 } ;
15391554
1555+ document . getElementById ( 'btnDeletePromotion' ) . onclick = async ( ) => {
1556+ const selectedId = promotionSelectedArticleId ;
1557+ const selectedArticle = promotionArticles . find ( ( article ) => article . id === selectedId ) ;
1558+ if ( ! selectedId || ! selectedArticle ) {
1559+ showPromotionSaveResult ( false , '삭제할 홍보 게시글을 먼저 선택하세요.' ) ;
1560+ return ;
1561+ }
1562+
1563+ const articleLabel = selectedArticle . title ? '"' + selectedArticle . title + '"' : '선택한 홍보 게시글' ;
1564+ const confirmMessage = isPromotionDirty ( )
1565+ ? '저장되지 않은 변경사항이 있습니다. ' + articleLabel + '을(를) 정말 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다.'
1566+ : articleLabel + '을(를) 정말 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다.' ;
1567+ if ( ! confirm ( confirmMessage ) ) return ;
1568+
1569+ promotionIsDeleting = true ;
1570+ updatePromotionEditorState ( ) ;
1571+ clearPromotionBanner ( ) ;
1572+ hidePromotionSaveResult ( ) ;
1573+
1574+ try {
1575+ const { res, data } = await deletePromotionArticleRequest ( selectedId ) ;
1576+ if ( res . status === 403 ) {
1577+ showPromotionSaveResult ( false , '개발자 계정으로 로그인하세요.' ) ;
1578+ return ;
1579+ }
1580+ if ( res . status === 404 ) {
1581+ if ( data . statuscode === '902-1' ) {
1582+ promotionArticles = promotionArticles . filter ( ( article ) => article . id !== selectedId ) ;
1583+ clearPromotionSelection ( { keepMessage : true } ) ;
1584+ showPromotionSaveResult ( false , data . message || '선택한 홍보 게시글을 찾을 수 없습니다. 목록을 새로고침하세요.' ) ;
1585+ } else {
1586+ showPromotionSaveResult ( false , data . message || '홍보 게시글 삭제 실패 (HTTP ' + res . status + ')' ) ;
1587+ }
1588+ return ;
1589+ }
1590+ if ( ! res . ok ) {
1591+ showPromotionSaveResult ( false , data . message || '홍보 게시글 삭제 실패 (HTTP ' + res . status + ')' ) ;
1592+ return ;
1593+ }
1594+
1595+ const deleteSuccessMessage = getApiSuccessMessage ( data , '홍보 게시글이 삭제되었습니다.' ) ;
1596+ const syncResult = await reloadPromotionList ( { keepMessage : true } ) ;
1597+ if ( ! syncResult . ok ) {
1598+ clearPromotionSelection ( { keepMessage : true } ) ;
1599+ showPromotionSaveResult ( true , deleteSuccessMessage ) ;
1600+ setPromotionBanner ( '삭제는 완료되었지만 목록 동기화에 실패했습니다. 목록을 새로고침해 확인하세요.' , 'warn' ) ;
1601+ return ;
1602+ }
1603+
1604+ clearPromotionSelection ( { keepMessage : true } ) ;
1605+ clearPromotionBanner ( ) ;
1606+ showPromotionSaveResult ( true , deleteSuccessMessage ) ;
1607+ showToast ( '홍보 게시글 삭제 완료' , 'success' ) ;
1608+ } catch ( e ) {
1609+ showPromotionSaveResult ( false , e . message || '요청 실패' ) ;
1610+ } finally {
1611+ promotionIsDeleting = false ;
1612+ updatePromotionEditorState ( ) ;
1613+ }
1614+ } ;
1615+
15401616 function extractCreatedPromotionId ( data ) {
15411617 return data ?. data ?. articleId || data ?. data ?. id || data ?. articleId || data ?. id || '' ;
15421618 }
0 commit comments