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