feat: [business-card] 3단계 워크플로우 구현 (요청→제작의뢰→처리완료)
- 모델: STATUS_ORDERED 추가, markAsOrdered() 헬퍼
- 서비스: order(), getOrderedRequests() 추가
- 컨트롤러: order() 액션 추가
- 관리자 뷰: 좌측 신규요청 + 우측 제작중 + 하단 처리완료
- 파트너 뷰: 제작중(파란) 상태 뱃지 추가
- 라우트: POST business-cards/{id}/order 추가
This commit is contained in:
@@ -30,7 +30,7 @@ public function index(Request $request): View|Response
|
||||
}
|
||||
|
||||
/**
|
||||
* 명함신청 처리 (관리자 전용 - 대기/완료 2분할)
|
||||
* 명함신청 처리 (관리자 전용)
|
||||
*/
|
||||
public function manage(Request $request): View|Response
|
||||
{
|
||||
@@ -45,11 +45,13 @@ public function manage(Request $request): View|Response
|
||||
$search = $request->get('search');
|
||||
$stats = $this->service->getStats();
|
||||
$pendingRequests = $this->service->getPendingRequests($search);
|
||||
$orderedRequests = $this->service->getOrderedRequests($search);
|
||||
$processedRequests = $this->service->getProcessedRequests($search);
|
||||
|
||||
return view('sales.business-cards.admin-index', compact(
|
||||
'stats',
|
||||
'pendingRequests',
|
||||
'orderedRequests',
|
||||
'processedRequests',
|
||||
'search'
|
||||
));
|
||||
@@ -75,6 +77,28 @@ public function store(Request $request)
|
||||
->with('success', '명함 신청이 완료되었습니다.');
|
||||
}
|
||||
|
||||
/**
|
||||
* 제작의뢰 (관리자 전용)
|
||||
*/
|
||||
public function order(Request $request, int $id)
|
||||
{
|
||||
if (! auth()->user()->isAdmin()) {
|
||||
abort(403, '관리자만 처리할 수 있습니다.');
|
||||
}
|
||||
|
||||
$this->service->order($id);
|
||||
|
||||
if ($request->expectsJson() || $request->header('Accept') === 'application/json') {
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => '제작의뢰 처리되었습니다.',
|
||||
]);
|
||||
}
|
||||
|
||||
return redirect()->route('sales.business-cards.manage')
|
||||
->with('success', '제작의뢰 처리되었습니다.');
|
||||
}
|
||||
|
||||
/**
|
||||
* 처리 완료 (관리자 전용)
|
||||
*/
|
||||
|
||||
@@ -10,10 +10,13 @@ class BusinessCardRequest extends Model
|
||||
{
|
||||
public const STATUS_PENDING = 'pending';
|
||||
|
||||
public const STATUS_ORDERED = 'ordered';
|
||||
|
||||
public const STATUS_PROCESSED = 'processed';
|
||||
|
||||
public const STATUS_LABELS = [
|
||||
self::STATUS_PENDING => '신청대기',
|
||||
self::STATUS_PENDING => '요청',
|
||||
self::STATUS_ORDERED => '제작의뢰',
|
||||
self::STATUS_PROCESSED => '처리완료',
|
||||
];
|
||||
|
||||
@@ -27,12 +30,15 @@ class BusinessCardRequest extends Model
|
||||
'quantity',
|
||||
'memo',
|
||||
'status',
|
||||
'ordered_by',
|
||||
'ordered_at',
|
||||
'processed_by',
|
||||
'processed_at',
|
||||
'process_memo',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'ordered_at' => 'datetime',
|
||||
'processed_at' => 'datetime',
|
||||
'quantity' => 'integer',
|
||||
];
|
||||
@@ -51,6 +57,11 @@ public function requester(): BelongsTo
|
||||
return $this->belongsTo(User::class, 'user_id');
|
||||
}
|
||||
|
||||
public function orderer(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'ordered_by');
|
||||
}
|
||||
|
||||
public function processor(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'processed_by');
|
||||
@@ -65,6 +76,11 @@ public function scopePending($query)
|
||||
return $query->where('status', self::STATUS_PENDING);
|
||||
}
|
||||
|
||||
public function scopeOrdered($query)
|
||||
{
|
||||
return $query->where('status', self::STATUS_ORDERED);
|
||||
}
|
||||
|
||||
public function scopeProcessed($query)
|
||||
{
|
||||
return $query->where('status', self::STATUS_PROCESSED);
|
||||
@@ -89,16 +105,34 @@ public function getIsPendingAttribute(): bool
|
||||
return $this->status === self::STATUS_PENDING;
|
||||
}
|
||||
|
||||
public function getIsOrderedAttribute(): bool
|
||||
{
|
||||
return $this->status === self::STATUS_ORDERED;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// 헬퍼 메서드
|
||||
// =========================================================================
|
||||
|
||||
public function markAsProcessed(int $processedBy, ?string $memo = null): bool
|
||||
public function markAsOrdered(int $orderedBy): bool
|
||||
{
|
||||
if (! $this->is_pending) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->status = self::STATUS_ORDERED;
|
||||
$this->ordered_by = $orderedBy;
|
||||
$this->ordered_at = now();
|
||||
|
||||
return $this->save();
|
||||
}
|
||||
|
||||
public function markAsProcessed(int $processedBy, ?string $memo = null): bool
|
||||
{
|
||||
if (! $this->is_ordered) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->status = self::STATUS_PROCESSED;
|
||||
$this->processed_by = $processedBy;
|
||||
$this->processed_at = now();
|
||||
|
||||
@@ -7,13 +7,15 @@
|
||||
class BusinessCardRequestService
|
||||
{
|
||||
/**
|
||||
* 뱃지용 대기 건수
|
||||
* 뱃지용 대기 건수 (요청 + 제작의뢰)
|
||||
*/
|
||||
public function getPendingCount(): int
|
||||
{
|
||||
$tenantId = session('selected_tenant_id', 1);
|
||||
|
||||
return BusinessCardRequest::ofTenant($tenantId)->pending()->count();
|
||||
return BusinessCardRequest::ofTenant($tenantId)
|
||||
->whereIn('status', [BusinessCardRequest::STATUS_PENDING, BusinessCardRequest::STATUS_ORDERED])
|
||||
->count();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -26,6 +28,7 @@ public function getStats(): array
|
||||
|
||||
return [
|
||||
'pending' => (clone $base)->pending()->count(),
|
||||
'ordered' => (clone $base)->ordered()->count(),
|
||||
'processed_today' => (clone $base)->processed()
|
||||
->whereDate('processed_at', today())
|
||||
->count(),
|
||||
@@ -34,26 +37,25 @@ public function getStats(): array
|
||||
}
|
||||
|
||||
/**
|
||||
* 대기 목록
|
||||
* 신규 요청 목록
|
||||
*/
|
||||
public function getPendingRequests(?string $search = null, int $perPage = 20)
|
||||
{
|
||||
$tenantId = session('selected_tenant_id', 1);
|
||||
|
||||
$query = BusinessCardRequest::ofTenant($tenantId)
|
||||
->pending()
|
||||
return $this->buildQuery(BusinessCardRequest::STATUS_PENDING, $search)
|
||||
->with('requester')
|
||||
->latest();
|
||||
->latest()
|
||||
->paginate($perPage, ['*'], 'pending_page');
|
||||
}
|
||||
|
||||
if ($search) {
|
||||
$query->where(function ($q) use ($search) {
|
||||
$q->where('name', 'like', "%{$search}%")
|
||||
->orWhere('phone', 'like', "%{$search}%")
|
||||
->orWhere('email', 'like', "%{$search}%");
|
||||
});
|
||||
}
|
||||
|
||||
return $query->paginate($perPage, ['*'], 'pending_page');
|
||||
/**
|
||||
* 제작의뢰 목록
|
||||
*/
|
||||
public function getOrderedRequests(?string $search = null, int $perPage = 20)
|
||||
{
|
||||
return $this->buildQuery(BusinessCardRequest::STATUS_ORDERED, $search)
|
||||
->with(['requester', 'orderer'])
|
||||
->latest('ordered_at')
|
||||
->paginate($perPage, ['*'], 'ordered_page');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -61,22 +63,10 @@ public function getPendingRequests(?string $search = null, int $perPage = 20)
|
||||
*/
|
||||
public function getProcessedRequests(?string $search = null, int $perPage = 20)
|
||||
{
|
||||
$tenantId = session('selected_tenant_id', 1);
|
||||
|
||||
$query = BusinessCardRequest::ofTenant($tenantId)
|
||||
->processed()
|
||||
return $this->buildQuery(BusinessCardRequest::STATUS_PROCESSED, $search)
|
||||
->with(['requester', 'processor'])
|
||||
->latest('processed_at');
|
||||
|
||||
if ($search) {
|
||||
$query->where(function ($q) use ($search) {
|
||||
$q->where('name', 'like', "%{$search}%")
|
||||
->orWhere('phone', 'like', "%{$search}%")
|
||||
->orWhere('email', 'like', "%{$search}%");
|
||||
});
|
||||
}
|
||||
|
||||
return $query->paginate($perPage, ['*'], 'processed_page');
|
||||
->latest('processed_at')
|
||||
->paginate($perPage, ['*'], 'processed_page');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -105,6 +95,18 @@ public function createRequest(array $data): BusinessCardRequest
|
||||
return BusinessCardRequest::create($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 제작의뢰
|
||||
*/
|
||||
public function order(int $id): BusinessCardRequest
|
||||
{
|
||||
$tenantId = session('selected_tenant_id', 1);
|
||||
$request = BusinessCardRequest::ofTenant($tenantId)->findOrFail($id);
|
||||
$request->markAsOrdered(auth()->id());
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* 처리 완료
|
||||
*/
|
||||
@@ -116,4 +118,20 @@ public function process(int $id, ?string $memo = null): BusinessCardRequest
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
private function buildQuery(string $status, ?string $search)
|
||||
{
|
||||
$tenantId = session('selected_tenant_id', 1);
|
||||
$query = BusinessCardRequest::ofTenant($tenantId)->where('status', $status);
|
||||
|
||||
if ($search) {
|
||||
$query->where(function ($q) use ($search) {
|
||||
$q->where('name', 'like', "%{$search}%")
|
||||
->orWhere('phone', 'like', "%{$search}%")
|
||||
->orWhere('email', 'like', "%{$search}%");
|
||||
});
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +1,36 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('title', '명함신청 관리')
|
||||
@section('title', '명함신청 처리')
|
||||
|
||||
@section('content')
|
||||
<div class="flex flex-col h-full">
|
||||
<!-- 페이지 헤더 -->
|
||||
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-4 mb-4 flex-shrink-0">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-gray-800">명함신청 관리</h1>
|
||||
<p class="text-sm text-gray-500 mt-1">영업파트너의 명함 신청을 확인하고 처리합니다</p>
|
||||
<h1 class="text-2xl font-bold text-gray-800">명함신청 처리</h1>
|
||||
<p class="text-sm text-gray-500 mt-1">
|
||||
요청 → <span class="text-blue-600 font-medium">제작의뢰</span> → <span class="text-emerald-600 font-medium">처리완료</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 통계 카드 -->
|
||||
<div class="grid grid-cols-3 gap-4 mb-4 flex-shrink-0">
|
||||
<div class="grid grid-cols-4 gap-3 mb-4 flex-shrink-0">
|
||||
<div class="bg-yellow-50 rounded-lg shadow-sm p-3">
|
||||
<div class="text-xs text-yellow-600">신청 대기</div>
|
||||
<div class="text-xs text-yellow-600">신규 요청</div>
|
||||
<div class="text-xl font-bold text-yellow-800">{{ number_format($stats['pending']) }}건</div>
|
||||
</div>
|
||||
<div class="bg-blue-50 rounded-lg shadow-sm p-3">
|
||||
<div class="text-xs text-blue-600">제작의뢰</div>
|
||||
<div class="text-xl font-bold text-blue-800">{{ number_format($stats['ordered']) }}건</div>
|
||||
</div>
|
||||
<div class="bg-emerald-50 rounded-lg shadow-sm p-3">
|
||||
<div class="text-xs text-emerald-600">오늘 처리</div>
|
||||
<div class="text-xl font-bold text-emerald-800">{{ number_format($stats['processed_today']) }}건</div>
|
||||
</div>
|
||||
<div class="bg-blue-50 rounded-lg shadow-sm p-3">
|
||||
<div class="text-xs text-blue-600">전체 신청</div>
|
||||
<div class="text-xl font-bold text-blue-800">{{ number_format($stats['total']) }}건</div>
|
||||
<div class="bg-gray-50 rounded-lg shadow-sm p-3">
|
||||
<div class="text-xs text-gray-500">전체</div>
|
||||
<div class="text-xl font-bold text-gray-700">{{ number_format($stats['total']) }}건</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -44,15 +50,15 @@ class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none foc
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- 2분할 레이아웃 -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4 flex-1 min-h-0">
|
||||
<!-- 좌측: 신청 대기 -->
|
||||
<!-- 2분할: 신규 요청 / 제작의뢰 -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4 mb-4" style="min-height: 300px;">
|
||||
<!-- 좌측: 신규 요청 -->
|
||||
<div class="bg-white rounded-lg shadow-sm overflow-hidden flex flex-col min-h-0">
|
||||
<div class="bg-yellow-500 text-white px-4 py-3 flex items-center gap-2 flex-shrink-0">
|
||||
<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="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
<span class="font-semibold">신청 대기</span>
|
||||
<span class="font-semibold">신규 요청</span>
|
||||
<span class="ml-auto bg-yellow-600 px-2 py-0.5 rounded-full text-xs">{{ $pendingRequests->total() }}건</span>
|
||||
</div>
|
||||
<div class="overflow-y-auto flex-1">
|
||||
@@ -67,7 +73,7 @@ class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none foc
|
||||
</thead>
|
||||
<tbody class="bg-white divide-y divide-gray-200">
|
||||
@forelse($pendingRequests as $req)
|
||||
<tr class="hover:bg-yellow-50" id="pending-row-{{ $req->id }}">
|
||||
<tr class="hover:bg-yellow-50">
|
||||
<td class="px-4 py-3">
|
||||
<div class="font-medium text-gray-900 text-sm">{{ $req->name }}</div>
|
||||
<div class="text-xs text-gray-500">{{ $req->title ?: '-' }}</div>
|
||||
@@ -82,15 +88,15 @@ class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none foc
|
||||
</td>
|
||||
<td class="px-4 py-3 text-center">
|
||||
<button type="button"
|
||||
onclick="processRequest({{ $req->id }}, '{{ $req->name }}')"
|
||||
class="px-3 py-1 bg-emerald-500 hover:bg-emerald-600 text-white text-xs font-medium rounded transition">
|
||||
처리완료
|
||||
onclick="orderRequest({{ $req->id }}, '{{ $req->name }}')"
|
||||
class="px-3 py-1 bg-blue-500 hover:bg-blue-600 text-white text-xs font-medium rounded transition">
|
||||
제작의뢰
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
@if($req->memo)
|
||||
<tr class="bg-yellow-25">
|
||||
<td colspan="4" class="px-4 py-1">
|
||||
<tr>
|
||||
<td colspan="4" class="px-4 py-1 bg-yellow-50">
|
||||
<div class="text-xs text-gray-500"><span class="font-medium">메모:</span> {{ $req->memo }}</div>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -101,7 +107,7 @@ class="px-3 py-1 bg-emerald-500 hover:bg-emerald-600 text-white text-xs font-med
|
||||
<svg class="w-12 h-12 mx-auto text-gray-300 mb-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
대기 중인 명함 신청이 없습니다.
|
||||
신규 요청이 없습니다.
|
||||
</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
@@ -115,14 +121,14 @@ class="px-3 py-1 bg-emerald-500 hover:bg-emerald-600 text-white text-xs font-med
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<!-- 우측: 처리 완료 -->
|
||||
<!-- 우측: 제작의뢰 -->
|
||||
<div class="bg-white rounded-lg shadow-sm overflow-hidden flex flex-col min-h-0">
|
||||
<div class="bg-emerald-500 text-white px-4 py-3 flex items-center gap-2 flex-shrink-0">
|
||||
<div class="bg-blue-500 text-white px-4 py-3 flex items-center gap-2 flex-shrink-0">
|
||||
<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="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z" />
|
||||
</svg>
|
||||
<span class="font-semibold">처리 완료</span>
|
||||
<span class="ml-auto bg-emerald-600 px-2 py-0.5 rounded-full text-xs">{{ $processedRequests->total() }}건</span>
|
||||
<span class="font-semibold">제작 중</span>
|
||||
<span class="ml-auto bg-blue-600 px-2 py-0.5 rounded-full text-xs">{{ $orderedRequests->total() }}건</span>
|
||||
</div>
|
||||
<div class="overflow-y-auto flex-1">
|
||||
<table class="min-w-full divide-y divide-gray-200">
|
||||
@@ -131,12 +137,13 @@ class="px-3 py-1 bg-emerald-500 hover:bg-emerald-600 text-white text-xs font-med
|
||||
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">신청자</th>
|
||||
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">연락처</th>
|
||||
<th class="px-4 py-2 text-center text-xs font-medium text-gray-500 uppercase">수량</th>
|
||||
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">처리일</th>
|
||||
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">의뢰일</th>
|
||||
<th class="px-4 py-2 text-center text-xs font-medium text-gray-500 uppercase">처리</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="bg-white divide-y divide-gray-200">
|
||||
@forelse($processedRequests as $req)
|
||||
<tr class="hover:bg-emerald-50">
|
||||
@forelse($orderedRequests as $req)
|
||||
<tr class="hover:bg-blue-50">
|
||||
<td class="px-4 py-3">
|
||||
<div class="font-medium text-gray-900 text-sm">{{ $req->name }}</div>
|
||||
<div class="text-xs text-gray-500">{{ $req->title ?: '-' }}</div>
|
||||
@@ -149,38 +156,136 @@ class="px-3 py-1 bg-emerald-500 hover:bg-emerald-600 text-white text-xs font-med
|
||||
{{ number_format($req->quantity) }}매
|
||||
</td>
|
||||
<td class="px-4 py-3">
|
||||
<div class="text-xs text-gray-700">{{ $req->processed_at?->format('m/d H:i') }}</div>
|
||||
<div class="text-xs text-gray-400">{{ $req->processor?->name }}</div>
|
||||
<div class="text-xs text-gray-700">{{ $req->ordered_at?->format('m/d H:i') }}</div>
|
||||
<div class="text-xs text-gray-400">{{ $req->orderer?->name }}</div>
|
||||
</td>
|
||||
<td class="px-4 py-3 text-center">
|
||||
<button type="button"
|
||||
onclick="processRequest({{ $req->id }}, '{{ $req->name }}')"
|
||||
class="px-3 py-1 bg-emerald-500 hover:bg-emerald-600 text-white text-xs font-medium rounded transition">
|
||||
처리완료
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="4" class="px-4 py-8 text-center text-gray-500 text-sm">
|
||||
<td colspan="5" class="px-4 py-8 text-center text-gray-500 text-sm">
|
||||
<svg class="w-12 h-12 mx-auto text-gray-300 mb-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z" />
|
||||
</svg>
|
||||
처리된 명함 신청이 없습니다.
|
||||
제작의뢰 중인 건이 없습니다.
|
||||
</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@if($processedRequests->hasPages())
|
||||
@if($orderedRequests->hasPages())
|
||||
<div class="px-4 py-2 border-t border-gray-200 flex-shrink-0 bg-gray-50">
|
||||
{{ $processedRequests->withQueryString()->links() }}
|
||||
{{ $orderedRequests->withQueryString()->links() }}
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 하단: 처리 완료 -->
|
||||
<div class="bg-white rounded-lg shadow-sm overflow-hidden flex-shrink-0">
|
||||
<div class="bg-emerald-500 text-white px-4 py-3 flex items-center gap-2">
|
||||
<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="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
<span class="font-semibold">처리 완료</span>
|
||||
<span class="ml-auto bg-emerald-600 px-2 py-0.5 rounded-full text-xs">{{ $processedRequests->total() }}건</span>
|
||||
</div>
|
||||
<div class="overflow-x-auto" style="max-height: 300px; overflow-y: auto;">
|
||||
<table class="min-w-full divide-y divide-gray-200">
|
||||
<thead class="bg-gray-50 sticky top-0">
|
||||
<tr>
|
||||
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">신청자</th>
|
||||
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">연락처</th>
|
||||
<th class="px-4 py-2 text-center text-xs font-medium text-gray-500 uppercase">수량</th>
|
||||
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">의뢰일</th>
|
||||
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">처리일</th>
|
||||
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">처리자</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="bg-white divide-y divide-gray-200">
|
||||
@forelse($processedRequests as $req)
|
||||
<tr class="hover:bg-emerald-50">
|
||||
<td class="px-4 py-2">
|
||||
<div class="font-medium text-gray-900 text-sm">{{ $req->name }}</div>
|
||||
<div class="text-xs text-gray-500">{{ $req->title ?: '-' }}</div>
|
||||
</td>
|
||||
<td class="px-4 py-2">
|
||||
<div class="text-sm text-gray-900">{{ $req->phone }}</div>
|
||||
</td>
|
||||
<td class="px-4 py-2 text-center text-sm text-gray-700">
|
||||
{{ number_format($req->quantity) }}매
|
||||
</td>
|
||||
<td class="px-4 py-2 text-xs text-gray-500">
|
||||
{{ $req->ordered_at?->format('m/d H:i') ?? '-' }}
|
||||
</td>
|
||||
<td class="px-4 py-2 text-xs text-gray-700">
|
||||
{{ $req->processed_at?->format('m/d H:i') }}
|
||||
</td>
|
||||
<td class="px-4 py-2 text-xs text-gray-500">
|
||||
{{ $req->processor?->name }}
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="6" class="px-4 py-6 text-center text-gray-500 text-sm">
|
||||
처리된 명함 신청이 없습니다.
|
||||
</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@if($processedRequests->hasPages())
|
||||
<div class="px-4 py-2 border-t border-gray-200 bg-gray-50">
|
||||
{{ $processedRequests->withQueryString()->links() }}
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
function orderRequest(id, name) {
|
||||
showConfirm(
|
||||
`<strong>${name}</strong>님의 명함을 제작의뢰 하시겠습니까?`,
|
||||
() => {
|
||||
fetch(`/sales/business-cards/${id}/order`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-TOKEN': '{{ csrf_token() }}',
|
||||
'Accept': 'application/json',
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
showToast(data.message, 'success');
|
||||
window.location.reload();
|
||||
} else {
|
||||
showToast(data.message || '처리에 실패했습니다.', 'error');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
showToast('서버 오류가 발생했습니다.', 'error');
|
||||
console.error(error);
|
||||
});
|
||||
},
|
||||
{ title: '제작의뢰', icon: 'question', confirmText: '의뢰하기' }
|
||||
);
|
||||
}
|
||||
|
||||
function processRequest(id, name) {
|
||||
showConfirm(
|
||||
`<strong>${name}</strong>님의 명함 신청을 처리완료 하시겠습니까?`,
|
||||
`<strong>${name}</strong>님의 명함 제작이 완료되었습니까?`,
|
||||
() => {
|
||||
fetch(`/sales/business-cards/${id}/process`, {
|
||||
method: 'POST',
|
||||
@@ -204,7 +309,7 @@ function processRequest(id, name) {
|
||||
console.error(error);
|
||||
});
|
||||
},
|
||||
{ title: '처리 확인', icon: 'question', confirmText: '처리완료' }
|
||||
{ title: '처리 완료', icon: 'question', confirmText: '처리완료' }
|
||||
);
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -118,7 +118,9 @@ class="w-full sm:w-auto px-6 py-2 bg-blue-600 hover:bg-blue-700 text-white font-
|
||||
<td class="px-4 py-3 text-sm text-gray-700 text-center">{{ number_format($req->quantity) }}매</td>
|
||||
<td class="px-4 py-3 text-center">
|
||||
@if($req->status === 'pending')
|
||||
<span class="px-2 py-0.5 text-xs font-medium rounded-full bg-yellow-100 text-yellow-800">대기중</span>
|
||||
<span class="px-2 py-0.5 text-xs font-medium rounded-full bg-yellow-100 text-yellow-800">요청</span>
|
||||
@elseif($req->status === 'ordered')
|
||||
<span class="px-2 py-0.5 text-xs font-medium rounded-full bg-blue-100 text-blue-800">제작중</span>
|
||||
@else
|
||||
<span class="px-2 py-0.5 text-xs font-medium rounded-full bg-emerald-100 text-emerald-800">처리완료</span>
|
||||
@endif
|
||||
|
||||
@@ -1287,6 +1287,7 @@
|
||||
Route::get('business-cards', [\App\Http\Controllers\Sales\BusinessCardRequestController::class, 'index'])->name('business-cards.index');
|
||||
Route::post('business-cards', [\App\Http\Controllers\Sales\BusinessCardRequestController::class, 'store'])->name('business-cards.store');
|
||||
Route::get('business-cards/manage', [\App\Http\Controllers\Sales\BusinessCardRequestController::class, 'manage'])->name('business-cards.manage');
|
||||
Route::post('business-cards/{id}/order', [\App\Http\Controllers\Sales\BusinessCardRequestController::class, 'order'])->name('business-cards.order');
|
||||
Route::post('business-cards/{id}/process', [\App\Http\Controllers\Sales\BusinessCardRequestController::class, 'process'])->name('business-cards.process');
|
||||
|
||||
// 매니저 검색 (리소스 라우트보다 먼저 정의해야 함!)
|
||||
|
||||
Reference in New Issue
Block a user