feat: 의견 창 이동·크기 조절 + 글자수 2000자·카운터
- 의견 창 헤더 드래그 이동, 우하단 모서리 크기 조절(resize), 비차단 플로팅 - 입력 글자수 1000→2000자, '0/2000' 카운터 표시 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -24,8 +24,8 @@ class Feedback extends BaseController
|
|||||||
if ($content === '') {
|
if ($content === '') {
|
||||||
return $this->response->setJSON(['ok' => false, 'message' => '의견 내용을 입력해 주세요.']);
|
return $this->response->setJSON(['ok' => false, 'message' => '의견 내용을 입력해 주세요.']);
|
||||||
}
|
}
|
||||||
if (mb_strlen($content) > 1000) {
|
if (mb_strlen($content) > 2000) {
|
||||||
$content = mb_substr($content, 0, 1000);
|
$content = mb_substr($content, 0, 2000);
|
||||||
}
|
}
|
||||||
|
|
||||||
helper('admin');
|
helper('admin');
|
||||||
|
|||||||
@@ -14,15 +14,17 @@ $apiPath = (string) parse_url(base_url('feedback'), PHP_URL_PATH);
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="fbModal" class="no-print" style="display:none;position:fixed;inset:0;z-index:11001;background:rgba(0,0,0,.35);">
|
<!-- 떠 있는(비차단) 의견 창 — 헤더 드래그로 이동, 우하단 모서리로 크기 조절 -->
|
||||||
<div style="position:absolute;right:16px;bottom:16px;width:min(380px,92vw);background:#fff;border-radius:12px;box-shadow:0 10px 40px rgba(0,0,0,.3);overflow:hidden;">
|
<div id="fbModal" class="no-print" style="display:none;position:fixed;inset:0;z-index:11001;pointer-events:none;">
|
||||||
<div style="display:flex;align-items:center;justify-content:space-between;padding:.7rem .9rem;background:#1a2b4b;color:#fff;">
|
<div id="fbPanel" style="position:fixed;right:16px;bottom:72px;width:380px;height:auto;min-width:300px;min-height:280px;max-width:95vw;max-height:88vh;background:#fff;border-radius:12px;box-shadow:0 10px 40px rgba(0,0,0,.3);overflow:hidden;resize:both;display:flex;flex-direction:column;pointer-events:auto;">
|
||||||
|
<div id="fbHead" style="display:flex;align-items:center;justify-content:space-between;padding:.7rem .9rem;background:#1a2b4b;color:#fff;cursor:move;flex-shrink:0;user-select:none;">
|
||||||
<span style="font-size:.85rem;font-weight:700;"><i class="fa-regular fa-comment-dots"></i> 이 화면 의견 보내기</span>
|
<span style="font-size:.85rem;font-weight:700;"><i class="fa-regular fa-comment-dots"></i> 이 화면 의견 보내기</span>
|
||||||
<button type="button" id="fbClose" style="background:transparent;border:0;color:#fff;font-size:1.1rem;cursor:pointer;">×</button>
|
<button type="button" id="fbClose" title="닫기" style="background:transparent;border:0;color:#fff;font-size:1.1rem;cursor:pointer;">×</button>
|
||||||
</div>
|
</div>
|
||||||
<div style="padding:.9rem;">
|
<div style="padding:.9rem;flex:1;overflow:auto;display:flex;flex-direction:column;min-height:0;">
|
||||||
<textarea id="fbContent" rows="5" maxlength="1000" placeholder="이 화면에서 불편하거나 바꿨으면 하는 점을 자유롭게 적어 주세요."
|
<textarea id="fbContent" maxlength="2000" placeholder="이 화면에서 불편하거나 바꿨으면 하는 점을 자유롭게 적어 주세요."
|
||||||
style="width:100%;border:1px solid #d1d5db;border-radius:8px;padding:.6rem;font-size:.85rem;resize:vertical;box-sizing:border-box;"></textarea>
|
style="width:100%;flex:1;min-height:80px;border:1px solid #d1d5db;border-radius:8px;padding:.6rem;font-size:.85rem;resize:none;box-sizing:border-box;"></textarea>
|
||||||
|
<div id="fbCount" style="text-align:right;font-size:.7rem;color:#9ca3af;margin-top:.2rem;">0/2000</div>
|
||||||
<label style="display:flex;align-items:center;gap:.4rem;margin-top:.5rem;font-size:.75rem;color:#6b7280;">
|
<label style="display:flex;align-items:center;gap:.4rem;margin-top:.5rem;font-size:.75rem;color:#6b7280;">
|
||||||
<input type="checkbox" id="fbShot" checked/> 현재 화면 캡처 첨부
|
<input type="checkbox" id="fbShot" checked/> 현재 화면 캡처 첨부
|
||||||
</label>
|
</label>
|
||||||
@@ -34,7 +36,7 @@ $apiPath = (string) parse_url(base_url('feedback'), PHP_URL_PATH);
|
|||||||
<div id="fbPreview" style="display:flex;flex-wrap:wrap;gap:.3rem;margin-top:.4rem;"></div>
|
<div id="fbPreview" style="display:flex;flex-wrap:wrap;gap:.3rem;margin-top:.4rem;"></div>
|
||||||
</div>
|
</div>
|
||||||
<div id="fbMsg" style="font-size:.75rem;margin-top:.4rem;min-height:1em;"></div>
|
<div id="fbMsg" style="font-size:.75rem;margin-top:.4rem;min-height:1em;"></div>
|
||||||
<div style="display:flex;justify-content:flex-end;gap:.4rem;margin-top:.5rem;">
|
<div style="display:flex;justify-content:flex-end;gap:.4rem;margin-top:.5rem;flex-shrink:0;">
|
||||||
<button type="button" id="fbCancel" style="border:1px solid #d1d5db;background:#fff;border-radius:8px;padding:.45rem .8rem;font-size:.8rem;cursor:pointer;">취소</button>
|
<button type="button" id="fbCancel" style="border:1px solid #d1d5db;background:#fff;border-radius:8px;padding:.45rem .8rem;font-size:.8rem;cursor:pointer;">취소</button>
|
||||||
<button type="button" id="fbSend" style="border:0;background:#243a5e;color:#fff;border-radius:8px;padding:.45rem .9rem;font-size:.8rem;font-weight:700;cursor:pointer;">보내기</button>
|
<button type="button" id="fbSend" style="border:0;background:#243a5e;color:#fff;border-radius:8px;padding:.45rem .9rem;font-size:.8rem;font-weight:700;cursor:pointer;">보내기</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -53,8 +55,12 @@ $apiPath = (string) parse_url(base_url('feedback'), PHP_URL_PATH);
|
|||||||
var shotChk = document.getElementById('fbShot');
|
var shotChk = document.getElementById('fbShot');
|
||||||
var fileInput = document.getElementById('fbFiles');
|
var fileInput = document.getElementById('fbFiles');
|
||||||
var preview = document.getElementById('fbPreview');
|
var preview = document.getElementById('fbPreview');
|
||||||
|
var counter = document.getElementById('fbCount');
|
||||||
var attachments = []; // 선택된 이미지 File 목록(최대 5)
|
var attachments = []; // 선택된 이미지 File 목록(최대 5)
|
||||||
|
|
||||||
|
function updateCount() { if (counter) counter.textContent = (content.value || '').length + '/2000'; }
|
||||||
|
content.addEventListener('input', updateCount);
|
||||||
|
|
||||||
function renderPreview() {
|
function renderPreview() {
|
||||||
preview.innerHTML = '';
|
preview.innerHTML = '';
|
||||||
attachments.forEach(function (file, i) {
|
attachments.forEach(function (file, i) {
|
||||||
@@ -80,12 +86,57 @@ $apiPath = (string) parse_url(base_url('feedback'), PHP_URL_PATH);
|
|||||||
renderPreview();
|
renderPreview();
|
||||||
});
|
});
|
||||||
|
|
||||||
function open() { modal.style.display = 'block'; msg.textContent = ''; content.focus(); }
|
function open() {
|
||||||
|
modal.style.display = 'block'; msg.textContent = '';
|
||||||
|
var panel = document.getElementById('fbPanel');
|
||||||
|
// 처음 열 때만 우하단 근처를 left/top 기준으로 배치(드래그·리사이즈 동작 일관)
|
||||||
|
if (!panel.dataset.positioned) {
|
||||||
|
var w = panel.offsetWidth || 380, h = panel.offsetHeight || 320;
|
||||||
|
panel.style.left = Math.max(8, window.innerWidth - w - 16) + 'px';
|
||||||
|
panel.style.top = Math.max(8, window.innerHeight - h - 16) + 'px';
|
||||||
|
panel.style.right = 'auto'; panel.style.bottom = 'auto';
|
||||||
|
panel.dataset.positioned = '1';
|
||||||
|
}
|
||||||
|
content.focus();
|
||||||
|
}
|
||||||
function close() { modal.style.display = 'none'; }
|
function close() { modal.style.display = 'none'; }
|
||||||
document.getElementById('fbOpen').addEventListener('click', open);
|
document.getElementById('fbOpen').addEventListener('click', open);
|
||||||
document.getElementById('fbClose').addEventListener('click', close);
|
document.getElementById('fbClose').addEventListener('click', close);
|
||||||
document.getElementById('fbCancel').addEventListener('click', close);
|
document.getElementById('fbCancel').addEventListener('click', close);
|
||||||
|
|
||||||
|
// 헤더 드래그로 창 이동 (크기 조절은 패널 우하단 모서리 resize:both 가 처리)
|
||||||
|
(function () {
|
||||||
|
var panel = document.getElementById('fbPanel');
|
||||||
|
var head = document.getElementById('fbHead');
|
||||||
|
var dragging = false, sx = 0, sy = 0, sl = 0, st = 0;
|
||||||
|
// 드래그 중 iframe(예: 지도)이 마우스를 가로채지 않도록 투명 오버레이
|
||||||
|
var ov = document.createElement('div');
|
||||||
|
ov.style.cssText = 'position:fixed;inset:0;z-index:11002;cursor:move;display:none;';
|
||||||
|
document.body.appendChild(ov);
|
||||||
|
|
||||||
|
head.addEventListener('mousedown', function (e) {
|
||||||
|
if (e.target.closest('#fbClose')) return;
|
||||||
|
e.preventDefault();
|
||||||
|
var r = panel.getBoundingClientRect();
|
||||||
|
// right/bottom 기준을 left/top 기준으로 전환(드래그 계산 일관성)
|
||||||
|
panel.style.left = r.left + 'px'; panel.style.top = r.top + 'px';
|
||||||
|
panel.style.right = 'auto'; panel.style.bottom = 'auto';
|
||||||
|
sx = e.clientX; sy = e.clientY; sl = r.left; st = r.top;
|
||||||
|
dragging = true; ov.style.display = 'block'; document.body.style.userSelect = 'none';
|
||||||
|
});
|
||||||
|
document.addEventListener('mousemove', function (e) {
|
||||||
|
if (!dragging) return;
|
||||||
|
var w = panel.offsetWidth, h = panel.offsetHeight;
|
||||||
|
var nl = Math.min(Math.max(0, sl + (e.clientX - sx)), window.innerWidth - w);
|
||||||
|
var nt = Math.min(Math.max(0, st + (e.clientY - sy)), window.innerHeight - h);
|
||||||
|
panel.style.left = nl + 'px'; panel.style.top = nt + 'px';
|
||||||
|
});
|
||||||
|
document.addEventListener('mouseup', function () {
|
||||||
|
if (!dragging) return;
|
||||||
|
dragging = false; ov.style.display = 'none'; document.body.style.userSelect = '';
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
||||||
// html2canvas 지연 로드
|
// html2canvas 지연 로드
|
||||||
function ensureH2C(cb) {
|
function ensureH2C(cb) {
|
||||||
if (typeof html2canvas === 'function') { cb(); return; }
|
if (typeof html2canvas === 'function') { cb(); return; }
|
||||||
@@ -129,7 +180,7 @@ $apiPath = (string) parse_url(base_url('feedback'), PHP_URL_PATH);
|
|||||||
sendBtn.disabled = false;
|
sendBtn.disabled = false;
|
||||||
if (d && d.ok) {
|
if (d && d.ok) {
|
||||||
msg.style.color = '#059669'; msg.textContent = '의견이 접수되었습니다. 감사합니다.';
|
msg.style.color = '#059669'; msg.textContent = '의견이 접수되었습니다. 감사합니다.';
|
||||||
content.value = '';
|
content.value = ''; updateCount();
|
||||||
attachments.length = 0; renderPreview();
|
attachments.length = 0; renderPreview();
|
||||||
setTimeout(close, 900);
|
setTimeout(close, 900);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user