585 lines
34 KiB
PHP
585 lines
34 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)
|
|
<button type="button" onclick="duplicateItem()" class="px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 text-sm">복사</button>
|
|
<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">×</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 duplicateItem() {
|
|
if (!confirm('이 절곡품을 복사하시겠습니까?\n같은 분류의 다음 번호가 자동 채번됩니다.')) return;
|
|
const form = document.createElement('form');
|
|
form.method = 'POST';
|
|
form.action = '{{ !$isCreate ? route("bending.base.duplicate", $itemId) : "" }}';
|
|
form.innerHTML = '@csrf';
|
|
document.body.appendChild(form);
|
|
form.submit();
|
|
}
|
|
|
|
// 이미지 미리보기
|
|
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
|