From 8c734bbf2d41b9082dda9b224c4cc521d5f66e4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=B3=B4=EA=B3=A4?= Date: Sat, 7 Mar 2026 23:29:18 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20[planning-design]=20=EC=8A=A4=ED=86=A0?= =?UTF-8?q?=EB=A6=AC=EB=B3=B4=EB=93=9C=20=EB=B8=94=EB=A1=9D=20=ED=85=9C?= =?UTF-8?q?=ED=94=8C=EB=A6=BF=20=EC=8B=9C=EC=8A=A4=ED=85=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 기본 프리셋 9종: 검색+목록, 상세폼, CRUD, 대시보드, 결재폼, 탭 레이아웃, 팝업/모달, 로그인, 빈 페이지 - 내 템플릿 저장/삽입/삭제 (localStorage 영구 보관) - 템플릿 검색 필터, 프리셋/커스텀 탭 분리 - 현재 페이지 블록을 한 번에 템플릿으로 저장하여 재활용 --- .../views/rd/planning-design/index.blade.php | 309 ++++++++++++++++++ 1 file changed, 309 insertions(+) diff --git a/resources/views/rd/planning-design/index.blade.php b/resources/views/rd/planning-design/index.blade.php index e999d9c4..5c2be821 100644 --- a/resources/views/rd/planning-design/index.blade.php +++ b/resources/views/rd/planning-design/index.blade.php @@ -736,6 +736,74 @@ } .sb-blk-empty-area:hover { border-color: var(--pc-indigo); background: #fafafe; } +/* Template Dropdown */ +.sb-tpl-dropdown { + position: relative; display: inline-block; +} +.sb-tpl-trigger { + padding: 3px 10px; border: 1px solid #e2e8f0; border-radius: 5px; + font-size: 10px; cursor: pointer; background: #fff; color: #475569; + display: flex; align-items: center; gap: 4px; white-space: nowrap; + transition: all .12s; +} +.sb-tpl-trigger:hover { border-color: var(--pc-indigo); color: var(--pc-indigo); } +.sb-tpl-panel { + position: absolute; top: 100%; right: 0; margin-top: 4px; z-index: 100; + width: 360px; max-height: 480px; background: #fff; + border: 1px solid #e2e8f0; border-radius: 10px; + box-shadow: 0 12px 32px rgba(0,0,0,0.12); display: flex; flex-direction: column; +} +.sb-tpl-tabs { + display: flex; border-bottom: 1px solid #e2e8f0; +} +.sb-tpl-tab { + flex: 1; padding: 8px; font-size: 11px; font-weight: 600; text-align: center; + cursor: pointer; color: #94a3b8; border-bottom: 2px solid transparent; + transition: all .12s; +} +.sb-tpl-tab:hover { color: #475569; } +.sb-tpl-tab.active { color: var(--pc-indigo); border-bottom-color: var(--pc-indigo); } +.sb-tpl-search { + margin: 8px 10px 4px; padding: 6px 10px; border: 1px solid #e2e8f0; + border-radius: 6px; font-size: 11px; outline: none; width: calc(100% - 20px); +} +.sb-tpl-search:focus { border-color: var(--pc-indigo); } +.sb-tpl-list { flex: 1; overflow-y: auto; padding: 4px 6px 8px; } +.sb-tpl-item { + display: flex; align-items: center; gap: 8px; padding: 8px 10px; + border-radius: 6px; cursor: pointer; transition: all .1s; +} +.sb-tpl-item:hover { background: #f1f5f9; } +.sb-tpl-item-icon { + width: 32px; height: 32px; border-radius: 6px; background: #f1f5f9; + display: flex; align-items: center; justify-content: center; font-size: 15px; + flex-shrink: 0; +} +.sb-tpl-item-info { flex: 1; min-width: 0; } +.sb-tpl-item-name { font-size: 12px; font-weight: 600; color: #1e293b; } +.sb-tpl-item-desc { font-size: 10px; color: #94a3b8; margin-top: 1px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } +.sb-tpl-item-actions { display: flex; gap: 2px; flex-shrink: 0; } +.sb-tpl-item-btn { + width: 22px; height: 22px; border: none; background: transparent; color: #cbd5e1; + border-radius: 4px; cursor: pointer; font-size: 12px; + display: flex; align-items: center; justify-content: center; +} +.sb-tpl-item-btn:hover { background: #fee2e2; color: #ef4444; } +.sb-tpl-save-bar { + padding: 8px 10px; border-top: 1px solid #e2e8f0; display: flex; gap: 6px; align-items: center; +} +.sb-tpl-save-input { + flex: 1; padding: 5px 8px; border: 1px solid #e2e8f0; border-radius: 5px; + font-size: 11px; outline: none; +} +.sb-tpl-save-input:focus { border-color: var(--pc-indigo); } +.sb-tpl-save-btn { + padding: 5px 12px; border: none; border-radius: 5px; font-size: 11px; + font-weight: 600; cursor: pointer; background: var(--pc-indigo); color: #fff; +} +.sb-tpl-save-btn:hover { opacity: 0.9; } +.sb-tpl-empty { text-align: center; padding: 20px; color: #cbd5e1; font-size: 11px; } + /* Menu Tree Editor Modal */ .sb-menu-modal-overlay { position: fixed; inset: 0; z-index: 9999; background: rgba(0,0,0,0.4); @@ -1358,6 +1426,62 @@ +
+ {{-- Template Dropdown --}} +
+ +
+
+
기본 템플릿
+
내 템플릿
+
+ +
+ {{-- Preset Templates --}} + + {{-- Custom Templates --}} + +
+ {{-- Save current blocks as template --}} +
+ + +
+
+
{{-- Block Editor Area --}} @@ -1818,6 +1942,11 @@ function planningCanvas() { sbSelectedBlock: null, sbBlockImageTarget: null, _sbBlockDragIdx: null, + sbTplOpen: false, + sbTplTab: 'preset', + sbTplSearch: '', + sbTplSaveName: '', + sbCustomTemplates: [], sb: { docInfo: { projectName: '', unitTask: '', version: 'D1.0' }, menuTree: [ @@ -1920,6 +2049,149 @@ function planningCanvas() { return page.blocks; }, + get sbPresetTemplates() { + return [ + { name: '검색 + 목록 화면', icon: '🔍', desc: '검색조건 + 데이터 테이블', blocks: [ + { type: 'heading2', content: '검색 조건' }, + { type: 'input', label: '검색어', placeholder: '품명, 품번 등을 입력하세요' }, + { type: 'badges', items: [ + { text: '검색', color: '#4338ca', textColor: '#fff' }, + { text: '초기화', color: '#f1f5f9', textColor: '#475569' }, + ]}, + { type: 'divider', content: '' }, + { type: 'heading2', content: '목록' }, + { type: 'table', cols: ['No', '항목명', '상태', '담당자', '등록일', '비고'], rows: [ + ['1', '샘플 항목 A', '진행중', '홍길동', '2026-03-01', ''], + ['2', '샘플 항목 B', '대기', '김철수', '2026-03-02', ''], + ['3', '', '', '', '', ''], + ]}, + { type: 'badges', items: [ + { text: '총 3건', color: '#e0e7ff', textColor: '#4338ca' }, + ]}, + ]}, + { name: '상세 정보 폼', icon: '📝', desc: '라벨+입력 필드 그룹', blocks: [ + { type: 'heading', content: '상세 정보' }, + { type: 'input', label: '품명', placeholder: '품명을 입력하세요' }, + { type: 'input', label: '품번', placeholder: '품번을 입력하세요' }, + { type: 'select', label: '분류', placeholder: '분류를 선택하세요' }, + { type: 'input', label: '규격', placeholder: '규격 정보' }, + { type: 'text', content: '' }, + { type: 'divider', content: '' }, + { type: 'badges', items: [ + { text: '저장', color: '#4338ca', textColor: '#fff' }, + { text: '취소', color: '#f1f5f9', textColor: '#475569' }, + ]}, + ]}, + { name: 'CRUD 화면', icon: '⚙️', desc: '제목 + 검색 + 테이블 + 버튼 조합', blocks: [ + { type: 'heading', content: '관리 화면 제목' }, + { type: 'text', content: '화면 설명 텍스트를 입력하세요.' }, + { type: 'divider', content: '' }, + { type: 'heading2', content: '검색 조건' }, + { type: 'input', label: '검색어', placeholder: '검색어 입력' }, + { type: 'select', label: '상태', placeholder: '전체' }, + { type: 'badges', items: [ + { text: '검색', color: '#4338ca', textColor: '#fff' }, + { text: '초기화', color: '#f1f5f9', textColor: '#475569' }, + { text: '+ 신규등록', color: '#10b981', textColor: '#fff' }, + { text: '엑셀 다운로드', color: '#f1f5f9', textColor: '#475569' }, + ]}, + { type: 'divider', content: '' }, + { type: 'table', cols: ['선택', 'No', '항목명', '분류', '상태', '담당자', '수정일'], rows: [ + ['☐', '1', '', '', '', '', ''], + ['☐', '2', '', '', '', '', ''], + ]}, + { type: 'badges', items: [ + { text: '선택 삭제', color: '#fef2f2', textColor: '#ef4444' }, + { text: '일괄 수정', color: '#e0e7ff', textColor: '#4338ca' }, + ]}, + ]}, + { name: '대시보드 카드', icon: '📊', desc: '통계 카드 + 요약 테이블', blocks: [ + { type: 'heading', content: '대시보드' }, + { type: 'card', title: '총 주문건', content: '1,234건 (+12.5% ↑)' }, + { type: 'card', title: '매출 합계', content: '₩85,600,000' }, + { type: 'card', title: '미처리 건수', content: '23건' }, + { type: 'divider', content: '' }, + { type: 'heading2', content: '최근 현황' }, + { type: 'table', cols: ['구분', '이번 달', '전월', '증감률'], rows: [ + ['수주', '450건', '380건', '+18.4%'], + ['출하', '420건', '395건', '+6.3%'], + ['반품', '12건', '18건', '-33.3%'], + ]}, + ]}, + { name: '결재/승인 폼', icon: '✅', desc: '결재라인 + 체크리스트 + 버튼', blocks: [ + { type: 'heading', content: '결재 요청서' }, + { type: 'table', cols: ['구분', '기안자', '검토', '승인'], rows: [ + ['직급', '사원', '과장', '부장'], + ['성명', '', '', ''], + ['상태', '기안', '대기', '대기'], + ]}, + { type: 'divider', content: '' }, + { type: 'heading2', content: '요청 내용' }, + { type: 'text', content: '결재 요청 상세 내용을 입력하세요.' }, + { type: 'divider', content: '' }, + { type: 'todo', items: [ + { text: '첨부파일 확인', checked: false }, + { text: '금액 확인', checked: false }, + { text: '기안 내용 검토', checked: false }, + ]}, + { type: 'badges', items: [ + { text: '결재 요청', color: '#4338ca', textColor: '#fff' }, + { text: '임시 저장', color: '#f1f5f9', textColor: '#475569' }, + ]}, + ]}, + { name: '탭 레이아웃', icon: '📑', desc: '탭 메뉴 + 콘텐츠 영역', blocks: [ + { type: 'heading', content: '화면 제목' }, + { type: 'badges', items: [ + { text: '기본정보', color: '#4338ca', textColor: '#fff' }, + { text: 'BOM', color: '#f1f5f9', textColor: '#475569' }, + { text: '이력', color: '#f1f5f9', textColor: '#475569' }, + { text: '첨부파일', color: '#f1f5f9', textColor: '#475569' }, + ]}, + { type: 'divider', content: '' }, + { type: 'callout', icon: 'ℹ️', content: '선택한 탭의 콘텐츠가 여기에 표시됩니다.' }, + { type: 'text', content: '' }, + ]}, + { name: '팝업/모달', icon: '💬', desc: '모달 다이얼로그 UI', blocks: [ + { type: 'card', title: '모달 제목', content: '' }, + { type: 'text', content: '모달 본문 내용을 입력하세요.' }, + { type: 'input', label: '입력 항목', placeholder: '값을 입력하세요' }, + { type: 'divider', content: '' }, + { type: 'badges', items: [ + { text: '확인', color: '#4338ca', textColor: '#fff' }, + { text: '취소', color: '#f1f5f9', textColor: '#475569' }, + ]}, + ]}, + { name: '로그인 화면', icon: '🔐', desc: '로그인 폼 UI', blocks: [ + { type: 'heading', content: '로그인' }, + { type: 'text', content: '시스템에 접속하려면 로그인하세요.' }, + { type: 'input', label: '아이디', placeholder: 'user@company.com' }, + { type: 'input', label: '비밀번호', placeholder: '••••••••' }, + { type: 'todo', items: [ + { text: '자동 로그인', checked: false }, + ]}, + { type: 'button', content: '로그인', color: '#4338ca' }, + { type: 'text', content: '비밀번호를 잊으셨나요?' }, + ]}, + { name: '빈 페이지 (기본 구조)', icon: '📄', desc: '제목 + 설명 + 구분선', blocks: [ + { type: 'heading', content: '' }, + { type: 'text', content: '' }, + { type: 'divider', content: '' }, + ]}, + ]; + }, + + get sbFilteredPresets() { + const q = this.sbTplSearch.toLowerCase().trim(); + if (!q) return this.sbPresetTemplates; + return this.sbPresetTemplates.filter(t => t.name.toLowerCase().includes(q) || t.desc.toLowerCase().includes(q)); + }, + + get sbFilteredCustoms() { + const q = this.sbTplSearch.toLowerCase().trim(); + if (!q) return this.sbCustomTemplates; + return this.sbCustomTemplates.filter(t => t.name.toLowerCase().includes(q)); + }, + get allPaletteItems() { return [ ...this.paletteItems.planning, @@ -1970,6 +2242,7 @@ function planningCanvas() { this.newProject(); } this.sbInitPages(); + this.sbCustomTemplates = JSON.parse(localStorage.getItem('sb_custom_templates') || '[]'); }, // ===== Project Management ===== @@ -2688,6 +2961,42 @@ function planningCanvas() { this.sbBlockImageTarget = null; }, + // ===== Template System ===== + sbInsertTemplate(tpl) { + const page = this.sbCurrentPage; + if (!page) return; + if (!page.blocks) page.blocks = []; + const blocks = JSON.parse(JSON.stringify(tpl.blocks)); + blocks.forEach(blk => { + blk.id = 'blk_' + Date.now() + '_' + Math.random().toString(36).slice(2, 5); + }); + page.blocks.push(...blocks); + this.autoSave(); + }, + + sbSaveAsTemplate() { + const name = this.sbTplSaveName.trim(); + if (!name) return; + const page = this.sbCurrentPage; + if (!page || !page.blocks || page.blocks.length === 0) { + alert('현재 페이지에 블록이 없습니다.'); + return; + } + const blocks = JSON.parse(JSON.stringify(page.blocks)); + // id 제거 (삽입 시 새로 생성) + blocks.forEach(blk => { delete blk.id; }); + this.sbCustomTemplates.push({ name, blocks }); + localStorage.setItem('sb_custom_templates', JSON.stringify(this.sbCustomTemplates)); + this.sbTplSaveName = ''; + this.sbTplTab = 'custom'; + }, + + sbDeleteCustomTemplate(idx) { + if (!confirm('이 템플릿을 삭제하시겠습니까?')) return; + this.sbCustomTemplates.splice(idx, 1); + localStorage.setItem('sb_custom_templates', JSON.stringify(this.sbCustomTemplates)); + }, + sbEditMenu() { // deep copy menuTree → draft (+ _open 플래그 추가) this.sbMenuDraft = JSON.parse(JSON.stringify(this.sb.menuTree)).map(m => {