- index, create, show 뷰의 fetch URL을 /api/admin/rd/... 로 수정 - api.php 라우트는 api/ prefix가 자동 적용됨
297 lines
16 KiB
PHP
297 lines
16 KiB
PHP
@extends('layouts.app')
|
|
|
|
@section('title', 'AI 견적 상세')
|
|
|
|
@section('content')
|
|
<!-- 페이지 헤더 -->
|
|
<div class="flex justify-between items-center mb-6">
|
|
<div class="flex items-center gap-3">
|
|
<h1 class="text-2xl font-bold text-gray-800 flex items-center gap-2">
|
|
<i class="ri-robot-line text-purple-600"></i>
|
|
{{ $quotation->title }}
|
|
</h1>
|
|
<span class="badge {{ $quotation->status_color }}">{{ $quotation->status_label }}</span>
|
|
</div>
|
|
<div class="flex gap-2">
|
|
<a href="{{ route('rd.ai-quotation.index') }}" 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>
|
|
@if($quotation->isCompleted() || $quotation->status === 'failed')
|
|
<button onclick="reanalyze()" id="reanalyzeBtn"
|
|
class="bg-purple-600 hover:bg-purple-700 text-white px-4 py-2 rounded-lg transition">
|
|
<i class="ri-refresh-line"></i> AI 재분석
|
|
</button>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 기본 정보 -->
|
|
<div class="bg-white rounded-lg shadow-sm p-6 mb-6">
|
|
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
|
|
<div>
|
|
<p class="text-xs text-gray-500 mb-1">AI Provider</p>
|
|
<p class="font-medium text-gray-800">{{ strtoupper($quotation->ai_provider) }}{{ $quotation->ai_model ? ' ('.$quotation->ai_model.')' : '' }}</p>
|
|
</div>
|
|
<div>
|
|
<p class="text-xs text-gray-500 mb-1">입력 유형</p>
|
|
<p class="font-medium text-gray-800">{{ ['text' => '텍스트', 'voice' => '음성', 'document' => '문서'][$quotation->input_type] ?? $quotation->input_type }}</p>
|
|
</div>
|
|
<div>
|
|
<p class="text-xs text-gray-500 mb-1">요청자</p>
|
|
<p class="font-medium text-gray-800">{{ $quotation->creator?->name ?? '-' }}</p>
|
|
</div>
|
|
<div>
|
|
<p class="text-xs text-gray-500 mb-1">생성일</p>
|
|
<p class="font-medium text-gray-800">{{ $quotation->created_at->format('Y-m-d H:i') }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
@if($quotation->isCompleted())
|
|
<!-- 업무 분석 결과 -->
|
|
@if($quotation->analysis_result)
|
|
@php $analysis = $quotation->analysis_result; @endphp
|
|
<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-search-eye-line text-blue-600"></i> AI 업무 분석 결과
|
|
</h2>
|
|
</div>
|
|
<div class="p-6">
|
|
<!-- 기업 분석 -->
|
|
@if(isset($analysis['company_analysis']))
|
|
@php $company = $analysis['company_analysis']; @endphp
|
|
<div class="flex flex-wrap gap-4 mb-6 pb-6 border-b border-gray-100">
|
|
<div class="px-4 py-2 bg-blue-50 rounded-lg">
|
|
<span class="text-xs text-blue-500 block">업종</span>
|
|
<span class="font-semibold text-blue-800">{{ $company['industry'] ?? '-' }}</span>
|
|
</div>
|
|
<div class="px-4 py-2 bg-green-50 rounded-lg">
|
|
<span class="text-xs text-green-500 block">규모</span>
|
|
<span class="font-semibold text-green-800">{{ $company['scale'] ?? '-' }}</span>
|
|
</div>
|
|
<div class="px-4 py-2 bg-purple-50 rounded-lg">
|
|
<span class="text-xs text-purple-500 block">디지털화 수준</span>
|
|
<span class="font-semibold text-purple-800">{{ $company['digitalization_level'] ?? '-' }}</span>
|
|
</div>
|
|
@if(!empty($company['current_systems']))
|
|
<div class="px-4 py-2 bg-gray-50 rounded-lg">
|
|
<span class="text-xs text-gray-500 block">현재 시스템</span>
|
|
<span class="font-semibold text-gray-800">{{ implode(', ', $company['current_systems']) }}</span>
|
|
</div>
|
|
@endif
|
|
</div>
|
|
@endif
|
|
|
|
<!-- 업무 영역별 분석 -->
|
|
@if(!empty($analysis['business_domains']))
|
|
<h3 class="text-sm font-semibold text-gray-600 uppercase mb-3">업무 영역 분석</h3>
|
|
<div class="space-y-4">
|
|
@foreach($analysis['business_domains'] as $domain)
|
|
<div class="border border-gray-200 rounded-lg p-4">
|
|
<div class="flex items-center justify-between mb-2">
|
|
<h4 class="font-semibold text-gray-800">{{ $domain['domain'] ?? '' }}</h4>
|
|
@php
|
|
$priorityColor = match($domain['priority'] ?? '') {
|
|
'필수' => 'bg-red-100 text-red-700',
|
|
'높음' => 'bg-orange-100 text-orange-700',
|
|
'보통' => 'bg-yellow-100 text-yellow-700',
|
|
default => 'bg-gray-100 text-gray-700',
|
|
};
|
|
@endphp
|
|
<span class="px-2 py-0.5 text-xs rounded-full {{ $priorityColor }}">{{ $domain['priority'] ?? '' }}</span>
|
|
</div>
|
|
<p class="text-sm text-gray-600 mb-2">{{ $domain['current_process'] ?? '' }}</p>
|
|
@if(!empty($domain['pain_points']))
|
|
<div class="flex flex-wrap gap-1.5">
|
|
@foreach($domain['pain_points'] as $point)
|
|
<span class="px-2 py-0.5 bg-red-50 text-red-600 text-xs rounded">{{ $point }}</span>
|
|
@endforeach
|
|
</div>
|
|
@endif
|
|
@if(!empty($domain['matched_modules']))
|
|
<div class="flex flex-wrap gap-1.5 mt-2">
|
|
@foreach($domain['matched_modules'] as $mod)
|
|
<span class="px-2 py-0.5 bg-purple-50 text-purple-600 text-xs rounded font-mono">{{ $mod }}</span>
|
|
@endforeach
|
|
</div>
|
|
@endif
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
@endif
|
|
|
|
<!-- 추천 모듈 + 견적 -->
|
|
<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-price-tag-3-line text-green-600"></i> 추천 모듈 및 견적
|
|
</h2>
|
|
</div>
|
|
<div class="overflow-x-auto">
|
|
<table class="w-full text-sm">
|
|
<thead class="bg-gray-50 border-b">
|
|
<tr>
|
|
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">구분</th>
|
|
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">모듈</th>
|
|
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">추천 근거</th>
|
|
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase">개발비</th>
|
|
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase">월 구독료</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="divide-y divide-gray-50">
|
|
@foreach($quotation->items as $item)
|
|
<tr class="hover:bg-gray-50">
|
|
<td class="px-4 py-3">
|
|
@if($item->is_required)
|
|
<span class="px-2 py-0.5 bg-red-100 text-red-700 text-xs rounded-full font-medium">필수</span>
|
|
@else
|
|
<span class="px-2 py-0.5 bg-gray-100 text-gray-600 text-xs rounded-full">선택</span>
|
|
@endif
|
|
</td>
|
|
<td class="px-4 py-3">
|
|
<div class="font-medium text-gray-800">{{ $item->module_name }}</div>
|
|
<div class="text-xs text-gray-400 font-mono">{{ $item->module_code }}</div>
|
|
</td>
|
|
<td class="px-4 py-3 text-gray-600 text-xs" style="max-width: 300px;">
|
|
{{ Str::limit($item->reason, 100) }}
|
|
</td>
|
|
<td class="px-4 py-3 text-right font-medium text-gray-800">{{ number_format((int)$item->dev_cost) }}원</td>
|
|
<td class="px-4 py-3 text-right font-medium text-gray-800">{{ number_format((int)$item->monthly_fee) }}원</td>
|
|
</tr>
|
|
@endforeach
|
|
</tbody>
|
|
<tfoot class="bg-purple-50 border-t-2 border-purple-200">
|
|
<tr>
|
|
<td colspan="3" class="px-4 py-3 text-right font-bold text-gray-800">합계</td>
|
|
<td class="px-4 py-3 text-right font-bold text-purple-700 text-base">{{ number_format((int)$quotation->total_dev_cost) }}원</td>
|
|
<td class="px-4 py-3 text-right font-bold text-purple-700 text-base">{{ number_format((int)$quotation->total_monthly_fee) }}원/월</td>
|
|
</tr>
|
|
</tfoot>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 구현 계획 (AI 생성) -->
|
|
@if(!empty($quotation->quotation_result['implementation_plan']))
|
|
@php $plan = $quotation->quotation_result['implementation_plan']; @endphp
|
|
<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-calendar-schedule-line text-indigo-600"></i> 구현 계획 (AI 추천)
|
|
</h2>
|
|
</div>
|
|
<div class="p-6">
|
|
<p class="text-sm text-gray-600 mb-4">예상 기간: <span class="font-semibold">{{ $plan['estimated_months'] ?? '?' }}개월</span></p>
|
|
@if(!empty($plan['phases']))
|
|
<div class="space-y-3">
|
|
@foreach($plan['phases'] as $phase)
|
|
<div class="flex items-center gap-4 p-3 bg-gray-50 rounded-lg">
|
|
<div class="w-10 h-10 bg-indigo-100 text-indigo-700 rounded-full flex items-center justify-center font-bold shrink-0">
|
|
{{ $phase['phase'] ?? '' }}
|
|
</div>
|
|
<div>
|
|
<p class="font-medium text-gray-800">{{ $phase['name'] ?? '' }}</p>
|
|
<div class="flex items-center gap-2 mt-1">
|
|
<span class="text-xs text-gray-500">{{ $phase['duration_weeks'] ?? '?' }}주</span>
|
|
@if(!empty($phase['modules']))
|
|
<span class="text-xs text-gray-400">|</span>
|
|
@foreach($phase['modules'] as $mod)
|
|
<span class="px-1.5 py-0.5 bg-indigo-50 text-indigo-600 text-xs rounded font-mono">{{ $mod }}</span>
|
|
@endforeach
|
|
@endif
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
@endif
|
|
|
|
<!-- 분석 요약 -->
|
|
@if(!empty($quotation->quotation_result['analysis_summary']))
|
|
<div class="bg-gradient-to-r from-purple-50 to-indigo-50 rounded-lg p-6 mb-6">
|
|
<h3 class="font-semibold text-gray-800 mb-2 flex items-center gap-2">
|
|
<i class="ri-lightbulb-line text-yellow-500"></i> AI 분석 요약
|
|
</h3>
|
|
<p class="text-gray-700">{{ $quotation->quotation_result['analysis_summary'] }}</p>
|
|
</div>
|
|
@endif
|
|
|
|
@elseif($quotation->status === 'failed')
|
|
<!-- 실패 상태 -->
|
|
<div class="bg-red-50 border border-red-200 rounded-lg p-6 mb-6">
|
|
<div class="flex items-center gap-3 mb-2">
|
|
<i class="ri-error-warning-line text-2xl text-red-500"></i>
|
|
<h2 class="text-lg font-semibold text-red-800">AI 분석 실패</h2>
|
|
</div>
|
|
<p class="text-red-600">AI 분석 중 오류가 발생했습니다. 다시 시도하거나 입력 내용을 수정해 주세요.</p>
|
|
</div>
|
|
|
|
@elseif($quotation->isProcessing())
|
|
<!-- 분석중 상태 -->
|
|
<div class="bg-blue-50 border border-blue-200 rounded-lg p-6 mb-6 text-center">
|
|
<i class="ri-loader-4-line text-4xl text-blue-500 animate-spin mb-2 block"></i>
|
|
<h2 class="text-lg font-semibold text-blue-800">AI 분석 진행중...</h2>
|
|
<p class="text-blue-600 text-sm mt-1">분석이 완료되면 자동으로 결과가 표시됩니다.</p>
|
|
</div>
|
|
@endif
|
|
|
|
<!-- 입력 원문 (접이식) -->
|
|
<details class="bg-white rounded-lg shadow-sm mb-6">
|
|
<summary class="px-6 py-4 cursor-pointer hover:bg-gray-50 transition font-semibold text-gray-700">
|
|
<i class="ri-file-text-line"></i> 인터뷰 원문 보기
|
|
</summary>
|
|
<div class="px-6 pb-6">
|
|
<div class="bg-gray-50 rounded-lg p-4 text-sm text-gray-700 whitespace-pre-wrap">{{ $quotation->input_text }}</div>
|
|
</div>
|
|
</details>
|
|
@endsection
|
|
|
|
@push('scripts')
|
|
<script>
|
|
async function reanalyze() {
|
|
if (!confirm('AI 분석을 다시 실행하시겠습니까? 기존 결과가 덮어씌워집니다.')) return;
|
|
|
|
const btn = document.getElementById('reanalyzeBtn');
|
|
const original = btn.innerHTML;
|
|
btn.disabled = true;
|
|
btn.innerHTML = '<i class="ri-loader-4-line animate-spin"></i> 재분석중...';
|
|
|
|
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}/analyze") }}', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Accept': 'application/json',
|
|
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.content || '',
|
|
'Authorization': token ? `Bearer ${token}` : '',
|
|
},
|
|
credentials: 'same-origin',
|
|
});
|
|
|
|
const result = await response.json();
|
|
|
|
if (result.success) {
|
|
location.reload();
|
|
} else {
|
|
alert(result.message || '재분석에 실패했습니다.');
|
|
location.reload();
|
|
}
|
|
} catch (err) {
|
|
alert('서버 통신 중 오류가 발생했습니다.');
|
|
} finally {
|
|
btn.disabled = false;
|
|
btn.innerHTML = original;
|
|
}
|
|
}
|
|
</script>
|
|
@endpush
|