@@ -1038,13 +1038,19 @@ Object.assign(CodemanApp.prototype, {
10381038 modal . querySelectorAll ( '.modal-tab-content' ) . forEach ( content => {
10391039 content . classList . toggle ( 'hidden' , content . id !== tabName ) ;
10401040 } ) ;
1041- // Update submit button text
1041+ // Update submit button (hide for manage tab)
10421042 const submitBtn = document . getElementById ( 'caseModalSubmit' ) ;
1043- submitBtn . textContent = tabName === 'case-create' ? 'Create' : 'Link' ;
1043+ if ( tabName === 'case-manage' ) {
1044+ submitBtn . style . display = 'none' ;
1045+ this . renderCaseManageList ( ) ;
1046+ } else {
1047+ submitBtn . style . display = '' ;
1048+ submitBtn . textContent = tabName === 'case-create' ? 'Create' : 'Link' ;
1049+ }
10441050 // Focus appropriate input
10451051 if ( tabName === 'case-create' ) {
10461052 document . getElementById ( 'newCaseName' ) . focus ( ) ;
1047- } else {
1053+ } else if ( tabName === 'case-link' ) {
10481054 document . getElementById ( 'linkCaseName' ) . focus ( ) ;
10491055 }
10501056 } ,
@@ -1152,6 +1158,107 @@ Object.assign(CodemanApp.prototype, {
11521158 } ,
11531159
11541160
1161+ // ═══════════════════════════════════════════════════════════════
1162+ // Case Management (reorder + delete)
1163+ // ═══════════════════════════════════════════════════════════════
1164+
1165+ renderCaseManageList ( ) {
1166+ const container = document . getElementById ( 'caseManageList' ) ;
1167+ const cases = this . cases || [ ] ;
1168+ if ( cases . length === 0 ) {
1169+ container . innerHTML = '<div class="form-hint" style="text-align: center; padding: 2rem 0;">No cases yet</div>' ;
1170+ return ;
1171+ }
1172+
1173+ let html = '' ;
1174+ cases . forEach ( ( c , idx ) => {
1175+ const isFirst = idx === 0 ;
1176+ const isLast = idx === cases . length - 1 ;
1177+ const pathDisplay = c . path ? c . path . replace ( / ^ \/ U s e r s \/ [ ^ / ] + / , '~' ) : '' ;
1178+ html += `
1179+ <div class="case-manage-item" data-case="${ escapeHtml ( c . name ) } ">
1180+ <div class="case-manage-info">
1181+ <span class="case-manage-name">${ escapeHtml ( c . name ) } </span>
1182+ <span class="case-manage-path">${ escapeHtml ( pathDisplay ) } </span>
1183+ </div>
1184+ <div class="case-manage-actions">
1185+ <button class="case-manage-btn" onclick="app.moveCaseUp('${ escapeHtml ( c . name ) } ')"
1186+ title="Move up" ${ isFirst ? 'disabled' : '' } >▲</button>
1187+ <button class="case-manage-btn" onclick="app.moveCaseDown('${ escapeHtml ( c . name ) } ')"
1188+ title="Move down" ${ isLast ? 'disabled' : '' } >▼</button>
1189+ <button class="case-manage-btn case-manage-btn-delete" onclick="app.deleteCase('${ escapeHtml ( c . name ) } ')"
1190+ title="Delete case">✕</button>
1191+ </div>
1192+ </div>
1193+ ` ;
1194+ } ) ;
1195+ container . innerHTML = html ;
1196+ } ,
1197+
1198+ async moveCaseUp ( name ) {
1199+ const cases = this . cases || [ ] ;
1200+ const idx = cases . findIndex ( c => c . name === name ) ;
1201+ if ( idx <= 0 ) return ;
1202+ // Swap positions (immutable)
1203+ const reordered = [ ...cases ] ;
1204+ [ reordered [ idx - 1 ] , reordered [ idx ] ] = [ reordered [ idx ] , reordered [ idx - 1 ] ] ;
1205+ this . cases = reordered ;
1206+ this . renderCaseManageList ( ) ;
1207+ await this . saveCaseOrder ( reordered . map ( c => c . name ) ) ;
1208+ } ,
1209+
1210+ async moveCaseDown ( name ) {
1211+ const cases = this . cases || [ ] ;
1212+ const idx = cases . findIndex ( c => c . name === name ) ;
1213+ if ( idx < 0 || idx >= cases . length - 1 ) return ;
1214+ const reordered = [ ...cases ] ;
1215+ [ reordered [ idx ] , reordered [ idx + 1 ] ] = [ reordered [ idx + 1 ] , reordered [ idx ] ] ;
1216+ this . cases = reordered ;
1217+ this . renderCaseManageList ( ) ;
1218+ await this . saveCaseOrder ( reordered . map ( c => c . name ) ) ;
1219+ } ,
1220+
1221+ async deleteCase ( name ) {
1222+ if ( ! confirm ( `Delete case "${ name } "? Linked cases will only be unlinked (folder preserved). Created cases will be permanently deleted.` ) ) {
1223+ return ;
1224+ }
1225+
1226+ try {
1227+ const res = await fetch ( `/api/cases/${ encodeURIComponent ( name ) } ` , { method : 'DELETE' } ) ;
1228+ const data = await res . json ( ) ;
1229+ if ( data . success ) {
1230+ this . showToast ( `Case "${ name } " ${ data . data ?. type === 'unlinked' ? 'unlinked' : 'deleted' } ` , 'success' ) ;
1231+ // Remove from current list and refresh
1232+ this . cases = ( this . cases || [ ] ) . filter ( c => c . name !== name ) ;
1233+ this . renderCaseManageList ( ) ;
1234+ // Refresh the dropdown
1235+ const select = document . getElementById ( 'quickStartCase' ) ;
1236+ const currentCase = select . value ;
1237+ await this . loadQuickStartCases ( currentCase === name ? null : currentCase ) ;
1238+ } else {
1239+ this . showToast ( data . error || 'Failed to delete case' , 'error' ) ;
1240+ }
1241+ } catch ( err ) {
1242+ this . showToast ( 'Failed to delete case: ' + err . message , 'error' ) ;
1243+ }
1244+ } ,
1245+
1246+ async saveCaseOrder ( order ) {
1247+ try {
1248+ await fetch ( '/api/cases/order' , {
1249+ method : 'PUT' ,
1250+ headers : { 'Content-Type' : 'application/json' } ,
1251+ body : JSON . stringify ( { order } )
1252+ } ) ;
1253+ // Refresh dropdown to reflect new order
1254+ const select = document . getElementById ( 'quickStartCase' ) ;
1255+ const currentCase = select . value ;
1256+ await this . loadQuickStartCases ( currentCase ) ;
1257+ } catch ( err ) {
1258+ this . showToast ( 'Failed to save case order: ' + err . message , 'error' ) ;
1259+ }
1260+ } ,
1261+
11551262 // ═══════════════════════════════════════════════════════════════
11561263 // Mobile Case Picker
11571264 // ═══════════════════════════════════════════════════════════════
@@ -1181,6 +1288,11 @@ Object.assign(CodemanApp.prototype, {
11811288 </svg>
11821289 </span>
11831290 <span class="mobile-case-item-name">${ escapeHtml ( c . name ) } </span>
1291+ <span class="mobile-case-item-delete" onclick="event.stopPropagation(); app.deleteCaseMobile('${ escapeHtml ( c . name ) } ')" title="Delete">
1292+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1293+ <line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
1294+ </svg>
1295+ </span>
11841296 <span class="mobile-case-item-check">
11851297 <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
11861298 <polyline points="20 6 9 17 4 12"/>
@@ -1226,6 +1338,25 @@ Object.assign(CodemanApp.prototype, {
12261338 }
12271339 } ,
12281340
1341+ async deleteCaseMobile ( name ) {
1342+ if ( ! confirm ( `Delete case "${ name } "?` ) ) return ;
1343+ try {
1344+ const res = await fetch ( `/api/cases/${ encodeURIComponent ( name ) } ` , { method : 'DELETE' } ) ;
1345+ const data = await res . json ( ) ;
1346+ if ( data . success ) {
1347+ this . showToast ( `Case "${ name } " ${ data . data ?. type === 'unlinked' ? 'unlinked' : 'deleted' } ` , 'success' ) ;
1348+ this . cases = ( this . cases || [ ] ) . filter ( c => c . name !== name ) ;
1349+ // Refresh mobile picker and dropdown
1350+ this . closeMobileCasePicker ( ) ;
1351+ await this . loadQuickStartCases ( ) ;
1352+ } else {
1353+ this . showToast ( data . error || 'Failed to delete case' , 'error' ) ;
1354+ }
1355+ } catch ( err ) {
1356+ this . showToast ( 'Failed to delete case: ' + err . message , 'error' ) ;
1357+ }
1358+ } ,
1359+
12291360 showCreateCaseFromMobile ( ) {
12301361 // Close mobile picker first
12311362 this . closeMobileCasePicker ( ) ;
0 commit comments