@tipp/ui 2.10.5 → 2.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dist/app/index.js +6 -6
  2. package/dist/app/platform/edit-coaching-time.js +6 -6
  3. package/dist/app/platform/edit-service-type.js +6 -6
  4. package/dist/app/platform/goal-manage-card-edit.js +6 -6
  5. package/dist/app/platform/on-offline-radio-card.js +6 -6
  6. package/dist/app/platform/reservation-card.js +6 -6
  7. package/dist/app/platform/session-card.js +6 -6
  8. package/dist/app/roleplay/index.cjs +8 -27
  9. package/dist/app/roleplay/index.cjs.map +1 -1
  10. package/dist/app/roleplay/index.js +10 -10
  11. package/dist/app/roleplay/parts/content.cjs +1 -24
  12. package/dist/app/roleplay/parts/content.cjs.map +1 -1
  13. package/dist/app/roleplay/parts/content.js +8 -8
  14. package/dist/app/roleplay/parts/header.cjs +4 -2
  15. package/dist/app/roleplay/parts/header.cjs.map +1 -1
  16. package/dist/app/roleplay/parts/header.d.cts +3 -1
  17. package/dist/app/roleplay/parts/header.d.ts +3 -1
  18. package/dist/app/roleplay/parts/header.js +1 -1
  19. package/dist/app/roleplay/parts/score-trend-section.js +6 -6
  20. package/dist/app/roleplay/parts/step-detail.cjs +1 -24
  21. package/dist/app/roleplay/parts/step-detail.cjs.map +1 -1
  22. package/dist/app/roleplay/parts/step-detail.js +1 -1
  23. package/dist/app/roleplay/rp-session-detail.cjs +8 -27
  24. package/dist/app/roleplay/rp-session-detail.cjs.map +1 -1
  25. package/dist/app/roleplay/rp-session-detail.d.cts +3 -1
  26. package/dist/app/roleplay/rp-session-detail.d.ts +3 -1
  27. package/dist/app/roleplay/rp-session-detail.js +10 -10
  28. package/dist/{chunk-CPSXONQO.js → chunk-BTU3IMVG.js} +2 -2
  29. package/dist/{chunk-VJREZH2Z.js → chunk-JDCA3TP5.js} +5 -3
  30. package/dist/chunk-JDCA3TP5.js.map +1 -0
  31. package/dist/{chunk-W7LO5KYT.js → chunk-NEJCZABQ.js} +4 -4
  32. package/dist/{chunk-6GODZ2YW.js → chunk-ORP7UCUU.js} +6 -4
  33. package/dist/chunk-ORP7UCUU.js.map +1 -0
  34. package/dist/{chunk-KJPBBBVK.js → chunk-YHA4OOND.js} +2 -25
  35. package/dist/chunk-YHA4OOND.js.map +1 -0
  36. package/dist/index.js +15 -15
  37. package/dist/molecules/dynamic-form-editor.js +4 -4
  38. package/dist/molecules/index.js +15 -15
  39. package/package.json +1 -1
  40. package/src/app/roleplay/parts/header.tsx +4 -0
  41. package/src/app/roleplay/parts/step-detail.tsx +1 -24
  42. package/src/app/roleplay/rp-session-detail.tsx +4 -0
  43. package/dist/chunk-6GODZ2YW.js.map +0 -1
  44. package/dist/chunk-KJPBBBVK.js.map +0 -1
  45. package/dist/chunk-VJREZH2Z.js.map +0 -1
  46. /package/dist/{chunk-CPSXONQO.js.map → chunk-BTU3IMVG.js.map} +0 -0
  47. /package/dist/{chunk-W7LO5KYT.js.map → chunk-NEJCZABQ.js.map} +0 -0
@@ -11,7 +11,9 @@ interface RpSessionDetailProps {
11
11
  }) => string | Promise<string>;
12
12
  onError?: (error: unknown, context: Record<string, unknown>) => void;
13
13
  sectionScrollMarginTop?: string;
14
+ /** 헤더 액션 행(PDF 다운로드·영상)의 '영상' 버튼 우측에 렌더되는 소비자 정의 액션 슬롯(예: website 공유하기 버튼). */
15
+ headerActions?: React__default.ReactNode;
14
16
  }
15
- declare function RpSessionDetail({ detail, history, isLoading, script, onResolveMediaUrl, onError: _onError, sectionScrollMarginTop, }: RpSessionDetailProps): React__default.ReactElement;
17
+ declare function RpSessionDetail({ detail, history, isLoading, script, onResolveMediaUrl, onError: _onError, sectionScrollMarginTop, headerActions, }: RpSessionDetailProps): React__default.ReactElement;
16
18
 
17
19
  export { RpSessionDetail, type RpSessionDetailProps };
@@ -11,7 +11,9 @@ interface RpSessionDetailProps {
11
11
  }) => string | Promise<string>;
12
12
  onError?: (error: unknown, context: Record<string, unknown>) => void;
13
13
  sectionScrollMarginTop?: string;
14
+ /** 헤더 액션 행(PDF 다운로드·영상)의 '영상' 버튼 우측에 렌더되는 소비자 정의 액션 슬롯(예: website 공유하기 버튼). */
15
+ headerActions?: React__default.ReactNode;
14
16
  }
15
- declare function RpSessionDetail({ detail, history, isLoading, script, onResolveMediaUrl, onError: _onError, sectionScrollMarginTop, }: RpSessionDetailProps): React__default.ReactElement;
17
+ declare function RpSessionDetail({ detail, history, isLoading, script, onResolveMediaUrl, onError: _onError, sectionScrollMarginTop, headerActions, }: RpSessionDetailProps): React__default.ReactElement;
16
18
 
17
19
  export { RpSessionDetail, type RpSessionDetailProps };
@@ -1,14 +1,14 @@
1
1
  "use client";
2
2
  import {
3
3
  RpSessionDetail
4
- } from "../../chunk-6GODZ2YW.js";
4
+ } from "../../chunk-ORP7UCUU.js";
5
5
  import "../../chunk-TFJJKM3W.js";
6
- import "../../chunk-CPSXONQO.js";
7
- import "../../chunk-KJPBBBVK.js";
6
+ import "../../chunk-BTU3IMVG.js";
7
+ import "../../chunk-YHA4OOND.js";
8
8
  import "../../chunk-WVY3C64M.js";
9
9
  import "../../chunk-5QIT4URV.js";
10
10
  import "../../chunk-GA6W62DO.js";
11
- import "../../chunk-VJREZH2Z.js";
11
+ import "../../chunk-JDCA3TP5.js";
12
12
  import "../../chunk-2XDAK4B7.js";
13
13
  import "../../chunk-2IRG44IV.js";
14
14
  import "../../chunk-DIISQDYA.js";
@@ -56,7 +56,6 @@ import "../../chunk-K3Q4XLCN.js";
56
56
  import "../../chunk-AT5PAJLW.js";
57
57
  import "../../chunk-VVWQZK5L.js";
58
58
  import "../../chunk-2ISIAPFS.js";
59
- import "../../chunk-AHYOAFOU.js";
60
59
  import "../../chunk-CUS3ORMD.js";
61
60
  import "../../chunk-6OT5OYGM.js";
62
61
  import "../../chunk-P4DJ5F6Z.js";
@@ -69,24 +68,25 @@ import "../../chunk-BXX3B3E4.js";
69
68
  import "../../chunk-NRPDVI76.js";
70
69
  import "../../chunk-2BPFXK4L.js";
71
70
  import "../../chunk-NWJSLIQ3.js";
72
- import "../../chunk-W7LO5KYT.js";
73
- import "../../chunk-V7KAHLW2.js";
71
+ import "../../chunk-AHYOAFOU.js";
72
+ import "../../chunk-NEJCZABQ.js";
74
73
  import "../../chunk-77H5BUYO.js";
75
74
  import "../../chunk-K7LTEKQS.js";
76
- import "../../chunk-SQ3FYBJT.js";
77
75
  import "../../chunk-2EPKY2VT.js";
78
76
  import "../../chunk-QLNAAZJX.js";
79
77
  import "../../chunk-PAUEQXYG.js";
80
- import "../../chunk-OOM522XP.js";
81
78
  import "../../chunk-N3FMP27I.js";
82
79
  import "../../chunk-BEKSO5JG.js";
83
80
  import "../../chunk-IJOOKMKQ.js";
84
81
  import "../../chunk-C6HO3IGE.js";
85
- import "../../chunk-NK6S7ZMB.js";
86
82
  import "../../chunk-3DZIX4CE.js";
87
83
  import "../../chunk-CBYIPRND.js";
88
84
  import "../../chunk-Z7WNKARH.js";
89
85
  import "../../chunk-D4U2ZV2A.js";
86
+ import "../../chunk-V7KAHLW2.js";
87
+ import "../../chunk-SQ3FYBJT.js";
88
+ import "../../chunk-NK6S7ZMB.js";
89
+ import "../../chunk-OOM522XP.js";
90
90
  import "../../chunk-FAL6S2XK.js";
91
91
  import "../../chunk-HBF6IIVZ.js";
92
92
  import "../../chunk-AELKCD6B.js";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  StepDetail
3
- } from "./chunk-KJPBBBVK.js";
3
+ } from "./chunk-YHA4OOND.js";
4
4
  import {
5
5
  StepSummary
6
6
  } from "./chunk-WVY3C64M.js";
@@ -128,4 +128,4 @@ function Content({
128
128
  export {
129
129
  Content
130
130
  };
131
- //# sourceMappingURL=chunk-CPSXONQO.js.map
131
+ //# sourceMappingURL=chunk-BTU3IMVG.js.map
@@ -70,7 +70,8 @@ function Header({
70
70
  detail,
71
71
  hasScript,
72
72
  onOpenReplay,
73
- onResolveMediaUrl
73
+ onResolveMediaUrl,
74
+ actions
74
75
  }) {
75
76
  var _a;
76
77
  const pdfHref = detail.embedLink;
@@ -175,7 +176,8 @@ function Header({
175
176
  /* @__PURE__ */ jsx("span", { style: { whiteSpace: "nowrap" }, children: "\uC601\uC0C1" })
176
177
  ]
177
178
  }
178
- ) : null
179
+ ) : null,
180
+ actions != null ? actions : null
179
181
  ]
180
182
  }
181
183
  )
@@ -187,4 +189,4 @@ function Header({
187
189
  export {
188
190
  Header
189
191
  };
190
- //# sourceMappingURL=chunk-VJREZH2Z.js.map
192
+ //# sourceMappingURL=chunk-JDCA3TP5.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/app/roleplay/parts/header.tsx"],"sourcesContent":["'use client';\n\nimport { Badge, Box, Button, Flex, Heading, Typo } from '../../../atoms';\nimport type { RpSessionDetailData } from '../types';\nimport { formatDate, formatDuration, statusLabelOf } from '../utils/session-utils';\n\nexport interface HeaderProps {\n detail: RpSessionDetailData;\n hasScript: boolean;\n onOpenReplay: () => void;\n onResolveMediaUrl?: (\n rawUrl: string,\n options: { disposition: 'inline' | 'attachment' }\n ) => string | Promise<string>;\n /** '영상' 버튼 우측에 렌더되는 소비자 정의 액션 슬롯. */\n actions?: React.ReactNode;\n}\n\nfunction PdfIcon(): React.ReactElement {\n return (\n <svg\n aria-hidden=\"true\"\n fill=\"none\"\n height=\"13\"\n stroke=\"currentColor\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth=\"2\"\n viewBox=\"0 0 24 24\"\n width=\"13\"\n >\n <path d=\"M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z\" />\n <path d=\"M14 3v5h5\" />\n <path d=\"M12 12v5\" />\n <path d=\"M9.5 14.5 12 17l2.5-2.5\" />\n </svg>\n );\n}\n\nfunction VideoIcon(): React.ReactElement {\n return (\n <svg\n aria-hidden=\"true\"\n fill=\"none\"\n height=\"13\"\n stroke=\"currentColor\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth=\"2\"\n viewBox=\"0 0 24 24\"\n width=\"13\"\n >\n <rect height=\"12\" rx=\"2\" width=\"13\" x=\"2\" y=\"6\" />\n <path d=\"m15 10 6-3v10l-6-3z\" />\n </svg>\n );\n}\n\nexport function Header({\n detail,\n hasScript,\n onOpenReplay,\n onResolveMediaUrl,\n actions,\n}: HeaderProps): React.ReactElement {\n // PDF 산출물은 embedLink(AI 리포트 PDF). trainingLogURL 은 대화 스크립트 JSON 이므로 PDF 가 아니다.\n const pdfHref = detail.embedLink;\n const hasReplayMedia = Boolean(\n detail.trainingVideoURL || detail.trainingAudioURL\n );\n\n const handlePdfClick = (): void => {\n if (!pdfHref || !onResolveMediaUrl) return;\n void Promise.resolve(\n onResolveMediaUrl(pdfHref, { disposition: 'attachment' })\n ).then((url) => {\n window.open(url, '_blank');\n });\n };\n\n const replayActive = hasScript && hasReplayMedia;\n\n return (\n <Box>\n <Flex\n align={{ md: 'start' }}\n direction={{ initial: 'column', md: 'row' }}\n gap=\"4\"\n justify=\"between\"\n >\n <Box style={{ minWidth: 0 }}>\n <Flex align=\"center\" gap=\"2\" mb=\"2\" wrap=\"wrap\">\n <Badge color=\"neutral\" size=\"small\" variant=\"soft\">\n {statusLabelOf(detail.status)}\n </Badge>\n <Typo as=\"span\" color=\"gray\" variant=\"body\">\n {detail.trainingSessionNum}회차\n {detail.characterTitle ? ` · ${detail.characterTitle}` : ''}\n </Typo>\n </Flex>\n <Heading\n size={{ initial: '5', md: '7' }}\n style={{ textWrap: 'balance' }}\n weight=\"bold\"\n >\n {detail.scenarioTitle ?? '롤플레잉 세션'}\n </Heading>\n <Typo as=\"p\" color=\"gray\" mono mt=\"1\" variant=\"body\">\n {formatDate(detail.createdAt)} · {formatDuration(detail.trainingTime)}\n </Typo>\n </Box>\n <Flex\n gap=\"2\"\n justify={{ initial: 'start', md: 'end' }}\n style={{ flexShrink: 0 }}\n wrap=\"wrap\"\n >\n {onResolveMediaUrl ? (\n <Button\n color=\"gray\"\n disabled={!pdfHref}\n onClick={pdfHref ? handlePdfClick : undefined}\n size={{ initial: 'small', md: 'medium' }}\n title={pdfHref ? undefined : '산출물 없음'}\n type=\"button\"\n variant=\"outline\"\n >\n <PdfIcon />\n <span style={{ whiteSpace: 'nowrap' }}>PDF 다운로드</span>\n </Button>\n ) : (\n <Button\n asChild={Boolean(pdfHref)}\n color=\"gray\"\n disabled={!pdfHref}\n size={{ initial: 'small', md: 'medium' }}\n title={pdfHref ? undefined : '산출물 없음'}\n type=\"button\"\n variant=\"outline\"\n >\n {pdfHref ? (\n <a href={pdfHref} rel=\"noopener noreferrer\" target=\"_blank\">\n <PdfIcon />\n <span style={{ whiteSpace: 'nowrap' }}>PDF 다운로드</span>\n </a>\n ) : (\n <span>\n <PdfIcon />\n <span style={{ whiteSpace: 'nowrap' }}>PDF 다운로드</span>\n </span>\n )}\n </Button>\n )}\n {hasScript ? (\n <Button\n color=\"gray\"\n disabled={!replayActive}\n onClick={onOpenReplay}\n size={{ initial: 'small', md: 'medium' }}\n title={replayActive ? undefined : '다시보기 없음'}\n type=\"button\"\n variant=\"outline\"\n >\n <VideoIcon />\n <span style={{ whiteSpace: 'nowrap' }}>영상</span>\n </Button>\n ) : null}\n {actions ?? null}\n </Flex>\n </Flex>\n </Box>\n );\n}\n"],"mappings":"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","names":[]}
@@ -1,6 +1,3 @@
1
- import {
2
- DynamicFormEditorPreviewPanel
3
- } from "./chunk-V7KAHLW2.js";
4
1
  import {
5
2
  DynamicFormEditorFieldEditorPanel
6
3
  } from "./chunk-3DZIX4CE.js";
@@ -22,6 +19,9 @@ import {
22
19
  import {
23
20
  DynamicFormEditorIssuesPanel
24
21
  } from "./chunk-D4U2ZV2A.js";
22
+ import {
23
+ DynamicFormEditorPreviewPanel
24
+ } from "./chunk-V7KAHLW2.js";
25
25
  import {
26
26
  Flex
27
27
  } from "./chunk-25HMMI7R.js";
@@ -227,4 +227,4 @@ function DynamicFormEditor(props) {
227
227
  export {
228
228
  DynamicFormEditor
229
229
  };
230
- //# sourceMappingURL=chunk-W7LO5KYT.js.map
230
+ //# sourceMappingURL=chunk-NEJCZABQ.js.map
@@ -3,10 +3,10 @@ import {
3
3
  } from "./chunk-TFJJKM3W.js";
4
4
  import {
5
5
  Content
6
- } from "./chunk-CPSXONQO.js";
6
+ } from "./chunk-BTU3IMVG.js";
7
7
  import {
8
8
  Header
9
- } from "./chunk-VJREZH2Z.js";
9
+ } from "./chunk-JDCA3TP5.js";
10
10
  import {
11
11
  SessionReplayDialog
12
12
  } from "./chunk-AS3JVRQ3.js";
@@ -83,7 +83,8 @@ function RpSessionDetail({
83
83
  script,
84
84
  onResolveMediaUrl,
85
85
  onError: _onError,
86
- sectionScrollMarginTop = "120px"
86
+ sectionScrollMarginTop = "120px",
87
+ headerActions
87
88
  }) {
88
89
  const [replayOpen, setReplayOpen] = useState(false);
89
90
  const speechShare = useMemo(
@@ -104,6 +105,7 @@ function RpSessionDetail({
104
105
  /* @__PURE__ */ jsx(
105
106
  Header,
106
107
  {
108
+ actions: headerActions,
107
109
  detail,
108
110
  hasScript,
109
111
  onOpenReplay: () => {
@@ -139,4 +141,4 @@ function RpSessionDetail({
139
141
  export {
140
142
  RpSessionDetail
141
143
  };
142
- //# sourceMappingURL=chunk-6GODZ2YW.js.map
144
+ //# sourceMappingURL=chunk-ORP7UCUU.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/app/roleplay/rp-session-detail.tsx"],"sourcesContent":["'use client';\n\nimport React, { useMemo, useState } from 'react';\nimport { Box, Card, Flex, Heading, Progress, Skeleton, Typo } from '../../atoms';\nimport type { RpSessionDetailData, RpSessionHistoryItem, RpSessionScript } from './types';\nimport { parseReportData } from './utils/report-parse';\nimport { computeSpeechShare } from './utils/session-utils';\nimport { Content } from './parts/content';\nimport { Header } from './parts/header';\nimport { SessionReplayDialog } from './parts/session-replay-dialog';\n\n/* ───────────────────────── Public contract ───────────────────────── */\n\nexport interface RpSessionDetailProps {\n detail: RpSessionDetailData;\n history: RpSessionHistoryItem[];\n isLoading?: boolean;\n script?: RpSessionScript | null;\n onResolveMediaUrl?: (\n rawUrl: string,\n options: { disposition: 'inline' | 'attachment' }\n ) => string | Promise<string>;\n onError?: (error: unknown, context: Record<string, unknown>) => void;\n sectionScrollMarginTop?: string;\n /** 헤더 액션 행(PDF 다운로드·영상)의 '영상' 버튼 우측에 렌더되는 소비자 정의 액션 슬롯(예: website 공유하기 버튼). */\n headerActions?: React.ReactNode;\n}\n\n/* ───────────────────────── Skeleton ───────────────────────── */\n\nfunction RpSessionDetailSkeleton(): React.ReactElement {\n return (\n <Box py=\"6\">\n <Flex direction=\"column\" gap=\"6\">\n <Box>\n <Skeleton>\n <Heading size=\"7\">롤플레잉 세션 제목 로딩 중</Heading>\n </Skeleton>\n <Box mt=\"2\">\n <Skeleton>\n <Typo as=\"p\" variant=\"body\">\n 날짜 · 소요 시간이 로딩되는 영역입니다\n </Typo>\n </Skeleton>\n </Box>\n </Box>\n <section>\n <Skeleton>\n <Heading size=\"5\">종합 점수</Heading>\n </Skeleton>\n <Box mt=\"3\">\n <Card variant=\"surface\">\n <Flex align=\"center\" direction=\"column\" gap=\"2\" py=\"3\">\n <Skeleton>\n <Heading size=\"7\">00</Heading>\n </Skeleton>\n </Flex>\n </Card>\n </Box>\n </section>\n <section>\n <Skeleton>\n <Heading size=\"5\">평가 지표 요약</Heading>\n </Skeleton>\n <Box mt=\"3\">\n <Flex direction=\"column\" gap=\"3\">\n {[0, 1, 2].map((i) => (\n <Skeleton key={i}>\n <Typo as=\"p\" variant=\"body\">\n 평가 지표 항목이 로딩되는 중입니다\n </Typo>\n </Skeleton>\n ))}\n </Flex>\n </Box>\n </section>\n </Flex>\n </Box>\n );\n}\n\n/* ───────────────────────── Analyzing state ───────────────────────── */\n\nfunction AnalyzingState(): React.ReactElement {\n return (\n <Flex\n align=\"center\"\n direction=\"column\"\n justify=\"center\"\n style={{\n border: '1px dashed var(--gray-6)',\n borderRadius: 'var(--radius-3)',\n background: 'var(--gray-a1)',\n padding: '40px 24px',\n textAlign: 'center',\n }}\n >\n <Heading size={{ initial: '4', md: '5' }} weight=\"bold\">\n AI 분석 중입니다\n </Heading>\n <Box mt=\"2\" style={{ maxWidth: '480px' }}>\n <Typo as=\"p\" variant=\"body\">\n 대화와 산출물은 저장되었고, AI가 지표별 근거와 코칭을 생성하는 중입니다.\n </Typo>\n </Box>\n <Box mt=\"4\" style={{ width: '100%', maxWidth: '280px' }}>\n <Progress size=\"2\" value={66} />\n </Box>\n </Flex>\n );\n}\n\n/* ───────────────────────── Main component ───────────────────────── */\n\nexport function RpSessionDetail({\n detail,\n history,\n isLoading = false,\n script,\n onResolveMediaUrl,\n onError: _onError,\n sectionScrollMarginTop = '120px',\n headerActions,\n}: RpSessionDetailProps): React.ReactElement {\n const [replayOpen, setReplayOpen] = useState(false);\n\n // script 미전달 시 발화량 표시 생략. 전달 시 script로 계산.\n const speechShare = useMemo(\n () => (script !== null && script !== undefined ? computeSpeechShare(script.items) : null),\n [script],\n );\n\n const report = useMemo(\n () => parseReportData(detail.reportData, detail.guideline, detail.displayMeta),\n [detail.reportData, detail.guideline, detail.displayMeta],\n );\n\n const hasScript = script !== null && script !== undefined;\n const isAnalyzing = detail.status === 'analyzing';\n\n if (isLoading) {\n return <RpSessionDetailSkeleton />;\n }\n\n return (\n <Box py=\"6\">\n <Flex direction=\"column\" gap={{ initial: '5', md: '7' }}>\n <Header\n actions={headerActions}\n detail={detail}\n hasScript={hasScript}\n onOpenReplay={() => {\n setReplayOpen(true);\n }}\n onResolveMediaUrl={onResolveMediaUrl}\n />\n\n {isAnalyzing ? (\n <AnalyzingState />\n ) : (\n <Content\n detail={detail}\n history={history}\n report={report}\n scrollMarginTop={sectionScrollMarginTop}\n speechShare={speechShare}\n />\n )}\n </Flex>\n\n <SessionReplayDialog\n detail={detail}\n onOpenChange={setReplayOpen}\n onResolveMediaUrl={onResolveMediaUrl}\n open={replayOpen}\n script={script}\n />\n </Box>\n );\n}\n"],"mappings":"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","names":[]}
@@ -83,7 +83,7 @@ function StepDetail({
83
83
  step.basis ? /* @__PURE__ */ jsx(RubricField, { label: "\uADFC\uAC70", value: step.basis }) : null,
84
84
  step.issue ? /* @__PURE__ */ jsx(RubricField, { label: "\uC544\uC26C\uC6B4 \uC810", value: step.issue }) : null
85
85
  ] }) }) : null,
86
- step.improve || step.example || step.idealExample ? /* @__PURE__ */ jsx(Box, { pt: "3", children: /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: "3", children: [
86
+ step.improve || step.example ? /* @__PURE__ */ jsx(Box, { pt: "3", children: /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: "3", children: [
87
87
  step.improve ? /* @__PURE__ */ jsx(RubricField, { label: "\uAC1C\uC120\uC810", value: step.improve }) : null,
88
88
  step.example ? /* @__PURE__ */ jsxs(
89
89
  Box,
@@ -107,29 +107,6 @@ function StepDetail({
107
107
  /* @__PURE__ */ jsx(Box, { mt: "1", style: { maxWidth: "65ch" }, children: /* @__PURE__ */ jsx(Typo, { as: "p", style: { color: "var(--accent-11)" }, variant: "body", children: step.example }) })
108
108
  ]
109
109
  }
110
- ) : null,
111
- step.idealExample ? /* @__PURE__ */ jsxs(
112
- Box,
113
- {
114
- p: "2",
115
- style: {
116
- borderRadius: "var(--radius-2)",
117
- background: "var(--gray-a2)"
118
- },
119
- children: [
120
- /* @__PURE__ */ jsx(Typo, { as: "p", color: "gray", variant: "body", weight: "medium", children: "\uBAA8\uBC94 \uC608\uC2DC" }),
121
- /* @__PURE__ */ jsx(Box, { mt: "1", style: { maxWidth: "65ch" }, children: /* @__PURE__ */ jsx(
122
- Typo,
123
- {
124
- as: "p",
125
- color: "gray",
126
- style: { whiteSpace: "pre-line" },
127
- variant: "body",
128
- children: step.idealExample
129
- }
130
- ) })
131
- ]
132
- }
133
110
  ) : null
134
111
  ] }) }) : null
135
112
  ] })
@@ -139,4 +116,4 @@ function StepDetail({
139
116
  export {
140
117
  StepDetail
141
118
  };
142
- //# sourceMappingURL=chunk-KJPBBBVK.js.map
119
+ //# sourceMappingURL=chunk-YHA4OOND.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/app/roleplay/parts/step-detail.tsx"],"sourcesContent":["'use client';\n\nimport { Box, Flex, Progress, Typo } from '../../../atoms';\nimport type { RpReportStep } from '../utils/report-parse';\nimport {\n stepCode,\n stepColor,\n stepGradeLabel,\n stepRatio,\n stepScoreText,\n stepVars,\n} from '../utils/session-utils';\n\nexport interface StepDetailProps {\n step: RpReportStep;\n scrollMarginTop?: string;\n}\n\nfunction RubricField({\n label,\n value,\n}: {\n label: string;\n value: string;\n}): React.ReactElement {\n return (\n <Box>\n <Typo as=\"p\" variant=\"body\" weight=\"medium\">\n {label}\n </Typo>\n <Box mt=\"1\" style={{ maxWidth: '65ch' }}>\n <Typo as=\"p\" variant=\"body\">\n {value}\n </Typo>\n </Box>\n </Box>\n );\n}\n\nexport function StepDetail({\n step,\n scrollMarginTop = '120px',\n}: StepDetailProps): React.ReactElement {\n const qv = stepVars(step);\n const ratio = stepRatio(step);\n const scoreText = stepScoreText(step);\n const gradeLabel = stepGradeLabel(step);\n\n return (\n <Box id={`step-${step.key}`} style={{ scrollMarginTop }}>\n <Flex align=\"center\" gap=\"2\" justify=\"between\" mb=\"1\">\n <Box style={{ minWidth: 0 }}>\n <Typo as=\"span\" color=\"gray\" mono variant=\"body\">\n {stepCode(step)}\n </Typo>\n <Typo as=\"p\" variant=\"subtitle\" weight=\"bold\">\n {step.label}\n </Typo>\n </Box>\n <Flex align=\"end\" direction=\"column\" gap=\"1\" style={{ flexShrink: 0 }}>\n {scoreText !== null ? (\n <Typo\n as=\"span\"\n mono\n style={{ color: qv.fg }}\n variant=\"subtitle\"\n weight=\"bold\"\n >\n {scoreText}\n </Typo>\n ) : null}\n {gradeLabel !== null ? (\n <Typo\n as=\"span\"\n mono\n style={{\n display: 'inline-block',\n borderRadius: 'var(--radius-2)',\n border: `1px solid ${qv.border}`,\n background: qv.bg,\n color: qv.fg,\n padding: '1px 8px',\n }}\n variant=\"body\"\n weight=\"medium\"\n >\n {gradeLabel}\n </Typo>\n ) : null}\n </Flex>\n </Flex>\n\n {ratio !== null ? (\n <Box mb=\"3\" mt=\"2\">\n <Progress color={stepColor(step)} size=\"2\" value={ratio * 100} />\n </Box>\n ) : null}\n\n {step.description ? (\n <Box mb=\"3\" mt={ratio !== null ? '0' : '2'} style={{ maxWidth: '65ch' }}>\n <Typo as=\"p\" color=\"gray\" variant=\"body\">\n {step.description}\n </Typo>\n </Box>\n ) : null}\n\n <Flex direction=\"column\" gap=\"0\">\n {step.basis || step.issue ? (\n <Box pb=\"3\" style={{ borderBottom: '1px solid var(--gray-a4)' }}>\n <Flex direction=\"column\" gap=\"3\">\n {step.basis ? <RubricField label=\"근거\" value={step.basis} /> : null}\n {step.issue ? <RubricField label=\"아쉬운 점\" value={step.issue} /> : null}\n </Flex>\n </Box>\n ) : null}\n\n {step.improve || step.example ? (\n <Box pt=\"3\">\n <Flex direction=\"column\" gap=\"3\">\n {step.improve ? <RubricField label=\"개선점\" value={step.improve} /> : null}\n {step.example ? (\n <Box\n p=\"2\"\n style={{\n borderRadius: 'var(--radius-2)',\n background: 'var(--accent-a2)',\n }}\n >\n <Typo\n as=\"p\"\n style={{ color: 'var(--accent-11)' }}\n variant=\"body\"\n weight=\"medium\"\n >\n 예시 스크립트\n </Typo>\n <Box mt=\"1\" style={{ maxWidth: '65ch' }}>\n <Typo as=\"p\" style={{ color: 'var(--accent-11)' }} variant=\"body\">\n {step.example}\n </Typo>\n </Box>\n </Box>\n ) : null}\n </Flex>\n </Box>\n ) : null}\n </Flex>\n </Box>\n );\n}\n"],"mappings":"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","names":[]}
package/dist/index.js CHANGED
@@ -123,9 +123,6 @@ import {
123
123
  DatePicker
124
124
  } from "./chunk-VVWQZK5L.js";
125
125
  import "./chunk-2ISIAPFS.js";
126
- import {
127
- TrainingInfoGuideline
128
- } from "./chunk-AHYOAFOU.js";
129
126
  import {
130
127
  LearningPost
131
128
  } from "./chunk-CUS3ORMD.js";
@@ -163,20 +160,18 @@ import {
163
160
  getTimeOptions,
164
161
  parseTimeOption
165
162
  } from "./chunk-NWJSLIQ3.js";
163
+ import {
164
+ TrainingInfoGuideline
165
+ } from "./chunk-AHYOAFOU.js";
166
166
  import {
167
167
  DynamicFormEditor
168
- } from "./chunk-W7LO5KYT.js";
169
- import "./chunk-V7KAHLW2.js";
168
+ } from "./chunk-NEJCZABQ.js";
170
169
  import {
171
170
  DynamicFormResponseItem
172
171
  } from "./chunk-77H5BUYO.js";
173
172
  import {
174
173
  getDynamicFormDisplayValue
175
174
  } from "./chunk-K7LTEKQS.js";
176
- import {
177
- DynamicForm,
178
- createYupValidation
179
- } from "./chunk-SQ3FYBJT.js";
180
175
  import {
181
176
  EmptyState
182
177
  } from "./chunk-2EPKY2VT.js";
@@ -186,9 +181,6 @@ import {
186
181
  import {
187
182
  GhostPost
188
183
  } from "./chunk-PAUEQXYG.js";
189
- import {
190
- AutocompleteTextfield
191
- } from "./chunk-OOM522XP.js";
192
184
  import {
193
185
  DescriptionJsonRender
194
186
  } from "./chunk-N3FMP27I.js";
@@ -202,13 +194,21 @@ import {
202
194
  import {
203
195
  DownloadCard
204
196
  } from "./chunk-C6HO3IGE.js";
205
- import {
206
- DynamicFormField
207
- } from "./chunk-NK6S7ZMB.js";
208
197
  import "./chunk-3DZIX4CE.js";
209
198
  import "./chunk-CBYIPRND.js";
210
199
  import "./chunk-Z7WNKARH.js";
211
200
  import "./chunk-D4U2ZV2A.js";
201
+ import "./chunk-V7KAHLW2.js";
202
+ import {
203
+ DynamicForm,
204
+ createYupValidation
205
+ } from "./chunk-SQ3FYBJT.js";
206
+ import {
207
+ DynamicFormField
208
+ } from "./chunk-NK6S7ZMB.js";
209
+ import {
210
+ AutocompleteTextfield
211
+ } from "./chunk-OOM522XP.js";
212
212
  import {
213
213
  DynamicFormProvider,
214
214
  useDynamicForm
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  DynamicFormEditor
3
- } from "../chunk-W7LO5KYT.js";
4
- import "../chunk-V7KAHLW2.js";
5
- import "../chunk-SQ3FYBJT.js";
6
- import "../chunk-NK6S7ZMB.js";
3
+ } from "../chunk-NEJCZABQ.js";
7
4
  import "../chunk-3DZIX4CE.js";
8
5
  import "../chunk-CBYIPRND.js";
9
6
  import "../chunk-Z7WNKARH.js";
10
7
  import "../chunk-D4U2ZV2A.js";
8
+ import "../chunk-V7KAHLW2.js";
9
+ import "../chunk-SQ3FYBJT.js";
10
+ import "../chunk-NK6S7ZMB.js";
11
11
  import "../chunk-FAL6S2XK.js";
12
12
  import "../chunk-AELKCD6B.js";
13
13
  import "../chunk-5XYPLDI5.js";
@@ -51,9 +51,6 @@ import {
51
51
  DatePicker
52
52
  } from "../chunk-VVWQZK5L.js";
53
53
  import "../chunk-2ISIAPFS.js";
54
- import {
55
- TrainingInfoGuideline
56
- } from "../chunk-AHYOAFOU.js";
57
54
  import {
58
55
  LearningPost
59
56
  } from "../chunk-CUS3ORMD.js";
@@ -81,18 +78,16 @@ import "../chunk-BXX3B3E4.js";
81
78
  import "../chunk-NRPDVI76.js";
82
79
  import "../chunk-2BPFXK4L.js";
83
80
  import "../chunk-NWJSLIQ3.js";
81
+ import {
82
+ TrainingInfoGuideline
83
+ } from "../chunk-AHYOAFOU.js";
84
84
  import {
85
85
  DynamicFormEditor
86
- } from "../chunk-W7LO5KYT.js";
87
- import "../chunk-V7KAHLW2.js";
86
+ } from "../chunk-NEJCZABQ.js";
88
87
  import {
89
88
  DynamicFormResponseItem
90
89
  } from "../chunk-77H5BUYO.js";
91
90
  import "../chunk-K7LTEKQS.js";
92
- import {
93
- DynamicForm,
94
- createYupValidation
95
- } from "../chunk-SQ3FYBJT.js";
96
91
  import {
97
92
  EmptyState
98
93
  } from "../chunk-2EPKY2VT.js";
@@ -102,9 +97,6 @@ import {
102
97
  import {
103
98
  GhostPost
104
99
  } from "../chunk-PAUEQXYG.js";
105
- import {
106
- AutocompleteTextfield
107
- } from "../chunk-OOM522XP.js";
108
100
  import {
109
101
  DescriptionJsonRender
110
102
  } from "../chunk-N3FMP27I.js";
@@ -115,13 +107,21 @@ import {
115
107
  import {
116
108
  DownloadCard
117
109
  } from "../chunk-C6HO3IGE.js";
118
- import {
119
- DynamicFormField
120
- } from "../chunk-NK6S7ZMB.js";
121
110
  import "../chunk-3DZIX4CE.js";
122
111
  import "../chunk-CBYIPRND.js";
123
112
  import "../chunk-Z7WNKARH.js";
124
113
  import "../chunk-D4U2ZV2A.js";
114
+ import "../chunk-V7KAHLW2.js";
115
+ import {
116
+ DynamicForm,
117
+ createYupValidation
118
+ } from "../chunk-SQ3FYBJT.js";
119
+ import {
120
+ DynamicFormField
121
+ } from "../chunk-NK6S7ZMB.js";
122
+ import {
123
+ AutocompleteTextfield
124
+ } from "../chunk-OOM522XP.js";
125
125
  import "../chunk-FAL6S2XK.js";
126
126
  import "../chunk-AELKCD6B.js";
127
127
  import "../chunk-5XYPLDI5.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tipp/ui",
3
- "version": "2.10.5",
3
+ "version": "2.11.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "exports": {
@@ -12,6 +12,8 @@ export interface HeaderProps {
12
12
  rawUrl: string,
13
13
  options: { disposition: 'inline' | 'attachment' }
14
14
  ) => string | Promise<string>;
15
+ /** '영상' 버튼 우측에 렌더되는 소비자 정의 액션 슬롯. */
16
+ actions?: React.ReactNode;
15
17
  }
16
18
 
17
19
  function PdfIcon(): React.ReactElement {
@@ -59,6 +61,7 @@ export function Header({
59
61
  hasScript,
60
62
  onOpenReplay,
61
63
  onResolveMediaUrl,
64
+ actions,
62
65
  }: HeaderProps): React.ReactElement {
63
66
  // PDF 산출물은 embedLink(AI 리포트 PDF). trainingLogURL 은 대화 스크립트 JSON 이므로 PDF 가 아니다.
64
67
  const pdfHref = detail.embedLink;
@@ -162,6 +165,7 @@ export function Header({
162
165
  <span style={{ whiteSpace: 'nowrap' }}>영상</span>
163
166
  </Button>
164
167
  ) : null}
168
+ {actions ?? null}
165
169
  </Flex>
166
170
  </Flex>
167
171
  </Box>
@@ -114,7 +114,7 @@ export function StepDetail({
114
114
  </Box>
115
115
  ) : null}
116
116
 
117
- {step.improve || step.example || step.idealExample ? (
117
+ {step.improve || step.example ? (
118
118
  <Box pt="3">
119
119
  <Flex direction="column" gap="3">
120
120
  {step.improve ? <RubricField label="개선점" value={step.improve} /> : null}
@@ -141,29 +141,6 @@ export function StepDetail({
141
141
  </Box>
142
142
  </Box>
143
143
  ) : null}
144
- {step.idealExample ? (
145
- <Box
146
- p="2"
147
- style={{
148
- borderRadius: 'var(--radius-2)',
149
- background: 'var(--gray-a2)',
150
- }}
151
- >
152
- <Typo as="p" color="gray" variant="body" weight="medium">
153
- 모범 예시
154
- </Typo>
155
- <Box mt="1" style={{ maxWidth: '65ch' }}>
156
- <Typo
157
- as="p"
158
- color="gray"
159
- style={{ whiteSpace: 'pre-line' }}
160
- variant="body"
161
- >
162
- {step.idealExample}
163
- </Typo>
164
- </Box>
165
- </Box>
166
- ) : null}
167
144
  </Flex>
168
145
  </Box>
169
146
  ) : null}
@@ -22,6 +22,8 @@ export interface RpSessionDetailProps {
22
22
  ) => string | Promise<string>;
23
23
  onError?: (error: unknown, context: Record<string, unknown>) => void;
24
24
  sectionScrollMarginTop?: string;
25
+ /** 헤더 액션 행(PDF 다운로드·영상)의 '영상' 버튼 우측에 렌더되는 소비자 정의 액션 슬롯(예: website 공유하기 버튼). */
26
+ headerActions?: React.ReactNode;
25
27
  }
26
28
 
27
29
  /* ───────────────────────── Skeleton ───────────────────────── */
@@ -118,6 +120,7 @@ export function RpSessionDetail({
118
120
  onResolveMediaUrl,
119
121
  onError: _onError,
120
122
  sectionScrollMarginTop = '120px',
123
+ headerActions,
121
124
  }: RpSessionDetailProps): React.ReactElement {
122
125
  const [replayOpen, setReplayOpen] = useState(false);
123
126
 
@@ -143,6 +146,7 @@ export function RpSessionDetail({
143
146
  <Box py="6">
144
147
  <Flex direction="column" gap={{ initial: '5', md: '7' }}>
145
148
  <Header
149
+ actions={headerActions}
146
150
  detail={detail}
147
151
  hasScript={hasScript}
148
152
  onOpenReplay={() => {
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/app/roleplay/rp-session-detail.tsx"],"sourcesContent":["'use client';\n\nimport React, { useMemo, useState } from 'react';\nimport { Box, Card, Flex, Heading, Progress, Skeleton, Typo } from '../../atoms';\nimport type { RpSessionDetailData, RpSessionHistoryItem, RpSessionScript } from './types';\nimport { parseReportData } from './utils/report-parse';\nimport { computeSpeechShare } from './utils/session-utils';\nimport { Content } from './parts/content';\nimport { Header } from './parts/header';\nimport { SessionReplayDialog } from './parts/session-replay-dialog';\n\n/* ───────────────────────── Public contract ───────────────────────── */\n\nexport interface RpSessionDetailProps {\n detail: RpSessionDetailData;\n history: RpSessionHistoryItem[];\n isLoading?: boolean;\n script?: RpSessionScript | null;\n onResolveMediaUrl?: (\n rawUrl: string,\n options: { disposition: 'inline' | 'attachment' }\n ) => string | Promise<string>;\n onError?: (error: unknown, context: Record<string, unknown>) => void;\n sectionScrollMarginTop?: string;\n}\n\n/* ───────────────────────── Skeleton ───────────────────────── */\n\nfunction RpSessionDetailSkeleton(): React.ReactElement {\n return (\n <Box py=\"6\">\n <Flex direction=\"column\" gap=\"6\">\n <Box>\n <Skeleton>\n <Heading size=\"7\">롤플레잉 세션 제목 로딩 중</Heading>\n </Skeleton>\n <Box mt=\"2\">\n <Skeleton>\n <Typo as=\"p\" variant=\"body\">\n 날짜 · 소요 시간이 로딩되는 영역입니다\n </Typo>\n </Skeleton>\n </Box>\n </Box>\n <section>\n <Skeleton>\n <Heading size=\"5\">종합 점수</Heading>\n </Skeleton>\n <Box mt=\"3\">\n <Card variant=\"surface\">\n <Flex align=\"center\" direction=\"column\" gap=\"2\" py=\"3\">\n <Skeleton>\n <Heading size=\"7\">00</Heading>\n </Skeleton>\n </Flex>\n </Card>\n </Box>\n </section>\n <section>\n <Skeleton>\n <Heading size=\"5\">평가 지표 요약</Heading>\n </Skeleton>\n <Box mt=\"3\">\n <Flex direction=\"column\" gap=\"3\">\n {[0, 1, 2].map((i) => (\n <Skeleton key={i}>\n <Typo as=\"p\" variant=\"body\">\n 평가 지표 항목이 로딩되는 중입니다\n </Typo>\n </Skeleton>\n ))}\n </Flex>\n </Box>\n </section>\n </Flex>\n </Box>\n );\n}\n\n/* ───────────────────────── Analyzing state ───────────────────────── */\n\nfunction AnalyzingState(): React.ReactElement {\n return (\n <Flex\n align=\"center\"\n direction=\"column\"\n justify=\"center\"\n style={{\n border: '1px dashed var(--gray-6)',\n borderRadius: 'var(--radius-3)',\n background: 'var(--gray-a1)',\n padding: '40px 24px',\n textAlign: 'center',\n }}\n >\n <Heading size={{ initial: '4', md: '5' }} weight=\"bold\">\n AI 분석 중입니다\n </Heading>\n <Box mt=\"2\" style={{ maxWidth: '480px' }}>\n <Typo as=\"p\" variant=\"body\">\n 대화와 산출물은 저장되었고, AI가 지표별 근거와 코칭을 생성하는 중입니다.\n </Typo>\n </Box>\n <Box mt=\"4\" style={{ width: '100%', maxWidth: '280px' }}>\n <Progress size=\"2\" value={66} />\n </Box>\n </Flex>\n );\n}\n\n/* ───────────────────────── Main component ───────────────────────── */\n\nexport function RpSessionDetail({\n detail,\n history,\n isLoading = false,\n script,\n onResolveMediaUrl,\n onError: _onError,\n sectionScrollMarginTop = '120px',\n}: RpSessionDetailProps): React.ReactElement {\n const [replayOpen, setReplayOpen] = useState(false);\n\n // script 미전달 시 발화량 표시 생략. 전달 시 script로 계산.\n const speechShare = useMemo(\n () => (script !== null && script !== undefined ? computeSpeechShare(script.items) : null),\n [script],\n );\n\n const report = useMemo(\n () => parseReportData(detail.reportData, detail.guideline, detail.displayMeta),\n [detail.reportData, detail.guideline, detail.displayMeta],\n );\n\n const hasScript = script !== null && script !== undefined;\n const isAnalyzing = detail.status === 'analyzing';\n\n if (isLoading) {\n return <RpSessionDetailSkeleton />;\n }\n\n return (\n <Box py=\"6\">\n <Flex direction=\"column\" gap={{ initial: '5', md: '7' }}>\n <Header\n detail={detail}\n hasScript={hasScript}\n onOpenReplay={() => {\n setReplayOpen(true);\n }}\n onResolveMediaUrl={onResolveMediaUrl}\n />\n\n {isAnalyzing ? (\n <AnalyzingState />\n ) : (\n <Content\n detail={detail}\n history={history}\n report={report}\n scrollMarginTop={sectionScrollMarginTop}\n speechShare={speechShare}\n />\n )}\n </Flex>\n\n <SessionReplayDialog\n detail={detail}\n onOpenChange={setReplayOpen}\n onResolveMediaUrl={onResolveMediaUrl}\n open={replayOpen}\n script={script}\n />\n </Box>\n );\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/app/roleplay/parts/step-detail.tsx"],"sourcesContent":["'use client';\n\nimport { Box, Flex, Progress, Typo } from '../../../atoms';\nimport type { RpReportStep } from '../utils/report-parse';\nimport {\n stepCode,\n stepColor,\n stepGradeLabel,\n stepRatio,\n stepScoreText,\n stepVars,\n} from '../utils/session-utils';\n\nexport interface StepDetailProps {\n step: RpReportStep;\n scrollMarginTop?: string;\n}\n\nfunction RubricField({\n label,\n value,\n}: {\n label: string;\n value: string;\n}): React.ReactElement {\n return (\n <Box>\n <Typo as=\"p\" variant=\"body\" weight=\"medium\">\n {label}\n </Typo>\n <Box mt=\"1\" style={{ maxWidth: '65ch' }}>\n <Typo as=\"p\" variant=\"body\">\n {value}\n </Typo>\n </Box>\n </Box>\n );\n}\n\nexport function StepDetail({\n step,\n scrollMarginTop = '120px',\n}: StepDetailProps): React.ReactElement {\n const qv = stepVars(step);\n const ratio = stepRatio(step);\n const scoreText = stepScoreText(step);\n const gradeLabel = stepGradeLabel(step);\n\n return (\n <Box id={`step-${step.key}`} style={{ scrollMarginTop }}>\n <Flex align=\"center\" gap=\"2\" justify=\"between\" mb=\"1\">\n <Box style={{ minWidth: 0 }}>\n <Typo as=\"span\" color=\"gray\" mono variant=\"body\">\n {stepCode(step)}\n </Typo>\n <Typo as=\"p\" variant=\"subtitle\" weight=\"bold\">\n {step.label}\n </Typo>\n </Box>\n <Flex align=\"end\" direction=\"column\" gap=\"1\" style={{ flexShrink: 0 }}>\n {scoreText !== null ? (\n <Typo\n as=\"span\"\n mono\n style={{ color: qv.fg }}\n variant=\"subtitle\"\n weight=\"bold\"\n >\n {scoreText}\n </Typo>\n ) : null}\n {gradeLabel !== null ? (\n <Typo\n as=\"span\"\n mono\n style={{\n display: 'inline-block',\n borderRadius: 'var(--radius-2)',\n border: `1px solid ${qv.border}`,\n background: qv.bg,\n color: qv.fg,\n padding: '1px 8px',\n }}\n variant=\"body\"\n weight=\"medium\"\n >\n {gradeLabel}\n </Typo>\n ) : null}\n </Flex>\n </Flex>\n\n {ratio !== null ? (\n <Box mb=\"3\" mt=\"2\">\n <Progress color={stepColor(step)} size=\"2\" value={ratio * 100} />\n </Box>\n ) : null}\n\n {step.description ? (\n <Box mb=\"3\" mt={ratio !== null ? '0' : '2'} style={{ maxWidth: '65ch' }}>\n <Typo as=\"p\" color=\"gray\" variant=\"body\">\n {step.description}\n </Typo>\n </Box>\n ) : null}\n\n <Flex direction=\"column\" gap=\"0\">\n {step.basis || step.issue ? (\n <Box pb=\"3\" style={{ borderBottom: '1px solid var(--gray-a4)' }}>\n <Flex direction=\"column\" gap=\"3\">\n {step.basis ? <RubricField label=\"근거\" value={step.basis} /> : null}\n {step.issue ? <RubricField label=\"아쉬운 점\" value={step.issue} /> : null}\n </Flex>\n </Box>\n ) : null}\n\n {step.improve || step.example || step.idealExample ? (\n <Box pt=\"3\">\n <Flex direction=\"column\" gap=\"3\">\n {step.improve ? <RubricField label=\"개선점\" value={step.improve} /> : null}\n {step.example ? (\n <Box\n p=\"2\"\n style={{\n borderRadius: 'var(--radius-2)',\n background: 'var(--accent-a2)',\n }}\n >\n <Typo\n as=\"p\"\n style={{ color: 'var(--accent-11)' }}\n variant=\"body\"\n weight=\"medium\"\n >\n 예시 스크립트\n </Typo>\n <Box mt=\"1\" style={{ maxWidth: '65ch' }}>\n <Typo as=\"p\" style={{ color: 'var(--accent-11)' }} variant=\"body\">\n {step.example}\n </Typo>\n </Box>\n </Box>\n ) : null}\n {step.idealExample ? (\n <Box\n p=\"2\"\n style={{\n borderRadius: 'var(--radius-2)',\n background: 'var(--gray-a2)',\n }}\n >\n <Typo as=\"p\" color=\"gray\" variant=\"body\" weight=\"medium\">\n 모범 예시\n </Typo>\n <Box mt=\"1\" style={{ maxWidth: '65ch' }}>\n <Typo\n as=\"p\"\n color=\"gray\"\n style={{ whiteSpace: 'pre-line' }}\n variant=\"body\"\n >\n {step.idealExample}\n </Typo>\n </Box>\n </Box>\n ) : null}\n </Flex>\n </Box>\n ) : null}\n </Flex>\n </Box>\n );\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/app/roleplay/parts/header.tsx"],"sourcesContent":["'use client';\n\nimport { Badge, Box, Button, Flex, Heading, Typo } from '../../../atoms';\nimport type { RpSessionDetailData } from '../types';\nimport { formatDate, formatDuration, statusLabelOf } from '../utils/session-utils';\n\nexport interface HeaderProps {\n detail: RpSessionDetailData;\n hasScript: boolean;\n onOpenReplay: () => void;\n onResolveMediaUrl?: (\n rawUrl: string,\n options: { disposition: 'inline' | 'attachment' }\n ) => string | Promise<string>;\n}\n\nfunction PdfIcon(): React.ReactElement {\n return (\n <svg\n aria-hidden=\"true\"\n fill=\"none\"\n height=\"13\"\n stroke=\"currentColor\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth=\"2\"\n viewBox=\"0 0 24 24\"\n width=\"13\"\n >\n <path d=\"M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z\" />\n <path d=\"M14 3v5h5\" />\n <path d=\"M12 12v5\" />\n <path d=\"M9.5 14.5 12 17l2.5-2.5\" />\n </svg>\n );\n}\n\nfunction VideoIcon(): React.ReactElement {\n return (\n <svg\n aria-hidden=\"true\"\n fill=\"none\"\n height=\"13\"\n stroke=\"currentColor\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth=\"2\"\n viewBox=\"0 0 24 24\"\n width=\"13\"\n >\n <rect height=\"12\" rx=\"2\" width=\"13\" x=\"2\" y=\"6\" />\n <path d=\"m15 10 6-3v10l-6-3z\" />\n </svg>\n );\n}\n\nexport function Header({\n detail,\n hasScript,\n onOpenReplay,\n onResolveMediaUrl,\n}: HeaderProps): React.ReactElement {\n // PDF 산출물은 embedLink(AI 리포트 PDF). trainingLogURL 은 대화 스크립트 JSON 이므로 PDF 가 아니다.\n const pdfHref = detail.embedLink;\n const hasReplayMedia = Boolean(\n detail.trainingVideoURL || detail.trainingAudioURL\n );\n\n const handlePdfClick = (): void => {\n if (!pdfHref || !onResolveMediaUrl) return;\n void Promise.resolve(\n onResolveMediaUrl(pdfHref, { disposition: 'attachment' })\n ).then((url) => {\n window.open(url, '_blank');\n });\n };\n\n const replayActive = hasScript && hasReplayMedia;\n\n return (\n <Box>\n <Flex\n align={{ md: 'start' }}\n direction={{ initial: 'column', md: 'row' }}\n gap=\"4\"\n justify=\"between\"\n >\n <Box style={{ minWidth: 0 }}>\n <Flex align=\"center\" gap=\"2\" mb=\"2\" wrap=\"wrap\">\n <Badge color=\"neutral\" size=\"small\" variant=\"soft\">\n {statusLabelOf(detail.status)}\n </Badge>\n <Typo as=\"span\" color=\"gray\" variant=\"body\">\n {detail.trainingSessionNum}회차\n {detail.characterTitle ? ` · ${detail.characterTitle}` : ''}\n </Typo>\n </Flex>\n <Heading\n size={{ initial: '5', md: '7' }}\n style={{ textWrap: 'balance' }}\n weight=\"bold\"\n >\n {detail.scenarioTitle ?? '롤플레잉 세션'}\n </Heading>\n <Typo as=\"p\" color=\"gray\" mono mt=\"1\" variant=\"body\">\n {formatDate(detail.createdAt)} · {formatDuration(detail.trainingTime)}\n </Typo>\n </Box>\n <Flex\n gap=\"2\"\n justify={{ initial: 'start', md: 'end' }}\n style={{ flexShrink: 0 }}\n wrap=\"wrap\"\n >\n {onResolveMediaUrl ? (\n <Button\n color=\"gray\"\n disabled={!pdfHref}\n onClick={pdfHref ? handlePdfClick : undefined}\n size={{ initial: 'small', md: 'medium' }}\n title={pdfHref ? undefined : '산출물 없음'}\n type=\"button\"\n variant=\"outline\"\n >\n <PdfIcon />\n <span style={{ whiteSpace: 'nowrap' }}>PDF 다운로드</span>\n </Button>\n ) : (\n <Button\n asChild={Boolean(pdfHref)}\n color=\"gray\"\n disabled={!pdfHref}\n size={{ initial: 'small', md: 'medium' }}\n title={pdfHref ? undefined : '산출물 없음'}\n type=\"button\"\n variant=\"outline\"\n >\n {pdfHref ? (\n <a href={pdfHref} rel=\"noopener noreferrer\" target=\"_blank\">\n <PdfIcon />\n <span style={{ whiteSpace: 'nowrap' }}>PDF 다운로드</span>\n </a>\n ) : (\n <span>\n <PdfIcon />\n <span style={{ whiteSpace: 'nowrap' }}>PDF 다운로드</span>\n </span>\n )}\n </Button>\n )}\n {hasScript ? (\n <Button\n color=\"gray\"\n disabled={!replayActive}\n onClick={onOpenReplay}\n size={{ initial: 'small', md: 'medium' }}\n title={replayActive ? undefined : '다시보기 없음'}\n type=\"button\"\n variant=\"outline\"\n >\n <VideoIcon />\n <span style={{ whiteSpace: 'nowrap' }}>영상</span>\n </Button>\n ) : null}\n </Flex>\n </Flex>\n </Box>\n );\n}\n"],"mappings":"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","names":[]}