Skip to content

Commit 0468247

Browse files
authored
리포트/거래 화면 디자인 패치 및 FAB 거래 입력 동작 복구 (#119)
* iOS Safari 입력 필드 폰트 크기 조정으로 자동 확대 방지 스타일 추가 * 보고서 페이지 레이아웃 내부 컨테이너 너비 제한 div 추가 * 거래 생성 시트 호출 로직 useCallback으로 리팩토링 및 FAB 클릭 동작 수정 * 수입 색상을 파랑 계열로, 지출 색상을 밝은 빨강 계열로 변경 및 관련 스타일 업데이트 * 수입 색상을 파랑 계열로 변경 및 관련 스타일 일괄 업데이트 * 최근 카테고리 버튼 활성 상태 스타일 추가 및 전환 효과 적용 * fix: FAB 거래 입력 시트 오픈 안전성 보강 sessionStorage 접근을 예외 안전하게 감싸 Safari 프라이빗 모드에서도 FAB 클릭이 깨지지 않도록 수정하고 이벤트/키 문자열을 공용 상수로 통합 Made-with: Cursor
1 parent 0613b3d commit 0468247

File tree

9 files changed

+136
-52
lines changed

9 files changed

+136
-52
lines changed

apps/web/src/components/dashboard/MonthlySummaryCard.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export function MonthlySummaryCard({ data }: Props) {
1717
<CardContent className="space-y-3">
1818
<div className="flex justify-between">
1919
<span className="text-muted-foreground">수입</span>
20-
<span className="font-semibold text-emerald-600 dark:text-emerald-400">
20+
<span className="font-semibold text-blue-600 dark:text-blue-400">
2121
+{formatCurrency(totalIncome)}
2222
</span>
2323
</div>
@@ -33,7 +33,7 @@ export function MonthlySummaryCard({ data }: Props) {
3333
<span
3434
className={`font-bold ${
3535
netIncome >= 0
36-
? 'text-emerald-600 dark:text-emerald-400'
36+
? 'text-blue-600 dark:text-blue-400'
3737
: 'text-rose-600 dark:text-rose-400'
3838
}`}
3939
>

apps/web/src/components/dashboard/NetAssetCard.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,11 @@ export function NetAssetCard({ data }: Props) {
1111
const { totalAssets, totalLiabilities, netAsset } = data
1212

1313
return (
14-
<Link to="/reports" className="block transition-opacity hover:opacity-80">
14+
<Link
15+
to="/reports"
16+
search={{ tab: 'BALANCE' }}
17+
className="block transition-opacity hover:opacity-80"
18+
>
1519
<Card className="cursor-pointer">
1620
<CardHeader>
1721
<CardTitle className="text-lg">순자산</CardTitle>
@@ -33,7 +37,7 @@ export function NetAssetCard({ data }: Props) {
3337
<span
3438
className={`font-bold ${
3539
netAsset >= 0
36-
? 'text-emerald-600 dark:text-emerald-400'
40+
? 'text-blue-600 dark:text-blue-400'
3741
: 'text-rose-600 dark:text-rose-400'
3842
}`}
3943
>

apps/web/src/components/layout/AppLayout.tsx

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1-
import { useRouterState } from '@tanstack/react-router'
1+
import { useNavigate, useRouterState } from '@tanstack/react-router'
22
import { useState } from 'react'
33
import { BottomNav } from './BottomNav'
44
import { Sidebar } from './Sidebar'
55
import { FAB } from './FAB'
66
import { cn } from '@repo/ui/lib/utils'
7+
import {
8+
emitOpenCreateTransactionEvent,
9+
markOpenCreateTransactionOnce,
10+
} from '../../lib/transactions-open'
711

812
interface AppLayoutProps {
913
children: React.ReactNode
@@ -14,6 +18,7 @@ const FAB_ROUTES = ['/dashboard', '/transactions']
1418

1519
export function AppLayout({ children }: AppLayoutProps) {
1620
const router = useRouterState()
21+
const navigate = useNavigate()
1722
const currentPath = router.location.pathname
1823
const [sidebarCollapsed, setSidebarCollapsed] = useState(false)
1924

@@ -29,10 +34,15 @@ export function AppLayout({ children }: AppLayoutProps) {
2934
)
3035

3136
const handleFABClick = () => {
32-
// TODO: Open transaction input bottom sheet/modal
33-
if (process.env.NODE_ENV !== 'production') {
34-
console.log('FAB clicked - open transaction input')
37+
if (currentPath === '/transactions') {
38+
emitOpenCreateTransactionEvent()
39+
return
3540
}
41+
42+
markOpenCreateTransactionOnce()
43+
void navigate({
44+
to: '/transactions',
45+
})
3646
}
3747

3848
if (!needsLayout) {

apps/web/src/lib/format.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export function getTransactionSign(type: string): '+' | '-' | '' {
2020
}
2121

2222
export function getAmountColorClass(type: string): string {
23-
if (type === 'INCOME') return 'text-emerald-600 dark:text-emerald-400'
23+
if (type === 'INCOME') return 'text-blue-600 dark:text-blue-400'
2424
if (type === 'EXPENSE') return 'text-rose-600 dark:text-rose-400'
2525
return 'text-muted-foreground'
2626
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
export const OPEN_CREATE_EVENT = 'transactions:open-create'
2+
const OPEN_CREATE_ONCE_KEY = 'transactions:open-create-once'
3+
4+
export function emitOpenCreateTransactionEvent() {
5+
if (typeof window === 'undefined') return
6+
window.dispatchEvent(new CustomEvent(OPEN_CREATE_EVENT))
7+
}
8+
9+
export function markOpenCreateTransactionOnce() {
10+
if (typeof window === 'undefined') return
11+
12+
try {
13+
window.sessionStorage.setItem(OPEN_CREATE_ONCE_KEY, '1')
14+
} catch {
15+
// ignore storage write errors
16+
}
17+
}
18+
19+
export function consumeOpenCreateTransactionOnce() {
20+
if (typeof window === 'undefined') return false
21+
22+
try {
23+
const shouldOpen = window.sessionStorage.getItem(OPEN_CREATE_ONCE_KEY) === '1'
24+
if (shouldOpen) {
25+
window.sessionStorage.removeItem(OPEN_CREATE_ONCE_KEY)
26+
}
27+
return shouldOpen
28+
} catch {
29+
return false
30+
}
31+
}

apps/web/src/routes/reports.tsx

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,7 @@ function AnnualBarChart({ data, ariaLabel }: AnnualBarChartProps) {
290290
<div key={month} className="flex flex-1 flex-col items-center gap-1">
291291
<div className="flex w-full items-end justify-center gap-0.5" style={{ height: 140 }}>
292292
<div
293-
className="w-full max-w-[14px] rounded-t bg-primary/70 transition-all"
293+
className="w-full max-w-[14px] rounded-t bg-blue-500/70 transition-all"
294294
style={{ height: incomeH || 2 }}
295295
title={`${month}월 수입 ${fmt(income)}원`}
296296
/>
@@ -307,7 +307,7 @@ function AnnualBarChart({ data, ariaLabel }: AnnualBarChartProps) {
307307
</div>
308308
<div className="mt-2 flex items-center justify-end gap-4 pr-1" aria-hidden="true">
309309
<div className="flex items-center gap-1.5">
310-
<span className="h-2 w-3 rounded-sm bg-primary/70" />
310+
<span className="h-2 w-3 rounded-sm bg-blue-500/70" />
311311
<span className="text-xs text-muted-foreground">수입</span>
312312
</div>
313313
<div className="flex items-center gap-1.5">
@@ -335,7 +335,7 @@ function NetAssetCard({ summary }: { summary: BalanceReport['summary'] }) {
335335
<div
336336
className={cn(
337337
'flex items-center gap-1 mt-1.5 text-sm font-medium',
338-
isPositive ? 'text-emerald-500' : isNegative ? 'text-red-500' : 'text-muted-foreground',
338+
isPositive ? 'text-blue-500' : isNegative ? 'text-red-400' : 'text-muted-foreground',
339339
)}
340340
>
341341
{isPositive ? (
@@ -351,11 +351,11 @@ function NetAssetCard({ summary }: { summary: BalanceReport['summary'] }) {
351351
<div className="grid grid-cols-2 gap-3 mt-4 pt-4 border-t">
352352
<div>
353353
<p className="text-xs text-muted-foreground mb-0.5">총 자산</p>
354-
<p className="text-sm font-semibold text-emerald-600">{formatAmount(summary.totalAssets)}</p>
354+
<p className="text-sm font-semibold text-blue-600">{formatAmount(summary.totalAssets)}</p>
355355
</div>
356356
<div>
357357
<p className="text-xs text-muted-foreground mb-0.5">총 부채</p>
358-
<p className="text-sm font-semibold text-red-500">{formatAmount(summary.totalLiabilities)}</p>
358+
<p className="text-sm font-semibold text-red-400">{formatAmount(summary.totalLiabilities)}</p>
359359
</div>
360360
</div>
361361
</div>
@@ -385,7 +385,7 @@ function AccountGroupSection({
385385
className="w-full flex items-center justify-between px-4 py-3 bg-muted/40 hover:bg-muted/60 transition-colors"
386386
>
387387
<span className="text-sm font-semibold">{group.major}</span>
388-
<span className={cn('text-sm font-bold', isAsset ? 'text-emerald-600' : 'text-red-500')}>
388+
<span className={cn('text-sm font-bold', isAsset ? 'text-blue-600' : 'text-red-400')}>
389389
{formatAmount(group.total)}
390390
</span>
391391
</button>
@@ -411,7 +411,7 @@ function AccountGroupSection({
411411
)
412412
})}
413413
</div>
414-
<span className={cn('text-sm font-medium', isAsset ? 'text-foreground' : 'text-red-500')}>
414+
<span className={cn('text-sm font-medium', isAsset ? 'text-foreground' : 'text-red-400')}>
415415
{formatAmount(account.balance)}
416416
</span>
417417
</div>
@@ -442,7 +442,7 @@ function CashFlowCard({ cashFlow }: { cashFlow: CashFlow }) {
442442
<span
443443
className={cn(
444444
'text-sm font-medium',
445-
value > 0 ? 'text-emerald-600' : value < 0 ? 'text-red-500' : 'text-muted-foreground',
445+
value > 0 ? 'text-blue-600' : value < 0 ? 'text-red-400' : 'text-muted-foreground',
446446
)}
447447
>
448448
{value === 0 ? '0원' : formatChange(value)}
@@ -455,7 +455,7 @@ function CashFlowCard({ cashFlow }: { cashFlow: CashFlow }) {
455455
<span
456456
className={cn(
457457
'text-sm font-bold',
458-
cashFlow.total > 0 ? 'text-emerald-600' : cashFlow.total < 0 ? 'text-red-500' : 'text-muted-foreground',
458+
cashFlow.total > 0 ? 'text-blue-600' : cashFlow.total < 0 ? 'text-red-400' : 'text-muted-foreground',
459459
)}
460460
>
461461
{cashFlow.total === 0 ? '0원' : formatChange(cashFlow.total)}
@@ -801,8 +801,8 @@ function AnnualReportTab() {
801801
{incomeCategories.length > 0 && (
802802
<>
803803
<div className={`flex items-center gap-1.5 px-4 pb-1 ${expenseCategories.length > 0 ? 'border-t pt-3' : 'pt-3'}`}>
804-
<TrendingUp className="size-3.5 text-primary" />
805-
<span className="text-xs font-medium text-primary">수입</span>
804+
<TrendingUp className="size-3.5 text-blue-600" />
805+
<span className="text-xs font-medium text-blue-600">수입</span>
806806
</div>
807807
{incomeCategories.map((cat) => (
808808
<div key={cat.categoryId} className="flex items-center justify-between px-4 py-2.5">
@@ -1001,6 +1001,7 @@ function ReportsPage() {
10011001

10021002
return (
10031003
<div className="min-h-[calc(100dvh-4rem)] bg-background pb-10 sm:min-h-screen">
1004+
<div className="mx-auto w-full max-w-6xl">
10041005

10051006
{/* Tab Bar */}
10061007
<div role="tablist" className="sticky top-0 z-10 flex border-b bg-background">
@@ -1290,6 +1291,7 @@ function ReportsPage() {
12901291
)}
12911292
</div>
12921293
</div>
1294+
</div>
12931295

12941296
{/* Drilldown sheet */}
12951297
{drillStat && (
@@ -1328,7 +1330,7 @@ function ReportsPage() {
13281330
{tx.isFixed ? ' · 고정' : ''}
13291331
</p>
13301332
</div>
1331-
<span className={`ml-4 shrink-0 text-sm font-semibold tabular-nums ${tx.type === 'INCOME' ? 'text-primary' : 'text-destructive'}`}>
1333+
<span className={`ml-4 shrink-0 text-sm font-semibold tabular-nums ${tx.type === 'INCOME' ? 'text-blue-600' : 'text-destructive'}`}>
13321334
{tx.type === 'INCOME' ? '+' : '-'}{fmt(tx.amount)}
13331335
</span>
13341336
</li>
@@ -1361,8 +1363,8 @@ function SummaryCard({
13611363
const isPos = diff > 0
13621364
const DiffIcon = isPos ? TrendingUp : TrendingDown
13631365
const diffColor = diffPositiveIsGood
1364-
? isPos ? 'text-primary' : 'text-muted-foreground'
1365-
: isPos ? 'text-destructive' : 'text-primary'
1366+
? isPos ? 'text-blue-600' : 'text-muted-foreground'
1367+
: isPos ? 'text-destructive' : 'text-blue-600'
13661368

13671369
return (
13681370
<div className="rounded-xl border bg-card p-4 shadow-sm">

0 commit comments

Comments
 (0)