diff --git a/resources/views/rd/planning-design/index.blade.php b/resources/views/rd/planning-design/index.blade.php index 1ddd9120..e32f44dc 100644 --- a/resources/views/rd/planning-design/index.blade.php +++ b/resources/views/rd/planning-design/index.blade.php @@ -490,6 +490,137 @@ } .pc-filter-clear:hover { text-decoration: underline; } +/* ===== Storyboard View ===== */ +.sb-wrap { flex: 1; display: flex; flex-direction: column; overflow: hidden; background: #e5e7eb; } +.sb-topbar { + display: flex; align-items: center; gap: 8px; padding: 8px 16px; + background: #fff; border-bottom: 1px solid #e2e8f0; flex-shrink: 0; +} +.sb-topbar label { font-size: 10px; font-weight: 600; color: #94a3b8; } +.sb-topbar input, .sb-topbar select { + padding: 4px 8px; border: 1px solid #e2e8f0; border-radius: 6px; + font-size: 12px; outline: none; +} +.sb-topbar input:focus { border-color: var(--pc-indigo); } +.sb-topbar-sep { width: 1px; height: 24px; background: #e2e8f0; } +.sb-pages-nav { + display: flex; align-items: center; gap: 4px; + font-size: 12px; font-weight: 600; color: #374151; +} +.sb-pages-nav button { + width: 28px; height: 28px; border-radius: 6px; border: 1px solid #e2e8f0; + background: #fff; cursor: pointer; display: flex; align-items: center; justify-content: center; + font-size: 14px; color: #64748b; +} +.sb-pages-nav button:hover { background: #f1f5f9; } +.sb-body { flex: 1; display: flex; overflow: hidden; } +.sb-page-list { + width: 140px; flex-shrink: 0; background: #f1f5f9; border-right: 1px solid #e2e8f0; + overflow-y: auto; padding: 8px; +} +.sb-page-thumb { + padding: 6px 8px; border-radius: 6px; cursor: pointer; margin-bottom: 4px; + font-size: 10px; color: #64748b; border: 2px solid transparent; + background: #fff; transition: all 0.15s; +} +.sb-page-thumb:hover { border-color: #cbd5e1; } +.sb-page-thumb.active { border-color: var(--pc-indigo); background: #eef2ff; color: #4338ca; } +.sb-page-thumb-num { font-weight: 700; font-size: 11px; color: #374151; } +.sb-editor { flex: 1; overflow: auto; padding: 24px; display: flex; justify-content: center; } + +/* Storyboard Page (A4-like) */ +.sb-page { + width: 1100px; min-height: 750px; background: #fff; border-radius: 4px; + box-shadow: 0 2px 12px rgba(0,0,0,0.1); display: flex; flex-direction: column; + flex-shrink: 0; +} +.sb-page-header { + display: grid; grid-template-columns: 1fr auto auto auto auto auto; + border-bottom: 2px solid #1e293b; font-size: 10px; +} +.sb-page-header > div { + padding: 6px 10px; border-right: 1px solid #cbd5e1; + display: flex; flex-direction: column; justify-content: center; +} +.sb-page-header > div:last-child { border-right: none; } +.sb-page-header .label { font-size: 8px; color: #94a3b8; font-weight: 600; text-transform: uppercase; } +.sb-page-header .value { font-size: 11px; font-weight: 700; color: #1e293b; } +.sb-page-header .value input { + border: none; outline: none; font-size: 11px; font-weight: 700; color: #1e293b; + width: 100%; background: transparent; +} +.sb-page-header .value input:focus { border-bottom: 1px solid var(--pc-indigo); } +.sb-page-body { display: flex; flex: 1; min-height: 0; } + +/* Left Menu Panel */ +.sb-menu-panel { + width: 160px; flex-shrink: 0; border-right: 1px solid #e2e8f0; + padding: 12px 0; font-size: 11px; overflow-y: auto; background: #f8fafc; +} +.sb-menu-section { font-size: 8px; font-weight: 700; color: #94a3b8; padding: 4px 12px; text-transform: uppercase; } +.sb-menu-item { + padding: 5px 12px 5px 16px; color: #64748b; cursor: default; + font-size: 11px; line-height: 1.4; +} +.sb-menu-item.active { color: #4338ca; font-weight: 700; background: #eef2ff; border-right: 3px solid #4338ca; } +.sb-menu-child { padding-left: 28px; font-size: 10px; } +.sb-menu-child.active { color: #4338ca; font-weight: 700; } +.sb-menu-logo { padding: 8px 12px; font-size: 13px; font-weight: 800; color: #1e293b; letter-spacing: -0.5px; } + +/* Main Content + Description */ +.sb-content-area { flex: 1; display: flex; flex-direction: column; min-width: 0; } +.sb-wireframe { + flex: 1; padding: 16px; min-height: 300px; position: relative; +} +.sb-wireframe-placeholder { + border: 2px dashed #d1d5db; border-radius: 8px; height: 100%; min-height: 280px; + display: flex; align-items: center; justify-content: center; flex-direction: column; + color: #94a3b8; font-size: 12px; gap: 8px; cursor: pointer; transition: all 0.15s; +} +.sb-wireframe-placeholder:hover { border-color: var(--pc-indigo); background: #fafafe; } +.sb-wireframe-content { + width: 100%; min-height: 280px; border: 1px solid #e2e8f0; border-radius: 8px; + padding: 16px; font-size: 13px; line-height: 1.7; color: #374151; + outline: none; overflow: auto; +} +.sb-wireframe-content:focus { border-color: var(--pc-indigo); } +.sb-wireframe-img { max-width: 100%; border-radius: 8px; } +.sb-desc-panel { + border-top: 2px solid #1e293b; padding: 12px 16px; background: #fafbfc; + max-height: 260px; overflow-y: auto; +} +.sb-desc-title { + font-size: 10px; font-weight: 700; color: #1e293b; margin-bottom: 8px; + text-transform: uppercase; letter-spacing: 0.5px; +} +.sb-desc-item { + display: flex; gap: 10px; padding: 6px 0; border-bottom: 1px solid #f1f5f9; + font-size: 12px; line-height: 1.6; +} +.sb-desc-item:last-child { border-bottom: none; } +.sb-desc-num { + width: 24px; height: 24px; border-radius: 50%; flex-shrink: 0; + background: #1e293b; color: #fff; font-size: 10px; font-weight: 700; + display: flex; align-items: center; justify-content: center; +} +.sb-desc-text { flex: 1; color: #374151; } +.sb-desc-text textarea { + width: 100%; border: none; outline: none; font-size: 12px; line-height: 1.6; + color: #374151; resize: none; background: transparent; min-height: 20px; +} +.sb-desc-text textarea:focus { background: #fff; border-radius: 4px; } +.sb-desc-add { + display: flex; align-items: center; gap: 6px; padding: 8px 0; + font-size: 11px; color: var(--pc-indigo); cursor: pointer; font-weight: 500; +} +.sb-desc-add:hover { color: #4f46e5; } +.sb-desc-remove { + width: 20px; height: 20px; border: none; background: transparent; color: #cbd5e1; + cursor: pointer; font-size: 14px; border-radius: 4px; display: flex; + align-items: center; justify-content: center; flex-shrink: 0; +} +.sb-desc-remove:hover { color: #ef4444; background: #fef2f2; } + /* Phase Swimlane (Timeline View) */ .pc-swimlane { position: absolute; top: 0; @@ -533,6 +664,7 @@ + @@ -947,6 +1079,157 @@
노드가 없습니다
+ {{-- Storyboard View --}} +
+ {{-- Storyboard Top Bar --}} +
+ + +
+ + +
+ + +
+
+ + + +
+
+ + +
+ + +
+ +
+ {{-- Page List (thumbnail) --}} +
+ +
+ + {{-- Page Editor --}} +
+ +
+
+
+ {{-- /Main Content Area --}} @@ -1110,6 +1393,26 @@ function planningCanvas() { _kanbanDragNodeId: null, _connTick: 0, + // Storyboard + sb: { + docInfo: { projectName: '', unitTask: '', version: 'D1.0' }, + menuTree: [ + { name: '대시보드', children: [] }, + { name: '판매관리', children: [] }, + { name: '생산관리', children: [] }, + { name: '출고관리', children: [] }, + { name: '품질관리', children: [ + { name: '제품검사관리' }, + { name: '실적신고관리' }, + { name: '품질인정심사' }, + ]}, + { name: '자재관리', children: [] }, + { name: '기준정보', children: [] }, + ], + pages: [], + currentPageIndex: 0, + }, + // Drag State dragging: false, dragNode: null, @@ -1182,6 +1485,10 @@ function planningCanvas() { ], }, + get sbCurrentPage() { + return this.sb.pages[this.sb.currentPageIndex] || null; + }, + get allPaletteItems() { return [ ...this.paletteItems.planning, @@ -1231,6 +1538,7 @@ function planningCanvas() { } else { this.newProject(); } + this.sbInitPages(); }, // ===== Project Management ===== @@ -1246,6 +1554,11 @@ function planningCanvas() { this.panX = 0; this.panY = 0; this.zoom = 1; + // 스토리보드 초기화 + this.sb.docInfo = { projectName: '', unitTask: '', version: 'D1.0' }; + this.sb.pages = []; + this.sb.currentPageIndex = 0; + this.sbInitPages(); localStorage.setItem(CURRENT_KEY, this.currentProjectId); this.pushHistory(); }, @@ -1256,6 +1569,7 @@ function planningCanvas() { title: this.projectTitle, nodes: JSON.parse(JSON.stringify(this.nodes)), connections: JSON.parse(JSON.stringify(this.connections)), + sb: JSON.parse(JSON.stringify(this.sb)), viewMode: this.viewMode, nodeCount: this.nodes.length, updatedAt: new Date().toISOString(), @@ -1287,6 +1601,15 @@ function planningCanvas() { this.selectedConnection = null; this.history = []; this.historyIndex = -1; + // 스토리보드 데이터 복원 + if (proj.sb) { + this.sb = JSON.parse(JSON.stringify(proj.sb)); + } else { + this.sb.docInfo = { projectName: '', unitTask: '', version: 'D1.0' }; + this.sb.pages = []; + this.sb.currentPageIndex = 0; + } + this.sbInitPages(); localStorage.setItem(CURRENT_KEY, id); this.pushHistory(); idCounter = Math.max(0, ...this.nodes.map(n => parseInt(n.id?.split('_')[1]) || 0)) + 1; @@ -1745,6 +2068,151 @@ function planningCanvas() { this.autoSave(); }, + // ===== Storyboard ===== + sbInitPages() { + if (!this.sb.pages || this.sb.pages.length === 0) { + this.sb.pages = [this.sbNewPageData()]; + this.sb.currentPageIndex = 0; + } + }, + + sbNewPageData() { + return { + id: 'sp_' + Date.now() + '_' + Math.random().toString(36).slice(2, 6), + path: '', + screenName: '', + screenId: '', + wireframeContent: '', + wireframeImage: '', + descriptions: [], + }; + }, + + sbAddPage() { + const newPage = this.sbNewPageData(); + this.sb.pages.push(newPage); + this.sb.currentPageIndex = this.sb.pages.length - 1; + this.autoSave(); + }, + + sbDeletePage() { + if (this.sb.pages.length <= 1) return; + if (!confirm('이 페이지를 삭제하시겠습니까?')) return; + this.sb.pages.splice(this.sb.currentPageIndex, 1); + if (this.sb.currentPageIndex >= this.sb.pages.length) { + this.sb.currentPageIndex = this.sb.pages.length - 1; + } + this.autoSave(); + }, + + sbPrevPage() { + if (this.sb.currentPageIndex > 0) this.sb.currentPageIndex--; + }, + + sbNextPage() { + if (this.sb.currentPageIndex < this.sb.pages.length - 1) this.sb.currentPageIndex++; + }, + + sbAddDescription() { + const page = this.sbCurrentPage; + if (!page) return; + if (!page.descriptions) page.descriptions = []; + page.descriptions.push({ text: '' }); + this.autoSave(); + }, + + sbUploadImage(e) { + const file = e.target.files[0]; + if (!file) return; + const reader = new FileReader(); + reader.onload = () => { + const page = this.sbCurrentPage; + if (page) { + page.wireframeImage = reader.result; + page.wireframeContent = ''; + this.autoSave(); + } + }; + reader.readAsDataURL(file); + e.target.value = ''; + }, + + sbEditMenu() { + const json = JSON.stringify(this.sb.menuTree, null, 2); + const input = prompt('메뉴 트리 JSON을 편집하세요:\n(취소하면 변경 없음)', json); + if (input === null) return; + try { + this.sb.menuTree = JSON.parse(input); + this.autoSave(); + } catch (err) { + alert('JSON 형식이 올바르지 않습니다.'); + } + }, + + sbExportHtml() { + let html = '' + + (this.sb.docInfo.projectName || 'Storyboard') + '' + + ''; + + this.sb.pages.forEach((pg, idx) => { + html += '
'; + html += '
단위업무명
' + (this.sb.docInfo.unitTask || '-') + '
'; + html += '
버전
' + (this.sb.docInfo.version || '-') + '
'; + html += '
Page
' + (idx + 1) + '
'; + html += '
경로
' + (pg.path || '-') + '
'; + html += '
화면명
' + (pg.screenName || '-') + '
'; + html += '
화면 ID
' + (pg.screenId || '-') + '
'; + html += '
'; + if (pg.wireframeImage) html += ''; + else if (pg.wireframeContent) html += '
' + pg.wireframeContent + '
'; + else html += '
와이어프레임 영역
'; + html += '
'; + if (pg.descriptions && pg.descriptions.length > 0) { + html += '
Description
'; + pg.descriptions.forEach((d, di) => { + html += '
' + String(di + 1).padStart(2, '0') + '
'; + html += '
' + (d.text || '').replace(/\n/g, '
') + '
'; + }); + html += '
'; + } + html += '
'; + }); + + html += ''; + const blob = new Blob([html], { type: 'text/html' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = (this.sb.docInfo.projectName || 'storyboard') + '.html'; + a.click(); + URL.revokeObjectURL(url); + }, + // ===== Context Menu ===== showContextMenu(e) { this.contextMenuPos = { x: e.clientX, y: e.clientY };