Codex 작업지시서 #5 · STAGE 4+5 (TM 워크플로 + agent 성과)

2026-05-06 · 출격 진행 중 / TM dispatch UI bottleneck 해결 · 3 stage / 4-5 commits / ~5-6h

출격 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페이 발송 ← 강조 큰 버튼        │ │
│ └─────────────────────────────────────────┘ │
└─────────────────────────────────────────────┘

구현 디테일

사진 thumbnaillead.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.tsxRSC — agent 목록 fetch
신규app/keeper-out/admin/agents/AgentsClient.tsx"use client" — 목록 + leaderboard
신규app/keeper-out/admin/agents/[code]/page.tsxRSC — agent 1명 detail
신규app/keeper-out/admin/agents/[code]/AgentDetailClient.tsx"use client" — stat + 매장 리스트
신규app/api/keeper-out/admin/agents/route.tsGET — agent 목록 + 성과 aggregation
신규app/api/keeper-out/admin/agents/[code]/route.tsGET — 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.가드레일 (이번 작업 공통)

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') {
     
   }
   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 검증 흐름

  1. 4 commit sha + 각 git show로 변경 검증
  2. 색깔 룰 grep — admin 영역 청록 0 hit
  3. staging push 후 Vercel preview URL → 카드 시각 확인 (사진/전화/outcome/발송)
  4. preview에서 agents/Mn5aB → 방민아 1건 lead 정확히 표시
  5. 이상 없으면 자동님 main 머지 결정 → production 라이브
  6. 출격 중 자동님이 폰에서 PendingQueue → [💸 발송] 누르면 사장님 phone에 1만원 도착 → 50만원→49만원

참조

  1. 출격 baseline — 2026-05-06 Day-1 baseline
  2. v3 anti-abuse 풀 설계 — 자동님 5/6 승인
  3. balerion forward 최종 — PRODUCTION LIVE
  4. STAGE 4 단독 보고서 — 기존 작업지시서
  5. 이 문서 — STAGE 4+5 통합 작업지시서