225 lines
9.9 KiB
PHP

@extends('admin.layouts.app')
@section('title', 'SMS 발송 이력')
@section('page_title', 'SMS 발송 이력')
@section('page_desc', '배치 단위로 조회합니다.')
@php
$STATUS_LABELS = [
'scheduled' => '예약대기',
'queued' => '대기',
'submitting' => '전송중',
'submitted' => '전송완료',
'partial' => '일부실패',
'failed' => '실패',
'canceled' => '취소',
];
$MODE_LABELS = [
'one' => '단건',
'many' => '대량',
'template' => '템플릿',
];
@endphp
@push('head')
<style>
/* logs list page only */
.logbar{display:flex; gap:10px; flex-wrap:wrap; align-items:end;}
.logbar__grow{flex:1; min-width:260px;}
.logbar__actions{display:flex; gap:8px; align-items:center;}
.lbtn{padding:8px 12px;font-size:13px;border-radius:12px;line-height:1.1;text-decoration:none;display:inline-flex;align-items:center;justify-content:center;gap:6px;
border:1px solid rgba(255,255,255,.10);background:rgba(255,255,255,.06);color:inherit;cursor:pointer;}
.lbtn:hover{background:rgba(255,255,255,.10);}
.lbtn--primary{background:rgba(59,130,246,.88);border-color:rgba(59,130,246,.95);color:#fff;}
.lbtn--primary:hover{background:rgba(59,130,246,.98);}
.lbtn--ghost{background:transparent;}
.pill{display:inline-flex;align-items:center;gap:6px;padding:6px 10px;border-radius:999px;font-size:12px;
border:1px solid rgba(255,255,255,.10);background:rgba(255,255,255,.06);}
.pill--ok{border-color:rgba(34,197,94,.35);background:rgba(34,197,94,.12);}
.pill--warn{border-color:rgba(245,158,11,.35);background:rgba(245,158,11,.12);}
.pill--bad{border-color:rgba(244,63,94,.35);background:rgba(244,63,94,.10);}
.pill--muted{opacity:.9}
.mono{padding:4px 8px;border-radius:10px;background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.10);
font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:12px;}
.msg-clip{max-width:520px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; opacity:.95;}
.table td{vertical-align:top;}
.a-input[type="date"]{
-webkit-appearance: auto !important;
appearance: auto !important;
min-height: 36px;
}
/* (다크톤 UI에서 달력 아이콘이 안 보이면) */
.a-input[type="date"]::-webkit-calendar-picker-indicator{
opacity: .85;
cursor: pointer;
}
.a-input[type="date"]::-webkit-calendar-picker-indicator:hover{
opacity: 1;
}
#logFilterForm .a-input[type="date"]{
width: 140px; /* 날짜만 보일 정도 */
padding-left: 10px;
padding-right: 10px;
}
#logFilterForm input[name="q"]{
max-width: 320px; /* 검색창 줄이기 */
}
/* (선택) 셀렉트도 조금만 슬림하게 */
#logFilterForm select.a-input{
min-width: 140px;
}
</style>
@endpush
@section('content')
<div class="a-card" style="padding:16px; margin-bottom:16px;">
<form method="GET" action="{{ route('admin.sms.logs') }}" class="logbar" id="logFilterForm">
<div style="min-width:170px;">
<div class="a-muted" style="margin-bottom:6px;">상태</div>
<select class="a-input" name="status">
<option value="">전체</option>
@foreach(['scheduled','queued','submitting','submitted','partial','failed','canceled'] as $st)
<option value="{{ $st }}" @selected(request('status')===$st)>
{{ $STATUS_LABELS[$st] ?? $st }}
</option>
@endforeach
</select>
</div>
<div style="min-width:140px;">
<div class="a-muted" style="margin-bottom:6px;">모드</div>
<select class="a-input" name="send_mode">
<option value="">전체</option>
@foreach(['one','many','template'] as $m)
<option value="{{ $m }}" @selected(request('send_mode')===$m)>
{{ $MODE_LABELS[$m] ?? $m }}
</option>
@endforeach
</select>
</div>
{{-- 기간: 달력 선택 (from/to) --}}
<div style="min-width:260px;">
<div class="a-muted" style="margin-bottom:6px;">기간</div>
<div style="display:flex; gap:8px; align-items:center; flex-wrap:wrap;">
<input class="a-input" type="date" name="date_from" id="dateFrom" value="{{ request('date_from') }}">
<span class="a-muted">~</span>
<input class="a-input" type="date" name="date_to" id="dateTo" value="{{ request('date_to') }}">
<button type="button" class="lbtn lbtn--ghost" id="dateClear">초기화</button>
</div>
</div>
<div style="min-width:260px; max-width:340px;">
<div class="a-muted" style="margin-bottom:6px;">검색</div>
<input class="a-input" name="q" value="{{ request('q') }}" placeholder="문구/발신번호/IP/작성자 등">
</div>
<div class="logbar__actions">
<button class="lbtn lbtn--primary" type="submit">조회</button>
<a class="lbtn" href="{{ route('admin.sms.send') }}">발송</a>
<a class="lbtn lbtn--ghost" href="{{ route('admin.sms.logs') }}">필터 초기화</a>
</div>
</form>
</div>
<div class="a-card" style="padding:16px;">
<div style="display:flex; justify-content:space-between; align-items:center; gap:10px; flex-wrap:wrap; margin-bottom:10px;">
<div class="a-muted"> <b>{{ $batches->total() }}</b></div>
<div class="a-muted">
@if((string)request('date_from') !== '' || (string)request('date_to') !== '')
기간: <b>{{ request('date_from') ?: '무제한' }}</b> ~ <b>{{ request('date_to') ?: '무제한' }}</b>
@endif
@if((string)request('q') !== '')
&nbsp;/ 검색어: <b>{{ request('q') }}</b>
@endif
</div>
</div>
<div style="overflow:auto;">
<table class="a-table table" style="width:100%; min-width:1100px;">
<thead>
<tr>
<th style="width:80px;">Batch</th>
<th style="width:160px;">생성일시</th>
<th style="width:160px;">작성자</th>
<th style="width:90px;">모드</th>
<th style="width:170px;">예약</th>
<th style="width:110px;">건수</th>
<th style="width:140px;">상태</th>
<th>문구</th>
</tr>
</thead>
<tbody>
@forelse($batches as $b)
@php
$st = (string)$b->status;
$pillClass = match ($st) {
'submitted' => 'pill--ok',
'partial','submitting','queued','scheduled' => 'pill--warn',
'failed','canceled' => 'pill--bad',
default => 'pill--muted',
};
$stLabel = $STATUS_LABELS[$st] ?? $st;
@endphp
<tr>
<td>
<a class="mono" style="text-decoration:none;"
href="{{ route('admin.sms.logs.show', ['batchId'=>$b->id]) }}">#{{ $b->id }}</a>
</td>
<td class="a-muted">{{ $b->created_at }}</td>
<td style="font-weight:700;">{{ $b->admin_name ?? ('#'.$b->admin_user_id) }}</td>
<td><span class="mono">{{ $MODE_LABELS[$b->send_mode] ?? $b->send_mode }}</span></td>
<td class="a-muted">{{ $b->scheduled_at ?? '-' }}</td>
<td><b>{{ $b->valid_count }}</b>/{{ $b->total_count }}</td>
<td><span class="pill {{ $pillClass }}"> {{ $stLabel }}</span></td>
<td><div class="msg-clip">{{ $b->message_raw }}</div></td>
</tr>
@empty
<tr><td colspan="8" class="a-muted">데이터가 없습니다.</td></tr>
@endforelse
</tbody>
</table>
</div>
<div style="margin-top:12px;">
{{ $batches->links() }}
</div>
</div>
@push('scripts')
<script>
(() => {
const from = document.getElementById('dateFrom');
const to = document.getElementById('dateTo');
const clear= document.getElementById('dateClear');
if (!from || !to) return;
// ✅ 클릭해도 안 뜨는 환경에서 강제 오픈
[from, to].forEach(el => {
el.addEventListener('click', () => {
if (typeof el.showPicker === 'function') el.showPicker();
});
});
// To가 비어있고 From만 찍으면 To를 From으로 자동 세팅(편의)
from.addEventListener('change', () => {
if (from.value && !to.value) to.value = from.value;
});
clear?.addEventListener('click', () => {
from.value = '';
to.value = '';
});
})();
</script>
@endpush
@endsection