- 서비스: copyTemplatesToCycles 메서드 추가 (중복 항목 스킵) - 컨트롤러: copyTemplates API 엔드포인트 추가 - UI: 다른 주기에 복사 버튼 + 체크박스 모달
280 lines
15 KiB
PHP
280 lines
15 KiB
PHP
<!-- 점검항목 탭 -->
|
|
<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>
|
|
<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
|
|
$allCycles = \App\Enums\InspectionCycle::all();
|
|
$templatesByCycle = $equipment->inspectionTemplates->groupBy('inspection_cycle');
|
|
@endphp
|
|
|
|
@if($equipment->inspectionTemplates->isNotEmpty())
|
|
<!-- 주기별 탭 -->
|
|
<div class="flex gap-1 mb-4 bg-gray-50 rounded-lg p-1" style="width: fit-content;">
|
|
@foreach($allCycles as $code => $label)
|
|
@php $count = isset($templatesByCycle[$code]) ? $templatesByCycle[$code]->count() : 0; @endphp
|
|
<button type="button"
|
|
class="tmpl-cycle-tab px-3 py-1.5 rounded-md text-xs font-medium transition {{ $loop->first ? 'bg-white text-blue-700 shadow-sm' : 'text-gray-500 hover:text-gray-700' }}"
|
|
data-cycle="{{ $code }}"
|
|
onclick="switchTemplateTab('{{ $code }}')">
|
|
{{ $label }}
|
|
@if($count > 0)
|
|
<span class="ml-1 inline-flex items-center justify-center w-4 h-4 text-[10px] font-bold bg-blue-100 text-blue-700 rounded-full">{{ $count }}</span>
|
|
@endif
|
|
</button>
|
|
@endforeach
|
|
</div>
|
|
|
|
<!-- 주기별 테이블 -->
|
|
@foreach($allCycles as $code => $label)
|
|
<div class="tmpl-cycle-content {{ $loop->first ? '' : 'hidden' }}" data-cycle="{{ $code }}">
|
|
@if(isset($templatesByCycle[$code]) && $templatesByCycle[$code]->isNotEmpty())
|
|
<div class="overflow-x-auto">
|
|
<table class="min-w-full divide-y divide-gray-200">
|
|
<thead class="bg-gray-50">
|
|
<tr>
|
|
<th class="px-3 py-2 text-center text-sm font-semibold text-gray-700">번호</th>
|
|
<th class="px-3 py-2 text-left text-sm font-semibold text-gray-700">점검개소</th>
|
|
<th class="px-3 py-2 text-left text-sm font-semibold text-gray-700">점검항목</th>
|
|
<th class="px-3 py-2 text-center text-sm font-semibold text-gray-700">시기</th>
|
|
<th class="px-3 py-2 text-center text-sm font-semibold text-gray-700">주기</th>
|
|
<th class="px-3 py-2 text-left text-sm font-semibold text-gray-700">점검방법</th>
|
|
<th class="px-3 py-2 text-center text-sm font-semibold text-gray-700">액션</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="divide-y divide-gray-200">
|
|
@foreach($templatesByCycle[$code] as $tmpl)
|
|
<tr>
|
|
<td class="px-3 py-2 text-sm text-center">{{ $tmpl->item_no }}</td>
|
|
<td class="px-3 py-2 text-sm">{{ $tmpl->check_point }}</td>
|
|
<td class="px-3 py-2 text-sm">{{ $tmpl->check_item }}</td>
|
|
<td class="px-3 py-2 text-sm text-center">{{ $tmpl->timing_label }}</td>
|
|
<td class="px-3 py-2 text-sm text-center">{{ $tmpl->check_frequency ?? '-' }}</td>
|
|
<td class="px-3 py-2 text-sm text-gray-600">{{ $tmpl->check_method ?? '-' }}</td>
|
|
<td class="px-3 py-2 text-sm text-center">
|
|
<button onclick="deleteTemplate({{ $tmpl->id }})" class="text-red-600 hover:text-red-900">삭제</button>
|
|
</td>
|
|
</tr>
|
|
@endforeach
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
@else
|
|
<p class="text-gray-400 text-center py-4 text-sm">{{ $label }} 점검항목이 없습니다.</p>
|
|
@endif
|
|
</div>
|
|
@endforeach
|
|
@else
|
|
<p class="text-gray-500 text-center py-8">등록된 점검항목이 없습니다.</p>
|
|
@endif
|
|
</div>
|
|
|
|
<!-- 점검항목 추가 모달 -->
|
|
<div id="templateModal" class="fixed inset-0 z-50 hidden">
|
|
<div class="fixed inset-0 bg-black/50" onclick="document.getElementById('templateModal').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-lg">
|
|
<div class="flex justify-between items-center p-6 border-b">
|
|
<h3 class="text-lg font-semibold">점검항목 추가</h3>
|
|
<button onclick="document.getElementById('templateModal').classList.add('hidden')" class="text-gray-400 hover:text-gray-600">×</button>
|
|
</div>
|
|
<form id="templateForm" class="p-6 space-y-4">
|
|
<div class="grid grid-cols-2 gap-4">
|
|
<div>
|
|
<label class="block text-sm font-semibold text-gray-700 mb-1">점검주기 <span class="text-red-500">*</span></label>
|
|
<select name="inspection_cycle" id="tmplInspectionCycle"
|
|
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
|
|
@foreach($allCycles as $code => $label)
|
|
<option value="{{ $code }}">{{ $label }}</option>
|
|
@endforeach
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-semibold text-gray-700 mb-1">항목번호 <span class="text-red-500">*</span></label>
|
|
<input type="number" name="item_no" required min="1"
|
|
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
|
|
</div>
|
|
</div>
|
|
<div class="grid grid-cols-2 gap-4">
|
|
<div>
|
|
<label class="block text-sm font-semibold text-gray-700 mb-1">점검개소 <span class="text-red-500">*</span></label>
|
|
<input type="text" name="check_point" required placeholder="겉모양"
|
|
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-semibold text-gray-700 mb-1">점검항목 <span class="text-red-500">*</span></label>
|
|
<input type="text" name="check_item" required placeholder="청결상태"
|
|
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
|
|
</div>
|
|
</div>
|
|
<div class="grid grid-cols-2 gap-4">
|
|
<div>
|
|
<label class="block text-sm font-semibold text-gray-700 mb-1">시기</label>
|
|
<select name="check_timing"
|
|
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
|
|
<option value="">선택</option>
|
|
<option value="operating">가동 중</option>
|
|
<option value="stopped">정지 시</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-semibold text-gray-700 mb-1">주기</label>
|
|
<input type="text" name="check_frequency" placeholder="1회/일"
|
|
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-semibold text-gray-700 mb-1">점검방법</label>
|
|
<textarea name="check_method" rows="2" placeholder="육안 확인"
|
|
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"></textarea>
|
|
</div>
|
|
<div class="flex justify-end gap-3 pt-2">
|
|
<button type="button" onclick="document.getElementById('templateModal').classList.add('hidden')"
|
|
class="bg-gray-200 hover:bg-gray-300 text-gray-800 px-4 py-2 rounded-lg">취소</button>
|
|
<button type="button" onclick="addTemplate()"
|
|
class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg">추가</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</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">×</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';
|
|
} else {
|
|
btn.className = 'tmpl-cycle-tab px-3 py-1.5 rounded-md text-xs font-medium transition text-gray-500 hover:text-gray-700';
|
|
}
|
|
});
|
|
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>
|