Skip to content
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
45176cb
feat: 판결 결과 컴포넌트 및 도메인 로직 초기 작업
wjdalss21 Jun 20, 2026
2d32cb4
feat: 판결 결과 페이지 구현 (#판결탭/유형탭/UI)
wjdalss21 Jun 20, 2026
7c205fe
chore: next.config에 Supabase Storage 및 카카오 이미지 도메인 추가
wjdalss21 Jun 20, 2026
9d0c982
chore: dev 브랜치 병합 충돌 해결
wjdalss21 Jun 20, 2026
8e502b5
Merge branch 'dev' of https://github.com/I5-Project/TALKY-OWL into fe…
wjdalss21 Jun 20, 2026
d6413b9
fix: JudgmentResult Avatar img 태그를 next/image로 교체
wjdalss21 Jun 20, 2026
5ba3e39
chore: dev 브랜치 병합 충돌 해결 (auth/index.ts)
wjdalss21 Jun 20, 2026
4f42530
fix: PR #90 리뷰 반영
wjdalss21 Jun 20, 2026
82daceb
fix: PR #90 추가 리뷰 반영
wjdalss21 Jun 20, 2026
11c1b7f
chore: 불필요한 스크린샷 PNG 파일 제거
wjdalss21 Jun 20, 2026
5014012
refactor: CalendarRecord 모델 제거 및 EmotionDiary title 필드 추가
wjdalss21 Jun 20, 2026
fc9aa28
chore: dev 브랜치 병합 충돌 해결
wjdalss21 Jun 20, 2026
4072cc2
feat: 사건기록 페이지 API 연동
wjdalss21 Jun 20, 2026
a02062e
feat: 사건기록 빈 상태 UI 개선
wjdalss21 Jun 20, 2026
884a100
feat: 사건박스 공통 컴포넌트 CaseRecordCard 분리
wjdalss21 Jun 20, 2026
f7cacbb
fix: 카카오 CDN http 프로토콜 이미지 허용 (img1.kakaocdn.net)
wjdalss21 Jun 20, 2026
52f3a38
feat: 홈 페이지 배경 그라데이션 추가 (primary-200 → white)
wjdalss21 Jun 20, 2026
56c29a5
fix: 홈 그라데이션 범위 15%로 조정
wjdalss21 Jun 20, 2026
cdfbccc
chore: dispute.hooks.ts dev 브랜치 import 충돌 사전 해결
wjdalss21 Jun 20, 2026
da89d60
chore: dispute.hooks.ts dev 브랜치 useDispute refetchInterval 옵션 반영
wjdalss21 Jun 20, 2026
9781464
chore: dispute.hooks.ts 머지 충돌 해결
wjdalss21 Jun 20, 2026
c0e03cf
chore: disputes/[id]/page.tsx 머지 충돌 해결
wjdalss21 Jun 20, 2026
6d1b482
fix: 코드레빗 및 juacheon 리뷰 반영
wjdalss21 Jun 20, 2026
a146819
Merge branch 'dev' of https://github.com/I5-Project/TALKY-OWL into fe…
wjdalss21 Jun 22, 2026
9cd5e80
feat: 2인 판결결과 점수 그래프 UI 수정
wjdalss21 Jun 22, 2026
3382c3b
chore: dev 머지 충돌 해결 (page.tsx import 섹션)
wjdalss21 Jun 22, 2026
79d66a1
feat: 2인 판결결과 그래프 디테일 수정
wjdalss21 Jun 22, 2026
b535d14
feat: 진술 탭 참여자 이름/MBTI 표시, 프로필 이미지 fallback 개선
wjdalss21 Jun 22, 2026
1c3a1d8
chore: dev 머지 충돌 해결 (statementCard 클릭 핸들러 + 참여자 이름 병합)
wjdalss21 Jun 22, 2026
d30a438
feat: JudgmentResult 참여자 이름 변수 통일 및 텍스트 치환 적용
wjdalss21 Jun 22, 2026
bad9966
Merge branch 'dev' of https://github.com/I5-Project/TALKY-OWL into fe…
wjdalss21 Jun 22, 2026
2fe63f6
fix: 코드레빗 리뷰 반영
wjdalss21 Jun 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions src/app/(page)/disputes/[id]/DisputePage.module.scss
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
@use '@/styles/abstracts/variables' as v;
@use '@/styles/abstracts/mixins' as m;
@use '@/styles/abstracts/functions' as fn;

// ===========================
Expand Down Expand Up @@ -208,6 +209,31 @@
color: var(--text-primary);
}

.statementFooter {
display: flex;
align-items: center;
gap: fn.r(6);
}

.statementAvatar {
position: relative;
width: fn.r(24);
height: fn.r(24);
border-radius: 50%;
overflow: hidden;
flex-shrink: 0;
}

.statementAvatarImg {
object-fit: cover;
}

.statementMbti {
@include m.text-label-s;

color: v.$color-black-400;
}

// ===========================
// Judgment Sub-tabs (판결 / 유형)
// ===========================
Expand Down
32 changes: 30 additions & 2 deletions src/app/(page)/disputes/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ export default function DisputePage({ params }: { params: Promise<{ id: string }
const isSolo = dispute !== undefined && dispute.participants.length === 1;
const roleAStatement = dispute?.statements?.find((s) => s.role === 'role_a');
const roleBStatement = dispute?.statements?.find((s) => s.role === 'role_b');
const participantA = dispute?.participants.find((p) => p.role === 'role_a');
const participantB = dispute?.participants.find((p) => p.role === 'role_b');
const canJudge = (isSolo && !!roleAStatement?.content) || dispute?.status === 'both_submitted';
const myRole = dispute?.participants.find((p) => p.userId === userMe?.id)?.role;

Expand Down Expand Up @@ -278,8 +280,21 @@ export default function DisputePage({ params }: { params: Promise<{ id: string }
onClick={myRole === 'role_a' && !isCompleted ? () => router.push(`/disputes/${id}/statement?edit=true`) : undefined}
{...(myRole === 'role_a' && !isCompleted ? { role: 'button', tabIndex: 0, onKeyDown: (e) => { if (e.key === 'Enter' || e.key === ' ') router.push(`/disputes/${id}/statement?edit=true`) } } : {})}
>
<p className={styles.statementLabel}>A의 진술</p>
<p className={styles.statementLabel}>{participantA?.name ?? 'A'}님의 진술</p>
<p className={styles.statementContent}>{roleAStatement.content}</p>
<div className={styles.statementFooter}>
<div className={styles.statementAvatar}>
<Image
src={participantA?.profileImageUrl ?? '/images/common/thumbnail-default.svg'}
alt=""
fill
className={styles.statementAvatarImg}
/>
</div>
{participantA?.mbti && (
<span className={styles.statementMbti}>{participantA.mbti}</span>
)}
</div>
</div>
)}
{roleBStatement && (
Expand All @@ -288,8 +303,21 @@ export default function DisputePage({ params }: { params: Promise<{ id: string }
onClick={myRole === 'role_b' && !isCompleted ? () => router.push(`/disputes/${id}/statement?edit=true`) : undefined}
{...(myRole === 'role_b' && !isCompleted ? { role: 'button', tabIndex: 0, onKeyDown: (e) => { if (e.key === 'Enter' || e.key === ' ') router.push(`/disputes/${id}/statement?edit=true`) } } : {})}
>
<p className={styles.statementLabel}>B의 진술</p>
<p className={styles.statementLabel}>{participantB?.name ?? 'B'}님의 진술</p>
<p className={styles.statementContent}>{roleBStatement.content}</p>
<div className={styles.statementFooter}>
<div className={styles.statementAvatar}>
<Image
src={participantB?.profileImageUrl ?? '/images/common/thumbnail-default.svg'}
alt=""
fill
className={styles.statementAvatarImg}
/>
</div>
{participantB?.mbti && (
<span className={styles.statementMbti}>{participantB.mbti}</span>
)}
</div>
</div>
)}
{!roleAStatement && !roleBStatement && (
Expand Down
9 changes: 5 additions & 4 deletions src/app/api/disputes/[id]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const updateDisputeSchema = z

type DisputeForDetail = Prisma.DisputeGetPayload<{
include: {
participants: { include: { user: { select: { name: true; profileImageUrl: true } } } }
participants: { include: { user: { select: { name: true; profileImageUrl: true; image: true; mbti: true } } } }
statements: true
}
}>
Expand All @@ -40,7 +40,8 @@ function toParticipantDto(p: DisputeForDetail['participants'][number]): DisputeP
userId: p.userId,
role: p.role.toLowerCase() as DisputeParticipantDto['role'],
name: p.user.name ?? null,
profileImageUrl: p.user.profileImageUrl ?? null,
profileImageUrl: p.user.profileImageUrl ?? p.user.image ?? null,
mbti: p.user.mbti ?? null,
joinedAt: p.joinedAt.toISOString(),
createdAt: p.createdAt.toISOString(),
}
Expand Down Expand Up @@ -102,7 +103,7 @@ export async function GET(
participants: { some: { userId } },
},
include: {
participants: { include: { user: { select: { name: true, profileImageUrl: true } } } },
participants: { include: { user: { select: { name: true, profileImageUrl: true, image: true, mbti: true } } } },
statements: true,
},
})
Expand Down Expand Up @@ -217,7 +218,7 @@ export async function PATCH(
}),
},
include: {
participants: { include: { user: { select: { name: true, profileImageUrl: true } } } },
participants: { include: { user: { select: { name: true, profileImageUrl: true, image: true, mbti: true } } } },
statements: true,
},
})
Expand Down
5 changes: 3 additions & 2 deletions src/app/api/disputes/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const createDisputeSchema = z.object({
})

type DisputeForList = Prisma.DisputeGetPayload<{
include: { participants: { include: { user: { select: { profileImageUrl: true } } } } }
include: { participants: { include: { user: { select: { profileImageUrl: true; image: true } } } } }
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
}>

function toParticipantDto(p: DisputeForList['participants'][number]): DisputeParticipantDto {
Expand All @@ -31,7 +31,8 @@ function toParticipantDto(p: DisputeForList['participants'][number]): DisputePar
userId: p.userId,
role: p.role.toLowerCase() as DisputeParticipantDto['role'],
name: null,
profileImageUrl: p.user.profileImageUrl ?? null,
profileImageUrl: p.user.profileImageUrl ?? p.user.image ?? null,
mbti: null,
joinedAt: p.joinedAt.toISOString(),
createdAt: p.createdAt.toISOString(),
}
Expand Down
98 changes: 97 additions & 1 deletion src/components/judgement/JudgmentResult.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -96,11 +96,107 @@
line-height: 1.6;
}

/* ──────────────────────────────────────
점수 그래프 (2인 판결 전용)
────────────────────────────────────── */

.scoreGraph {
display: flex;
flex-direction: column;
gap: fn.r(12);
margin-bottom: fn.r(16);
}

.scoreHeader {
display: flex;
flex-direction: column;
align-items: center;
gap: fn.r(8);
}

.scoreAvatar {
width: fn.r(48);
height: fn.r(48);
border-radius: 50%;
overflow: hidden;
flex-shrink: 0;
}

.scoreAvatarImg {
border-radius: 50%;
object-fit: cover;
width: fn.r(48);
height: fn.r(48);
}

.scoreTitle {
@include m.text-body-m;

color: v.$color-black-700;
text-align: center;
}

.scoreTitleName {
font-weight: v.$font-weight-bold;
}

.barWrapper {
position: relative;
padding-top: fn.r(10);
padding-bottom: fn.r(28);
}

.bar {
display: flex;
width: 100%;
height: fn.r(16);
border-radius: fn.r(4);
overflow: hidden;
}

.barSegment {
height: 100%;
transition: width 0.4s ease;
}

.barDark { background-color: var(--status-disabled-text); }
.barLight { background-color: v.$color-black-200; }

.barCenter {
position: absolute;
top: 0;
left: 50%;
transform: translateX(-50%);
display: flex;
flex-direction: column;
align-items: center;
}

.centerLine {
width: 0;
height: fn.r(36);
border-left: fn.r(1.5) dashed var(--border-strong);
}

.centerLabel {
margin-top: fn.r(4);
font-size: v.$font-size-caption;
color: v.$color-black-400;
white-space: nowrap;
}

.barLabels {
display: flex;
justify-content: space-between;
font-size: v.$font-size-caption;
color: v.$color-black-500;
}

/* AI 고지 문구 */
.aiNotice {
@include m.text-caption;

color: v.$color-black-400;
color: var(--text-secondary);
text-align: center;
}

Expand Down
84 changes: 76 additions & 8 deletions src/components/judgement/JudgmentResult.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ interface Props {
disputeId: string;
}

function replaceRoleNames(text: string, nameA: string, nameB: string): string {
return text.replace(/A님/g, `${nameA}님`).replace(/B님/g, `${nameB}님`)
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved

function Avatar({ src }: { src: string | null }) {
return (
<div className={styles.avatar}>
Expand All @@ -31,6 +35,8 @@ export default function JudgmentResult({ disputeId }: Props) {
const isSolo = participants.length === 1;
const participantA = participants.find((p) => p.role === 'role_a');
const participantB = participants.find((p) => p.role === 'role_b');
const nameA = participantA?.name ?? 'A';
const nameB = participantB?.name ?? 'B';

const showReconcile = !!judgment.aSuggestedLine || (!isSolo && !!judgment.bSuggestedLine);

Expand All @@ -40,14 +46,76 @@ export default function JudgmentResult({ disputeId }: Props) {
<section className={styles.section}>
<h3 className={styles.sectionTitle}>판결결과</h3>

{/* 점수 그래프 — 2인 판결에서만 노출 */}
{!isSolo && (() => {
const scoreA = Math.max(0, Number(judgment.verdictScoreA) || 0)
const scoreB = Math.max(0, Number(judgment.verdictScoreB) || 0)
const total = scoreA + scoreB
const ratioA = total === 0 ? 50 : (scoreA / total) * 100
const ratioB = total === 0 ? 50 : (scoreB / total) * 100

const moreGuiltyParticipant =
judgment.moreResponsibleRole === 'role_a' ? participantA
: judgment.moreResponsibleRole === 'role_b' ? participantB
: null

return (
<div className={styles.scoreGraph}>
<div className={styles.scoreHeader}>
<div className={styles.scoreAvatar}>
<Image
src={moreGuiltyParticipant?.profileImageUrl ?? '/images/common/thumbnail-default.png'}
alt=""
width={48}
height={48}
className={styles.scoreAvatarImg}
/>
</div>
<p className={styles.scoreTitle}>
{moreGuiltyParticipant ? (
<>
<span className={styles.scoreTitleName}>{moreGuiltyParticipant.name ?? '상대방'}님</span>
이 더 잘못했어요
</>
) : (
'두 분의 잘못이 비슷해요'
)}
</p>
</div>

<div className={styles.barWrapper}>
<div className={styles.bar}>
<div
className={`${styles.barSegment} ${ratioA >= ratioB ? styles.barDark : styles.barLight}`}
style={{ width: `${ratioA}%` }}
/>
<div
className={`${styles.barSegment} ${ratioB > ratioA ? styles.barDark : styles.barLight}`}
style={{ width: `${ratioB}%` }}
/>
</div>
<div className={styles.barCenter}>
<div className={styles.centerLine} />
<span className={styles.centerLabel}>잘못한 점수</span>
</div>
</div>

<div className={styles.barLabels}>
<span>{nameA}님</span>
<span>{nameB}님</span>
</div>
</div>
)
})()}

<div className={styles.cards}>
{judgment.aFault && (
<div className={styles.card}>
<div className={styles.cardHeader}>
<Avatar src={participantA?.profileImageUrl ?? null} />
<span className={styles.cardLabel}>{participantA?.name ?? 'A'}님의 잘못</span>
<span className={styles.cardLabel}>{nameA}님의 잘못</span>
</div>
<p className={styles.cardContent}>{judgment.aFault}</p>
<p className={styles.cardContent}>{replaceRoleNames(judgment.aFault, nameA, nameB)}</p>
</div>
)}

Expand All @@ -56,9 +124,9 @@ export default function JudgmentResult({ disputeId }: Props) {
<div className={styles.card}>
<div className={styles.cardHeader}>
<Avatar src={participantB?.profileImageUrl ?? null} />
<span className={styles.cardLabel}>{participantB?.name ?? 'B'}님의 잘못</span>
<span className={styles.cardLabel}>{nameB}님의 잘못</span>
</div>
<p className={styles.cardContent}>{judgment.bFault}</p>
<p className={styles.cardContent}>{replaceRoleNames(judgment.bFault, nameA, nameB)}</p>
</div>
)}
</div>
Expand All @@ -84,19 +152,19 @@ export default function JudgmentResult({ disputeId }: Props) {
<div className={styles.suggestedCard}>
<div className={styles.cardHeader}>
<Avatar src={participantA?.profileImageUrl ?? null} />
<span className={styles.cardLabel}>{participantA?.name ?? 'A'}님</span>
<span className={styles.cardLabel}>{nameA}님</span>
</div>
<p className={styles.cardContent}>{judgment.aSuggestedLine}</p>
<p className={styles.cardContent}>{replaceRoleNames(judgment.aSuggestedLine, nameA, nameB)}</p>
</div>
)}

{!isSolo && judgment.bSuggestedLine && (
<div className={styles.suggestedCard}>
<div className={styles.cardHeader}>
<Avatar src={participantB?.profileImageUrl ?? null} />
<span className={styles.cardLabel}>{participantB?.name ?? 'B'}님</span>
<span className={styles.cardLabel}>{nameB}님</span>
</div>
<p className={styles.cardContent}>{judgment.bSuggestedLine}</p>
<p className={styles.cardContent}>{replaceRoleNames(judgment.bSuggestedLine, nameA, nameB)}</p>
</div>
)}
</div>
Expand Down
Loading