220 lines
10 KiB
PHP
220 lines
10 KiB
PHP
@extends('admin.layouts.app')
|
|
|
|
@section('title', 'SMS 발송 이력')
|
|
@section('page_title', 'SMS 발송 이력')
|
|
@section('page_desc', '배치 단위로 조회합니다.')
|
|
@php
|
|
$STATUS_LABELS = $labels ?? [];
|
|
$MODE_LABELS = $modeLabels ?? [];
|
|
@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(array_keys($STATUS_LABELS) as $st)
|
|
<option value="{{ $st }}" @selected(request('status')===$st)>{{ $STATUS_LABELS[$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(array_keys($MODE_LABELS) as $m)
|
|
<option value="{{ $m }}" @selected(request('send_mode')===$m)>{{ $MODE_LABELS[$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') !== '')
|
|
/ 검색어: <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 class="row-link" data-url="{{ route('admin.sms.logs.show', ['batchId'=>$b->id]) }}">
|
|
<td>
|
|
<a href="{{ route('admin.sms.logs.show', ['batchId'=>$b->id]) }}"
|
|
class="batch-link-btn mono">
|
|
#{{ $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->onEachSide(1)->links('vendor.pagination.admin') }}
|
|
</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 = '';
|
|
});
|
|
})();
|
|
|
|
document.addEventListener('click', (e) => {
|
|
const tr = e.target.closest('tr.row-link');
|
|
if (!tr) return;
|
|
|
|
// a, button, input 등 "클릭 가능한 요소"를 눌렀으면 기본 동작 존중
|
|
if (e.target.closest('a,button,input,select,textarea,label')) return;
|
|
|
|
const url = tr.getAttribute('data-url');
|
|
if (url) window.location.href = url;
|
|
});
|
|
</script>
|
|
@endpush
|
|
@endsection
|