agent(협상카드 강화학습)와 anchoring(구간별 자동 조정)이 쌓아온 결과를 볼 수 있는 화면이 없어 DB 를 직접 열어야 확인이 됐다. 두 축은 성격이 달라 한 화면에 섞지 않고 탭으로 나눈다. 백엔드(읽기 전용 — 값의 주인은 agent·anchoring): - crud/learning_crud.py: learning·anchoring 스키마 경량 조회(negodata ORM 미매핑 테이블) - services/learning_service.py + router/v1/learning: /v1/learning/cards · /anchoring 프론트: - 사이드바 '협상 학습'(/learning) 추가, 탭 = 협상카드 | 앵커링 - 협상카드: 학습 협상·기록·타결 비율 요약 + 카드별 사용/타결/평균 점수(0 기준 좌우 막대) - 앵커링: 구간별 현재 인하폭 + 조정 이력(표본·성공률·값 변화) 검증: 로컬 실협상 3건으로 데이터 생성 후 두 탭 실화면 확인, tsc·eslint 통과.
64 lines
2.2 KiB
TypeScript
64 lines
2.2 KiB
TypeScript
import { useState } from 'react';
|
|
import { PageContainer } from '@/components/layout/PageContainer';
|
|
import { Typography } from '@/components/ui/typography';
|
|
import { AnchoringStatusView, CardLearningView } from '@/features/learning/LearningView';
|
|
import { useAnchoringData, useLearningData } from '@/features/learning/api';
|
|
|
|
// 협상 학습. AI가 협상을 거치며 쌓은 결과를 보는 화면이다.
|
|
// 협상카드 = 어떤 카드가 잘 먹혔는지(강화학습), 앵커링 = 구간별 인하폭이 어떻게 조정됐는지.
|
|
// 두 축은 성격이 달라 탭으로 나눈다 — 각각 한 화면에서 끝나야 훑기 좋다.
|
|
type Tab = 'cards' | 'anchoring';
|
|
|
|
export default function LearningPage() {
|
|
const [tab, setTab] = useState<Tab>('cards');
|
|
const learning = useLearningData();
|
|
const anchoring = useAnchoringData();
|
|
const isLoading = tab === 'cards' ? learning.isLoading : anchoring.isLoading;
|
|
|
|
return (
|
|
<PageContainer>
|
|
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
|
<Typography variant="h2">협상 학습</Typography>
|
|
<div className="flex gap-1 rounded-lg border border-border bg-card p-1">
|
|
<TabButton active={tab === 'cards'} onClick={() => setTab('cards')}>
|
|
협상카드
|
|
</TabButton>
|
|
<TabButton active={tab === 'anchoring'} onClick={() => setTab('anchoring')}>
|
|
앵커링
|
|
</TabButton>
|
|
</div>
|
|
</div>
|
|
|
|
{isLoading ? (
|
|
<Typography variant="muted">학습 현황을 불러오는 중…</Typography>
|
|
) : tab === 'cards' ? (
|
|
<CardLearningView data={learning.data} />
|
|
) : (
|
|
<AnchoringStatusView data={anchoring.data} />
|
|
)}
|
|
</PageContainer>
|
|
);
|
|
}
|
|
|
|
function TabButton({
|
|
active,
|
|
onClick,
|
|
children,
|
|
}: {
|
|
active: boolean;
|
|
onClick: () => void;
|
|
children: React.ReactNode;
|
|
}) {
|
|
return (
|
|
<button
|
|
type="button"
|
|
onClick={onClick}
|
|
className={`rounded-md px-3 py-1.5 text-xs font-semibold transition-colors ${
|
|
active ? 'bg-primary text-primary-foreground' : 'text-muted-foreground hover:bg-muted'
|
|
}`}
|
|
>
|
|
{children}
|
|
</button>
|
|
);
|
|
}
|