Files
sam-manage/resources/views/system/alerts/index.blade.php
권혁성 a559fa2303 feat:시스템 알림 AI 분석용 복사 버튼 추가
- StatAlert 모델에 ai_analysis_summary accessor 추가
- 알림 상세 아코디언에 'AI 분석용 복사' 버튼 추가
- 클립보드 복사 시 심각도/도메인/유형/메시지 등 포맷팅

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 11:20:47 +09:00

243 lines
12 KiB
PHP

@extends('layouts.app')
@section('title', '시스템 알림')
@section('content')
<!-- 페이지 헤더 -->
<div class="flex justify-between items-center mb-6">
<h1 class="text-2xl font-bold text-gray-800">시스템 알림</h1>
<div class="flex items-center gap-2">
@if($stats['unread'] > 0)
<button hx-post="{{ route('system.alerts.read-all') }}"
hx-confirm="모든 알림을 읽음 처리하시겠습니까?"
class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg text-sm">
전체 읽음 ({{ $stats['unread'] }})
</button>
@endif
</div>
</div>
<!-- 통계 카드 -->
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-6">
<div class="bg-white rounded-lg shadow-sm p-4">
<div class="text-sm text-gray-500">전체 알림</div>
<div class="text-2xl font-bold text-gray-800">{{ number_format($stats['total']) }}</div>
</div>
<div class="bg-white rounded-lg shadow-sm p-4">
<div class="text-sm text-gray-500">미읽음</div>
<div class="text-2xl font-bold text-blue-600">{{ number_format($stats['unread']) }}</div>
</div>
<div class="bg-white rounded-lg shadow-sm p-4">
<div class="text-sm text-gray-500">미해결</div>
<div class="text-2xl font-bold text-yellow-600">{{ number_format($stats['unresolved']) }}</div>
</div>
<div class="bg-white rounded-lg shadow-sm p-4">
<div class="text-sm text-gray-500">긴급 (미해결)</div>
<div class="text-2xl font-bold text-red-600">{{ number_format($stats['critical']) }}</div>
</div>
</div>
<!-- 필터 -->
<div class="bg-white rounded-lg shadow-sm p-4 mb-6">
<form method="GET" class="flex flex-wrap gap-4 items-end">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">도메인</label>
<select name="domain" onchange="this.form.submit()" class="border rounded-lg px-3 py-2 text-sm">
<option value="">전체</option>
@foreach($domains as $domain)
<option value="{{ $domain }}" {{ request('domain') === $domain ? 'selected' : '' }}>
{{ match($domain) {
'backup' => '백업',
'sales' => '매출',
'finance' => '재무',
'production' => '생산',
'system' => '시스템',
default => $domain,
} }}
</option>
@endforeach
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">심각도</label>
<select name="severity" onchange="this.form.submit()" class="border rounded-lg px-3 py-2 text-sm">
<option value="">전체</option>
<option value="critical" {{ request('severity') === 'critical' ? 'selected' : '' }}>긴급</option>
<option value="warning" {{ request('severity') === 'warning' ? 'selected' : '' }}>경고</option>
<option value="info" {{ request('severity') === 'info' ? 'selected' : '' }}>정보</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">상태</label>
<select name="status" onchange="this.form.submit()" class="border rounded-lg px-3 py-2 text-sm">
<option value="">전체</option>
<option value="unread" {{ request('status') === 'unread' ? 'selected' : '' }}>미읽음</option>
<option value="unresolved" {{ request('status') === 'unresolved' ? 'selected' : '' }}>미해결</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">시작일</label>
<input type="date" name="date_from" value="{{ request('date_from') }}"
class="border rounded-lg px-3 py-2 text-sm">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">종료일</label>
<input type="date" name="date_to" value="{{ request('date_to') }}"
class="border rounded-lg px-3 py-2 text-sm">
</div>
<div>
<button type="submit" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg text-sm">
검색
</button>
<a href="{{ route('system.alerts.index') }}" class="bg-gray-200 hover:bg-gray-300 text-gray-700 px-4 py-2 rounded-lg text-sm ml-1">
초기화
</a>
</div>
</form>
</div>
<!-- 알림 목록 -->
<div class="bg-white rounded-lg shadow-sm overflow-hidden">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase w-20">심각도</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase w-20">도메인</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">제목</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase w-36">시간</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase w-16">읽음</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase w-16">해결</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase w-32">작업</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
@forelse($alerts as $alert)
<tr class="{{ !$alert->is_read ? 'bg-blue-50' : '' }} hover:bg-gray-50 cursor-pointer"
onclick="toggleDetail({{ $alert->id }})">
<td class="px-4 py-3 whitespace-nowrap">
<span class="px-2 py-1 text-xs font-medium rounded {{ $alert->severity_color }}">
{{ $alert->severity_label }}
</span>
</td>
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-600">
{{ $alert->domain_label }}
</td>
<td class="px-4 py-3 text-sm text-gray-900 {{ !$alert->is_read ? 'font-semibold' : '' }}">
{{ $alert->title }}
</td>
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-500">
{{ $alert->created_at->format('m/d H:i') }}
</td>
<td class="px-4 py-3 whitespace-nowrap text-center">
@if($alert->is_read)
<span class="text-green-500 text-xs">O</span>
@else
<span class="text-gray-300 text-xs">-</span>
@endif
</td>
<td class="px-4 py-3 whitespace-nowrap text-center">
@if($alert->is_resolved)
<span class="text-green-500 text-xs">O</span>
@else
<span class="text-gray-300 text-xs">-</span>
@endif
</td>
<td class="px-4 py-3 whitespace-nowrap text-sm" onclick="event.stopPropagation()">
@if(!$alert->is_read)
<button hx-post="{{ route('system.alerts.read', $alert->id) }}"
class="text-blue-600 hover:text-blue-800 text-xs mr-2">
읽음
</button>
@endif
@if(!$alert->is_resolved)
<button hx-post="{{ route('system.alerts.resolve', $alert->id) }}"
hx-confirm="이 알림을 해결 처리하시겠습니까?"
class="text-green-600 hover:text-green-800 text-xs">
해결
</button>
@else
<span class="text-gray-400 text-xs">{{ $alert->resolved_at?->format('m/d H:i') }}</span>
@endif
</td>
</tr>
<!-- 상세 내용 -->
<tr id="detail-{{ $alert->id }}" class="hidden">
<td colspan="7" class="px-4 py-4 bg-slate-50 border-t border-b border-slate-200">
<div class="text-sm text-gray-700">
<div class="mb-2 flex items-center justify-between">
<div>
<span class="font-medium text-gray-500">유형:</span>
{{ $alert->alert_type }}
</div>
<button onclick="event.stopPropagation(); copyAiAnalysis({{ $alert->id }})"
class="inline-flex items-center gap-1 px-3 py-1.5 bg-purple-600 hover:bg-purple-700 text-white text-xs rounded-lg transition">
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"/>
</svg>
AI 분석용 복사
</button>
</div>
@if($alert->message)
<div class="mb-2">
<span class="font-medium text-gray-500">상세:</span>
<pre class="mt-1 bg-gray-900 text-green-400 text-xs rounded-lg p-3 font-mono whitespace-pre-wrap">{{ $alert->message }}</pre>
</div>
@endif
@if($alert->current_value || $alert->threshold_value)
<div class="flex gap-4 text-xs text-gray-500">
<span><strong>현재값:</strong> {{ $alert->current_value }}</span>
<span><strong>임계값:</strong> {{ $alert->threshold_value }}</span>
</div>
@endif
</div>
</td>
</tr>
@empty
<tr>
<td colspan="7" class="px-4 py-8 text-center text-gray-500">
시스템 알림이 없습니다.
</td>
</tr>
@endforelse
</tbody>
</table>
<!-- 페이지네이션 -->
@if($alerts->hasPages())
<div class="px-4 py-3 border-t">
{{ $alerts->links() }}
</div>
@endif
</div>
<!-- AI 분석용 데이터 -->
@foreach($alerts as $alert)
<textarea id="ai-analysis-{{ $alert->id }}" class="hidden">{{ $alert->ai_analysis_summary }}</textarea>
@endforeach
<script>
function toggleDetail(id) {
const detail = document.getElementById('detail-' + id);
if (detail) {
detail.classList.toggle('hidden');
}
}
function copyAiAnalysis(id) {
const textarea = document.getElementById('ai-analysis-' + id);
if (textarea) {
navigator.clipboard.writeText(textarea.value).then(() => {
alert('AI 분석용 내용이 클립보드에 복사되었습니다.\nClaude나 ChatGPT에 붙여넣기 하세요.');
}).catch(err => {
console.error('복사 실패:', err);
alert('복사에 실패했습니다.');
});
}
}
</script>
@endsection