feat:영업파트너 관리 상세/수정 모달 구현

- 목록에서 상세/수정 버튼 클릭 시 모달로 표시
- 모달 열림 시 배경 스크롤 방지
- ESC 키로 모달 닫기 지원

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
pro
2026-01-29 21:27:05 +09:00
parent 12c7dfc947
commit 036a021b71
5 changed files with 638 additions and 2 deletions

View File

@@ -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'
));
}
/**
* 수정 폼
*/

View File

@@ -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

View 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>

View 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>

View File

@@ -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');