feat:영업파트너 관리 상세/수정 모달 구현
- 목록에서 상세/수정 버튼 클릭 시 모달로 표시 - 모달 열림 시 배경 스크롤 방지 - ESC 키로 모달 닫기 지원 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -117,6 +117,38 @@ public function show(int $id): View
|
||||
return view('sales.managers.show', compact('partner', 'level', 'children', 'delegationCandidates'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 상세 모달용
|
||||
*/
|
||||
public function modalShow(int $id): View
|
||||
{
|
||||
$partner = User::with(['parent', 'children', 'userRoles.role', 'salesDocuments', 'approver'])
|
||||
->findOrFail($id);
|
||||
|
||||
$level = $this->service->getPartnerLevel($partner);
|
||||
|
||||
$children = User::where('parent_id', $partner->id)
|
||||
->with('userRoles.role')
|
||||
->get();
|
||||
|
||||
return view('sales.managers.partials.show-modal', compact('partner', 'level', 'children'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 수정 모달용
|
||||
*/
|
||||
public function modalEdit(int $id): View
|
||||
{
|
||||
$partner = User::with(['userRoles.role', 'salesDocuments', 'parent'])->findOrFail($id);
|
||||
$roles = $this->service->getSalesRoles();
|
||||
$currentRoleIds = $partner->userRoles->pluck('role_id')->toArray();
|
||||
$documentTypes = SalesManagerDocument::DOCUMENT_TYPES;
|
||||
|
||||
return view('sales.managers.partials.edit-modal', compact(
|
||||
'partner', 'roles', 'currentRoleIds', 'documentTypes'
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* 수정 폼
|
||||
*/
|
||||
|
||||
@@ -151,8 +151,8 @@ class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none foc
|
||||
{{ $partner->created_at->format('Y-m-d') }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
||||
<a href="{{ route('sales.managers.show', $partner->id) }}" class="text-blue-600 hover:text-blue-900 mr-3">상세</a>
|
||||
<a href="{{ route('sales.managers.edit', $partner->id) }}" class="text-indigo-600 hover:text-indigo-900 mr-3">수정</a>
|
||||
<button type="button" onclick="openShowModal({{ $partner->id }})" class="text-blue-600 hover:text-blue-900 mr-3">상세</button>
|
||||
<button type="button" onclick="openEditModal({{ $partner->id }})" class="text-indigo-600 hover:text-indigo-900 mr-3">수정</button>
|
||||
@if($partner->isPendingApproval())
|
||||
<form action="{{ route('sales.managers.approve', $partner->id) }}" method="POST" class="inline"
|
||||
onsubmit="return confirm('승인하시겠습니까?')">
|
||||
@@ -187,4 +187,123 @@ class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none foc
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 파트너 모달 -->
|
||||
<div id="partnerModal" class="hidden fixed inset-0 z-50">
|
||||
<!-- 배경 오버레이 -->
|
||||
<div class="absolute inset-0 bg-black bg-opacity-50" onclick="closePartnerModal()"></div>
|
||||
<!-- 모달 컨텐츠 -->
|
||||
<div class="absolute inset-0 flex items-center justify-center p-4 pointer-events-none">
|
||||
<div id="partnerModalContent" class="bg-white rounded-xl shadow-2xl w-full max-w-3xl pointer-events-auto">
|
||||
<!-- 로딩 표시 -->
|
||||
<div id="modalLoading" class="p-12 text-center">
|
||||
<svg class="w-8 h-8 animate-spin text-blue-600 mx-auto" fill="none" viewBox="0 0 24 24">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
<p class="mt-2 text-gray-500">로딩 중...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
// 상세 모달 열기
|
||||
function openShowModal(id) {
|
||||
const modal = document.getElementById('partnerModal');
|
||||
const content = document.getElementById('partnerModalContent');
|
||||
|
||||
// 모달 표시
|
||||
modal.classList.remove('hidden');
|
||||
document.body.style.overflow = 'hidden'; // 배경 스크롤 방지
|
||||
|
||||
// 로딩 표시
|
||||
content.innerHTML = `
|
||||
<div id="modalLoading" class="p-12 text-center">
|
||||
<svg class="w-8 h-8 animate-spin text-blue-600 mx-auto" fill="none" viewBox="0 0 24 24">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
<p class="mt-2 text-gray-500">로딩 중...</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// AJAX로 상세 정보 가져오기
|
||||
fetch(`/sales/managers/${id}/modal-show`, {
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
'Accept': 'text/html'
|
||||
}
|
||||
})
|
||||
.then(response => response.text())
|
||||
.then(html => {
|
||||
content.innerHTML = html;
|
||||
})
|
||||
.catch(error => {
|
||||
content.innerHTML = `
|
||||
<div class="p-6 text-center">
|
||||
<p class="text-red-500">오류가 발생했습니다.</p>
|
||||
<button onclick="closePartnerModal()" class="mt-4 px-4 py-2 bg-gray-600 text-white rounded-lg">닫기</button>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
}
|
||||
|
||||
// 수정 모달 열기
|
||||
function openEditModal(id) {
|
||||
const modal = document.getElementById('partnerModal');
|
||||
const content = document.getElementById('partnerModalContent');
|
||||
|
||||
// 모달 표시
|
||||
modal.classList.remove('hidden');
|
||||
document.body.style.overflow = 'hidden'; // 배경 스크롤 방지
|
||||
|
||||
// 로딩 표시
|
||||
content.innerHTML = `
|
||||
<div id="modalLoading" class="p-12 text-center">
|
||||
<svg class="w-8 h-8 animate-spin text-blue-600 mx-auto" fill="none" viewBox="0 0 24 24">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
<p class="mt-2 text-gray-500">로딩 중...</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// AJAX로 수정 폼 가져오기
|
||||
fetch(`/sales/managers/${id}/modal-edit`, {
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
'Accept': 'text/html'
|
||||
}
|
||||
})
|
||||
.then(response => response.text())
|
||||
.then(html => {
|
||||
content.innerHTML = html;
|
||||
})
|
||||
.catch(error => {
|
||||
content.innerHTML = `
|
||||
<div class="p-6 text-center">
|
||||
<p class="text-red-500">오류가 발생했습니다.</p>
|
||||
<button onclick="closePartnerModal()" class="mt-4 px-4 py-2 bg-gray-600 text-white rounded-lg">닫기</button>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
}
|
||||
|
||||
// 모달 닫기
|
||||
function closePartnerModal() {
|
||||
const modal = document.getElementById('partnerModal');
|
||||
modal.classList.add('hidden');
|
||||
document.body.style.overflow = ''; // 배경 스크롤 복원
|
||||
}
|
||||
|
||||
// ESC 키로 모달 닫기
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape') {
|
||||
closePartnerModal();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
|
||||
193
resources/views/sales/managers/partials/edit-modal.blade.php
Normal file
193
resources/views/sales/managers/partials/edit-modal.blade.php
Normal file
@@ -0,0 +1,193 @@
|
||||
{{-- 영업파트너 수정 모달 내용 --}}
|
||||
<div class="p-6 max-h-[80vh] overflow-y-auto">
|
||||
<!-- 헤더 -->
|
||||
<div class="flex justify-between items-start mb-6">
|
||||
<div>
|
||||
<h2 class="text-xl font-bold text-gray-800">영업파트너 수정</h2>
|
||||
<p class="text-sm text-gray-500 mt-1">{{ $partner->name }} ({{ $partner->email }})</p>
|
||||
</div>
|
||||
<button type="button" onclick="closePartnerModal()" class="text-gray-400 hover:text-gray-600">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 폼 -->
|
||||
<form action="{{ route('sales.managers.update', $partner->id) }}" method="POST" enctype="multipart/form-data">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
|
||||
<!-- 기본 정보 -->
|
||||
<div class="bg-gray-50 rounded-lg p-4 mb-4">
|
||||
<h3 class="text-sm font-semibold text-gray-800 mb-3">기본 정보</h3>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-700 mb-1">로그인 ID</label>
|
||||
<input type="text" value="{{ $partner->user_id ?? $partner->email }}" disabled
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg bg-gray-100 text-gray-500 text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-700 mb-1">이름 <span class="text-red-500">*</span></label>
|
||||
<input type="text" name="name" value="{{ $partner->name }}" required
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-700 mb-1">이메일 <span class="text-red-500">*</span></label>
|
||||
<input type="email" name="email" value="{{ $partner->email }}" required
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-700 mb-1">전화번호</label>
|
||||
<input type="text" name="phone" value="{{ $partner->phone }}"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-700 mb-1">비밀번호</label>
|
||||
<input type="password" name="password" placeholder="변경 시에만 입력"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-700 mb-1">비밀번호 확인</label>
|
||||
<input type="password" name="password_confirmation" placeholder="변경 시에만 입력"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 text-sm">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 역할 -->
|
||||
<div class="bg-gray-50 rounded-lg p-4 mb-4">
|
||||
<h3 class="text-sm font-semibold text-gray-800 mb-3">역할 <span class="text-red-500">*</span></h3>
|
||||
<div class="flex flex-wrap gap-4">
|
||||
@foreach($roles as $role)
|
||||
<label class="flex items-center">
|
||||
<input type="checkbox" name="role_ids[]" value="{{ $role->id }}"
|
||||
{{ in_array($role->id, $currentRoleIds) ? 'checked' : '' }}
|
||||
class="w-4 h-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500">
|
||||
<span class="ml-2 text-sm text-gray-700">
|
||||
{{ $role->description ?? $role->name }}
|
||||
</span>
|
||||
</label>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 추천인 정보 -->
|
||||
@if($partner->parent)
|
||||
<div class="bg-gray-50 rounded-lg p-4 mb-4">
|
||||
<h3 class="text-sm font-semibold text-gray-800 mb-2">추천인(유치자)</h3>
|
||||
<div class="px-3 py-2 bg-white border border-gray-200 rounded-lg text-sm text-gray-700">
|
||||
{{ $partner->parent->name }} ({{ $partner->parent->email }})
|
||||
</div>
|
||||
<p class="mt-1 text-xs text-gray-500">추천인은 변경할 수 없습니다.</p>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- 기존 첨부 서류 -->
|
||||
@if($partner->salesDocuments->isNotEmpty())
|
||||
<div class="bg-gray-50 rounded-lg p-4 mb-4">
|
||||
<h3 class="text-sm font-semibold text-gray-800 mb-3">기존 첨부 서류</h3>
|
||||
<div class="space-y-2">
|
||||
@foreach($partner->salesDocuments as $document)
|
||||
<div class="flex items-center justify-between p-2 bg-white rounded border">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="px-2 py-0.5 text-xs bg-gray-100 text-gray-600 rounded">{{ $document->document_type_label }}</span>
|
||||
<span class="text-sm text-gray-700">{{ $document->original_name }}</span>
|
||||
<span class="text-xs text-gray-400">{{ $document->formatted_size }}</span>
|
||||
</div>
|
||||
<button type="button" onclick="deleteDocument({{ $partner->id }}, {{ $document->id }})"
|
||||
class="text-red-500 hover:text-red-700">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- 새 첨부 서류 -->
|
||||
<div class="bg-gray-50 rounded-lg p-4 mb-4">
|
||||
<h3 class="text-sm font-semibold text-gray-800 mb-3">새 서류 추가</h3>
|
||||
<div id="modal-document-list" class="space-y-3">
|
||||
<div class="flex items-center gap-3">
|
||||
<select name="documents[0][document_type]"
|
||||
class="w-32 px-2 py-1.5 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
@foreach($documentTypes as $value => $label)
|
||||
<option value="{{ $value }}">{{ $label }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<input type="file" name="documents[0][file]" accept="image/*,.pdf,.doc,.docx"
|
||||
class="flex-1 text-sm text-gray-500 file:mr-2 file:py-1.5 file:px-3 file:rounded-lg file:border-0 file:text-sm file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100">
|
||||
<input type="text" name="documents[0][description]" placeholder="설명(선택)"
|
||||
class="w-32 px-2 py-1.5 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" onclick="addDocumentRow()"
|
||||
class="mt-3 text-sm text-blue-600 hover:text-blue-800 flex items-center gap-1">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
|
||||
</svg>
|
||||
서류 추가
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 푸터 버튼 -->
|
||||
<div class="flex justify-end gap-3">
|
||||
<button type="button" onclick="closePartnerModal()"
|
||||
class="px-4 py-2 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 transition text-sm">
|
||||
취소
|
||||
</button>
|
||||
<button type="submit"
|
||||
class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition text-sm">
|
||||
수정
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let modalDocIndex = 1;
|
||||
|
||||
function addDocumentRow() {
|
||||
const container = document.getElementById('modal-document-list');
|
||||
const html = `
|
||||
<div class="flex items-center gap-3">
|
||||
<select name="documents[${modalDocIndex}][document_type]"
|
||||
class="w-32 px-2 py-1.5 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
@foreach($documentTypes as $value => $label)
|
||||
<option value="{{ $value }}">{{ $label }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<input type="file" name="documents[${modalDocIndex}][file]" accept="image/*,.pdf,.doc,.docx"
|
||||
class="flex-1 text-sm text-gray-500 file:mr-2 file:py-1.5 file:px-3 file:rounded-lg file:border-0 file:text-sm file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100">
|
||||
<input type="text" name="documents[${modalDocIndex}][description]" placeholder="설명(선택)"
|
||||
class="w-32 px-2 py-1.5 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
<button type="button" onclick="this.parentElement.remove()" class="text-red-500 hover:text-red-700">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
container.insertAdjacentHTML('beforeend', html);
|
||||
modalDocIndex++;
|
||||
}
|
||||
|
||||
function deleteDocument(partnerId, documentId) {
|
||||
if (!confirm('이 서류를 삭제하시겠습니까?')) return;
|
||||
|
||||
fetch(`/sales/managers/${partnerId}/documents/${documentId}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
}).then(response => {
|
||||
if (response.ok) {
|
||||
openEditModal(partnerId); // 모달 새로고침
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
290
resources/views/sales/managers/partials/show-modal.blade.php
Normal file
290
resources/views/sales/managers/partials/show-modal.blade.php
Normal file
@@ -0,0 +1,290 @@
|
||||
{{-- 영업파트너 상세 모달 내용 --}}
|
||||
<div class="p-6 max-h-[80vh] overflow-y-auto">
|
||||
<!-- 헤더 -->
|
||||
<div class="flex justify-between items-start mb-6">
|
||||
<div>
|
||||
<h2 class="text-xl font-bold text-gray-800">{{ $partner->name }}</h2>
|
||||
<p class="text-sm text-gray-500 mt-1">레벨 {{ $level }} 영업파트너</p>
|
||||
<div class="flex items-center gap-2 mt-2">
|
||||
@foreach($partner->userRoles as $userRole)
|
||||
@php
|
||||
$roleColor = match($userRole->role->name ?? '') {
|
||||
'sales' => 'bg-blue-100 text-blue-800',
|
||||
'manager' => 'bg-purple-100 text-purple-800',
|
||||
'recruiter' => 'bg-green-100 text-green-800',
|
||||
default => 'bg-gray-100 text-gray-800',
|
||||
};
|
||||
$roleLabel = match($userRole->role->name ?? '') {
|
||||
'sales' => '영업',
|
||||
'manager' => '매니저',
|
||||
'recruiter' => '유치담당',
|
||||
default => $userRole->role->name ?? '-',
|
||||
};
|
||||
@endphp
|
||||
<span class="px-2 py-1 text-xs font-medium rounded-full {{ $roleColor }}">
|
||||
{{ $roleLabel }}
|
||||
</span>
|
||||
@endforeach
|
||||
<span class="px-2 py-1 text-xs font-medium rounded-full {{ $partner->approval_status_color }}">
|
||||
{{ $partner->approval_status_label }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" onclick="closePartnerModal()" class="text-gray-400 hover:text-gray-600">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 승인 대기 알림 -->
|
||||
@if($partner->isPendingApproval())
|
||||
<div class="mb-6 bg-yellow-50 border border-yellow-200 rounded-lg p-4">
|
||||
<div class="flex items-start gap-3">
|
||||
<svg class="w-5 h-5 text-yellow-500 flex-shrink-0 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
<div class="flex-1">
|
||||
<h3 class="font-semibold text-yellow-800 text-sm">승인 대기 중</h3>
|
||||
<p class="text-xs text-yellow-700 mt-1">첨부된 서류를 확인 후 승인 또는 반려해주세요.</p>
|
||||
<div class="mt-3 flex gap-2">
|
||||
<form action="{{ route('sales.managers.approve', $partner->id) }}" method="POST" class="inline">
|
||||
@csrf
|
||||
<button type="submit" onclick="return confirm('승인하시겠습니까?')"
|
||||
class="px-3 py-1.5 bg-green-600 text-white rounded-lg hover:bg-green-700 transition text-xs">
|
||||
승인
|
||||
</button>
|
||||
</form>
|
||||
<button type="button" onclick="showRejectForm()"
|
||||
class="px-3 py-1.5 bg-red-600 text-white rounded-lg hover:bg-red-700 transition text-xs">
|
||||
반려
|
||||
</button>
|
||||
</div>
|
||||
<!-- 반려 폼 (숨김) -->
|
||||
<form id="rejectForm" action="{{ route('sales.managers.reject', $partner->id) }}" method="POST" class="hidden mt-3">
|
||||
@csrf
|
||||
<textarea name="rejection_reason" rows="2" required
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-red-500"
|
||||
placeholder="반려 사유를 입력해주세요."></textarea>
|
||||
<div class="mt-2 flex gap-2">
|
||||
<button type="submit" class="px-3 py-1.5 bg-red-600 text-white rounded-lg hover:bg-red-700 transition text-xs">
|
||||
반려 확인
|
||||
</button>
|
||||
<button type="button" onclick="hideRejectForm()" class="px-3 py-1.5 border border-gray-300 rounded-lg hover:bg-gray-50 transition text-xs">
|
||||
취소
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- 반려된 경우 사유 표시 -->
|
||||
@if($partner->isRejected() && $partner->rejection_reason)
|
||||
<div class="mb-6 bg-red-50 border border-red-200 rounded-lg p-4">
|
||||
<div class="flex items-start gap-3">
|
||||
<svg class="w-5 h-5 text-red-500 flex-shrink-0 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
<div>
|
||||
<h3 class="font-semibold text-red-800 text-sm">반려됨</h3>
|
||||
<p class="text-sm text-red-700 mt-1">{{ $partner->rejection_reason }}</p>
|
||||
@if($partner->approver)
|
||||
<p class="text-xs text-red-600 mt-1">처리자: {{ $partner->approver->name }} ({{ $partner->approved_at->format('Y-m-d H:i') }})</p>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||
<!-- 기본 정보 -->
|
||||
<div class="bg-gray-50 rounded-lg p-4">
|
||||
<h3 class="text-sm font-semibold text-gray-800 mb-3">기본 정보</h3>
|
||||
<dl class="space-y-2 text-sm">
|
||||
<div class="flex justify-between">
|
||||
<dt class="text-gray-500">로그인 ID</dt>
|
||||
<dd class="font-medium text-gray-900">{{ $partner->user_id ?? $partner->email }}</dd>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<dt class="text-gray-500">이름</dt>
|
||||
<dd class="font-medium text-gray-900">{{ $partner->name }}</dd>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<dt class="text-gray-500">이메일</dt>
|
||||
<dd class="font-medium text-gray-900">{{ $partner->email }}</dd>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<dt class="text-gray-500">전화번호</dt>
|
||||
<dd class="font-medium text-gray-900">{{ $partner->phone ?? '-' }}</dd>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<dt class="text-gray-500">추천인(유치자)</dt>
|
||||
<dd class="font-medium text-gray-900">
|
||||
@if($partner->parent)
|
||||
{{ $partner->parent->name }}
|
||||
@else
|
||||
<span class="text-gray-400">최상위</span>
|
||||
@endif
|
||||
</dd>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<dt class="text-gray-500">등록일</dt>
|
||||
<dd class="font-medium text-gray-900">{{ $partner->created_at->format('Y-m-d H:i') }}</dd>
|
||||
</div>
|
||||
@if($partner->isApproved() && $partner->approved_at)
|
||||
<div class="flex justify-between">
|
||||
<dt class="text-gray-500">승인일</dt>
|
||||
<dd class="font-medium text-gray-900">{{ $partner->approved_at->format('Y-m-d H:i') }}</dd>
|
||||
</div>
|
||||
@endif
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<!-- 통계 -->
|
||||
<div class="bg-gray-50 rounded-lg p-4">
|
||||
<h3 class="text-sm font-semibold text-gray-800 mb-3">활동 통계</h3>
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<div class="bg-blue-100 rounded-lg p-3 text-center">
|
||||
<div class="text-xl font-bold text-blue-800">{{ $children->count() }}</div>
|
||||
<div class="text-xs text-blue-600">하위 파트너</div>
|
||||
</div>
|
||||
<div class="bg-green-100 rounded-lg p-3 text-center">
|
||||
<div class="text-xl font-bold text-green-800">{{ $partner->salesDocuments->count() }}</div>
|
||||
<div class="text-xs text-green-600">첨부 서류</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 역할 관리 (승인된 파트너만) -->
|
||||
@if($partner->isApproved())
|
||||
<div class="mt-4 bg-gray-50 rounded-lg p-4">
|
||||
<h3 class="text-sm font-semibold text-gray-800 mb-3">역할 관리</h3>
|
||||
@php
|
||||
$currentRoles = $partner->userRoles->pluck('role.name')->toArray();
|
||||
$roleLabels = ['sales' => '영업', 'manager' => '매니저', 'recruiter' => '유치담당'];
|
||||
$roleColors = [
|
||||
'sales' => 'bg-blue-100 text-blue-800 border-blue-200',
|
||||
'manager' => 'bg-purple-100 text-purple-800 border-purple-200',
|
||||
'recruiter' => 'bg-green-100 text-green-800 border-green-200',
|
||||
];
|
||||
@endphp
|
||||
<div class="flex flex-wrap gap-2 mb-3">
|
||||
@forelse($currentRoles as $roleName)
|
||||
@if(isset($roleLabels[$roleName]))
|
||||
<div class="flex items-center gap-1 px-2 py-1 rounded-full border {{ $roleColors[$roleName] ?? 'bg-gray-100' }}">
|
||||
<span class="text-xs font-medium">{{ $roleLabels[$roleName] }}</span>
|
||||
<form action="{{ route('sales.managers.remove-role', $partner->id) }}" method="POST" class="inline">
|
||||
@csrf
|
||||
<input type="hidden" name="role_name" value="{{ $roleName }}">
|
||||
<button type="submit" onclick="return confirm('이 역할을 제거하시겠습니까?')"
|
||||
class="ml-1 text-gray-400 hover:text-red-500">
|
||||
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
@endif
|
||||
@empty
|
||||
<span class="text-gray-400 text-xs">역할이 없습니다</span>
|
||||
@endforelse
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
@foreach(['sales' => '영업', 'manager' => '매니저', 'recruiter' => '유치담당'] as $roleName => $label)
|
||||
@if(!in_array($roleName, $currentRoles))
|
||||
<form action="{{ route('sales.managers.assign-role', $partner->id) }}" method="POST" class="inline">
|
||||
@csrf
|
||||
<input type="hidden" name="role_name" value="{{ $roleName }}">
|
||||
<button type="submit"
|
||||
class="px-2 py-1 text-xs border border-gray-300 rounded-full hover:bg-gray-100 transition">
|
||||
+ {{ $label }}
|
||||
</button>
|
||||
</form>
|
||||
@endif
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- 첨부 서류 -->
|
||||
@if($partner->salesDocuments->isNotEmpty())
|
||||
<div class="mt-4 bg-gray-50 rounded-lg p-4">
|
||||
<h3 class="text-sm font-semibold text-gray-800 mb-3">첨부 서류</h3>
|
||||
<div class="space-y-2">
|
||||
@foreach($partner->salesDocuments as $document)
|
||||
<div class="flex items-center justify-between p-2 bg-white rounded border">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="px-2 py-0.5 text-xs bg-gray-100 text-gray-600 rounded">{{ $document->document_type_label }}</span>
|
||||
<span class="text-sm text-gray-700">{{ $document->original_name }}</span>
|
||||
</div>
|
||||
<a href="{{ route('sales.managers.documents.download', [$partner->id, $document->id]) }}"
|
||||
class="text-xs text-blue-600 hover:underline">다운로드</a>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- 하위 파트너 -->
|
||||
@if($children->isNotEmpty())
|
||||
<div class="mt-4 bg-gray-50 rounded-lg p-4">
|
||||
<h3 class="text-sm font-semibold text-gray-800 mb-3">하위 파트너 ({{ $children->count() }}명)</h3>
|
||||
<div class="space-y-2">
|
||||
@foreach($children->take(5) as $child)
|
||||
<div class="flex items-center justify-between p-2 bg-white rounded border">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-sm font-medium text-gray-900">{{ $child->name }}</span>
|
||||
@foreach($child->userRoles as $userRole)
|
||||
@php
|
||||
$roleColor = match($userRole->role->name ?? '') {
|
||||
'sales' => 'bg-blue-100 text-blue-800',
|
||||
'manager' => 'bg-purple-100 text-purple-800',
|
||||
'recruiter' => 'bg-green-100 text-green-800',
|
||||
default => 'bg-gray-100 text-gray-800',
|
||||
};
|
||||
$roleLabel = match($userRole->role->name ?? '') {
|
||||
'sales' => '영업',
|
||||
'manager' => '매니저',
|
||||
'recruiter' => '유치담당',
|
||||
default => $userRole->role->name ?? '-',
|
||||
};
|
||||
@endphp
|
||||
<span class="px-1.5 py-0.5 text-xs font-medium rounded {{ $roleColor }}">{{ $roleLabel }}</span>
|
||||
@endforeach
|
||||
</div>
|
||||
<span class="px-2 py-0.5 text-xs rounded-full {{ $child->approval_status_color }}">
|
||||
{{ $child->approval_status_label }}
|
||||
</span>
|
||||
</div>
|
||||
@endforeach
|
||||
@if($children->count() > 5)
|
||||
<p class="text-xs text-gray-500 text-center mt-2">외 {{ $children->count() - 5 }}명...</p>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- 푸터 버튼 -->
|
||||
<div class="mt-6 flex justify-end gap-3">
|
||||
<button type="button" onclick="closePartnerModal()"
|
||||
class="px-4 py-2 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 transition text-sm">
|
||||
닫기
|
||||
</button>
|
||||
<button type="button" onclick="openEditModal({{ $partner->id }})"
|
||||
class="px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition text-sm">
|
||||
수정
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function showRejectForm() {
|
||||
document.getElementById('rejectForm').classList.remove('hidden');
|
||||
}
|
||||
function hideRejectForm() {
|
||||
document.getElementById('rejectForm').classList.add('hidden');
|
||||
}
|
||||
</script>
|
||||
@@ -790,6 +790,8 @@
|
||||
|
||||
// 영업 담당자 관리
|
||||
Route::resource('managers', \App\Http\Controllers\Sales\SalesManagerController::class);
|
||||
Route::get('managers/{id}/modal-show', [\App\Http\Controllers\Sales\SalesManagerController::class, 'modalShow'])->name('managers.modal-show');
|
||||
Route::get('managers/{id}/modal-edit', [\App\Http\Controllers\Sales\SalesManagerController::class, 'modalEdit'])->name('managers.modal-edit');
|
||||
Route::post('managers/{id}/approve', [\App\Http\Controllers\Sales\SalesManagerController::class, 'approve'])->name('managers.approve');
|
||||
Route::post('managers/{id}/reject', [\App\Http\Controllers\Sales\SalesManagerController::class, 'reject'])->name('managers.reject');
|
||||
Route::post('managers/{id}/delegate-role', [\App\Http\Controllers\Sales\SalesManagerController::class, 'delegateRole'])->name('managers.delegate-role');
|
||||
|
||||
Reference in New Issue
Block a user