Skip to content

Commit cf0da05

Browse files
trz-21claude
andcommitted
fix: 레이아웃 폭 깨짐·입력 UI 정비 — tailwind content glob에 src 추가
스크린샷 리뷰에서 발견한 실제 결함 수정. - tailwind.config: content glob에 ./src/** 누락 → Container의 max-w-5xl 등 src 전용 클래스가 purge되어 본문이 전폭으로 펼쳐지고 헤더가 잘림. ./src/** 추가로 max-w-5xl·min-w-0·액센트 보더 등 전부 정상 생성 - AnalysisCard: 긴 쿠팡 URL이 truncate 안 돼 가로 오버플로 → Link·내부 flex에 min-w-0, 날짜 shrink-0 whitespace-nowrap로 잘림 처리 - UrlInput: 삭제 버튼 줄바꿈("삭 제") → ✕ 아이콘 버튼, URL 추가 + 아이콘, 리뷰수 select 폭 제한(sm:w-48), 제출 w-full sm:w-auto, 간격 정돈 - dashboard: 히어로 inner 카드 패딩·그리드 min-w-0 정돈 - 분석 상세: ResultHeader 스탯 3열 그리드로 위계 정리, InsightReport 긴 텍스트 break-words/min-w-0, AnalysisProgress 패딩 반응형 검증: type-check 통과, 테스트 76/76 통과, 생성 CSS에 64rem 확인 Refs #28 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 4adfa83 commit cf0da05

7 files changed

Lines changed: 78 additions & 47 deletions

File tree

frontend/app/(dashboard)/analyses/[id]/page.tsx

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -40,21 +40,23 @@ function ResultHeader({
4040
);
4141
return (
4242
<div className="rounded-2xl bg-gradient-to-br from-brand-700 to-brand-900 p-6 text-white shadow-md">
43-
<p className="text-sm font-medium text-brand-100">분석 완료</p>
44-
<div className="mt-4 grid grid-cols-3 gap-4">
45-
<div>
46-
<p className="text-2xl font-bold sm:text-3xl">{result.products.length}</p>
47-
<p className="mt-0.5 text-xs text-brand-100">분석 상품</p>
43+
<p className="text-sm font-semibold text-brand-100">분석 완료</p>
44+
<div className="mt-5 grid grid-cols-1 gap-5 sm:grid-cols-3 sm:gap-6">
45+
<div className="min-w-0">
46+
<p className="text-2xl font-bold leading-none sm:text-3xl">
47+
{result.products.length}
48+
</p>
49+
<p className="mt-2 text-xs font-medium text-brand-100">분석 상품</p>
4850
</div>
49-
<div>
50-
<p className="text-2xl font-bold sm:text-3xl">{totalReviews}</p>
51-
<p className="mt-0.5 text-xs text-brand-100">총 리뷰 수</p>
51+
<div className="min-w-0">
52+
<p className="text-2xl font-bold leading-none sm:text-3xl">{totalReviews}</p>
53+
<p className="mt-2 text-xs font-medium text-brand-100">총 리뷰 수</p>
5254
</div>
53-
<div>
54-
<p className="text-base font-semibold sm:text-lg">
55+
<div className="min-w-0">
56+
<p className="break-words text-sm font-semibold leading-snug sm:text-base">
5557
{completedAt ? formatCompletedAt(completedAt) : '—'}
5658
</p>
57-
<p className="mt-0.5 text-xs text-brand-100">생성 시각</p>
59+
<p className="mt-2 text-xs font-medium text-brand-100">생성 시각</p>
5860
</div>
5961
</div>
6062
</div>

frontend/app/(dashboard)/dashboard/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ export default function DashboardPage() {
5757
<p className="mt-3 max-w-2xl text-sm leading-relaxed text-brand-100">
5858
경쟁 상품의 쿠팡 URL을 입력하면 AI가 리뷰를 분석해 개선 포인트를 알려드립니다.
5959
</p>
60-
<div className="mt-5 rounded-xl bg-white p-4 shadow-sm sm:p-5">
60+
<div className="mt-5 rounded-xl bg-white p-5 shadow-sm sm:p-6">
6161
<UrlInput onSubmit={handleSubmit} loading={submitting} />
6262
{error && <p className="mt-3 text-sm text-red-600">{error}</p>}
6363
</div>
@@ -80,7 +80,7 @@ export default function DashboardPage() {
8080
description="위에서 경쟁 상품 URL을 입력해 첫 분석을 시작해 보세요."
8181
/>
8282
) : (
83-
<div className="grid gap-4 sm:grid-cols-2">
83+
<div className="grid min-w-0 grid-cols-1 gap-4 sm:grid-cols-2">
8484
{analyses.map((analysis) => (
8585
<AnalysisCard key={analysis.id} analysis={analysis} />
8686
))}

frontend/src/features/analysis/components/AnalysisCard.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,16 +46,16 @@ export function AnalysisCard({ analysis }: AnalysisCardProps) {
4646
return (
4747
<Link
4848
href={`/analyses/${analysis.id}`}
49-
className="group block rounded-xl border border-gray-200 bg-white p-4 shadow-sm transition hover:-translate-y-0.5 hover:border-brand-300 hover:shadow-md"
49+
className="group block min-w-0 rounded-xl border border-gray-200 bg-white p-4 shadow-sm transition hover:-translate-y-0.5 hover:border-brand-300 hover:shadow-md"
5050
>
5151
<div className="flex items-center justify-between gap-2">
5252
<Badge variant={STATUS_VARIANT[analysis.status]} className="inline-flex items-center gap-1.5">
5353
<span className={`h-1.5 w-1.5 rounded-full ${STATUS_DOT[analysis.status]}`} aria-hidden="true" />
5454
{STATUS_LABEL[analysis.status]}
5555
</Badge>
56-
<span className="text-xs text-gray-400">{formatDate(analysis.created_at)}</span>
56+
<span className="shrink-0 whitespace-nowrap text-xs text-gray-400">{formatDate(analysis.created_at)}</span>
5757
</div>
58-
<div className="mt-3 flex items-center gap-2">
58+
<div className="mt-3 flex min-w-0 items-center gap-2">
5959
<svg
6060
xmlns="http://www.w3.org/2000/svg"
6161
viewBox="0 0 24 24"

frontend/src/features/analysis/components/AnalysisProgress.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ export function AnalysisProgress({ status }: { status: AnalysisStatus }) {
8787
const isActive = status !== 'completed' && status !== 'failed';
8888

8989
return (
90-
<div className="rounded-2xl border border-gray-200 bg-white p-8 shadow-sm">
90+
<div className="rounded-2xl border border-gray-200 bg-white p-6 shadow-sm sm:p-8">
9191
<ol className="flex items-start">
9292
{STEPS.map((step, index) => {
9393
const state = stepStateFor(index, status);

frontend/src/features/analysis/components/InsightReport.tsx

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -189,8 +189,10 @@ function RatingDistribution({ product }: { product: ProductSummary }) {
189189
return (
190190
<div className="flex flex-col gap-3">
191191
<div className="flex items-center justify-between gap-2">
192-
<h3 className="text-sm font-medium text-gray-900">{product.product_name}</h3>
193-
<span className="inline-flex items-center gap-1.5 rounded-full bg-amber-50 px-2.5 py-1 text-xs font-medium text-amber-700">
192+
<h3 className="min-w-0 break-words text-sm font-medium text-gray-900">
193+
{product.product_name}
194+
</h3>
195+
<span className="inline-flex shrink-0 items-center gap-1.5 rounded-full bg-amber-50 px-2.5 py-1 text-xs font-medium text-amber-700">
194196
<span className="text-amber-400">
195197
<svg viewBox="0 0 20 20" fill="currentColor" className="h-3.5 w-3.5" aria-hidden="true">
196198
<path d="M10 1.5l2.4 4.9 5.4.8-3.9 3.8.92 5.4L10 13.9 5.18 16.4l.92-5.4L2.2 7.2l5.4-.8L10 1.5z" />
@@ -251,9 +253,11 @@ export function InsightReport({ result }: InsightReportProps) {
251253
<span className="flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-brand-600 text-sm font-bold text-white shadow-sm shadow-brand-600/30">
252254
{point.rank}
253255
</span>
254-
<div>
255-
<p className="font-medium text-gray-900">{point.title}</p>
256-
<p className="mt-0.5 text-sm text-gray-600">{point.detail}</p>
256+
<div className="min-w-0">
257+
<p className="font-medium text-gray-900 break-words">{point.title}</p>
258+
<p className="mt-0.5 text-sm leading-relaxed text-gray-600 break-words">
259+
{point.detail}
260+
</p>
257261
</div>
258262
</li>
259263
))}
@@ -276,14 +280,14 @@ export function InsightReport({ result }: InsightReportProps) {
276280
key={i}
277281
className="rounded-lg border border-amber-100 bg-amber-50/50 p-3.5 transition-colors hover:bg-amber-50"
278282
>
279-
<p className="font-medium text-gray-900">{weakness.title}</p>
280-
<p className="mt-1 flex items-start gap-1.5 text-sm text-amber-800">
281-
<span className="mt-0.5 text-amber-500" aria-hidden="true">
283+
<p className="font-medium text-gray-900 break-words">{weakness.title}</p>
284+
<p className="mt-1 flex items-start gap-1.5 text-sm leading-relaxed text-amber-800">
285+
<span className="mt-0.5 shrink-0 text-amber-500" aria-hidden="true">
282286
<svg viewBox="0 0 20 20" fill="currentColor" className="h-4 w-4">
283287
<path d="M10 1l2.5 5.5L18 7l-4 4 1 6-5-2.8L5 17l1-6-4-4 5.5-.5L10 1z" />
284288
</svg>
285289
</span>
286-
{weakness.opportunity}
290+
<span className="min-w-0 break-words">{weakness.opportunity}</span>
287291
</p>
288292
</li>
289293
))}
@@ -304,9 +308,9 @@ export function InsightReport({ result }: InsightReportProps) {
304308
{insights.purchase_drivers.map((driver, i) => (
305309
<li
306310
key={i}
307-
className="inline-flex items-center gap-1.5 rounded-full border border-emerald-200 bg-emerald-50 px-3 py-1 text-sm font-medium text-emerald-700"
311+
className="inline-flex max-w-full items-center gap-1.5 rounded-full border border-emerald-200 bg-emerald-50 px-3 py-1 text-sm font-medium text-emerald-700"
308312
>
309-
<span className="text-emerald-500" aria-hidden="true">
313+
<span className="shrink-0 text-emerald-500" aria-hidden="true">
310314
<svg viewBox="0 0 20 20" fill="currentColor" className="h-3.5 w-3.5">
311315
<path fillRule="evenodd" d="M16.7 5.3a1 1 0 010 1.4l-7.5 7.5a1 1 0 01-1.4 0l-3.5-3.5a1 1 0 111.4-1.4l2.8 2.79 6.8-6.79a1 1 0 011.4 0z" clipRule="evenodd" />
312316
</svg>
@@ -324,7 +328,7 @@ export function InsightReport({ result }: InsightReportProps) {
324328
accent="brand"
325329
icon={<ScaleIcon />}
326330
>
327-
<div className="grid gap-5 sm:grid-cols-2">
331+
<div className="grid gap-4 sm:grid-cols-2">
328332
<div className="rounded-lg border border-red-100 bg-red-50/40 p-4">
329333
<h3 className="mb-3 flex items-center gap-1.5 text-sm font-semibold text-red-700">
330334
<span className="text-red-400" aria-hidden="true">
@@ -340,7 +344,7 @@ export function InsightReport({ result }: InsightReportProps) {
340344
<ul className="flex flex-col gap-2">
341345
{insights.top_complaints.map((c, i) => (
342346
<li key={i} className="flex items-center justify-between gap-2">
343-
<span className="text-sm text-gray-800">{c.text}</span>
347+
<span className="min-w-0 break-words text-sm text-gray-800">{c.text}</span>
344348
<span className="flex shrink-0 items-center gap-2">
345349
<Badge variant={severityVariant(c.severity)}>
346350
{severityLabel(c.severity)}
@@ -367,8 +371,8 @@ export function InsightReport({ result }: InsightReportProps) {
367371
<ul className="flex flex-col gap-2">
368372
{insights.top_positives.map((p, i) => (
369373
<li key={i} className="flex items-center justify-between gap-2">
370-
<span className="text-sm text-gray-800">{p.text}</span>
371-
<span className="text-xs text-gray-500">{p.count}</span>
374+
<span className="min-w-0 break-words text-sm text-gray-800">{p.text}</span>
375+
<span className="shrink-0 text-xs text-gray-500">{p.count}</span>
372376
</li>
373377
))}
374378
</ul>

frontend/src/features/analysis/components/UrlInput.tsx

Lines changed: 39 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export function UrlInput({ onSubmit, loading = false }: UrlInputProps) {
3838

3939
return (
4040
<form onSubmit={handleSubmit} className="flex flex-col gap-4">
41-
<div className="flex flex-col gap-3">
41+
<div className="flex flex-col gap-2.5">
4242
{urls.map((url, index) => (
4343
<div key={index} className="flex items-center gap-2">
4444
<Input
@@ -47,40 +47,64 @@ export function UrlInput({ onSubmit, loading = false }: UrlInputProps) {
4747
onChange={(e) => updateUrl(index, e.target.value)}
4848
placeholder="경쟁 상품 쿠팡 URL"
4949
aria-label={`URL ${index + 1}`}
50-
className="flex-1"
50+
className="min-w-0 flex-1"
5151
/>
52-
<Button
52+
<button
5353
type="button"
54-
variant="outline"
55-
size="sm"
5654
onClick={() => removeUrl(index)}
5755
disabled={urls.length <= 1}
5856
aria-label={`URL ${index + 1} 삭제`}
57+
className="flex h-9 w-9 shrink-0 items-center justify-center rounded-md border border-gray-300 text-gray-400 transition hover:border-gray-400 hover:bg-gray-50 hover:text-gray-600 focus:border-brand-500 focus:outline-none focus:ring-2 focus:ring-brand-200 disabled:cursor-not-allowed disabled:opacity-40 disabled:hover:border-gray-300 disabled:hover:bg-transparent disabled:hover:text-gray-400"
5958
>
60-
삭제
61-
</Button>
59+
<svg
60+
xmlns="http://www.w3.org/2000/svg"
61+
viewBox="0 0 24 24"
62+
fill="none"
63+
stroke="currentColor"
64+
strokeWidth={2}
65+
strokeLinecap="round"
66+
strokeLinejoin="round"
67+
className="h-4 w-4"
68+
aria-hidden="true"
69+
>
70+
<path d="M18 6 6 18" />
71+
<path d="m6 6 12 12" />
72+
</svg>
73+
</button>
6274
</div>
6375
))}
6476
</div>
6577

66-
<Button
78+
<button
6779
type="button"
68-
variant="ghost"
69-
size="sm"
7080
onClick={addUrl}
7181
disabled={urls.length >= MAX_URLS}
72-
className="self-start border border-dashed border-gray-300"
82+
className="inline-flex items-center justify-center gap-1.5 self-start rounded-md border border-dashed border-gray-300 px-3 py-2 text-sm font-medium text-gray-600 transition hover:border-brand-400 hover:text-brand-600 focus:border-brand-500 focus:outline-none focus:ring-2 focus:ring-brand-200 disabled:cursor-not-allowed disabled:opacity-40 disabled:hover:border-gray-300 disabled:hover:text-gray-600"
7383
>
84+
<svg
85+
xmlns="http://www.w3.org/2000/svg"
86+
viewBox="0 0 24 24"
87+
fill="none"
88+
stroke="currentColor"
89+
strokeWidth={2}
90+
strokeLinecap="round"
91+
strokeLinejoin="round"
92+
className="h-4 w-4"
93+
aria-hidden="true"
94+
>
95+
<path d="M12 5v14" />
96+
<path d="M5 12h14" />
97+
</svg>
7498
URL 추가
75-
</Button>
99+
</button>
76100

77-
<label className="flex flex-col gap-1 text-sm font-medium text-gray-700">
101+
<label className="flex flex-col gap-1.5 text-sm font-medium text-gray-700">
78102
분석할 리뷰 수
79103
<select
80104
value={reviewLimit}
81105
onChange={(e) => setReviewLimit(Number(e.target.value))}
82106
aria-label="분석할 리뷰 수"
83-
className="rounded-md border border-gray-300 px-3 py-2 text-gray-900 focus:border-brand-500 focus:outline-none"
107+
className="w-full rounded-md border border-gray-300 px-3 py-2 text-gray-900 focus:border-brand-500 focus:outline-none focus:ring-2 focus:ring-brand-200 sm:w-48"
84108
>
85109
{REVIEW_LIMIT_OPTIONS.map((opt) => (
86110
<option key={opt} value={opt}>
@@ -90,7 +114,7 @@ export function UrlInput({ onSubmit, loading = false }: UrlInputProps) {
90114
</select>
91115
</label>
92116

93-
<Button type="submit" disabled={!canSubmit} className="mt-2">
117+
<Button type="submit" disabled={!canSubmit} className="mt-1 w-full sm:w-auto sm:self-start">
94118
{loading ? '분석 중...' : '분석 시작'}
95119
</Button>
96120
</form>

frontend/tailwind.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const config: Config = {
55
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
66
"./components/**/*.{js,ts,jsx,tsx,mdx}",
77
"./app/**/*.{js,ts,jsx,tsx,mdx}",
8+
"./src/**/*.{js,ts,jsx,tsx,mdx}",
89
],
910
theme: {
1011
extend: {

0 commit comments

Comments
 (0)