Files
sam-manage/resources/views/rd/ai-quotation/edit.blade.php

404 lines
22 KiB
PHP
Raw Normal View History

@extends('layouts.app')
@section('title', '견적서 편집')
@section('content')
@php
$options = $quotation->options ?? [];
$client = $options['client'] ?? [];
$project = $options['project'] ?? [];
$pricing = $options['pricing'] ?? [];
$terms = $options['terms'] ?? [];
@endphp
<!-- 페이지 헤더 -->
<div class="flex justify-between items-center mb-6">
<h1 class="text-2xl font-bold text-gray-800 flex items-center gap-2">
<i class="ri-edit-line text-blue-600"></i>
견적서 편집
<span class="text-base font-normal text-gray-400">{{ $quotation->quote_number }}</span>
</h1>
<div class="flex gap-2">
<a href="{{ route('rd.ai-quotation.show', $quotation->id) }}" class="bg-white hover:bg-gray-100 text-gray-700 px-4 py-2 rounded-lg border transition">
<i class="ri-arrow-left-line"></i> 상세보기
</a>
</div>
</div>
<form id="editForm">
<!-- 고객 정보 -->
<div class="bg-white rounded-lg shadow-sm mb-6">
<div class="px-6 py-4 border-b border-gray-100">
<h2 class="text-lg font-semibold text-gray-800 flex items-center gap-2">
<i class="ri-user-line text-blue-600"></i> 고객 정보
</h2>
</div>
<div class="p-6">
<div class="grid gap-4" style="grid-template-columns: repeat(2, 1fr);">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">회사명</label>
<input type="text" name="client_company" value="{{ $client['company'] ?? '' }}"
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">담당자</label>
<input type="text" name="client_contact" value="{{ $client['contact'] ?? '' }}"
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">연락처</label>
<input type="text" name="client_phone" value="{{ $client['phone'] ?? '' }}"
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">이메일</label>
<input type="email" name="client_email" value="{{ $client['email'] ?? '' }}"
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm">
</div>
<div style="grid-column: span 2;">
<label class="block text-sm font-medium text-gray-700 mb-1">주소</label>
<input type="text" name="client_address" value="{{ $client['address'] ?? '' }}"
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm">
</div>
</div>
</div>
</div>
<!-- 프로젝트 정보 -->
<div class="bg-white rounded-lg shadow-sm mb-6">
<div class="px-6 py-4 border-b border-gray-100">
<h2 class="text-lg font-semibold text-gray-800 flex items-center gap-2">
<i class="ri-building-line text-green-600"></i> 프로젝트 정보
</h2>
</div>
<div class="p-6">
<div class="grid gap-4" style="grid-template-columns: repeat(2, 1fr);">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">현장명</label>
<input type="text" name="project_name" value="{{ $project['name'] ?? '' }}"
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">위치</label>
<input type="text" name="project_location" value="{{ $project['location'] ?? '' }}"
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm">
</div>
</div>
</div>
</div>
<!-- 품목 편집 -->
<div class="bg-white rounded-lg shadow-sm mb-6">
<div class="px-6 py-4 border-b border-gray-100 flex justify-between items-center">
<h2 class="text-lg font-semibold text-gray-800 flex items-center gap-2">
<i class="ri-list-check-2 text-purple-600"></i> 품목 편집
</h2>
<button type="button" onclick="addItemRow()" class="px-3 py-1.5 bg-blue-600 text-white text-sm rounded-lg hover:bg-blue-700 transition">
<i class="ri-add-line"></i> 추가
</button>
</div>
<div class="overflow-x-auto">
<table class="w-full text-sm" id="itemsTable">
<thead class="bg-gray-50 border-b">
<tr>
<th class="px-3 py-3 text-center text-xs font-medium text-gray-500" style="width: 40px;">No</th>
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500">분류</th>
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500">위치</th>
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500">품목명</th>
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500">규격</th>
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500" style="width: 60px;">단위</th>
<th class="px-3 py-3 text-center text-xs font-medium text-gray-500" style="width: 70px;">수량</th>
<th class="px-3 py-3 text-right text-xs font-medium text-gray-500" style="width: 120px;">단가</th>
<th class="px-3 py-3 text-right text-xs font-medium text-gray-500" style="width: 120px;">금액</th>
<th class="px-3 py-3 text-center text-xs font-medium text-gray-500" style="width: 40px;"></th>
</tr>
</thead>
<tbody id="itemsBody">
@foreach($quotation->items as $index => $item)
<tr class="item-row border-b border-gray-100" data-index="{{ $index }}">
<td class="px-3 py-2 text-center text-gray-500 row-number">{{ $index + 1 }}</td>
<td class="px-3 py-2">
<select name="items[{{ $index }}][item_category]" class="w-full px-2 py-1.5 border border-gray-300 rounded text-xs">
<option value="material" {{ $item->item_category === 'material' ? 'selected' : '' }}>재료비</option>
<option value="labor" {{ $item->item_category === 'labor' ? 'selected' : '' }}>노무비</option>
<option value="install" {{ $item->item_category === 'install' ? 'selected' : '' }}>설치비</option>
</select>
</td>
<td class="px-3 py-2">
<input type="text" name="items[{{ $index }}][floor_code]" value="{{ $item->floor_code }}"
class="w-full px-2 py-1.5 border border-gray-300 rounded text-xs" placeholder="B1-A01">
</td>
<td class="px-3 py-2">
<input type="text" name="items[{{ $index }}][item_name]" value="{{ $item->module_name }}"
class="w-full px-2 py-1.5 border border-gray-300 rounded text-xs" required>
</td>
<td class="px-3 py-2">
<input type="text" name="items[{{ $index }}][specification]" value="{{ $item->specification }}"
class="w-full px-2 py-1.5 border border-gray-300 rounded text-xs" placeholder="3000×2500">
</td>
<td class="px-3 py-2">
<input type="text" name="items[{{ $index }}][unit]" value="{{ $item->unit ?? 'SET' }}"
class="w-full px-2 py-1.5 border border-gray-300 rounded text-xs">
</td>
<td class="px-3 py-2">
<input type="number" name="items[{{ $index }}][quantity]" value="{{ (float)$item->quantity }}"
class="w-full px-2 py-1.5 border border-gray-300 rounded text-xs text-center item-qty"
min="0" step="1" onchange="calcRow(this)">
</td>
<td class="px-3 py-2">
<input type="text" name="items[{{ $index }}][unit_price]" value="{{ number_format((float)$item->unit_price) }}"
class="w-full px-2 py-1.5 border border-gray-300 rounded text-xs text-right money-input item-price"
inputmode="numeric" onfocus="moneyFocus(this)" onblur="moneyBlur(this)" onchange="calcRow(this)">
</td>
<td class="px-3 py-2">
<span class="item-total font-medium text-gray-800">{{ number_format((float)$item->total_price) }}</span>
</td>
<td class="px-3 py-2 text-center">
<button type="button" onclick="removeRow(this)" class="text-red-400 hover:text-red-600 transition">
<i class="ri-delete-bin-line"></i>
</button>
</td>
</tr>
@endforeach
</tbody>
<tfoot class="bg-gray-50 border-t-2">
<tr>
<td colspan="8" class="px-3 py-3 text-right font-bold text-gray-700">소계</td>
<td class="px-3 py-3 text-right font-bold text-blue-700" id="subtotalDisplay">{{ number_format((int)($pricing['subtotal'] ?? 0)) }}</td>
<td></td>
</tr>
</tfoot>
</table>
</div>
</div>
<!-- 가격 조정 + 조건 -->
<div class="grid gap-6 mb-6" style="grid-template-columns: 1fr 1fr;">
<!-- 가격 조정 -->
<div class="bg-white rounded-lg shadow-sm">
<div class="px-6 py-4 border-b border-gray-100">
<h2 class="text-lg font-semibold text-gray-800 flex items-center gap-2">
<i class="ri-calculator-line text-orange-600"></i> 가격 조정
</h2>
</div>
<div class="p-6 space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">할인율 (%)</label>
<input type="number" name="discount_rate" id="discountRate" value="{{ $pricing['discount_rate'] ?? 0 }}"
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm"
min="0" max="100" step="0.1" onchange="recalcTotal()">
</div>
<div class="space-y-2 text-sm border-t pt-4">
<div class="flex justify-between"><span class="text-gray-600">소계</span><span id="pricingSubtotal">{{ number_format((int)($pricing['subtotal'] ?? 0)) }}</span></div>
<div class="flex justify-between text-red-600"><span>할인</span><span id="pricingDiscount">-{{ number_format((int)($pricing['discount_amount'] ?? 0)) }}</span></div>
<div class="flex justify-between"><span class="text-gray-600">부가세 (10%)</span><span id="pricingVat">{{ number_format((int)($pricing['vat_amount'] ?? 0)) }}</span></div>
<div class="flex justify-between text-lg border-t pt-2">
<span class="font-bold text-blue-700">최종 금액</span>
<span class="font-bold text-blue-700" id="pricingFinal">{{ number_format((int)($pricing['final_amount'] ?? 0)) }}</span>
</div>
</div>
</div>
</div>
<!-- 조건 입력 -->
<div class="bg-white rounded-lg shadow-sm">
<div class="px-6 py-4 border-b border-gray-100">
<h2 class="text-lg font-semibold text-gray-800 flex items-center gap-2">
<i class="ri-file-list-3-line text-teal-600"></i> 견적 조건
</h2>
</div>
<div class="p-6 space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">유효기간</label>
<input type="date" name="terms_valid_until" value="{{ $terms['valid_until'] ?? now()->addDays(30)->format('Y-m-d') }}"
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">결제조건</label>
<input type="text" name="terms_payment" value="{{ $terms['payment'] ?? '계약 시 50%, 설치 완료 후 50%' }}"
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">납기조건</label>
<input type="text" name="terms_delivery" value="{{ $terms['delivery'] ?? '계약 후 4주 이내' }}"
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm">
</div>
</div>
</div>
</div>
<!-- 저장 버튼 -->
<div class="flex justify-end gap-3">
<a href="{{ route('rd.ai-quotation.show', $quotation->id) }}" class="px-6 py-2.5 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 transition">
취소
</a>
<button type="submit" id="saveBtn" class="px-6 py-2.5 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition flex items-center gap-2">
<i class="ri-save-line"></i> 저장
</button>
</div>
</form>
@endsection
@push('scripts')
<script>
let rowCounter = {{ $quotation->items->count() }};
function moneyFocus(el) { el.value = el.value.replace(/,/g, ''); }
function moneyBlur(el) {
const val = parseInt(el.value) || 0;
el.value = val.toLocaleString();
}
function parseMoneyValue(el) { return parseInt(String(el.value).replace(/,/g, '')) || 0; }
function calcRow(el) {
const row = el.closest('tr');
const qty = parseFloat(row.querySelector('.item-qty').value) || 0;
const priceEl = row.querySelector('.item-price');
const price = parseMoneyValue(priceEl);
const total = Math.round(qty * price);
row.querySelector('.item-total').textContent = total.toLocaleString();
recalcTotal();
}
function recalcTotal() {
let subtotal = 0;
document.querySelectorAll('.item-row').forEach(row => {
const text = row.querySelector('.item-total')?.textContent || '0';
subtotal += parseInt(text.replace(/,/g, '')) || 0;
});
const discountRate = parseFloat(document.getElementById('discountRate').value) || 0;
const discountAmount = Math.round(subtotal * discountRate / 100);
const afterDiscount = subtotal - discountAmount;
const vat = Math.round(afterDiscount * 0.1);
const final_amount = afterDiscount + vat;
document.getElementById('subtotalDisplay').textContent = subtotal.toLocaleString();
document.getElementById('pricingSubtotal').textContent = subtotal.toLocaleString() + '원';
document.getElementById('pricingDiscount').textContent = '-' + discountAmount.toLocaleString() + '원';
document.getElementById('pricingVat').textContent = vat.toLocaleString() + '원';
document.getElementById('pricingFinal').textContent = final_amount.toLocaleString() + '원';
}
function addItemRow() {
const idx = rowCounter++;
const tbody = document.getElementById('itemsBody');
const tr = document.createElement('tr');
tr.className = 'item-row border-b border-gray-100';
tr.dataset.index = idx;
tr.innerHTML = `
<td class="px-3 py-2 text-center text-gray-500 row-number"></td>
<td class="px-3 py-2">
<select name="items[${idx}][item_category]" class="w-full px-2 py-1.5 border border-gray-300 rounded text-xs">
<option value="material">재료비</option>
<option value="labor">노무비</option>
<option value="install">설치비</option>
</select>
</td>
<td class="px-3 py-2"><input type="text" name="items[${idx}][floor_code]" class="w-full px-2 py-1.5 border border-gray-300 rounded text-xs" placeholder="B1-A01"></td>
<td class="px-3 py-2"><input type="text" name="items[${idx}][item_name]" class="w-full px-2 py-1.5 border border-gray-300 rounded text-xs" required></td>
<td class="px-3 py-2"><input type="text" name="items[${idx}][specification]" class="w-full px-2 py-1.5 border border-gray-300 rounded text-xs" placeholder="3000×2500"></td>
<td class="px-3 py-2"><input type="text" name="items[${idx}][unit]" value="SET" class="w-full px-2 py-1.5 border border-gray-300 rounded text-xs"></td>
<td class="px-3 py-2"><input type="number" name="items[${idx}][quantity]" value="1" class="w-full px-2 py-1.5 border border-gray-300 rounded text-xs text-center item-qty" min="0" step="1" onchange="calcRow(this)"></td>
<td class="px-3 py-2"><input type="text" name="items[${idx}][unit_price]" value="0" class="w-full px-2 py-1.5 border border-gray-300 rounded text-xs text-right money-input item-price" inputmode="numeric" onfocus="moneyFocus(this)" onblur="moneyBlur(this)" onchange="calcRow(this)"></td>
<td class="px-3 py-2"><span class="item-total font-medium text-gray-800">0</span></td>
<td class="px-3 py-2 text-center"><button type="button" onclick="removeRow(this)" class="text-red-400 hover:text-red-600 transition"><i class="ri-delete-bin-line"></i></button></td>
`;
tbody.appendChild(tr);
renumberRows();
}
function removeRow(btn) {
if (document.querySelectorAll('.item-row').length <= 1) {
alert('최소 1개의 품목이 필요합니다.');
return;
}
btn.closest('tr').remove();
renumberRows();
recalcTotal();
}
function renumberRows() {
document.querySelectorAll('.item-row').forEach((row, idx) => {
row.querySelector('.row-number').textContent = idx + 1;
});
}
// 폼 제출
document.getElementById('editForm').addEventListener('submit', async function(e) {
e.preventDefault();
const btn = document.getElementById('saveBtn');
const original = btn.innerHTML;
btn.disabled = true;
btn.innerHTML = '<i class="ri-loader-4-line animate-spin"></i> 저장중...';
// 데이터 수집
const items = [];
document.querySelectorAll('.item-row').forEach(row => {
const inputs = row.querySelectorAll('input, select');
const item = {};
inputs.forEach(input => {
const name = input.name;
if (!name) return;
const key = name.replace(/items\[\d+\]\[/, '').replace(']', '');
let val = input.value;
if (key === 'unit_price') val = String(val).replace(/,/g, '');
item[key] = val;
});
items.push(item);
});
const payload = {
client: {
company: document.querySelector('[name="client_company"]').value,
contact: document.querySelector('[name="client_contact"]').value,
phone: document.querySelector('[name="client_phone"]').value,
email: document.querySelector('[name="client_email"]').value,
address: document.querySelector('[name="client_address"]').value,
},
project: {
name: document.querySelector('[name="project_name"]').value,
location: document.querySelector('[name="project_location"]').value,
},
terms: {
valid_until: document.querySelector('[name="terms_valid_until"]').value,
payment: document.querySelector('[name="terms_payment"]').value,
delivery: document.querySelector('[name="terms_delivery"]').value,
},
discount_rate: parseFloat(document.getElementById('discountRate').value) || 0,
items: items,
};
try {
const token = document.querySelector('meta[name="api-token"]')?.content
|| sessionStorage.getItem('api_token') || '';
const response = await fetch('{{ url("/api/admin/rd/ai-quotation/{$quotation->id}") }}', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.content || '',
'Authorization': token ? `Bearer ${token}` : '',
},
credentials: 'same-origin',
body: JSON.stringify(payload),
});
const result = await response.json();
if (result.success) {
window.location.href = '{{ route("rd.ai-quotation.show", $quotation->id) }}';
} else {
alert(result.message || '저장에 실패했습니다.');
}
} catch (err) {
console.error('저장 실패:', err);
alert('서버 통신 중 오류가 발생했습니다.');
} finally {
btn.disabled = false;
btn.innerHTML = original;
}
});
</script>
@endpush