테넌트 목록/모달 UI 개선
- 저장소 사용량 표시 추가 (테이블 + 모달) - 이메일/전화번호 컬럼 병합 (연락처) - 전화번호 하이픈 포맷 적용 - 생성일 yymmdd 형식 변경 및 ID 뒤로 이동 - 테이블 헤더 가운데 정렬 - 액션 컬럼을 관리(colspan)로 변경
This commit is contained in:
@@ -44,9 +44,13 @@ class Tenant extends Model
|
||||
|
||||
protected $casts = [
|
||||
'max_users' => 'integer',
|
||||
'storage_limit' => 'integer',
|
||||
'storage_used' => 'integer',
|
||||
'trial_ends_at' => 'datetime',
|
||||
'expires_at' => 'datetime',
|
||||
'last_paid_at' => 'datetime',
|
||||
'storage_warning_sent_at' => 'datetime',
|
||||
'storage_grace_period_until' => 'datetime',
|
||||
'created_at' => 'datetime',
|
||||
'updated_at' => 'datetime',
|
||||
'deleted_at' => 'datetime',
|
||||
@@ -143,4 +147,110 @@ public function getBillingTypeLabelAttribute(): ?string
|
||||
default => $this->billing_tp_code,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 저장소 사용률 (%) - Blade 뷰에서 사용
|
||||
*/
|
||||
public function getStorageUsagePercentAttribute(): float
|
||||
{
|
||||
$limit = $this->storage_limit ?? 10737418240; // 기본 10GB
|
||||
if ($limit <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return round(($this->storage_used ?? 0) / $limit * 100, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 저장소 사용량 포맷 (예: "2.5 GB / 10 GB")
|
||||
*/
|
||||
public function getStorageUsageFormattedAttribute(): string
|
||||
{
|
||||
$used = $this->formatBytes($this->storage_used ?? 0);
|
||||
$limit = $this->formatBytes($this->storage_limit ?? 10737418240);
|
||||
|
||||
return "{$used} / {$limit}";
|
||||
}
|
||||
|
||||
/**
|
||||
* 저장소 사용량만 포맷 (예: "2.5 GB")
|
||||
*/
|
||||
public function getStorageUsedFormattedAttribute(): string
|
||||
{
|
||||
return $this->formatBytes($this->storage_used ?? 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 저장소 한도만 포맷 (예: "10 GB")
|
||||
*/
|
||||
public function getStorageLimitFormattedAttribute(): string
|
||||
{
|
||||
return $this->formatBytes($this->storage_limit ?? 10737418240);
|
||||
}
|
||||
|
||||
/**
|
||||
* 저장소 상태 배지 색상 (Blade 뷰에서 사용)
|
||||
*/
|
||||
public function getStorageBadgeColorAttribute(): string
|
||||
{
|
||||
$percent = $this->storage_usage_percent;
|
||||
|
||||
return match (true) {
|
||||
$percent >= 90 => 'error',
|
||||
$percent >= 70 => 'warning',
|
||||
default => 'success',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 바이트를 읽기 쉬운 형식으로 변환
|
||||
*/
|
||||
protected function formatBytes(int $bytes): string
|
||||
{
|
||||
if ($bytes <= 0) {
|
||||
return '0 B';
|
||||
}
|
||||
|
||||
$units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||
$factor = floor(log($bytes, 1024));
|
||||
$factor = min($factor, count($units) - 1);
|
||||
|
||||
return round($bytes / pow(1024, $factor), 1).' '.$units[$factor];
|
||||
}
|
||||
|
||||
/**
|
||||
* 전화번호 포맷 (하이픈 추가)
|
||||
*/
|
||||
public function getPhoneFormattedAttribute(): ?string
|
||||
{
|
||||
if (! $this->phone) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 숫자만 추출
|
||||
$numbers = preg_replace('/[^0-9]/', '', $this->phone);
|
||||
|
||||
// 휴대폰 (010, 011, 016, 017, 018, 019)
|
||||
if (preg_match('/^(01[0-9])(\d{3,4})(\d{4})$/', $numbers, $matches)) {
|
||||
return $matches[1].'-'.$matches[2].'-'.$matches[3];
|
||||
}
|
||||
|
||||
// 서울 (02)
|
||||
if (preg_match('/^(02)(\d{3,4})(\d{4})$/', $numbers, $matches)) {
|
||||
return $matches[1].'-'.$matches[2].'-'.$matches[3];
|
||||
}
|
||||
|
||||
// 지역번호 (031, 032, ...)
|
||||
if (preg_match('/^(0\d{2})(\d{3,4})(\d{4})$/', $numbers, $matches)) {
|
||||
return $matches[1].'-'.$matches[2].'-'.$matches[3];
|
||||
}
|
||||
|
||||
// 대표번호 (1588, 1544, ...)
|
||||
if (preg_match('/^(1\d{3})(\d{4})$/', $numbers, $matches)) {
|
||||
return $matches[1].'-'.$matches[2];
|
||||
}
|
||||
|
||||
// 포맷 불가 시 원본 반환
|
||||
return $this->phone;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,6 +93,29 @@ class="px-3 py-1.5 text-sm font-medium text-white bg-green-600 rounded-lg hover:
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{{-- 저장소 사용량 --}}
|
||||
<div class="mt-4 pt-4 border-t border-gray-200">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<span class="text-sm font-medium text-gray-700">저장소 사용량</span>
|
||||
<span class="text-sm text-gray-500">{{ $tenant->storage_usage_formatted }}</span>
|
||||
</div>
|
||||
<div class="w-full bg-gray-200 rounded-full h-3">
|
||||
<div class="h-3 rounded-full transition-all duration-300
|
||||
{{ $tenant->storage_badge_color === 'error' ? 'bg-red-500' : '' }}
|
||||
{{ $tenant->storage_badge_color === 'warning' ? 'bg-yellow-500' : '' }}
|
||||
{{ $tenant->storage_badge_color === 'success' ? 'bg-green-500' : '' }}"
|
||||
style="width: {{ min($tenant->storage_usage_percent, 100) }}%"></div>
|
||||
</div>
|
||||
<div class="flex items-center justify-between mt-1">
|
||||
<span class="text-xs text-gray-500">{{ $tenant->storage_usage_percent }}% 사용</span>
|
||||
@if($tenant->storage_usage_percent >= 90)
|
||||
<span class="text-xs text-red-600 font-medium">용량 부족 경고</span>
|
||||
@elseif($tenant->storage_usage_percent >= 70)
|
||||
<span class="text-xs text-yellow-600 font-medium">용량 주의</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,19 +2,19 @@
|
||||
<table class="min-w-full divide-y divide-gray-200">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-left text-sm font-semibold text-gray-700 uppercase tracking-wider">ID</th>
|
||||
<th class="px-3 py-2 text-left text-sm font-semibold text-gray-700 uppercase tracking-wider">회사명</th>
|
||||
<th class="px-3 py-2 text-left text-sm font-semibold text-gray-700 uppercase tracking-wider">코드</th>
|
||||
<th class="px-3 py-2 text-left text-sm font-semibold text-gray-700 uppercase tracking-wider">상태</th>
|
||||
<th class="px-3 py-2 text-center text-sm font-semibold text-gray-700 uppercase tracking-wider">ID</th>
|
||||
<th class="px-3 py-2 text-center text-sm font-semibold text-gray-700 uppercase tracking-wider">생성일</th>
|
||||
<th class="px-3 py-2 text-center text-sm font-semibold text-gray-700 uppercase tracking-wider">회사명</th>
|
||||
<th class="px-3 py-2 text-center text-sm font-semibold text-gray-700 uppercase tracking-wider">코드</th>
|
||||
<th class="px-3 py-2 text-center text-sm font-semibold text-gray-700 uppercase tracking-wider">상태</th>
|
||||
<th class="px-3 py-2 text-center text-sm font-semibold text-gray-700 uppercase tracking-wider">유형</th>
|
||||
<th class="px-3 py-2 text-left text-sm font-semibold text-gray-700 uppercase tracking-wider">이메일</th>
|
||||
<th class="px-3 py-2 text-left text-sm font-semibold text-gray-700 uppercase tracking-wider">전화번호</th>
|
||||
<th class="px-3 py-2 text-center text-sm font-semibold text-gray-700 uppercase tracking-wider">연락처</th>
|
||||
<th class="px-3 py-2 text-center text-sm font-semibold text-gray-700 uppercase tracking-wider">사용자</th>
|
||||
<th class="px-3 py-2 text-center text-sm font-semibold text-gray-700 uppercase tracking-wider">부서</th>
|
||||
<th class="px-3 py-2 text-center text-sm font-semibold text-gray-700 uppercase tracking-wider">메뉴</th>
|
||||
<th class="px-3 py-2 text-center text-sm font-semibold text-gray-700 uppercase tracking-wider">역할</th>
|
||||
<th class="px-3 py-2 text-left text-sm font-semibold text-gray-700 uppercase tracking-wider">생성일</th>
|
||||
<th class="px-6 py-3 text-right text-sm font-semibold text-gray-700 uppercase tracking-wider">액션</th>
|
||||
<th class="px-3 py-2 text-center text-sm font-semibold text-gray-700 uppercase tracking-wider">사용량</th>
|
||||
<th class="px-3 py-2 text-center text-sm font-semibold text-gray-700 uppercase tracking-wider" colspan="2">관리</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="bg-white divide-y divide-gray-200">
|
||||
@@ -25,6 +25,9 @@
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||
{{ $tenant->id }}
|
||||
</td>
|
||||
<td class="px-3 py-2 whitespace-nowrap text-sm text-gray-500">
|
||||
{{ $tenant->created_at?->format('ymd') ?? '-' }}
|
||||
</td>
|
||||
<td class="px-3 py-2 whitespace-nowrap">
|
||||
<div class="text-sm font-medium text-gray-900 cursor-pointer hover:text-blue-600"
|
||||
data-context-menu="tenant"
|
||||
@@ -61,11 +64,11 @@
|
||||
{{ $typeLabels[$type] ?? $type }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-3 py-2 whitespace-nowrap text-sm text-gray-500">
|
||||
{{ $tenant->email ?? '-' }}
|
||||
</td>
|
||||
<td class="px-3 py-2 whitespace-nowrap text-sm text-gray-500">
|
||||
{{ $tenant->phone ?? '-' }}
|
||||
<td class="px-3 py-2 whitespace-nowrap">
|
||||
<div class="text-sm text-gray-900">{{ $tenant->email ?? '-' }}</div>
|
||||
@if($tenant->phone)
|
||||
<div class="text-xs text-gray-500">{{ $tenant->phone_formatted }}</div>
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-3 py-2 whitespace-nowrap text-sm text-center text-gray-900">
|
||||
{{ $tenant->users_count ?? 0 }}
|
||||
@@ -79,39 +82,55 @@
|
||||
<td class="px-3 py-2 whitespace-nowrap text-sm text-center text-gray-900">
|
||||
{{ $tenant->roles_count ?? 0 }}
|
||||
</td>
|
||||
<td class="px-3 py-2 whitespace-nowrap text-sm text-gray-500">
|
||||
{{ $tenant->created_at?->format('Y-m-d') ?? '-' }}
|
||||
<td class="px-3 py-2 whitespace-nowrap">
|
||||
<div class="flex flex-col items-center">
|
||||
<div class="w-24 bg-gray-200 rounded-full h-2 mb-1">
|
||||
<div class="h-2 rounded-full
|
||||
{{ $tenant->storage_badge_color === 'error' ? 'bg-red-500' : '' }}
|
||||
{{ $tenant->storage_badge_color === 'warning' ? 'bg-yellow-500' : '' }}
|
||||
{{ $tenant->storage_badge_color === 'success' ? 'bg-green-500' : '' }}"
|
||||
style="width: {{ min($tenant->storage_usage_percent, 100) }}%"></div>
|
||||
</div>
|
||||
<span class="text-xs text-gray-500">{{ $tenant->storage_used_formatted }}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium" onclick="event.stopPropagation()">
|
||||
@if($tenant->deleted_at)
|
||||
<!-- 삭제된 항목 - 복원은 일반관리자도 가능, 영구삭제는 슈퍼관리자만 -->
|
||||
<button onclick="confirmRestore({{ $tenant->id }}, '{{ $tenant->company_name }}')"
|
||||
class="text-green-600 hover:text-green-900 mr-3">
|
||||
복원
|
||||
</button>
|
||||
@if(auth()->user()?->is_super_admin)
|
||||
<button onclick="confirmForceDelete({{ $tenant->id }}, '{{ $tenant->company_name }}')"
|
||||
class="text-red-600 hover:text-red-900">
|
||||
영구삭제
|
||||
</button>
|
||||
@endif
|
||||
@if($tenant->deleted_at)
|
||||
{{-- 삭제된 항목 --}}
|
||||
<td class="px-2 py-2 whitespace-nowrap text-center text-sm font-medium" onclick="event.stopPropagation()">
|
||||
<button onclick="confirmRestore({{ $tenant->id }}, '{{ $tenant->company_name }}')"
|
||||
class="text-green-600 hover:text-green-900">
|
||||
복원
|
||||
</button>
|
||||
</td>
|
||||
<td class="px-2 py-2 whitespace-nowrap text-center text-sm font-medium" onclick="event.stopPropagation()">
|
||||
@if(auth()->user()?->is_super_admin)
|
||||
<button onclick="confirmForceDelete({{ $tenant->id }}, '{{ $tenant->company_name }}')"
|
||||
class="text-red-600 hover:text-red-900">
|
||||
영구삭제
|
||||
</button>
|
||||
@else
|
||||
<!-- 활성 항목 -->
|
||||
<a href="{{ route('tenants.edit', $tenant->id) }}"
|
||||
onclick="event.stopPropagation()"
|
||||
class="text-blue-600 hover:text-blue-900 mr-3">
|
||||
수정
|
||||
</a>
|
||||
<button onclick="confirmDelete({{ $tenant->id }}, '{{ $tenant->company_name }}')"
|
||||
class="text-red-600 hover:text-red-900">
|
||||
삭제
|
||||
</button>
|
||||
<span class="text-gray-400">-</span>
|
||||
@endif
|
||||
</td>
|
||||
@else
|
||||
{{-- 활성 항목 --}}
|
||||
<td class="px-2 py-2 whitespace-nowrap text-center text-sm font-medium" onclick="event.stopPropagation()">
|
||||
<a href="{{ route('tenants.edit', $tenant->id) }}"
|
||||
class="text-blue-600 hover:text-blue-900">
|
||||
수정
|
||||
</a>
|
||||
</td>
|
||||
<td class="px-2 py-2 whitespace-nowrap text-center text-sm font-medium" onclick="event.stopPropagation()">
|
||||
<button onclick="confirmDelete({{ $tenant->id }}, '{{ $tenant->company_name }}')"
|
||||
class="text-red-600 hover:text-red-900">
|
||||
삭제
|
||||
</button>
|
||||
</td>
|
||||
@endif
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="13" class="px-6 py-12 text-center text-gray-500">
|
||||
<td colspan="14" class="px-6 py-12 text-center text-gray-500">
|
||||
등록된 테넌트가 없습니다.
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
Reference in New Issue
Block a user