2026-02-11 16:17:15 +09:00

346 lines
14 KiB
PHP

@extends('admin.layouts.app')
@section('title', '회원 관리')
@section('page_title', '회원 관리')
@section('page_desc', '회원상태/기본정보/인증을 조회합니다.')
@section('content_class', 'a-content--full')
@push('head')
<style>
/* members index only */
.bar{display:flex;justify-content:space-between;align-items:flex-end;gap:12px;flex-wrap:wrap;}
.bar__left .t{font-weight:900;font-size:16px;}
.bar__left .d{font-size:12px;margin-top:4px;}
.bar__right{display:flex;gap:10px;flex-wrap:wrap;align-items:flex-end;}
.filters{display:flex;gap:8px;flex-wrap:wrap;align-items:flex-end;}
.filters .q{width:260px;}
.filters .qf{width:140px;}
.filters .st{width:220px;}
.filters .dt{width:150px;}
.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);text-decoration:none;}
.lbtn--ghost{background:transparent;}
.lbtn--sm{padding:7px 10px;font-size:12px;border-radius:11px;}
.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);}
.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--bad{border-color:rgba(244,63,94,.35);background:rgba(244,63,94,.10);}
.pill--warn{border-color:rgba(245,158,11,.35);background:rgba(245,158,11,.12);}
.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;}
.table td{vertical-align:top;}
.badges{display:flex;gap:6px;flex-wrap:wrap;}
.nameLine{font-weight:900;}
.ageBox{display:flex;gap:8px;flex-wrap:wrap;align-items:center;}
.ageChip{padding:4px 8px;border-radius:999px;border:1px solid rgba(255,255,255,.10);background:rgba(255,255,255,.06);font-size:12px;}
</style>
@endpush
@section('content')
@php
// stat_3 매핑 (1~6)
$stat3Map = $stat3Map ?? [
'1' => '1. 로그인정상',
'2' => '2. 로그인만가능',
'3' => '3. 로그인불가(접근금지)',
'4' => '4. 탈퇴완료(아이디보관)',
'5' => '5. 탈퇴완료',
'6' => '6. 휴면회원',
];
// stat_3 pill 색상
$stat3Pill = function(string $s3): string {
return match ($s3) {
'1' => 'pill--ok',
'2' => 'pill--warn',
'3' => 'pill--bad',
'4', '5' => 'pill--muted',
'6' => 'pill--warn',
default => 'pill--muted',
};
};
// 성별 라벨
$genderLabel = function(?string $g): string {
$g = (string)($g ?? '');
return match ($g) {
'1' => '남자',
'0' => '여자',
default => '-',
};
};
// 세는나이: 현재년도 - 출생년도 + 1
$koreanAge = function($birth): ?int {
$b = (string)($birth ?? '');
if ($b === '' || $b === '0000-00-00') return null;
try {
$y = \Carbon\Carbon::parse($b)->year;
if ($y < 1900) return null;
return (int) now()->year - $y + 1;
} catch (\Throwable $e) {
return null;
}
};
// 만나이: 생일 지났으면 diffInYears 그대로, 아니면 -1 반영되는 Carbon diffInYears 사용
$manAge = function($birth): ?int {
$b = (string)($birth ?? '');
if ($b === '' || $b === '0000-00-00') return null;
try {
$dob = \Carbon\Carbon::parse($b);
if ($dob->year < 1900) return null;
return (int) $dob->diffInYears(now()); // full years
} catch (\Throwable $e) {
return null;
}
};
// authMap 어떤 형태든 Y만 뽑기 (email/cell/account만, vow/otp는 제외)
$pickAuthOk = function($auth): array {
$out = ['email'=>false,'cell'=>false,'account'=>false];
$stateOf = function($v): string {
if (is_string($v)) return $v;
if (is_array($v)) {
if (isset($v['auth_state'])) return (string)$v['auth_state'];
if (isset($v['state'])) return (string)$v['state'];
return '';
}
if (is_object($v)) {
if (isset($v->auth_state)) return (string)$v->auth_state;
if (isset($v->state)) return (string)$v->state;
return '';
}
return '';
};
// assoc
if (is_array($auth) && (array_key_exists('email',$auth) || array_key_exists('cell',$auth) || array_key_exists('account',$auth))) {
foreach (['email','cell','account'] as $t) {
$out[$t] = ($stateOf($auth[$t] ?? '') === 'Y');
}
return $out;
}
// rows list
if (is_array($auth)) {
foreach ($auth as $row) {
$type = '';
$state = '';
if (is_array($row)) {
$type = (string)($row['auth_type'] ?? $row['type'] ?? '');
$state = (string)($row['auth_state'] ?? $row['state'] ?? '');
} elseif (is_object($row)) {
$type = (string)($row->auth_type ?? $row->type ?? '');
$state = (string)($row->auth_state ?? $row->state ?? '');
}
if (in_array($type, ['email','cell','account'], true) && $state === 'Y') {
$out[$type] = true;
}
}
return $out;
}
return $out;
};
$qf = (string)($filters['qf'] ?? 'all');
@endphp
<div class="a-card" style="padding:16px; margin-bottom:16px;">
<div class="bar">
<div class="bar__left">
<div class="t">회원 관리</div>
<div class="a-muted d">회원상태/기본정보/인증을 조회합니다.</div>
</div>
<div class="bar__right">
<form method="GET" action="{{ route('admin.members.index') }}" class="filters">
<div>
<select class="a-input qf" name="qf" id="qf">
<option value="mem_no" {{ $qf==='mem_no'?'selected':'' }}>회원번호</option>
<option value="name" {{ $qf==='name'?'selected':'' }}>이름</option>
<option value="email" {{ $qf==='email'?'selected':'' }}>이메일</option>
<option value="phone" {{ $qf==='phone'?'selected':'' }}>휴대폰</option>
</select>
</div>
<div>
<input class="a-input q"
id="q"
name="q"
value="{{ $filters['q'] ?? '' }}"
placeholder="회원번호/이름/이메일/휴대폰">
</div>
<div>
<select class="a-input st" name="stat_3">
<option value="">회원상태 전체</option>
@foreach($stat3Map as $k=>$label)
<option value="{{ $k }}" {{ (($filters['stat_3'] ?? '')===(string)$k)?'selected':'' }}>
{{ $label }}
</option>
@endforeach
</select>
</div>
<div><input class="a-input dt" type="date" name="date_from" value="{{ $filters['date_from'] ?? '' }}"></div>
<div><input class="a-input dt" type="date" name="date_to" value="{{ $filters['date_to'] ?? '' }}"></div>
<div style="display:flex; gap:8px; align-items:flex-end;">
<button class="lbtn lbtn--ghost" type="submit">검색</button>
<a class="lbtn lbtn--ghost" href="{{ route('admin.members.index') }}">초기화</a>
</div>
</form>
</div>
</div>
</div>
<div class="a-card" style="padding:16px;">
<div class="a-muted" style="margin-bottom:10px;"> <b>{{ $page->total() }}</b></div>
<div style="overflow:auto;">
<table class="a-table table" style="width:100%; min-width:1100px;">
<thead>
<tr>
<th style="width:90px;">MEM_NO</th>
<th style="width:220px;">성명</th>
<th style="width:180px;">성별/나이</th>
<th>이메일</th>
<th style="width:200px;">회원상태</th>
<th style="width:240px;">인증(Y만)</th>
<th style="width:180px;">가입일</th>
<th style="width:90px; text-align:right;">관리</th>
</tr>
</thead>
<tbody>
@forelse($page as $m)
@php
$no = (int)($m->mem_no ?? 0);
$gender = $genderLabel($m->gender ?? null);
$ageK = $koreanAge($m->birth ?? null);
$ageM = $manAge($m->birth ?? null);
$s3 = (string)($m->stat_3 ?? '');
$s3Label = $stat3Map[$s3] ?? ($s3 !== '' ? $s3 : '-');
$s3Pill = $stat3Pill($s3);
$authRaw = $authMap[$no] ?? ($authMap[(string)$no] ?? null);
$ok = $pickAuthOk($authRaw);
$joinAt = $m->dt_reg ?? '-';
@endphp
<tr>
<td class="a-muted">{{ $no }}</td>
{{-- 성명 --}}
<td><div class="nameLine">{{ $m->name ?? '-' }}</div></td>
{{-- 성별/나이(세는나이 + 만나이) --}}
<td>
<div class="ageBox">
<span class="mono">{{ $gender }}</span>
@if($ageK !== null || $ageM !== null)
<span class="ageChip">
{{ $ageK !== null ? "{$ageK}" : '-' }}
<span class="a-muted" style="margin-left:6px;">( {{ $ageM !== null ? "{$ageM}" : '-' }})</span>
</span>
@else
<span class="a-muted">-</span>
@endif
</div>
</td>
{{-- 이메일 --}}
<td>
@if(!empty($m->email))
<span class="mono">{{ $m->email }}</span>
@else
<span class="a-muted">-</span>
@endif
</td>
{{-- 회원상태(색상 분기) --}}
<td>
<span class="pill {{ $s3Pill }}"> {{ $s3Label }}</span>
</td>
{{-- 인증: Y만 표시 --}}
<td>
<div class="badges">
@if($ok['email'])
<span class="a-chip">이메일</span>
@endif
@if($ok['cell'])
<span class="a-chip">휴대폰</span>
@endif
@if($ok['account'])
<span class="a-chip">계좌</span>
@endif
@if(!$ok['email'] && !$ok['cell'] && !$ok['account'])
<span class="a-muted">-</span>
@endif
</div>
</td>
{{-- 가입일 --}}
<td class="a-muted">{{ $joinAt }}</td>
<td style="text-align:right;">
<a class="lbtn lbtn--ghost lbtn--sm"
href="{{ route('admin.members.show', ['memNo'=>$no]) }}">
보기
</a>
</td>
</tr>
@empty
<tr>
<td colspan="8" class="a-muted" style="padding:18px;">데이터가 없습니다.</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<div style="margin-top:12px;">
{{ $page->onEachSide(1)->links('vendor.pagination.admin') }}
</div>
</div>
<script>
(function(){
const qf = document.getElementById('qf');
const q = document.getElementById('q');
if (!qf || !q) return;
const ph = {
all: '회원번호/이름/이메일/휴대폰',
mem_no: '회원번호',
name: '이름',
email: '이메일',
phone: '휴대폰(숫자만)',
};
const apply = () => {
const v = qf.value || 'all';
q.placeholder = ph[v] || ph.all;
};
qf.addEventListener('change', apply);
apply();
})();
</script>
@endsection