feat: [equipment] 점검항목 다른 주기로 복사 기능 추가

- 서비스: copyTemplatesToCycles 메서드 추가 (중복 항목 스킵)
- 컨트롤러: copyTemplates API 엔드포인트 추가
- UI: 다른 주기에 복사 버튼 + 체크박스 모달
This commit is contained in:
김보곤
2026-02-28 14:17:18 +09:00
parent 090275e133
commit 60aef7992b
4 changed files with 208 additions and 5 deletions

View File

@@ -2,10 +2,17 @@
<div class="bg-white rounded-lg shadow-sm p-6 mb-6">
<div class="flex justify-between items-center mb-4 pb-2 border-b">
<h2 class="text-lg font-semibold text-gray-800">점검항목 템플릿</h2>
<button onclick="document.getElementById('templateModal').classList.remove('hidden')"
class="bg-blue-600 hover:bg-blue-700 text-white px-3 py-1.5 rounded-lg text-sm transition">
+ 항목 추가
</button>
<div class="flex gap-2">
<button onclick="openCopyModal()"
class="bg-green-600 hover:bg-green-700 text-white px-3 py-1.5 rounded-lg text-sm transition"
id="copyTemplateBtn" style="display: none;">
다른 주기에 복사
</button>
<button onclick="document.getElementById('templateModal').classList.remove('hidden')"
class="bg-blue-600 hover:bg-blue-700 text-white px-3 py-1.5 rounded-lg text-sm transition">
+ 항목 추가
</button>
</div>
</div>
@php
@@ -144,8 +151,45 @@ class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg">추가</bu
</div>
</div>
<!-- 다른 주기에 복사 모달 -->
<div id="copyTemplateModal" class="fixed inset-0 z-50 hidden">
<div class="fixed inset-0 bg-black/50" onclick="document.getElementById('copyTemplateModal').classList.add('hidden')"></div>
<div class="fixed inset-0 flex items-center justify-center p-4">
<div class="bg-white rounded-xl shadow-xl w-full max-w-md">
<div class="flex justify-between items-center p-6 border-b">
<h3 class="text-lg font-semibold">점검항목 복사</h3>
<button onclick="document.getElementById('copyTemplateModal').classList.add('hidden')" class="text-gray-400 hover:text-gray-600">&times;</button>
</div>
<div class="p-6">
<p class="text-sm text-gray-600 mb-4">
<span id="copySourceLabel" class="font-semibold text-blue-700"></span> 점검항목을 선택한 주기에 복사합니다.
</p>
<div class="space-y-2 mb-6" id="copyTargetCycles">
@foreach($allCycles as $code => $label)
<label class="copy-cycle-option flex items-center gap-3 p-2 rounded-lg hover:bg-gray-50 cursor-pointer" data-cycle="{{ $code }}">
<input type="checkbox" name="target_cycles[]" value="{{ $code }}"
class="w-4 h-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500">
<span class="text-sm text-gray-700">{{ $label }}</span>
<span class="copy-cycle-count text-xs text-gray-400 ml-auto"></span>
</label>
@endforeach
</div>
<div class="flex justify-end gap-3">
<button type="button" onclick="document.getElementById('copyTemplateModal').classList.add('hidden')"
class="bg-gray-200 hover:bg-gray-300 text-gray-800 px-4 py-2 rounded-lg text-sm">취소</button>
<button type="button" onclick="executeCopyTemplates()"
class="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded-lg text-sm" id="copyExecuteBtn">복사</button>
</div>
</div>
</div>
</div>
</div>
<script>
let currentTemplateTab = 'daily';
function switchTemplateTab(cycle) {
currentTemplateTab = cycle;
document.querySelectorAll('.tmpl-cycle-tab').forEach(btn => {
if (btn.dataset.cycle === cycle) {
btn.className = 'tmpl-cycle-tab px-3 py-1.5 rounded-md text-xs font-medium transition bg-white text-blue-700 shadow-sm';
@@ -156,8 +200,80 @@ function switchTemplateTab(cycle) {
document.querySelectorAll('.tmpl-cycle-content').forEach(el => {
el.classList.toggle('hidden', el.dataset.cycle !== cycle);
});
// 모달의 주기 기본값도 전환
const cycleSelect = document.getElementById('tmplInspectionCycle');
if (cycleSelect) cycleSelect.value = cycle;
updateCopyButton();
}
function updateCopyButton() {
const btn = document.getElementById('copyTemplateBtn');
const content = document.querySelector(`.tmpl-cycle-content[data-cycle="${currentTemplateTab}"]`);
const hasItems = content && content.querySelector('table');
btn.style.display = hasItems ? '' : 'none';
}
function openCopyModal() {
const cycleLabels = @json($allCycles);
document.getElementById('copySourceLabel').textContent = cycleLabels[currentTemplateTab];
document.querySelectorAll('.copy-cycle-option').forEach(el => {
const code = el.dataset.cycle;
const checkbox = el.querySelector('input[type="checkbox"]');
if (code === currentTemplateTab) {
el.style.display = 'none';
checkbox.checked = false;
} else {
el.style.display = '';
checkbox.checked = false;
const countEl = el.querySelector('.copy-cycle-count');
const tab = document.querySelector(`.tmpl-cycle-tab[data-cycle="${code}"]`);
const badge = tab ? tab.querySelector('span') : null;
countEl.textContent = badge ? `${badge.textContent.trim()}개 항목` : '';
}
});
document.getElementById('copyTemplateModal').classList.remove('hidden');
}
function executeCopyTemplates() {
const checked = document.querySelectorAll('#copyTargetCycles input[type="checkbox"]:checked');
if (checked.length === 0) {
alert('복사할 주기를 선택해주세요.');
return;
}
const targetCycles = Array.from(checked).map(cb => cb.value);
const equipmentId = @json($equipment->id);
const btn = document.getElementById('copyExecuteBtn');
btn.disabled = true;
btn.textContent = '복사 중...';
fetch(`/api/admin/equipment/${equipmentId}/templates/copy`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
},
body: JSON.stringify({
source_cycle: currentTemplateTab,
target_cycles: targetCycles,
}),
})
.then(res => res.json())
.then(data => {
if (data.success) {
alert(data.message);
location.reload();
} else {
alert(data.message || '복사 실패');
}
})
.catch(() => alert('오류가 발생했습니다.'))
.finally(() => {
btn.disabled = false;
btn.textContent = '복사';
});
}
document.addEventListener('DOMContentLoaded', () => updateCopyButton());
</script>