출격 1차 baseline 발견 : 진단 단(영업원→사장님)은 100% 동작, 단 TM dispatch UI 어려워서 1만원 발송 0건.
이 작업의 핵심 = TM이 사진/데이터 빠르게 확인 + 전화 + 발송까지 한 화면 흐름 .
출격 진행 중이라 staging only push , main 머지는 자동님 검토 후.
01. 3 stage 분할
STAGE 4 · ~2-3h · 1 commit
LeadDetailClient 한눈 UX
16섹션 → Top fold(7개) + Collapsible(10개) 재배치. 영업원이 폰에서 lead 클릭 시 스크롤 0회에 매장 피드백·매장정보·사진 보임. 자세히는
기존 STAGE 4 작업지시서 참조.
STAGE 5-A · ~1.5-2h · 1 commit
PendingQueue 카드 향상 (★ 가장 critical)
TM이 카드 보면서 사진 즉시 확인 → [📞 전화] 큰 버튼 → 콜 후 outcome 빠른 입력 → confirmed 시 [💸 발송] 강조. 출격 dispatch bottleneck 해결.
STAGE 5-B · ~2h · 2 commits
agent 성과 페이지 신설
/keeper-out/admin/agents 목록 + /keeper-out/admin/agents/[code] detail. 각 agent (방민아·김윤주·자동님) 성과 stat. 기존 leaderboard와 통합 또는 별도.
02. STAGE 5-A — PendingQueue 카드 향상 스펙
현재 카드 (자동님 발화: "알아보기 힘듦")
[기존 PendingQueue.tsx 카드]
- 사장님 이름·매장·영업원 코드+이름·진단 시각·pain·카메라·요금 (텍스트만)
- [📞 콜 시작] 작은 버튼
- [📋 진단 미리보기] 작은 버튼
- outcome 드롭다운 6개 작음
- [💸 일괄 발송] 위치 모호
신 카드 (한눈 + 큰 액션)
┌─────────────────────────────────────────────┐
│ [Hero] │
│ 김밥천국 정왕역점 🔥 18:33 scenario B │
│ 사장님 테스트 │
│ │
│ [매장 사진 thumbnail row] │
│ [📷 사진1] [📷 사진2] ← 클릭 시 lightbox │
│ │
│ [매장 정보 5 stat] │
│ 보안: 캡스 │ 3년 │ 카메라 4대 │ 월 35,000원 │
│ │
│ [Pain points chip] │
│ 🔴 출동지연 → SLA 보증 강조 │
│ 🔴 요금부담 → 결합 영업 │
│ 🔴 약정부담 → 위약금 대납 시나리오 │
│ │
│ ┌─────────────────────────────────────────┐ │
│ │ 📞 010-4881-8346 ← tel: 큰 버튼 │ │
│ └─────────────────────────────────────────┘ │
│ │
│ [outcome 빠른 액션 6 버튼] │
│ [✅확인] [📵부재] [❌점주아님] │
│ [🚫거절] [⚠️의심] [📞오류] │
│ │
│ ★ outcome=confirmed 시: │
│ ┌─────────────────────────────────────────┐ │
│ │ 💸 1만원 N페이 발송 ← 강조 큰 버튼 │ │
│ └─────────────────────────────────────────┘ │
└─────────────────────────────────────────────┘
구현 디테일
사진 thumbnail lead.photo_urls.slice(0, 2) 인라인 + 클릭 시 새 탭 (target="_blank") — lightbox는 옵션
[📞 전화] 버튼 <a href={`tel:${phone}`}> 큰 CTA. 모바일에서 즉시 전화. 데스크톱에서 외부 dialer.
outcome 빠른 액션 드롭다운 대신 버튼 6개 그리드 2열×3 . 이모지 + 라벨. 큰 click target.
[💸 발송] 노출 룰 tm_outcome === 'confirmed' AND payout_status === 'pending_review' 시만 큰 버튼 표시. 그 외 hide.
지급 endpoint 이미 박힌 POST /api/keeper-out/admin/leads/[id]/dispatch 호출 (df00a7a)
발송 후 UI 카드 완료 상태로 fade out + 큐에서 제거 (refresh)
03. STAGE 5-B — agent 성과 페이지 스펙
신규 파일
변경 파일 역할
신규 app/keeper-out/admin/agents/page.tsx RSC — agent 목록 fetch
신규 app/keeper-out/admin/agents/AgentsClient.tsx "use client" — 목록 + leaderboard
신규 app/keeper-out/admin/agents/[code]/page.tsx RSC — agent 1명 detail
신규 app/keeper-out/admin/agents/[code]/AgentDetailClient.tsx "use client" — stat + 매장 리스트
신규 app/api/keeper-out/admin/agents/route.ts GET — agent 목록 + 성과 aggregation
신규 app/api/keeper-out/admin/agents/[code]/route.ts GET — agent 1명 + 본인 leads
성과 query 패턴
-- agent별 stat (오늘 + 이번주 + 누적)
SELECT
count(*) FILTER (WHERE created_at::date = current_date) as today,
count(*) FILTER (WHERE created_at > date_trunc('week', current_date)) as this_week,
count(*) FILTER (WHERE status = 'installed') as installed_total,
sum(expected_benefit) FILTER (WHERE status = 'installed') as benefit_sum
FROM keeper_outbound_leads
WHERE agent_1 = '' OR agent_2 = '' OR agent_3 = '';
UI 디자인 (모바일 우선)
[Agent Detail — /keeper-out/admin/agents/Mn5aB]
┌─────────────────────────────────────────────┐
│ 방민아 (Mn5aB) 🟢 active │
│ promoter · day_cap 50 · week_cap 200 │
│ │
│ [오늘] [이번주] [누적] │
│ 1건 1건 1건 │
│ 진단 진단 진단 │
│ │
│ [본인 매장 리스트] │
│ ┌─────────────────────────────────────┐ │
│ │ 김밥천국 정왕역점 (B 92만) │ │
│ │ pending_review · 18:33 │ │
│ │ → /keeper-out/admin/leads/{id} 링크 │ │
│ └─────────────────────────────────────┘ │
└─────────────────────────────────────────────┘
04. 가드레일 (이번 작업 공통)
청록 #009090·#00b4b4·#006B6B·#007575·#e6f7f7 사용 X (admin = 키퍼 오렌지)
app/diagnose/* 영역 수정 X
"한화" / "키퍼" / "하나비전" 사용자 face 텍스트 X
main 직접 push X — staging only
next.config / package.json 수정 X
untracked 13개 (CLAUDE.md / catchrank / landing / context / data / scripts / docs / public/assets) 보존
색깔: 키퍼 오렌지 #ff5c00 / hover #cc4900 / light #fff6f1 / border #e8e4db
기존 endpoint(/api/keeper-out/admin/pending·/dispatch·/tm-call) 재사용 — 새 endpoint는 agents만
PostgREST embed 패턴 차용 (LEAD_DETAIL_SELECT 처럼)
mobile-first (max-w-md 또는 max-w-2xl, 큰 click target)
05. 코덱스 프롬프트 (복붙)
📋 같은 코덱스 세션에 붙여넣기
STAGE 4+5 — TM 워크플로 + agent 성과. 출격 1차 baseline 정합성 작업.
[CONTEXT]
- 리포: /home/tlswkehd/projects/keeper-direct
- 브랜치: staging
- main HEAD: bd23ceb (P1+P1.5+P2+STAGE 1+STAGE 2 v3 + STAGE 2.5 wiring + balerion forward 모두 머지됨)
- 출격 진행 중: 5/6 11시 시작. 실제 lead 1건 진입 (방민아=Mn5aB attribution)
- 자동님 발화: "텔레마케터가 대시보드를 보고 사진데이터하고 매장데이터가 잘 들어왓는지 확인한 후에, 전화를 걸 수 있는 uiux가 필요" + "각 에이전트별 성과를 볼수있는 에이전트별 페이지가 필요"
[현재 dispatch bottleneck]
- 진단 단(영업원→사장님)은 100% 동작
- balance 50만원 그대로 — TM이 dispatch UI 어려워서 [💸 발송] 못 누름
- pending_review row 1건 (김밥천국 정왕역점) 큐에 있음
═══════════════════════════════════════════════
STAGE 4 — LeadDetailClient 한눈 UX (1 commit, ~2-3h)
═══════════════════════════════════════════════
기존 STAGE 4 보고서 그대로 진행:
- LeadDetailClient.tsx 16섹션 → Top fold(7) + Collapsible(10) 재배치
- 신규 컴포넌트 app/keeper-out/admin/components/CollapsibleSection.tsx
- 자세한 스펙은 docs/keeper-out/anti-abuse/06-p3-implementation-brief.md §3-2 + STAGE 4 보고서
Top fold 7개:
1. Hero / 2. 매장 정보 / 4. Pain Points / 5. 사진 갤러리 /
6. 영업자 / 7. 지급 요약 / 12. Status 진행
Collapsible 10개:
3·8·9·10·11·13·14·15·16·17
커밋: feat(keeper-out/admin): lead detail 한눈 UX (Top fold 7 + Collapsible 10)
═══════════════════════════════════════════════
STAGE 5-A — PendingQueue 카드 향상 (1 commit, ~1.5-2h, ★ 가장 critical)
═══════════════════════════════════════════════
파일: app/keeper-out/admin/pending/PendingQueue.tsx 재작성
(또는 새 sub-components)
신 카드 레이아웃:
A. Hero (1 row): 매장명 (큰글씨) · 시각 · scenario badge B vs A
B. 매장 사진 thumbnail row (인라인):
lead.photo_urls.slice(0, 2).map((url) =>
)
사진 0장이면 "📷 사진 없음" 텍스트
C. 매장 정보 5 stat (한 줄 또는 grid):
보안 / device_age / camera_count / monthly_fee / combined_isp
D. Pain Points chip + 영업분기 매핑:
pain_points.map((p) => 🔴 {p} → {PAIN_TALKING[p]} )
PAIN_TALKING은 LeadDetailClient의 매핑 동일 (출동지연→SLA 등)
E. ★ [📞 전화] 큰 CTA 버튼:
📞 {formatPhone(phone)}
F. outcome 빠른 액션 6 버튼 (2열×3 그리드):
기존 OUTCOME_LABELS 6개 — 드롭다운 X, 버튼 그리드:
[✅ 확인] [📵 부재] [❌ 점주 아님]
[🚫 거절] [⚠️ 의심] [📞 번호 오류]
각 버튼 클릭 시 markOutcome(id, outcome) 기존 함수 사용
G. ★ outcome=confirmed 시 [💸 발송] 큰 버튼 노출:
if (lead.tm_outcome === 'confirmed' && lead.payout_status === 'pending_review') {
💸 1만원 N페이 발송 (잔액 50만원)
}
dispatch 함수 = POST /api/keeper-out/admin/leads/[id]/dispatch (기존 endpoint, df00a7a)
H. 발송 후 처리:
await load() refresh → 카드가 큐에서 제거됨
체크리스트:
- mobile-first (max-w-2xl, 모바일 viewport에서 잘 읽힘)
- 큰 click target (44x44 이상 모든 button/link)
- 색깔: 키퍼 오렌지 #ff5c00 (전화 버튼) + green-600 (발송 버튼)
- agent_1_data.code 표시 (자동님이 영업원 어떤 코드인지 확인 가능)
커밋: feat(keeper-out/admin): pending queue 카드 향상 — 사진 + tel CTA + outcome 빠른 액션 + 발송 강조
═══════════════════════════════════════════════
STAGE 5-B — agent 성과 페이지 (2 commits, ~2h)
═══════════════════════════════════════════════
(B-1) GET /api/keeper-out/admin/agents/route.ts 신설:
export async function GET() {
const agents = await sbSelect("keeper_outbound_agents",
"select=*&active=eq.true&order=name.asc");
// 각 agent의 성과 aggregation (Supabase RPC 또는 client-side)
// 1차 단순 fetch로 시작, agent별 lead count는 클라이언트에서 계산
const allLeads = await sbSelect("keeper_outbound_leads",
"select=id,agent_1,agent_2,agent_3,status,scenario,expected_benefit,payout_status,created_at");
const stats = agents.map(agent => {
const myLeads = allLeads.filter(l =>
l.agent_1 === agent.id || l.agent_2 === agent.id || l.agent_3 === agent.id
);
const today = myLeads.filter(l =>
new Date(l.created_at).toDateString() === new Date().toDateString()
).length;
const installed = myLeads.filter(l => l.status === 'installed').length;
const sent = myLeads.filter(l => l.payout_status === 'sent').length;
const benefit_sum = myLeads
.filter(l => l.status === 'installed')
.reduce((s, l) => s + (l.expected_benefit || 0), 0);
return { ...agent, today, installed, sent, benefit_sum, total_leads: myLeads.length };
});
return NextResponse.json({ ok: true, agents: stats });
}
(B-2) GET /api/keeper-out/admin/agents/[code]/route.ts 신설:
export async function GET(_req, { params }) {
const { code } = await params;
const agents = await sbSelect("keeper_outbound_agents",
`select=*&code=eq.${code}&limit=1`);
if (!agents[0]) return NextResponse.json({ error: "not found" }, { status: 404 });
const agent = agents[0];
// 본인 leads (3 attribution 슬롯 OR)
const leads = await sbSelect("keeper_outbound_leads",
`select=id,store_name,phone,scenario,status,payout_status,expected_benefit,created_at` +
`&or=(agent_1.eq.${agent.id},agent_2.eq.${agent.id},agent_3.eq.${agent.id})` +
`&order=created_at.desc&limit=100`);
return NextResponse.json({ ok: true, agent, leads });
}
(B-3) UI 페이지:
app/keeper-out/admin/agents/page.tsx (RSC):
import AgentsClient from "./AgentsClient";
export default function Page() {
return ;
}
app/keeper-out/admin/agents/AgentsClient.tsx ("use client"):
- useEffect로 GET /api/keeper-out/admin/agents
- 카드 그리드 (각 agent):
- name + code + role
- 오늘/이번주/누적 stat
- day_cap_count / week_cap_count 진행 (현재 사용 X — 추후)
- 클릭 시 /keeper-out/admin/agents/{code}로 이동
app/keeper-out/admin/agents/[code]/page.tsx (RSC):
import AgentDetailClient from "./AgentDetailClient";
export default async function Page({ params }) {
const { code } = await params;
return ;
}
app/keeper-out/admin/agents/[code]/AgentDetailClient.tsx ("use client"):
- useEffect로 GET /api/keeper-out/admin/agents/{code}
- Hero: name + code + role + active badge
- Stat 카드 3개 (오늘 / 이번주 / 누적)
- Stat 카드 보조: installed / sent / benefit_sum
- 본인 매장 리스트 (Link href={/keeper-out/admin/leads/{id}})
- 빈 상태: "아직 진단 받은 매장 없음"
(B-4) AdminClient.tsx에 nav 추가:
영업원 성과
- 기존 nav 옆에 추가
- 기존 leaderboard와 별개 또는 통합 (코덱스 자체 판단)
커밋 단위 2개:
1. feat(keeper-out/admin): agents 성과 API endpoints (목록 + 1명)
2. feat(keeper-out/admin): agents 성과 페이지 (목록 + detail) + nav 추가
═══════════════════════════════════════════════
가드레일 (전체 공통)
═══════════════════════════════════════════════
✗ 청록 #009090 / #00b4b4 등 X (admin은 키퍼 오렌지)
✗ /diagnose/* 수정 X
✗ "한화" / "키퍼" / "하나비전" 사용자 face 텍스트 X
✗ next.config / package.json 수정 X
✗ main 직접 push X — staging only
✗ untracked 13개 보존
✓ 색깔: #ff5c00 / #cc4900 / #fff6f1 / #e8e4db
✓ mobile-first (큰 button, max-w 적절)
✓ 기존 endpoint 재사용 (PendingQueue dispatch / tm-call 그대로)
✓ PostgREST embed (LEAD_DETAIL_SELECT 패턴 차용)
✓ Next.js 15 params Promise (await params)
[검증]
1. npx tsc --noEmit → 0
2. npx eslint <변경 TS/TSX> → 0
3. npm run build → success
4. dev /keeper-out/admin/pending → 카드에 사진/매장정보/전화 버튼/outcome 6/발송 버튼 모두 노출
5. dev /keeper-out/admin/agents → 카드 그리드
6. dev /keeper-out/admin/agents/Mn5aB → 방민아 detail (1건 lead 표시)
7. dev /keeper-out/admin/agents/Kj7yJ → 김윤주 detail (0건)
[보고 형식]
## STAGE 4+5 결과 보고
### 1. commit sha (4개)
- feat(keeper-out/admin): lead detail 한눈 UX
- feat(keeper-out/admin): pending queue 카드 향상
- feat(keeper-out/admin): agents 성과 API endpoints
- feat(keeper-out/admin): agents 성과 페이지
### 2. 변경 파일 (신규/수정)
### 3. 검증 결과 (각 stage별 tsc/eslint/build/dev)
### 4. PendingQueue 카드 시각 확인 — 사진/전화/outcome/발송 모두 노출되는지
### 5. agents 페이지 — 방민아 1건 / 김윤주 0건 정확하게 표시되는지
### 6. 결정 사항 (자체 판단)
### 7. 막힌 곳 / 의문
[시작]
지금 STAGE 4 → 5-A → 5-B 순서로 진행. 각 stage 완료 후 staging push만.
main 머지는 PM 검증 후 자동님 결정.
06. PM 검증 흐름
4 commit sha + 각 git show로 변경 검증
색깔 룰 grep — admin 영역 청록 0 hit
staging push 후 Vercel preview URL → 카드 시각 확인 (사진/전화/outcome/발송)
preview에서 agents/Mn5aB → 방민아 1건 lead 정확히 표시
이상 없으면 자동님 main 머지 결정 → production 라이브
출격 중 자동님이 폰에서 PendingQueue → [💸 발송] 누르면 사장님 phone에 1만원 도착 → 50만원→49만원