import { useCallback, useEffect, useMemo, useState } from "react"; import { useNavigate } from "react-router-dom"; import { AppShell } from "../components/shell/AppShell"; import { Badge, Button, Card, Dot, Icon, Kicker } from "../components/ui"; import { ApiError, personaReviewApi, teacherApi, type PersonaReviewAction, type PersonaReviewStatus, type PersonaReviewSummary, type TeacherLearnerGrowth, type TeacherGrowthPoint, type TeacherSafetyAlert, type TeacherDashboardResponse, } from "../lib/api"; type LoadState = "loading" | "ready" | "error"; function formatDateTime(value: string | null): string { if (!value) return "-"; const date = new Date(value); if (Number.isNaN(date.getTime())) return value; return date.toLocaleString("ko-KR", { month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", }); } function formatScore(value: number | null | undefined): string { if (typeof value !== "number" || Number.isNaN(value)) return "평가 부족"; return `${Math.round(value * 100)}%`; } function formatDelta(value: number | null | undefined): string { if (typeof value !== "number" || Number.isNaN(value)) return "변화 부족"; const sign = value > 0 ? "+" : ""; return `${sign}${Math.round(value * 100)}%p`; } function trendLabel(value: string): string { if (value === "up") return "상승"; if (value === "down") return "하락"; if (value === "flat") return "유지"; return "평가 부족"; } function sessionOpenLabel(status: string): string { return status === "ended" ? "상세 리뷰" : "진행 기록"; } function sessionReviewStatusLabel(status: string | null | undefined): string { if (status === "closed") return "검토 완료"; if (status === "viewed") return "메모 저장"; return "검토 대기"; } function sessionReviewTone(status: string | null | undefined): "accent" | "neutral" | "warn" { if (status === "closed") return "accent"; if (status === "viewed") return "warn"; return "neutral"; } function personaReviewStatusLabel(status: PersonaReviewStatus): string { if (status === "review") return "검수 대기"; if (status === "draft") return "수정 대기"; if (status === "approved") return "승인됨"; return "보관됨"; } function personaReviewTone(status: PersonaReviewStatus): "accent" | "neutral" | "warn" { if (status === "review") return "accent"; if (status === "draft") return "warn"; return "neutral"; } function EmptyState({ title, desc }: { title: string; desc: string }) { return (
{title} {desc}
); } export default function Professor() { const navigate = useNavigate(); const [dashboard, setDashboard] = useState(null); const [personaReviews, setPersonaReviews] = useState([]); const [loadState, setLoadState] = useState("loading"); const [error, setError] = useState(null); const [personaReviewLoading, setPersonaReviewLoading] = useState(true); const [personaReviewError, setPersonaReviewError] = useState(null); const [personaDecisionBusy, setPersonaDecisionBusy] = useState(null); const [updatedAt, setUpdatedAt] = useState(null); const loadDashboard = useCallback(async () => { setLoadState("loading"); setError(null); try { const next = await teacherApi.dashboard(); setDashboard(next); setUpdatedAt(new Date()); setLoadState("ready"); } catch (err) { setError(err instanceof Error ? err.message : "교수자 대시보드를 불러오지 못했습니다."); setLoadState("error"); } }, []); const loadPersonaReviews = useCallback(async () => { setPersonaReviewLoading(true); setPersonaReviewError(null); try { const next = await personaReviewApi.list(); setPersonaReviews(next); } catch (err) { // 404(검수 엔드포인트 부재/검수 데이터 없음)는 장애가 아니므로 // 빨간 에러 배너 대신 정상 빈 상태로만 표시한다. if (err instanceof ApiError && err.status === 404) { setPersonaReviews([]); } else { // 진짜 장애(5xx/네트워크)일 때만 정돈된 에러 카드를 노출한다. setPersonaReviewError("검수 데이터를 불러오지 못했습니다."); } } finally { setPersonaReviewLoading(false); } }, []); const decidePersonaReview = useCallback( async (personaId: string, action: PersonaReviewAction) => { setPersonaDecisionBusy(`${personaId}:${action}`); setPersonaReviewError(null); try { const next = await personaReviewApi.decide(personaId, action); setPersonaReviews((current) => { if (next.status === "draft" || next.status === "review") { return current.map((item) => (item.persona_id === next.persona_id ? next : item)); } return current.filter((item) => item.persona_id !== next.persona_id); }); } catch (err) { console.warn("[professor] failed to decide persona review", err); setPersonaReviewError("페르소나 검수 결정을 저장하지 못했습니다."); } finally { setPersonaDecisionBusy(null); } }, [], ); useEffect(() => { void loadDashboard(); void loadPersonaReviews(); }, [loadDashboard, loadPersonaReviews]); const kpis = useMemo( () => [ { label: "검토 대기", value: dashboard?.pending_reviews?.length ?? 0, hint: "종료 회기", icon: "review" as const, emphasis: "primary" as const, }, { label: "위기 알림", value: dashboard?.safety_alerts?.length ?? 0, hint: "109 확인", icon: "alert" as const, emphasis: "warn" as const, }, { label: "페르소나 검수", value: personaReviews.length, hint: "승인 대기", icon: "check" as const, }, { label: "최근 세션", value: dashboard?.recent_sessions?.length ?? 0, hint: "종료 · 진행", icon: "play" as const, }, { label: "전체 학생", value: dashboard?.total_learners ?? 0, hint: "담당 코호트", icon: "users" as const, }, { label: "활성 세션", value: dashboard?.active_sessions ?? 0, hint: "진행 중", icon: "play" as const, }, ], [dashboard, personaReviews.length], ); const pendingCount = dashboard?.pending_reviews?.length ?? 0; const hasPending = pendingCount > 0; const totalSessions = (dashboard?.active_sessions ?? 0) + (dashboard?.ended_sessions ?? 0); const personaReviewCount = personaReviews.length; const safetyAlerts = dashboard?.safety_alerts ?? []; const learnerGrowth = dashboard?.learner_growth ?? []; const pendingReviews = dashboard?.pending_reviews ?? []; const recentSessions = dashboard?.recent_sessions ?? []; const firstPendingReview = pendingReviews[0]; return (
교수 콘솔

{hasPending ? `${pendingCount}건의 리뷰가 대기 중입니다.` : "검토할 실제 회기가 없습니다."}

현재 기록된 학습 세션을 기준으로 표시합니다. 기록이 없는 항목은 비워 두며, 임의 코호트나 추정 위험도는 보여주지 않습니다.

{firstPendingReview ? ( ) : null}
검토 큐 {dashboard?.message ?? "학습 세션을 확인하는 중입니다."} {dashboard ? `${dashboard.cohort_label} · 실제 기록` : "확인 중"} {updatedAt ? ` · ${updatedAt.toLocaleTimeString("ko-KR")} 갱신` : ""}
{pendingCount} 리뷰 대기 {totalSessions} 누적 회기
{kpis.map((kpi) => { const emphasis = "emphasis" in kpi ? kpi.emphasis : null; return (
{kpi.label} {kpi.value} {kpi.hint}
); })}
{error ? (
{error}
) : null}
학습자 성장 추적

이력·항목별 추이

0 ? "accent" : "neutral"}> {learnerGrowth.length}명
{learnerGrowth.length > 0 ? (
{learnerGrowth.map((learner) => ( ))}
) : ( )}
검토 대기

아직 닫지 않은 종료 회기

{pendingReviews.length}건
{pendingReviews.length > 0 ? (
{pendingReviews.map((session) => ( ))}
) : ( )}
위기 알림

109 안전 확인 큐

0 ? "warn" : "neutral"}> {safetyAlerts.length}건
{safetyAlerts.length > 0 ? (
{safetyAlerts.map((alert) => ( ))}
) : ( )}
페르소나 검수

공개 전 승인 큐

0 ? "accent" : "neutral"}> {personaReviewCount}건
{personaReviewError ? (
{personaReviewError} 네트워크 또는 서버 오류가 발생했습니다.
) : personaReviews.length > 0 ? (
{personaReviews.map((persona) => { return (
{persona.code}
{persona.display_name} v{persona.version} · {persona.difficulty} ·{" "} {persona.theory_target.join(" · ") || "공통"}

{persona.source_provenance || "출처 정보 없음"}

{personaReviewStatusLabel(persona.status)} {formatDateTime(persona.created_at ?? null)}
{persona.status === "review" ? ( ) : null}
); })}
) : ( )}
페르소나 저작

콘텐츠 저작실

별도 작업면
페르소나 스튜디오

배경 서사, 임상 개념화, 역린·금기, 회기별 시나리오, 프롬프트 미리보기와 검수 요청을 한 작업면에서 관리합니다.

초안 {personaReviewCount}건 공개 전 검수 큐 연동
최근 세션

최근 회기 기록

{recentSessions.length}건
{recentSessions.length > 0 ? (
{recentSessions.map((session) => { const openLabel = sessionOpenLabel(session.status); return ( ); })}
) : ( )}
); } function GrowthCard({ learner }: { learner: TeacherLearnerGrowth }) { const points = learner.points ?? []; const topTechniques = learner.top_techniques ?? []; const recentPoints = points.slice(-3).reverse(); return (
{learner.learner_label} {learner.ended_sessions}/{learner.sessions}회기 완료 · {formatDateTime(learner.latest_at)}
{trendLabel(learner.trend)}
최근 적절성 {formatScore(learner.latest_score)} 변화 {formatDelta(learner.score_delta)} 평균 라포 {formatScore(learner.avg_rapport == null ? null : (learner.avg_rapport + 1) / 2)}
{points.map((point) => ( ))}
{topTechniques.length > 0 ? ( topTechniques.map((tag) => {tag}) ) : ( 기법 태그 부족 )}
{recentPoints.length > 0 ? ( recentPoints.map((point) => (
{point.persona_code} · {point.session_no}회기 {formatScore(point.score)} · 기법 {point.technique_count} · 점검 {point.watch_count}
)) ) : (
회기 평가 부족 종료 회기와 턴별 평가가 필요합니다.
)}
); } function GrowthBar({ point }: { point: TeacherGrowthPoint }) { const hasScore = typeof point.score === "number" && !Number.isNaN(point.score); const height = hasScore ? Math.max(10, Math.round((point.score ?? 0) * 100)) : 10; return ( {point.session_no} ); } function SafetyAlertRow({ alert }: { alert: TeacherSafetyAlert }) { return (
{alert.learner_label} {alert.persona_code || "세션"} · 위험도 {alert.ko_risk_level} ·{" "} {formatDateTime(alert.created_at)} {alert.session_id}
{alert.resource_title} {alert.resource_number}
); } const PF_CSS = ` .pf-root{ max-width:1280px; margin:0 auto; display:flex; flex-direction:column; gap:16px; } .pf-head{ display:flex; align-items:flex-end; justify-content:space-between; gap:var(--sp-4); flex-wrap:wrap; } .pf-head__actions{ display:flex; align-items:center; justify-content:flex-end; gap:8px; flex-wrap:wrap; } .pf-head h1{ margin:6px 0 0; color:var(--text-strong); font-size:var(--fs-h2); line-height:1.28; letter-spacing:0; } .pf-head p{ margin:6px 0 0; max-width:760px; color:var(--text-body); font-size:var(--fs-xs); line-height:1.55; } .pf-signal-strip{ order:1; display:grid; grid-template-columns:1fr; gap:0; min-width:0; } .pf-triage{ display:grid; grid-template-columns:auto minmax(0,1fr) auto; align-items:center; gap:12px; min-width:0; padding:14px 16px; border:1px solid var(--hair); border-radius:var(--radius); background:var(--bg-surface); box-shadow:var(--shadow-sm); } .pf-triage.is-active{ background:color-mix(in srgb,var(--accent-tint) 42%,var(--bg-surface)); } .pf-triage__copy{ min-width:0; } .pf-triage__copy .vg-kicker{ margin-bottom:4px; } .pf-triage__copy b{ display:block; color:var(--text-strong); font-size:var(--fs-body); line-height:1.35; } .pf-triage__copy span, .pf-triage__meta small{ color:var(--text-muted); font-size:var(--fs-xs); } .pf-triage__meta{ display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:0; min-width:180px; border:1px solid var(--border-subtle); border-radius:var(--radius); background:color-mix(in srgb,var(--bg-surface) 86%,transparent); overflow:hidden; } .pf-triage__meta span{ display:grid; align-content:center; gap:2px; padding:9px 12px; } .pf-triage__meta span + span{ border-left:1px solid var(--hair); } .pf-triage__meta b{ color:var(--text-strong); font-family:var(--font-num); font-size:17px; line-height:1; } .pf-error{ display:flex; align-items:center; gap:10px; padding:12px 14px; border-radius:var(--radius); background:var(--crit-tint); color:var(--crit-text); font-size:var(--fs-sm); } .pf-kpis{ display:grid; grid-template-columns:repeat(6,minmax(0,1fr)); border:1px solid var(--hair); border-radius:var(--radius); overflow:hidden; background:var(--bg-surface); box-shadow:var(--shadow-sm); } .pf-kpi{ position:relative; min-width:0; padding:14px 16px; display:grid; grid-template-columns:minmax(0,1fr) auto; gap:4px 10px; } .pf-kpi:nth-child(n+2){border-left:1px solid var(--hair);} .pf-kpi:nth-child(n+4){border-top:0;} .pf-kpi--primary, .pf-kpi--warn{ background:color-mix(in srgb,var(--accent-tint) 32%,var(--bg-surface)); } .pf-kpi--warn{ background:color-mix(in srgb,var(--warn-tint) 36%,var(--bg-surface)); } .pf-kpi--primary .pf-kpi__ic{ color:var(--text-on-accent); background:var(--accent); } .pf-kpi--warn .pf-kpi__ic{ color:var(--warn-text); background:var(--warn-tint); } .pf-kpi__ic{ grid-column:2; grid-row:1 / span 3; width:30px; height:30px; display:grid; place-items:center; border-radius:var(--radius); color:var(--accent); background:var(--accent-tint); } .pf-kpi__lab{ display:block; color:var(--text-muted); font-size:var(--fs-xs); font-weight:700; } .pf-kpi b{ display:block; color:var(--text-strong); font-family:var(--font-num); font-size:24px; line-height:1; } .pf-kpi small{ color:var(--text-muted); font-size:12px; line-height:1.35; } .pf-workspace{ order:2; display:grid; grid-template-columns:minmax(320px,390px) minmax(0,1fr); align-items:start; gap:14px; min-width:0; } .pf-queue-stack{ display:flex; flex-direction:column; gap:14px; min-width:0; } .pf-section{ display:flex; flex-direction:column; gap:10px; min-width:0; } .pf-section__head{ display:flex; align-items:flex-end; justify-content:space-between; gap:12px; } .pf-section__head h2{ margin:4px 0 0; color:var(--text-strong); font-size:var(--fs-body); font-weight:700; line-height:1.3; letter-spacing:0; } .pf-panel{ padding:0; overflow:hidden; } .pf-studio-card{ display:grid; grid-template-columns:minmax(0,1fr) auto; gap:14px; align-items:center; padding:14px; background:linear-gradient(180deg,var(--bg-surface),color-mix(in srgb,var(--accent-tint) 18%,var(--bg-surface))); } .pf-studio-card__copy{ min-width:0; display:grid; gap:7px; } .pf-studio-card__copy b{ color:var(--text-strong); font-size:var(--fs-body); } .pf-studio-card__copy p{ margin:0; color:var(--text-muted); font-size:var(--fs-xs); line-height:1.55; } .pf-studio-card__meta{ display:flex; flex-wrap:wrap; gap:6px; } .pf-studio-card__meta span{ padding:4px 7px; border:1px solid var(--hair); border-radius:var(--radius-sm); color:var(--text-body); background:var(--bg-surface-2); font-size:11px; line-height:1.2; } .pf-studio-card__actions{ display:flex; justify-content:flex-end; } .pf-section--growth{ order:3; min-width:0; } .pf-growth-panel{ background:linear-gradient(180deg,var(--bg-surface),color-mix(in srgb,var(--accent-tint) 18%,var(--bg-surface))); } .pf-growth-list{ max-height:min(420px,44vh); overflow:auto; scrollbar-gutter:stable; display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:12px; padding:12px; } .pf-growth-card{ min-width:0; display:flex; flex-direction:column; gap:12px; padding:13px; border:1px solid var(--hair); border-radius:var(--radius-sm); background:color-mix(in srgb,var(--bg-surface) 82%,var(--bg-surface-2)); box-shadow:none; } .pf-growth-card__top{ display:flex; align-items:flex-start; justify-content:space-between; gap:10px; min-width:0; } .pf-growth-card__id{ min-width:0; display:grid; gap:3px; } .pf-growth-card__id b{ color:var(--text-strong); font-size:var(--fs-sm); line-height:1.35; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; } .pf-growth-card__id span, .pf-growth-card__metrics small, .pf-growth-point span, .pf-recent__review-state{ color:var(--text-muted); font-size:12px; line-height:1.4; } .pf-recent__review-state{ display:block; margin-top:3px; line-height:1.2; } .pf-growth-card__metrics{ display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:8px; } .pf-growth-card__metrics span{ min-width:0; display:grid; gap:3px; padding:9px 10px; border:1px solid var(--paper-2); border-radius:var(--radius-sm); background:var(--bg-surface-2); } .pf-growth-card__metrics b{ color:var(--text-strong); font-family:var(--font-num); font-size:15px; line-height:1.15; white-space:nowrap; } .pf-growth-bars{ height:82px; display:flex; align-items:flex-end; gap:6px; padding:8px 8px 6px; border:1px solid var(--paper-2); border-radius:var(--radius-sm); background:color-mix(in srgb,var(--bg-surface-2) 82%,transparent); } .pf-growth-bar{ flex:1 1 0; min-width:14px; height:100%; display:grid; grid-template-rows:minmax(0,1fr) 14px; gap:4px; align-items:end; } .pf-growth-bar i{ display:block; width:100%; min-height:6px; border-radius:6px 6px 3px 3px; background:linear-gradient(180deg,var(--accent),var(--accent-deep)); } .pf-growth-bar.is-empty i{ background:repeating-linear-gradient(135deg,var(--paper-2),var(--paper-2) 3px,var(--hair) 3px,var(--hair) 6px); } .pf-growth-bar small{ color:var(--text-muted); font-family:var(--font-num); font-size:10px; text-align:center; line-height:1; } .pf-growth-card__tags{ min-height:26px; display:flex; flex-wrap:wrap; gap:6px; align-content:flex-start; } .pf-growth-card__tags span{ max-width:100%; padding:4px 7px; border:1px solid var(--paper-2); border-radius:999px; color:var(--text-body); background:var(--bg-surface-2); font-size:11px; line-height:1.2; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; } .pf-growth-card__points{ display:grid; gap:7px; } .pf-growth-point{ min-width:0; display:grid; grid-template-columns:minmax(86px,.5fr) minmax(0,1fr); gap:8px; align-items:center; } .pf-growth-point b{ color:var(--text-strong); font-size:12px; line-height:1.35; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; } .pf-growth-point span{ overflow:hidden; text-overflow:ellipsis; white-space:nowrap; } .pf-empty{ min-height:118px; display:grid; place-items:center; gap:6px; padding:var(--sp-5); text-align:center; } .pf-empty b{ color:var(--text-strong); font-size:var(--fs-body); } .pf-empty span{ color:var(--text-muted); font-size:var(--fs-xs); } .pf-list{ max-height:min(320px,42vh); overflow:auto; scrollbar-gutter:stable; display:flex; flex-direction:column; } .pf-personas{ max-height:min(320px,42vh); overflow:auto; scrollbar-gutter:stable; display:flex; flex-direction:column; } .pf-persona{ display:grid; grid-template-columns:minmax(0,1fr); gap:8px; align-items:start; padding:12px; border-top:1px solid var(--paper-2); } .pf-persona:first-child{border-top:0;} .pf-persona__main{ min-width:0; display:grid; grid-template-columns:40px minmax(0,1fr); gap:10px; align-items:center; } .pf-persona__code{ width:40px; height:32px; display:grid; place-items:center; border-radius:var(--radius); color:var(--accent-deep); background:var(--accent-tint); font-family:var(--font-num); font-weight:800; font-size:12px; } .pf-persona__main b{ display:block; color:var(--text-strong); font-size:var(--fs-sm); line-height:1.35; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; } .pf-persona__main span, .pf-persona p, .pf-persona__meta span{ color:var(--text-muted); font-size:var(--fs-xs); line-height:1.45; } .pf-persona p{ margin:0; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; } .pf-persona__meta{ display:flex; align-items:center; gap:10px; justify-content:space-between; white-space:nowrap; } .pf-persona__actions{ display:flex; justify-content:flex-end; gap:8px; min-width:0; } .pf-persona__actions .vg-btn{ min-width:72px; padding-inline:10px; } .pf-alerts{ max-height:min(300px,38vh); overflow:auto; scrollbar-gutter:stable; display:flex; flex-direction:column; } .pf-alert{ display:grid; grid-template-columns:auto minmax(0,1fr) auto; gap:10px; align-items:center; padding:12px; border-top:1px solid var(--paper-2); background:color-mix(in srgb,var(--warn-tint) 34%,transparent); } .pf-alert:first-child{border-top:0;} .pf-alert__ic{ width:30px; height:30px; display:grid; place-items:center; border-radius:var(--radius); color:var(--warn-text); background:var(--warn-tint); } .pf-alert__main{ min-width:0; display:grid; gap:3px; } .pf-alert__main b{ color:var(--text-strong); font-size:var(--fs-sm); } .pf-alert__main span, .pf-alert__main code, .pf-alert__resource span{ color:var(--text-muted); font-size:12px; } .pf-alert__main code{ overflow:hidden; text-overflow:ellipsis; white-space:nowrap; } .pf-alert__resource{ display:grid; gap:2px; justify-items:end; min-width:86px; } .pf-alert__resource b{ color:var(--warn-text); font-family:var(--font-num); font-size:18px; } .pf-session{ width:100%; font:inherit; text-align:left; background:transparent; color:inherit; display:grid; grid-template-columns:8px minmax(0,1fr) auto; gap:8px 10px; align-items:start; padding:12px; border:0; border-top:1px solid var(--paper-2); cursor:default; } .pf-session:first-child{border-top:0;} .pf-session--action{ cursor:pointer; } .pf-session--action:hover{ background:var(--bg-surface-2); } .pf-session--action:focus-visible{ outline:2px solid var(--accent); outline-offset:-2px; } .pf-session__dot{ width:8px; height:8px; border-radius:50%; background:var(--accent); } .pf-session__main{ min-width:0; display:flex; flex-direction:column; gap:3px; } .pf-session__main b{ color:var(--text-strong); font-size:var(--fs-sm); } .pf-session__main span,.pf-session__main code{ color:var(--text-muted); font-size:var(--fs-xs); overflow-wrap:anywhere; } .pf-session__main code,.pf-recent__learner code{ font-family:var(--font-num); overflow:hidden; text-overflow:ellipsis; } .pf-session__meta{ grid-column:2 / 4; display:flex; align-items:center; justify-content:space-between; gap:8px; color:var(--text-muted); font-family:var(--font-num); font-size:var(--fs-xs); white-space:normal; } .pf-session__open{ grid-column:3; grid-row:1; display:inline-flex; align-items:center; gap:5px; align-self:center; justify-self:end; min-height:30px; padding:0 9px; border:1px solid var(--hair); border-radius:var(--radius-sm); color:var(--accent-deep); background:var(--accent-tint); font-size:var(--fs-xs); font-weight:760; white-space:nowrap; } .pf-session--action:hover .pf-session__open{ border-color:var(--accent); } .pf-recent-list{ max-height:min(620px,calc(100vh - 220px)); overflow:auto; scrollbar-gutter:stable; min-width:0; } .pf-recent-head, .pf-recent-row{ display:grid; grid-template-columns: minmax(128px,1.25fr) minmax(58px,.55fr) minmax(66px,.55fr) minmax(82px,.85fr) minmax(32px,.35fr) minmax(70px,.6fr) minmax(66px,.55fr) minmax(86px,.65fr); align-items:center; gap:8px; min-width:0; } .pf-recent-head{ position:sticky; top:0; z-index:1; color:var(--text-muted); font-size:var(--fs-xs); font-weight:700; padding:9px 12px; border-bottom:1px solid var(--hair); background:var(--bg-surface); white-space:nowrap; } .pf-recent-row{ width:100%; font:inherit; text-align:left; color:inherit; background:transparent; padding:10px 12px; border:0; border-top:1px solid var(--paper-2); } .pf-recent-row--action{ cursor:pointer; } .pf-recent-row--action:hover{ background:var(--bg-surface-2); } .pf-recent-row--action:focus-visible{ outline:2px solid var(--accent); outline-offset:-2px; } .pf-recent-head + .pf-recent-row{ border-top:0; } .pf-recent__learner, .pf-recent__cell{ min-width:0; color:var(--text-body); font-size:var(--fs-sm); overflow-wrap:anywhere; } .pf-recent__cell::before{ display:none; } .pf-recent__learner{ min-width:0; display:flex; flex-direction:column; gap:3px; } .pf-recent__learner b{ color:var(--text-strong); font-size:var(--fs-sm); line-height:1.35; overflow-wrap:anywhere; } .pf-recent__learner code{ max-width:100%; color:var(--text-muted); font-size:11px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; } .pf-recent__cell .vg-badge{ justify-self:start; } .pf-recent__cell--open{ display:flex; justify-content:flex-end; } .pf-recent__open{ display:inline-flex; align-items:center; justify-content:center; gap:4px; min-height:30px; padding:0 7px; border:1px solid var(--hair); border-radius:var(--radius-sm); color:var(--accent-deep); background:var(--accent-tint); font-size:var(--fs-xs); font-weight:760; white-space:nowrap; } .pf-recent-row--action:hover .pf-recent__open{ border-color:var(--accent); } @media (max-width:1100px){ .pf-signal-strip, .pf-workspace{ grid-template-columns:1fr; } .pf-triage{ grid-template-columns:auto minmax(0,max-content) auto; justify-content:start; } .pf-kpis{ grid-template-columns:repeat(4,minmax(0,1fr)); } .pf-kpi:nth-child(n+2){border-left:1px solid var(--hair);} .pf-kpi:nth-child(n+4){border-top:0;} .pf-growth-list{ grid-template-columns:repeat(2,minmax(0,1fr)); } .pf-list, .pf-personas{ max-height:360px; } .pf-recent-list{ max-height:460px; } } @media (max-width:860px){ .pf-head__actions{ width:100%; justify-content:flex-start; } .pf-triage{ grid-template-columns:auto minmax(0,1fr); } .pf-triage__meta{ grid-column:1 / -1; width:100%; } .pf-kpis{grid-template-columns:repeat(2,minmax(0,1fr));} .pf-kpi:nth-child(n+2){border-left:0;} .pf-kpi:nth-child(even){border-left:1px solid var(--hair);} .pf-kpi:nth-child(n+3){border-top:1px solid var(--hair);} .pf-growth-list{ grid-template-columns:1fr; } .pf-recent-list{ display:flex; flex-direction:column; gap:10px; padding:10px; background:var(--bg-surface-2); overflow-x:hidden; } .pf-recent-head{ display:none; } .pf-recent-row{ display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:10px 12px; padding:12px; border:1px solid var(--paper-2); border-radius:var(--radius); background:var(--bg-surface); } .pf-recent__learner{ grid-column:1 / -1; padding-bottom:10px; border-bottom:1px solid var(--paper-2); } .pf-recent__learner code{ white-space:normal; overflow-wrap:anywhere; } .pf-recent__cell{ display:grid; grid-template-columns:minmax(74px,.4fr) minmax(0,1fr); gap:8px; align-items:center; } .pf-recent__cell--open{ justify-content:stretch; } .pf-recent__cell::before{ display:block; content:attr(data-label); color:var(--text-muted); font-size:var(--fs-xs); font-weight:700; line-height:1.35; } .pf-session{ grid-template-columns:8px minmax(0,1fr); } .pf-session__meta{ grid-column:2; justify-content:flex-start; } .pf-session__open{ grid-column:2; grid-row:auto; justify-self:start; } } @media (max-width:520px){ .pf-kpis{grid-template-columns:repeat(2,minmax(0,1fr));} .pf-kpi, .pf-kpi + .pf-kpi{border-left:0;} .pf-kpi:nth-child(even){border-left:1px solid var(--hair);} .pf-kpi:nth-child(n+3){border-top:1px solid var(--hair);} .pf-persona__actions{ display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); } .pf-persona__actions .vg-btn{ width:100%; } .pf-growth-list{ padding:10px; } .pf-growth-card__metrics{ grid-template-columns:1fr; } .pf-growth-point{ grid-template-columns:1fr; gap:2px; } .pf-growth-point b, .pf-growth-point span{ white-space:normal; } .pf-alert{ grid-template-columns:auto minmax(0,1fr); } .pf-alert__resource{ grid-column:2; justify-items:start; } .pf-recent-list{ padding:8px; } .pf-recent-row{ grid-template-columns:1fr; gap:10px; } .pf-recent__cell{ grid-template-columns:minmax(64px,.32fr) minmax(0,1fr); } .pf-recent__cell--open .pf-recent__open{ justify-self:start; } } `;