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 => {