Files
sam-manage/resources/views/bending/base/form.blade.php

573 lines
33 KiB
PHP

@extends('layouts.app')
@section('title', ($mode === 'create' ? '기초자료 등록' : ($mode === 'edit' ? '기초자료 수정' : '기초자료 상세')))
@section('content')
@php
$opt = is_array($item) ? $item : ($item?->options ?? []);
$isView = $mode === 'view';
$isCreate = $mode === 'create';
$bendingData = $opt['bendingData'] ?? [];
$itemId = is_array($item) ? ($item['id'] ?? null) : $item?->id;
$itemCode = is_array($item) ? ($item['code'] ?? '') : ($itemCode ?? '');
$itemName = is_array($item) ? ($item['name'] ?? '') : ($itemName ?? '');
$itemBending = $opt['item_bending'] ?? '';
$isCase = in_array($itemBending, ['케이스']);
@endphp
<div class="container-fluid px-4 py-3">
{{-- 헤더 --}}
<div class="flex items-center justify-between mb-4">
<div class="flex items-center gap-3">
<a href="{{ route('bending.base.index') }}" class="text-gray-500 hover:text-gray-700">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/></svg>
</a>
<h1 class="text-xl font-bold text-gray-800">
{{ $mode === 'create' ? '기초자료 등록' : ($mode === 'edit' ? '기초자료 수정' : '기초자료 상세') }}
@if($item) <span class="text-sm font-normal text-gray-500 ml-2">{{ $itemCode }}</span> @endif
</h1>
</div>
<div class="flex gap-2">
@if(!$isCreate)
<button type="button" onclick="showHistory()" class="px-3 py-2 bg-gray-500 text-white rounded-lg hover:bg-gray-600 text-sm" title="수정 이력">H</button>
@endif
@if($isView)
<a href="{{ route('bending.base.edit', $itemId) }}" class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 text-sm">수정</a>
@endif
@if(!$isCreate)
<form method="POST" action="{{ route('bending.base.destroy', $itemId) }}" onsubmit="return confirm('삭제하시겠습니까?')">
@csrf @method('DELETE')
<button type="submit" class="px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 text-sm">삭제</button>
</form>
@endif
</div>
</div>
@if(session('success'))
<script>
document.addEventListener('DOMContentLoaded', () => {
showToast("{{ session('success') }}", 'success');
});
</script>
@endif
@if($errors->any())
<div class="mb-4 p-3 bg-red-50 border border-red-200 rounded-lg text-sm text-red-700">
<ul class="list-disc list-inside">
@foreach($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<form method="POST" action="{{ $isCreate ? route('bending.base.store') : route('bending.base.update', $itemId) }}" id="bendingForm" enctype="multipart/form-data">
@csrf
@if(!$isCreate) @method('PUT') @endif
<div class="flex gap-4" style="align-items: flex-start;">
{{-- : 기본정보 + 케이스 전용 + 절곡 테이블 --}}
<div style="flex: 1 1 0; min-width: 0;">
{{-- 기본 정보 --}}
<div class="bg-white rounded-lg shadow p-4 mb-4">
<h2 class="text-sm font-bold text-gray-700 mb-3 border-b pb-2">기본 정보</h2>
<div class="grid grid-cols-4 gap-3">
<div>
<label class="block text-xs text-gray-500 mb-1">코드 <span class="text-red-500">*</span></label>
@if($isCreate)
{{-- 등록: 분류코드 선택 순번 자동 채번 --}}
<select name="code" required class="w-full border border-gray-300 rounded px-3 py-1.5 text-sm">
<option value="">분류코드 선택</option>
@foreach([
'RS' => '가이드레일 SUS마감재', 'RM' => '가이드레일 본체/보강', 'RC' => '가이드레일 C형',
'RD' => '가이드레일 D형', 'RE' => '가이드레일 측면마감', 'RT' => '가이드레일 절단판',
'RH' => '가이드레일 뒷보강', 'RN' => '가이드레일 비인정',
'CP' => '케이스 밑면판/점검구', 'CF' => '케이스 전면판', 'CB' => '케이스 후면코너/후면부',
'CL' => '케이스 린텔', 'CX' => '케이스 상부덮개',
'BS' => '하단마감재 SUS', 'BE' => '하단마감재 EGI', 'BH' => '하단마감재 보강평철',
'TS' => '철재 하단마감재 SUS', 'TE' => '철재 하단마감재 EGI',
'XE' => '마구리', 'LE' => 'L-BAR',
'ZP' => '특수 밑면/점검구', 'ZF' => '특수 전면판', 'ZB' => '특수 후면',
] as $prefix => $label)
<option value="BD-{{ $prefix }}" {{ old('code') === "BD-{$prefix}" ? 'selected' : '' }}>BD-{{ $prefix }} ({{ $label }})</option>
@endforeach
</select>
<p class="text-xs text-gray-400 mt-0.5">순번은 저장 자동 채번</p>
@elseif($isView)
{{-- 조회: 읽기전용 --}}
<input type="text" value="{{ $itemCode }}" disabled
class="w-full border border-gray-300 rounded px-3 py-1.5 text-sm bg-gray-100 font-mono">
@else
{{-- 수정: 편집 가능 (중복 검사 포함) --}}
<input type="text" name="code" value="{{ old('code', $itemCode) }}" required
class="w-full border border-gray-300 rounded px-3 py-1.5 text-sm font-mono">
@endif
</div>
<div>
<label class="block text-xs text-gray-500 mb-1">이름 <span class="text-red-500">*</span></label>
<input type="text" name="name" value="{{ old('name', $itemName) }}" {{ $isView ? 'disabled' : 'required' }}
class="w-full border border-gray-300 rounded px-3 py-1.5 text-sm {{ $isView ? 'bg-gray-100' : '' }}">
</div>
<div>
<label class="block text-xs text-gray-500 mb-1">품명 <span class="text-red-500">*</span></label>
<input type="text" name="item_name" value="{{ old('item_name', $opt['item_name'] ?? '') }}" {{ $isView ? 'disabled' : 'required' }}
class="w-full border border-gray-300 rounded px-3 py-1.5 text-sm {{ $isView ? 'bg-gray-100' : '' }}">
</div>
<div>
<label class="block text-xs text-gray-500 mb-1">대분류 <span class="text-red-500">*</span></label>
<select name="item_sep" {{ $isView ? 'disabled' : 'required' }}
class="w-full border border-gray-300 rounded px-3 py-1.5 text-sm {{ $isView ? 'bg-gray-100' : '' }}">
<option value="">선택</option>
<option value="스크린" {{ ($opt['item_sep'] ?? '') === '스크린' ? 'selected' : '' }}>스크린</option>
<option value="철재" {{ ($opt['item_sep'] ?? '') === '철재' ? 'selected' : '' }}>철재</option>
</select>
</div>
<div>
<label class="block text-xs text-gray-500 mb-1">분류 <span class="text-red-500">*</span></label>
<input type="text" name="item_bending" id="itemBendingInput" value="{{ old('item_bending', $itemBending) }}" {{ $isView ? 'disabled' : 'required' }}
class="w-full border border-gray-300 rounded px-3 py-1.5 text-sm {{ $isView ? 'bg-gray-100' : '' }}"
list="item_bending_list" onchange="toggleCaseFields()">
<datalist id="item_bending_list">
<option value="가이드레일"><option value="케이스"><option value="하단마감재">
<option value="마구리"><option value="L-BAR"><option value="보강평철"><option value="연기차단재">
</datalist>
</div>
<div>
<label class="block text-xs text-gray-500 mb-1">재질 <span class="text-red-500">*</span></label>
<input type="text" name="material" value="{{ old('material', $opt['material'] ?? '') }}" {{ $isView ? 'disabled' : 'required' }}
class="w-full border border-gray-300 rounded px-3 py-1.5 text-sm {{ $isView ? 'bg-gray-100' : '' }}"
list="material_list">
<datalist id="material_list">
<option value="SUS 1.2T"><option value="SUS 1.5T"><option value="EGI 1.55T"><option value="EGI 1.15T"><option value="화이바원단">
</datalist>
</div>
<div>
<label class="block text-xs text-gray-500 mb-1">규격</label>
<input type="text" name="item_spec" value="{{ old('item_spec', $opt['item_spec'] ?? '') }}" {{ $isView ? 'disabled' : '' }}
class="w-full border border-gray-300 rounded px-3 py-1.5 text-sm {{ $isView ? 'bg-gray-100' : '' }}" placeholder="120*70">
</div>
<div>
<label class="block text-xs text-gray-500 mb-1">모델</label>
<input type="text" name="model_name" value="{{ old('model_name', $opt['model_name'] ?? '') }}" {{ $isView ? 'disabled' : '' }}
class="w-full border border-gray-300 rounded px-3 py-1.5 text-sm {{ $isView ? 'bg-gray-100' : '' }}"
list="model_list">
<datalist id="model_list">
<option value="KSS01"><option value="KSS02"><option value="KSE01">
<option value="KWE01"><option value="KTE01"><option value="KQTS01"><option value="KDSS01">
</datalist>
</div>
<div>
<label class="block text-xs text-gray-500 mb-1">인정여부</label>
<select name="model_UA" {{ $isView ? 'disabled' : '' }}
class="w-full border border-gray-300 rounded px-3 py-1.5 text-sm {{ $isView ? 'bg-gray-100' : '' }}">
<option value="">선택</option>
<option value="인정" {{ ($opt['model_UA'] ?? '') === '인정' ? 'selected' : '' }}>인정</option>
<option value="비인정" {{ ($opt['model_UA'] ?? '') === '비인정' ? 'selected' : '' }}>비인정</option>
</select>
</div>
<div>
<label class="block text-xs text-gray-500 mb-1">등록일</label>
<input type="date" name="registration_date" value="{{ old('registration_date', $opt['registration_date'] ?? date('Y-m-d')) }}" {{ $isView ? 'disabled' : '' }}
class="w-full border border-gray-300 rounded px-3 py-1.5 text-sm {{ $isView ? 'bg-gray-100' : '' }}">
</div>
<div>
<label class="block text-xs text-gray-500 mb-1">작성자</label>
<input type="text" name="author" value="{{ old('author', $opt['author'] ?? Auth::user()?->name ?? '') }}" {{ $isView ? 'disabled' : '' }}
class="w-full border border-gray-300 rounded px-3 py-1.5 text-sm {{ $isView ? 'bg-gray-100' : '' }}">
</div>
<div>
<label class="block text-xs text-gray-500 mb-1">검색어</label>
<input type="text" name="search_keyword" value="{{ old('search_keyword', $opt['search_keyword'] ?? '') }}" {{ $isView ? 'disabled' : '' }}
class="w-full border border-gray-300 rounded px-3 py-1.5 text-sm {{ $isView ? 'bg-gray-100' : '' }}">
</div>
</div>
</div>
{{-- 케이스 전용 필드 --}}
<div id="caseFields" class="bg-white rounded-lg shadow p-4 mb-4 {{ $isCase ? '' : 'hidden' }}">
<h2 class="text-sm font-bold text-gray-700 mb-3 border-b pb-2">케이스 전용</h2>
<div class="grid grid-cols-5 gap-3">
<div>
<label class="block text-xs text-gray-500 mb-1">점검구 방향</label>
<select name="exit_direction" {{ $isView ? 'disabled' : '' }}
class="w-full border border-gray-300 rounded px-3 py-1.5 text-sm {{ $isView ? 'bg-gray-100' : '' }}">
<option value="">선택</option>
@foreach(['양면', '밑면', '후면 점검구'] as $dir)
<option value="{{ $dir }}" {{ ($opt['exit_direction'] ?? '') === $dir ? 'selected' : '' }}>{{ $dir }}</option>
@endforeach
</select>
</div>
<div>
<label class="block text-xs text-gray-500 mb-1">전면밑 (mm)</label>
<input type="number" name="front_bottom_width" value="{{ old('front_bottom_width', $opt['front_bottom_width'] ?? '') }}" {{ $isView ? 'disabled' : '' }}
class="w-full border border-gray-300 rounded px-3 py-1.5 text-sm {{ $isView ? 'bg-gray-100' : '' }}">
</div>
<div>
<label class="block text-xs text-gray-500 mb-1">레일폭 (mm)</label>
<input type="number" name="rail_width" value="{{ old('rail_width', $opt['rail_width'] ?? '') }}" {{ $isView ? 'disabled' : '' }}
class="w-full border border-gray-300 rounded px-3 py-1.5 text-sm {{ $isView ? 'bg-gray-100' : '' }}">
</div>
<div>
<label class="block text-xs text-gray-500 mb-1">케이스 너비 (mm)</label>
<input type="number" name="box_width" value="{{ old('box_width', $opt['box_width'] ?? '') }}" {{ $isView ? 'disabled' : '' }}
class="w-full border border-gray-300 rounded px-3 py-1.5 text-sm {{ $isView ? 'bg-gray-100' : '' }}">
</div>
<div>
<label class="block text-xs text-gray-500 mb-1">케이스 높이 (mm)</label>
<input type="number" name="box_height" value="{{ old('box_height', $opt['box_height'] ?? '') }}" {{ $isView ? 'disabled' : '' }}
class="w-full border border-gray-300 rounded px-3 py-1.5 text-sm {{ $isView ? 'bg-gray-100' : '' }}">
</div>
</div>
</div>
{{-- 절곡 입력 테이블 --}}
<div class="bg-white rounded-lg shadow p-4 mb-4">
<div class="flex items-center justify-between mb-3 border-b pb-2">
<h2 class="text-sm font-bold text-gray-700">절곡 입력 테이블</h2>
@if(!$isView)
<div class="flex gap-2">
<button type="button" onclick="addColumn()" class="px-3 py-1 bg-green-600 text-white rounded text-xs"> 추가</button>
<button type="button" onclick="removeColumn()" class="px-3 py-1 bg-red-600 text-white rounded text-xs"> 삭제</button>
<button type="button" onclick="clearTable()" class="px-3 py-1 bg-gray-600 text-white rounded text-xs">비우기</button>
</div>
@endif
</div>
<div class="overflow-x-auto">
<table class="text-xs border-collapse w-full" id="bendTable">
<thead>
<tr class="bg-gray-100" id="headerRow">
<th class="border px-2 py-1 text-gray-600" style="min-width:55px;">구분</th>
</tr>
</thead>
<tbody>
<tr id="inputRow">
<td class="border px-2 py-1 bg-gray-50 font-medium">입력</td>
</tr>
<tr id="rateRow">
<td class="border px-2 py-1 bg-gray-50 font-medium">연신율</td>
</tr>
<tr id="adjustedRow">
<td class="border px-2 py-1 bg-gray-50 font-medium">연신율후</td>
</tr>
<tr id="sumRow">
<td class="border px-2 py-1 bg-yellow-50 font-medium">합계</td>
</tr>
<tr id="colorRow">
<td class="border px-2 py-1 bg-gray-50 font-medium">음영</td>
</tr>
<tr id="angleRow">
<td class="border px-2 py-1 bg-gray-50 font-medium">A각</td>
</tr>
</tbody>
</table>
</div>
<div class="mt-2 text-sm text-gray-600">
폭합계: <span id="widthSumDisplay" class="font-bold text-blue-700">0</span>
| 절곡횟수: <span id="bendCountDisplay" class="font-bold text-blue-700">0</span>
</div>
</div>
</div>
{{-- : 이미지 + 메모 --}}
<div class="shrink-0" style="width: 280px; min-width: 200px; max-width: 320px;">
<div class="bg-white rounded-lg shadow p-4 mb-4">
<h2 class="text-sm font-bold text-gray-700 mb-3 border-b pb-2">형상 이미지</h2>
<div class="border-2 border-dashed border-gray-300 rounded-lg p-3 text-center min-h-[200px] flex items-center justify-center" id="imageContainer">
@if(!empty($imageFile))
<img src="{{ $item['image_url'] ?? route('files.view', $imageFile['id']) }}" alt="전개도" class="max-w-full rounded" id="currentImage" data-proxy-url="{{ route('files.proxy', $imageFile['id']) }}">
@else
<span class="text-gray-400 text-sm" id="noImageText">이미지 없음</span>
@endif
</div>
@if(!$isView)
<div class="flex gap-1 mt-2">
<input type="file" name="image" accept="image/*" onchange="previewImage(this)" class="text-xs flex-1 min-w-0">
<button type="button" onclick="openCanvasEditor()" class="px-2 py-1 bg-indigo-600 text-white rounded text-xs hover:bg-indigo-700 whitespace-nowrap">
<i class="ri-edit-line"></i> 그리기
</button>
</div>
<img id="image-preview" class="hidden max-w-full rounded mt-2">
<input type="hidden" name="canvas_image" id="canvasImageData">
<p class="text-xs text-gray-400 mt-1">Ctrl+V로 붙여넣기 가능</p>
@endif
</div>
<div class="bg-white rounded-lg shadow p-4">
<h2 class="text-sm font-bold text-gray-700 mb-3 border-b pb-2">비고</h2>
<textarea name="memo" rows="4" {{ $isView ? 'disabled' : '' }}
class="w-full border border-gray-300 rounded px-3 py-1.5 text-sm {{ $isView ? 'bg-gray-100' : '' }}">{{ old('memo', $opt['memo'] ?? '') }}</textarea>
</div>
@if(!$isView)
<div class="mt-4">
<button type="submit" class="w-full px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 text-sm font-medium">
{{ $isCreate ? '등록' : '저장' }}
</button>
</div>
@endif
</div>
</div>
{{-- 절곡 데이터 hidden --}}
<input type="hidden" name="modified_by" value="{{ Auth::user()?->name ?? '' }}">
<input type="hidden" name="bendingData" id="bendingDataInput">
</form>
@if(!$isView)
@include('components.canvas-editor')
@endif
{{-- 이력 모달 --}}
@if(!$isCreate)
<dialog id="historyDialog" class="rounded-lg shadow-xl p-0 backdrop:bg-black/50" style="max-width:500px; border:none;">
<div class="p-4">
<div class="flex items-center justify-between mb-3">
<h3 class="font-bold text-gray-800">수정 이력</h3>
<button type="button" onclick="document.getElementById('historyDialog').close()" class="text-gray-400 hover:text-gray-600 text-xl">&times;</button>
</div>
<table class="w-full text-sm">
<tbody>
<tr class="border-b"><td class="py-2 text-gray-500 pr-4">코드</td><td class="py-2 font-mono">{{ $itemCode }}</td></tr>
<tr class="border-b"><td class="py-2 text-gray-500 pr-4">등록일</td><td class="py-2">{{ $opt['registration_date'] ?? '-' }}</td></tr>
<tr class="border-b"><td class="py-2 text-gray-500 pr-4">작성자</td><td class="py-2">{{ $opt['author'] ?? '-' }}</td></tr>
<tr class="border-b"><td class="py-2 text-gray-500 pr-4">최종수정자</td><td class="py-2">{{ $opt['modified_by'] ?? '-' }}</td></tr>
<tr class="border-b"><td class="py-2 text-gray-500 pr-4">현재 수정자</td><td class="py-2 text-blue-600 font-medium">{{ Auth::user()?->name ?? '-' }}</td></tr>
<tr class="border-b"><td class="py-2 text-gray-500 pr-4">생성일시</td><td class="py-2">{{ is_array($item) ? ($item['created_at'] ?? '-') : '-' }}</td></tr>
<tr class="border-b"><td class="py-2 text-gray-500 pr-4">수정일시</td><td class="py-2">{{ is_array($item) ? ($item['updated_at'] ?? '-') : '-' }}</td></tr>
@if(!empty($opt['change_log']))
<tr><td colspan="2" class="py-2">
<div class="text-gray-500 mb-1">변경 기록</div>
<div class="bg-gray-50 rounded p-2 text-xs max-h-[200px] overflow-y-auto">
@foreach(array_reverse($opt['change_log']) as $log)
<div class="mb-1 pb-1 border-b border-gray-200 last:border-0">
<span class="text-gray-400">{{ $log['date'] ?? '' }}</span>
<span class="text-gray-700">{{ $log['memo'] ?? '' }}</span>
</div>
@endforeach
</div>
</td></tr>
@endif
</tbody>
</table>
</div>
</dialog>
@endif
</div>
@push('scripts')
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/5.3.0/fabric.min.js"></script>
<script src="{{ asset('js/canvas-editor.js') }}"></script>
<script>
const isView = {{ $isView ? 'true' : 'false' }};
let bendingData = @json($bendingData ?: []);
document.addEventListener('DOMContentLoaded', () => {
// 절곡품 폼에서 "다른이름 저장"으로 넘어온 경우 데이터 자동 로드
if (!isView && new URLSearchParams(location.search).get('from') === 'product') {
try {
const saved = sessionStorage.getItem('newPartData');
if (saved) {
const data = JSON.parse(saved);
sessionStorage.removeItem('newPartData');
if (data.item_name) { const el = document.querySelector('input[name="item_name"]'); if (el) el.value = data.item_name; }
if (data.material) { const el = document.querySelector('input[name="material"]'); if (el) el.value = data.material; }
if (data.bendingData?.length) { bendingData = data.bendingData; }
}
} catch(e) { console.error('newPartData load error:', e); }
}
if (bendingData.length > 0) {
bendingData.forEach(col => addColumnUI(col));
} else if (!isView) {
for (let i = 0; i < 7; i++) addColumnUI(null);
}
recalcAll();
toggleCaseFields();
});
function toggleCaseFields() {
const val = document.getElementById('itemBendingInput')?.value ?? '';
const caseDiv = document.getElementById('caseFields');
if (caseDiv) caseDiv.classList.toggle('hidden', val !== '케이스');
}
function addColumnUI(data) {
const idx = document.querySelectorAll('#headerRow th').length;
const dis = isView ? 'disabled' : '';
document.getElementById('headerRow').insertAdjacentHTML('beforeend',
`<th class="border px-1 py-1 text-center min-w-[50px]">${idx}</th>`);
document.getElementById('inputRow').insertAdjacentHTML('beforeend',
`<td class="border px-1 py-1"><input type="number" class="w-full text-center border-0 bg-transparent bend-input" data-col="${idx}" value="${data?.input ?? ''}" ${dis} step="any" oninput="recalcAll()"></td>`);
document.getElementById('rateRow').insertAdjacentHTML('beforeend',
`<td class="border px-1 py-1"><input type="number" class="w-full text-center border-0 bg-transparent bend-rate" step="1" data-col="${idx}" value="${data?.rate ?? ''}" ${dis} placeholder="" oninput="recalcAll()"></td>`);
document.getElementById('adjustedRow').insertAdjacentHTML('beforeend',
`<td class="border px-1 py-1 text-center bend-adjusted" data-col="${idx}">-</td>`);
document.getElementById('sumRow').insertAdjacentHTML('beforeend',
`<td class="border px-1 py-1 text-center font-bold bg-yellow-50 bend-sum" data-col="${idx}">-</td>`);
document.getElementById('colorRow').insertAdjacentHTML('beforeend',
`<td class="border px-1 py-1 text-center"><input type="checkbox" class="bend-color" data-col="${idx}" ${data?.color ? 'checked' : ''} ${dis}></td>`);
document.getElementById('angleRow').insertAdjacentHTML('beforeend',
`<td class="border px-1 py-1 text-center"><input type="checkbox" class="bend-angle" data-col="${idx}" ${data?.aAngle ? 'checked' : ''} ${dis}></td>`);
}
function addColumn() { addColumnUI(null); }
function removeColumn() {
const cols = document.querySelectorAll('#headerRow th').length;
if (cols <= 1) return;
['headerRow','inputRow','rateRow','adjustedRow','sumRow','colorRow','angleRow'].forEach(id => {
document.getElementById(id).lastElementChild.remove();
});
recalcAll();
}
function clearTable() {
document.querySelectorAll('.bend-input').forEach(el => el.value = '');
document.querySelectorAll('.bend-rate').forEach(el => el.value = '');
document.querySelectorAll('.bend-color').forEach(el => el.checked = false);
document.querySelectorAll('.bend-angle').forEach(el => el.checked = false);
recalcAll();
}
function recalcAll() {
const inputs = document.querySelectorAll('.bend-input');
let cumSum = 0;
let bendCount = 0;
inputs.forEach((inp, i) => {
const val = parseFloat(inp.value) || 0;
const rateEl = document.querySelectorAll('.bend-rate')[i];
const rate = rateEl?.value?.trim() ?? '';
let adjusted = val;
if (rate === '-1') { adjusted = val - 1; bendCount++; }
else if (rate === '1') { adjusted = val + 1; bendCount++; }
else if (rate !== '' && rate !== '0') { bendCount++; }
const adjEl = document.querySelectorAll('.bend-adjusted')[i];
if (adjEl) adjEl.textContent = val ? adjusted + (rate ? ` (${rate})` : '') : '-';
cumSum += adjusted;
const sumEl = document.querySelectorAll('.bend-sum')[i];
if (sumEl) sumEl.textContent = val ? cumSum : '-';
});
document.getElementById('widthSumDisplay').textContent = cumSum || 0;
document.getElementById('bendCountDisplay').textContent = bendCount;
serializeBendingData();
}
function serializeBendingData() {
const inputs = document.querySelectorAll('.bend-input');
const data = [];
let cumSum = 0;
inputs.forEach((inp, i) => {
const val = parseFloat(inp.value) || 0;
const rate = document.querySelectorAll('.bend-rate')[i]?.value?.trim() ?? '';
let adjusted = val;
if (rate === '-1') adjusted = val - 1;
else if (rate === '1') adjusted = val + 1;
cumSum += adjusted;
data.push({
no: i + 1,
input: val,
rate: rate,
sum: cumSum,
color: document.querySelectorAll('.bend-color')[i]?.checked ?? false,
aAngle: document.querySelectorAll('.bend-angle')[i]?.checked ?? false,
});
});
document.getElementById('bendingDataInput').value = JSON.stringify(data);
}
// 이미지 미리보기
function previewImage(input) {
const file = input.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = e => {
const preview = document.getElementById('image-preview');
preview.src = e.target.result;
preview.classList.remove('hidden');
};
reader.readAsDataURL(file);
}
// Ctrl+V 클립보드 이미지 붙여넣기
document.addEventListener('paste', function(e) {
if (isView) return;
const items = e.clipboardData?.items;
if (!items) return;
for (const item of items) {
if (item.type.startsWith('image/')) {
const file = item.getAsFile();
const dt = new DataTransfer();
dt.items.add(file);
const input = document.querySelector('input[name="image"]');
if (input) {
input.files = dt.files;
previewImage(input);
}
break;
}
}
});
document.getElementById('bendingForm')?.addEventListener('submit', () => serializeBendingData());
// 이력 보기
function showHistory() {
const dialog = document.getElementById('historyDialog');
if (dialog) dialog.showModal();
}
// Canvas Editor
function openCanvasEditor() {
// 현재 이미지 src 가져오기 (미리보기 > 기존 이미지 > 없음)
const preview = document.getElementById('image-preview');
const current = document.getElementById('currentImage');
let imgSrc = null;
if (preview && !preview.classList.contains('hidden') && preview.src) {
imgSrc = preview.src;
} else if (current) {
// Canvas에서는 프록시 URL 사용 (R2 직접 URL은 CORS 차단됨)
imgSrc = current.dataset.proxyUrl || current.src;
}
CanvasEditor.open(imgSrc)
.then(dataURL => {
// hidden input에 Base64 저장 (폼 submit 시 전송)
document.getElementById('canvasImageData').value = dataURL;
// 파일 input 초기화 (canvas 이미지가 우선)
const fileInput = document.querySelector('input[name="image"]');
if (fileInput) fileInput.value = '';
// image-preview 숨기기 (중복 방지)
const previewEl = document.getElementById('image-preview');
if (previewEl) previewEl.classList.add('hidden');
// 기존 이미지 컨테이너에 바로 교체 표시
const container = document.getElementById('imageContainer');
const noText = document.getElementById('noImageText');
if (noText) noText.remove();
let img = document.getElementById('currentImage');
if (!img) {
img = document.createElement('img');
img.id = 'currentImage';
img.alt = '전개도';
img.className = 'max-w-full rounded';
container.appendChild(img);
}
img.src = dataURL;
})
.catch(() => {});
}
</script>
@endpush
@endsection