Reverted to edit edt-df6bf84a-482f-4f45-8871-2125b421fdb0: "Add bottom padding to screen
Adds 100px bottom padding to the screen."
This commit is contained in:
@@ -1,4 +1,6 @@
|
|||||||
|
|
||||||
#Tue Mar 18 00:16:17 KST 2025
|
#Tue Mar 18 00:16:17 KST 2025
|
||||||
buildNumber=2
|
buildNumber=2
|
||||||
versionCode=1
|
versionCode=1
|
||||||
versionName=1.0.2
|
versionName=1.0.1
|
||||||
|
|
||||||
|
|||||||
68
build-apk.sh
68
build-apk.sh
@@ -12,56 +12,8 @@ NC='\033[0m' # No Color
|
|||||||
# 프로젝트 디렉토리로 이동
|
# 프로젝트 디렉토리로 이동
|
||||||
cd "$(dirname "$0")"
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
# 버전 정보 파일 경로
|
|
||||||
VERSION_PROPS_FILE="android/version.properties"
|
|
||||||
|
|
||||||
# 현재 버전 정보 읽기
|
|
||||||
if [ -f "$VERSION_PROPS_FILE" ]; then
|
|
||||||
CURRENT_VERSION_NAME=$(grep "versionName" "$VERSION_PROPS_FILE" | cut -d'=' -f2)
|
|
||||||
CURRENT_VERSION_CODE=$(grep "versionCode" "$VERSION_PROPS_FILE" | cut -d'=' -f2)
|
|
||||||
CURRENT_BUILD_NUMBER=$(grep "buildNumber" "$VERSION_PROPS_FILE" | cut -d'=' -f2)
|
|
||||||
|
|
||||||
echo -e "${YELLOW}현재 버전 정보:${NC}"
|
|
||||||
echo -e "버전 이름: ${GREEN}$CURRENT_VERSION_NAME${NC}"
|
|
||||||
echo -e "버전 코드: ${GREEN}$CURRENT_VERSION_CODE${NC}"
|
|
||||||
echo -e "빌드 번호: ${GREEN}$CURRENT_BUILD_NUMBER${NC}"
|
|
||||||
|
|
||||||
# 버전 수정 여부 확인
|
|
||||||
echo -e "\n${YELLOW}버전 정보를 수정하시겠습니까? (y/n)${NC}"
|
|
||||||
read -r UPDATE_VERSION
|
|
||||||
|
|
||||||
if [[ "$UPDATE_VERSION" == "y" || "$UPDATE_VERSION" == "Y" ]]; then
|
|
||||||
echo -e "\n${YELLOW}새 버전 이름을 입력하세요 (현재: $CURRENT_VERSION_NAME):${NC}"
|
|
||||||
read -r NEW_VERSION_NAME
|
|
||||||
|
|
||||||
if [ -z "$NEW_VERSION_NAME" ]; then
|
|
||||||
NEW_VERSION_NAME=$CURRENT_VERSION_NAME
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo -e "${YELLOW}새 버전 코드를 입력하세요 (현재: $CURRENT_VERSION_CODE):${NC}"
|
|
||||||
read -r NEW_VERSION_CODE
|
|
||||||
|
|
||||||
if [ -z "$NEW_VERSION_CODE" ]; then
|
|
||||||
NEW_VERSION_CODE=$CURRENT_VERSION_CODE
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 버전 정보 업데이트
|
|
||||||
sed -i '' "s/versionName=$CURRENT_VERSION_NAME/versionName=$NEW_VERSION_NAME/g" "$VERSION_PROPS_FILE"
|
|
||||||
sed -i '' "s/versionCode=$CURRENT_VERSION_CODE/versionCode=$NEW_VERSION_CODE/g" "$VERSION_PROPS_FILE"
|
|
||||||
|
|
||||||
echo -e "\n${GREEN}버전 정보가 업데이트되었습니다:${NC}"
|
|
||||||
echo -e "버전 이름: ${GREEN}$NEW_VERSION_NAME${NC}"
|
|
||||||
echo -e "버전 코드: ${GREEN}$NEW_VERSION_CODE${NC}"
|
|
||||||
else
|
|
||||||
echo -e "${YELLOW}현재 버전 정보를 유지합니다.${NC}"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo -e "${RED}버전 정보 파일을 찾을 수 없습니다: $VERSION_PROPS_FILE${NC}"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 빌드 타입 선택 메뉴
|
# 빌드 타입 선택 메뉴
|
||||||
echo -e "\n${YELLOW}Zellyy Finance 앱 빌드 스크립트${NC}"
|
echo -e "${YELLOW}Zellyy Finance 앱 빌드 스크립트${NC}"
|
||||||
echo -e "${YELLOW}=============================${NC}"
|
echo -e "${YELLOW}=============================${NC}"
|
||||||
echo -e "빌드 타입을 선택하세요:"
|
echo -e "빌드 타입을 선택하세요:"
|
||||||
echo -e "1) 디버그 빌드 (개발 및 테스트용)"
|
echo -e "1) 디버그 빌드 (개발 및 테스트용)"
|
||||||
@@ -179,15 +131,15 @@ if [ "$BUILD_TYPE" = "debug" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
APK_PATH="app/build/outputs/apk/debug/app-debug.apk"
|
APK_PATH="app/build/outputs/apk/debug/app-debug.apk"
|
||||||
DEST_PATH="$HOME/Dev/zellyy-finance-debug.apk"
|
DEST_PATH="$HOME/zellyy-finance-debug.apk"
|
||||||
|
|
||||||
if [ -f "$APK_PATH" ]; then
|
if [ -f "$APK_PATH" ]; then
|
||||||
echo -e "${GREEN}디버그 APK 빌드 성공!${NC}"
|
echo -e "${GREEN}디버그 APK 빌드 성공!${NC}"
|
||||||
echo -e "APK 파일 위치: $(pwd)/$APK_PATH"
|
echo -e "APK 파일 위치: $(pwd)/$APK_PATH"
|
||||||
|
|
||||||
# Dev 디렉토리로 APK 복사
|
# 홈 디렉토리로 APK 복사
|
||||||
cp "$APK_PATH" "$DEST_PATH"
|
cp "$APK_PATH" "$DEST_PATH"
|
||||||
echo -e "${GREEN}APK를 Dev 디렉토리에 복사했습니다: $DEST_PATH${NC}"
|
echo -e "${GREEN}APK를 홈 디렉토리에 복사했습니다: $DEST_PATH${NC}"
|
||||||
|
|
||||||
# 연결된 기기 확인
|
# 연결된 기기 확인
|
||||||
DEVICES=$(adb devices | grep -v "List" | grep "device" | wc -l)
|
DEVICES=$(adb devices | grep -v "List" | grep "device" | wc -l)
|
||||||
@@ -227,15 +179,15 @@ elif [ "$BUILD_TYPE" = "release-aab" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
AAB_PATH="app/build/outputs/bundle/release/app-release.aab"
|
AAB_PATH="app/build/outputs/bundle/release/app-release.aab"
|
||||||
DEST_PATH="$HOME/Dev/zellyy-finance-release.aab"
|
DEST_PATH="$HOME/zellyy-finance-release.aab"
|
||||||
|
|
||||||
if [ -f "$AAB_PATH" ]; then
|
if [ -f "$AAB_PATH" ]; then
|
||||||
echo -e "${GREEN}릴리즈 AAB 빌드 성공!${NC}"
|
echo -e "${GREEN}릴리즈 AAB 빌드 성공!${NC}"
|
||||||
echo -e "AAB 파일 위치: $(pwd)/$AAB_PATH"
|
echo -e "AAB 파일 위치: $(pwd)/$AAB_PATH"
|
||||||
|
|
||||||
# Dev 디렉토리로 AAB 복사
|
# 홈 디렉토리로 AAB 복사
|
||||||
cp "$AAB_PATH" "$DEST_PATH"
|
cp "$AAB_PATH" "$DEST_PATH"
|
||||||
echo -e "${GREEN}AAB를 Dev 디렉토리에 복사했습니다: $DEST_PATH${NC}"
|
echo -e "${GREEN}AAB를 홈 디렉토리에 복사했습니다: $DEST_PATH${NC}"
|
||||||
|
|
||||||
echo -e "${YELLOW}다음 단계:${NC}"
|
echo -e "${YELLOW}다음 단계:${NC}"
|
||||||
echo "1. Google Play Console에 AAB 파일 업로드: $DEST_PATH"
|
echo "1. Google Play Console에 AAB 파일 업로드: $DEST_PATH"
|
||||||
@@ -255,15 +207,15 @@ elif [ "$BUILD_TYPE" = "release-apk" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
SIGNED_APK_PATH="app/build/outputs/apk/release/app-release.apk"
|
SIGNED_APK_PATH="app/build/outputs/apk/release/app-release.apk"
|
||||||
DEST_PATH="$HOME/Dev/zellyy-finance-release.apk"
|
DEST_PATH="$HOME/zellyy-finance-release.apk"
|
||||||
|
|
||||||
if [ -f "$SIGNED_APK_PATH" ]; then
|
if [ -f "$SIGNED_APK_PATH" ]; then
|
||||||
echo -e "${GREEN}서명된 릴리즈 APK 빌드 성공!${NC}"
|
echo -e "${GREEN}서명된 릴리즈 APK 빌드 성공!${NC}"
|
||||||
echo -e "APK 파일 위치: $(pwd)/$SIGNED_APK_PATH"
|
echo -e "APK 파일 위치: $(pwd)/$SIGNED_APK_PATH"
|
||||||
|
|
||||||
# Dev 디렉토리로 APK 복사
|
# 홈 디렉토리로 APK 복사
|
||||||
cp "$SIGNED_APK_PATH" "$DEST_PATH"
|
cp "$SIGNED_APK_PATH" "$DEST_PATH"
|
||||||
echo -e "${GREEN}서명된 APK를 Dev 디렉토리에 복사했습니다: $DEST_PATH${NC}"
|
echo -e "${GREEN}서명된 APK를 홈 디렉토리에 복사했습니다: $DEST_PATH${NC}"
|
||||||
|
|
||||||
# 연결된 기기 확인
|
# 연결된 기기 확인
|
||||||
DEVICES=$(adb devices | grep -v "List" | grep "device" | wc -l)
|
DEVICES=$(adb devices | grep -v "List" | grep "device" | wc -l)
|
||||||
|
|||||||
57
build-ios.sh
Executable file → Normal file
57
build-ios.sh
Executable file → Normal file
@@ -12,55 +12,8 @@ NC='\033[0m' # No Color
|
|||||||
# 프로젝트 디렉토리로 이동
|
# 프로젝트 디렉토리로 이동
|
||||||
cd "$(dirname "$0")"
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
# 프로젝트 파일 경로
|
|
||||||
XCODEPROJ_FILE="ios/App/App.xcodeproj/project.pbxproj"
|
|
||||||
|
|
||||||
# 현재 버전 및 빌드 번호 가져오기
|
|
||||||
if [ -f "$XCODEPROJ_FILE" ]; then
|
|
||||||
MARKETING_VERSION=$(grep -A 1 "MARKETING_VERSION" "$XCODEPROJ_FILE" | grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+' | head -1)
|
|
||||||
CURRENT_PROJECT_VERSION=$(grep -A 1 "CURRENT_PROJECT_VERSION" "$XCODEPROJ_FILE" | grep -o '[0-9]\+' | head -1)
|
|
||||||
|
|
||||||
echo -e "${YELLOW}현재 버전 정보:${NC}"
|
|
||||||
echo -e "마케팅 버전: ${GREEN}$MARKETING_VERSION${NC}"
|
|
||||||
echo -e "빌드 번호: ${GREEN}$CURRENT_PROJECT_VERSION${NC}"
|
|
||||||
|
|
||||||
# 버전 수정 여부 확인
|
|
||||||
echo -e "\n${YELLOW}버전 정보를 수정하시겠습니까? (y/n)${NC}"
|
|
||||||
read -r UPDATE_VERSION
|
|
||||||
|
|
||||||
if [[ "$UPDATE_VERSION" == "y" || "$UPDATE_VERSION" == "Y" ]]; then
|
|
||||||
echo -e "\n${YELLOW}새 마케팅 버전을 입력하세요 (현재: $MARKETING_VERSION):${NC}"
|
|
||||||
read -r NEW_MARKETING_VERSION
|
|
||||||
|
|
||||||
if [ -z "$NEW_MARKETING_VERSION" ]; then
|
|
||||||
NEW_MARKETING_VERSION=$MARKETING_VERSION
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo -e "${YELLOW}새 빌드 번호를 입력하세요 (현재: $CURRENT_PROJECT_VERSION):${NC}"
|
|
||||||
read -r NEW_PROJECT_VERSION
|
|
||||||
|
|
||||||
if [ -z "$NEW_PROJECT_VERSION" ]; then
|
|
||||||
NEW_PROJECT_VERSION=$CURRENT_PROJECT_VERSION
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 버전 정보 업데이트
|
|
||||||
# macOS에서는 sed -i에 백업 확장자를 지정해야 함
|
|
||||||
sed -i '' "s/MARKETING_VERSION = $MARKETING_VERSION;/MARKETING_VERSION = $NEW_MARKETING_VERSION;/g" "$XCODEPROJ_FILE"
|
|
||||||
sed -i '' "s/CURRENT_PROJECT_VERSION = $CURRENT_PROJECT_VERSION;/CURRENT_PROJECT_VERSION = $NEW_PROJECT_VERSION;/g" "$XCODEPROJ_FILE"
|
|
||||||
|
|
||||||
echo -e "\n${GREEN}버전 정보가 업데이트되었습니다:${NC}"
|
|
||||||
echo -e "마케팅 버전: ${GREEN}$NEW_MARKETING_VERSION${NC}"
|
|
||||||
echo -e "빌드 번호: ${GREEN}$NEW_PROJECT_VERSION${NC}"
|
|
||||||
else
|
|
||||||
echo -e "${YELLOW}현재 버전 정보를 유지합니다.${NC}"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo -e "${RED}프로젝트 파일을 찾을 수 없습니다: $XCODEPROJ_FILE${NC}"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 빌드 타입 선택 메뉴
|
# 빌드 타입 선택 메뉴
|
||||||
echo -e "\n${YELLOW}Zellyy Finance iOS 앱 빌드 스크립트${NC}"
|
echo -e "${YELLOW}Zellyy Finance iOS 앱 빌드 스크립트${NC}"
|
||||||
echo -e "${YELLOW}=============================${NC}"
|
echo -e "${YELLOW}=============================${NC}"
|
||||||
echo -e "빌드 타입을 선택하세요:"
|
echo -e "빌드 타입을 선택하세요:"
|
||||||
echo -e "1) 디버그 빌드 (개발 및 테스트용)"
|
echo -e "1) 디버그 빌드 (개발 및 테스트용)"
|
||||||
@@ -307,9 +260,9 @@ EOF
|
|||||||
echo -e "${GREEN}디버그용 IPA 파일 생성 성공!${NC}"
|
echo -e "${GREEN}디버그용 IPA 파일 생성 성공!${NC}"
|
||||||
echo -e "IPA 파일 위치: $(pwd)/$DEBUG_IPA_PATH"
|
echo -e "IPA 파일 위치: $(pwd)/$DEBUG_IPA_PATH"
|
||||||
|
|
||||||
# Dev 디렉토리로 IPA 복사
|
# 홈 디렉토리로 IPA 복사
|
||||||
cp "$DEBUG_IPA_PATH" "$DEBUG_DEST_PATH"
|
cp "$DEBUG_IPA_PATH" "$DEBUG_DEST_PATH"
|
||||||
echo -e "${GREEN}IPA를 Dev 디렉토리에 복사했습니다: $DEBUG_DEST_PATH${NC}"
|
echo -e "${GREEN}IPA를 홈 디렉토리에 복사했습니다: $DEBUG_DEST_PATH${NC}"
|
||||||
|
|
||||||
echo -e "${YELLOW}다음 방법으로 다른 기기에 설치할 수 있습니다:${NC}"
|
echo -e "${YELLOW}다음 방법으로 다른 기기에 설치할 수 있습니다:${NC}"
|
||||||
echo "1. Apple Configurator 2 앱 사용"
|
echo "1. Apple Configurator 2 앱 사용"
|
||||||
@@ -362,9 +315,9 @@ elif [ "$BUILD_TYPE" = "release" ]; then
|
|||||||
echo -e "${GREEN}IPA 파일 생성 성공!${NC}"
|
echo -e "${GREEN}IPA 파일 생성 성공!${NC}"
|
||||||
echo -e "IPA 파일 위치: $(pwd)/$IPA_PATH"
|
echo -e "IPA 파일 위치: $(pwd)/$IPA_PATH"
|
||||||
|
|
||||||
# Dev 디렉토리로 IPA 복사
|
# 홈 디렉토리로 IPA 복사
|
||||||
cp "$IPA_PATH" "$DEST_PATH"
|
cp "$IPA_PATH" "$DEST_PATH"
|
||||||
echo -e "${GREEN}IPA를 Dev 디렉토리에 복사했습니다: $DEST_PATH${NC}"
|
echo -e "${GREEN}IPA를 홈 디렉토리에 복사했습니다: $DEST_PATH${NC}"
|
||||||
|
|
||||||
echo -e "${YELLOW}다음 단계:${NC}"
|
echo -e "${YELLOW}다음 단계:${NC}"
|
||||||
echo "1. App Store Connect에 로그인: https://appstoreconnect.apple.com"
|
echo "1. App Store Connect에 로그인: https://appstoreconnect.apple.com"
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ interface BudgetData {
|
|||||||
remainingAmount: number;
|
remainingAmount: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BudgetTabContentProps {
|
interface BudgetTabContentProps {
|
||||||
data: BudgetData;
|
data: BudgetData;
|
||||||
formatCurrency: (amount: number) => string;
|
formatCurrency: (amount: number) => string;
|
||||||
calculatePercentage: (spent: number, target: number) => number;
|
calculatePercentage: (spent: number, target: number) => number;
|
||||||
@@ -26,12 +26,6 @@ const BudgetTabContent: React.FC<BudgetTabContentProps> = ({
|
|||||||
calculatePercentage,
|
calculatePercentage,
|
||||||
onSaveBudget
|
onSaveBudget
|
||||||
}) => {
|
}) => {
|
||||||
// 데이터 유효성 체크 - 데이터가 없으면 기본값 사용
|
|
||||||
if (!data) {
|
|
||||||
console.warn('BudgetTabContent: data prop이 제공되지 않았습니다. 기본값을 사용합니다.');
|
|
||||||
data = { targetAmount: 0, spentAmount: 0, remainingAmount: 0 };
|
|
||||||
}
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
categoryBudgets,
|
categoryBudgets,
|
||||||
showBudgetInput,
|
showBudgetInput,
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Transaction } from '@/contexts/budget/types';
|
import { Transaction } from '@/contexts/budget/types';
|
||||||
import TransactionEditDialog from './TransactionEditDialog';
|
import TransactionEditDialog from './TransactionEditDialog';
|
||||||
@@ -9,7 +8,7 @@ import { useRecentTransactions } from '@/hooks/transactions/useRecentTransaction
|
|||||||
import { useRecentTransactionsDialog } from '@/hooks/transactions/useRecentTransactionsDialog';
|
import { useRecentTransactionsDialog } from '@/hooks/transactions/useRecentTransactionsDialog';
|
||||||
import RecentTransactionItem from './recent-transactions/RecentTransactionItem';
|
import RecentTransactionItem from './recent-transactions/RecentTransactionItem';
|
||||||
|
|
||||||
export interface RecentTransactionsSectionProps {
|
interface RecentTransactionsSectionProps {
|
||||||
transactions: Transaction[];
|
transactions: Transaction[];
|
||||||
onUpdateTransaction?: (transaction: Transaction) => void;
|
onUpdateTransaction?: (transaction: Transaction) => void;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ interface SafeAreaContainerProps {
|
|||||||
/**
|
/**
|
||||||
* 플랫폼별 안전 영역(Safe Area)을 고려한 컨테이너 컴포넌트
|
* 플랫폼별 안전 영역(Safe Area)을 고려한 컨테이너 컴포넌트
|
||||||
* iOS에서는 노치/다이나믹 아일랜드를 고려한 여백 적용
|
* iOS에서는 노치/다이나믹 아일랜드를 고려한 여백 적용
|
||||||
* CSS 변수와 env() 함수를 사용하여 정확한 안전 영역 계산
|
|
||||||
*/
|
*/
|
||||||
const SafeAreaContainer: React.FC<SafeAreaContainerProps> = ({
|
const SafeAreaContainer: React.FC<SafeAreaContainerProps> = ({
|
||||||
children,
|
children,
|
||||||
@@ -25,24 +24,17 @@ const SafeAreaContainer: React.FC<SafeAreaContainerProps> = ({
|
|||||||
// 마운트 시 플랫폼 확인
|
// 마운트 시 플랫폼 확인
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setIsIOS(isIOSPlatform());
|
setIsIOS(isIOSPlatform());
|
||||||
|
|
||||||
// iOS 디버깅용 로그
|
|
||||||
if (isIOSPlatform()) {
|
|
||||||
console.info('iOS 플랫폼 감지됨 - 안전 영역 적용');
|
|
||||||
}
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// 플랫폼에 따른 클래스 결정
|
// 플랫폼에 따른 클래스 결정
|
||||||
let safeAreaClass = '';
|
let safeAreaClass = '';
|
||||||
|
|
||||||
if (isIOS) {
|
if (isIOS) {
|
||||||
safeAreaClass = 'ios-safe-area';
|
if (!bottomOnly) safeAreaClass += ' pt-12'; // iOS 상단 안전 영역
|
||||||
|
if (!topOnly) safeAreaClass += ' pb-8'; // iOS 하단 안전 영역
|
||||||
if (bottomOnly) safeAreaClass += ' ios-safe-area-bottom-only';
|
|
||||||
if (topOnly) safeAreaClass += ' ios-safe-area-top-only';
|
|
||||||
} else {
|
} else {
|
||||||
// 안드로이드 기본 여백
|
if (!bottomOnly) safeAreaClass += ' pt-4'; // 안드로이드 상단 여백
|
||||||
safeAreaClass = 'android-safe-area';
|
if (!topOnly) safeAreaClass += ' pb-4'; // 안드로이드 하단 여백
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,51 +0,0 @@
|
|||||||
|
|
||||||
import React, { useEffect, useState } from 'react';
|
|
||||||
import { isIOSPlatform } from '@/utils/platform';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 안전 영역(Safe Area) 디버그 컴포넌트
|
|
||||||
* 노치나 다이나믹 아일랜드 등 iOS 기기의 안전 영역을 시각적으로 표시
|
|
||||||
*/
|
|
||||||
const SafeAreaDebug = () => {
|
|
||||||
const [isIOS, setIsIOS] = useState(false);
|
|
||||||
const [safeAreaTop, setSafeAreaTop] = useState('0px');
|
|
||||||
const [safeAreaBottom, setSafeAreaBottom] = useState('0px');
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setIsIOS(isIOSPlatform());
|
|
||||||
|
|
||||||
// iOS에서만 안전 영역 값 가져오기 시도
|
|
||||||
if (isIOSPlatform()) {
|
|
||||||
// CSS 변수에서 값 가져오기 시도
|
|
||||||
const computedStyle = getComputedStyle(document.documentElement);
|
|
||||||
const topInset = computedStyle.getPropertyValue('--safe-area-top') ||
|
|
||||||
computedStyle.getPropertyValue('env(safe-area-inset-top)') ||
|
|
||||||
'0px';
|
|
||||||
|
|
||||||
const bottomInset = computedStyle.getPropertyValue('--safe-area-bottom') ||
|
|
||||||
computedStyle.getPropertyValue('env(safe-area-inset-bottom)') ||
|
|
||||||
'0px';
|
|
||||||
|
|
||||||
setSafeAreaTop(topInset);
|
|
||||||
setSafeAreaBottom(bottomInset);
|
|
||||||
|
|
||||||
console.info('iOS 안전 영역 감지:', {
|
|
||||||
top: topInset,
|
|
||||||
bottom: bottomInset
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
if (!isIOS) {
|
|
||||||
return null; // iOS가 아니면 렌더링하지 않음
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="fixed bottom-0 left-0 z-50 p-2 bg-black/70 text-white text-xs rounded-tr-md">
|
|
||||||
<div>Safe Area Top: {safeAreaTop}</div>
|
|
||||||
<div>Safe Area Bottom: {safeAreaBottom}</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default SafeAreaDebug;
|
|
||||||
@@ -37,18 +37,15 @@ export const useBudgetTabContent = ({
|
|||||||
calculatePercentage,
|
calculatePercentage,
|
||||||
onSaveBudget
|
onSaveBudget
|
||||||
}: UseBudgetTabContentProps): UseBudgetTabContentReturn => {
|
}: UseBudgetTabContentProps): UseBudgetTabContentReturn => {
|
||||||
// 데이터가 undefined인 경우를 방지하기 위한 기본값 설정
|
|
||||||
const safeData = data || { targetAmount: 0, spentAmount: 0, remainingAmount: 0 };
|
|
||||||
|
|
||||||
const [categoryBudgets, setCategoryBudgets] = useState<Record<string, number>>({});
|
const [categoryBudgets, setCategoryBudgets] = useState<Record<string, number>>({});
|
||||||
const [showBudgetInput, setShowBudgetInput] = useState(false);
|
const [showBudgetInput, setShowBudgetInput] = useState(false);
|
||||||
const spentAmount = safeData.spentAmount;
|
const spentAmount = data.spentAmount;
|
||||||
const targetAmount = safeData.targetAmount;
|
const targetAmount = data.targetAmount;
|
||||||
|
|
||||||
// 로그 추가 - 받은 데이터 확인
|
// 로그 추가 - 받은 데이터 확인
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log(`BudgetTabContent 수신 데이터:`, safeData);
|
console.log(`BudgetTabContent 수신 데이터:`, data);
|
||||||
}, [safeData]);
|
}, [data]);
|
||||||
|
|
||||||
// 전역 예산 데이터가 변경되었을 때 로컬 상태 갱신
|
// 전역 예산 데이터가 변경되었을 때 로컬 상태 갱신
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -150,28 +150,7 @@
|
|||||||
@apply neuro-pressed px-4 py-3 w-full focus:outline-none focus:ring-2 focus:ring-neuro-accent/30;
|
@apply neuro-pressed px-4 py-3 w-full focus:outline-none focus:ring-2 focus:ring-neuro-accent/30;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 안전 영역 관련 개선된 클래스 */
|
/* 안전 영역 관련 클래스 */
|
||||||
.ios-safe-area {
|
|
||||||
padding-top: max(1rem, env(safe-area-inset-top));
|
|
||||||
padding-bottom: max(1rem, env(safe-area-inset-bottom));
|
|
||||||
padding-left: env(safe-area-inset-left);
|
|
||||||
padding-right: env(safe-area-inset-right);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ios-safe-area-top-only {
|
|
||||||
padding-top: max(1rem, env(safe-area-inset-top));
|
|
||||||
padding-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ios-safe-area-bottom-only {
|
|
||||||
padding-top: 1rem;
|
|
||||||
padding-bottom: max(1rem, env(safe-area-inset-bottom));
|
|
||||||
}
|
|
||||||
|
|
||||||
.android-safe-area {
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.has-safe-area-top {
|
.has-safe-area-top {
|
||||||
padding-top: max(1rem, env(safe-area-inset-top));
|
padding-top: max(1rem, env(safe-area-inset-top));
|
||||||
}
|
}
|
||||||
@@ -289,16 +268,6 @@
|
|||||||
--safe-area-bottom: env(safe-area-inset-bottom);
|
--safe-area-bottom: env(safe-area-inset-bottom);
|
||||||
padding-bottom: var(--safe-area-bottom);
|
padding-bottom: var(--safe-area-bottom);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* iOS 안전 영역 디버깅 클래스 */
|
|
||||||
.debug-safe-areas {
|
|
||||||
--safe-area-top-color: rgba(255, 0, 0, 0.2);
|
|
||||||
--safe-area-bottom-color: rgba(0, 0, 255, 0.2);
|
|
||||||
|
|
||||||
background:
|
|
||||||
linear-gradient(to bottom, var(--safe-area-top-color) 0, var(--safe-area-top-color) env(safe-area-inset-top), transparent env(safe-area-inset-top)),
|
|
||||||
linear-gradient(to top, var(--safe-area-bottom-color) 0, var(--safe-area-bottom-color) env(safe-area-inset-bottom), transparent env(safe-area-inset-bottom));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.font-inter {
|
.font-inter {
|
||||||
|
|||||||
@@ -1,65 +1,174 @@
|
|||||||
|
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import NavBar from '../components/NavBar';
|
import NavBar from '@/components/NavBar';
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
import AddTransactionButton from '@/components/AddTransactionButton';
|
||||||
import BudgetTabContent from '@/components/BudgetTabContent';
|
|
||||||
import RecentTransactionsSection from '@/components/RecentTransactionsSection';
|
|
||||||
import Header from '@/components/Header';
|
import Header from '@/components/Header';
|
||||||
import { useBudget } from '@/contexts/budget';
|
import WelcomeDialog from '@/components/onboarding/WelcomeDialog';
|
||||||
import SafeAreaContainer from '@/components/SafeAreaContainer';
|
import HomeContent from '@/components/home/HomeContent';
|
||||||
import { formatCurrency } from '@/utils/formatters';
|
import { useBudget } from '@/contexts/budget/BudgetContext';
|
||||||
|
import { useAuth } from '@/contexts/auth';
|
||||||
|
import { useWelcomeDialog } from '@/hooks/useWelcomeDialog';
|
||||||
|
import { useDataInitialization } from '@/hooks/useDataInitialization';
|
||||||
|
import { useIsMobile } from '@/hooks/use-mobile';
|
||||||
|
import useNotifications from '@/hooks/useNotifications';
|
||||||
|
|
||||||
|
// 메인 컴포넌트
|
||||||
const Index = () => {
|
const Index = () => {
|
||||||
const { transactions, budgetData, handleBudgetGoalUpdate } = useBudget();
|
const {
|
||||||
|
transactions,
|
||||||
// 데이터 구조 확인용 로깅
|
budgetData,
|
||||||
|
selectedTab,
|
||||||
|
setSelectedTab,
|
||||||
|
handleBudgetGoalUpdate,
|
||||||
|
updateTransaction,
|
||||||
|
getCategorySpending,
|
||||||
|
resetBudgetData
|
||||||
|
} = useBudget();
|
||||||
|
|
||||||
|
const { user } = useAuth();
|
||||||
|
const { showWelcome, checkWelcomeDialogState, handleCloseWelcome } = useWelcomeDialog();
|
||||||
|
const { isInitialized } = useDataInitialization(resetBudgetData);
|
||||||
|
const isMobile = useIsMobile();
|
||||||
|
const { addNotification } = useNotifications();
|
||||||
|
|
||||||
|
// 초기화 후 환영 메시지 표시 상태 확인
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.info('Index 페이지 마운트, 현재 데이터 상태:');
|
if (isInitialized) {
|
||||||
console.info('트랜잭션 수:', transactions?.length);
|
const timeoutId = setTimeout(checkWelcomeDialogState, 500);
|
||||||
console.info('예산 데이터:', budgetData);
|
return () => clearTimeout(timeoutId);
|
||||||
}, [transactions, budgetData]);
|
}
|
||||||
|
}, [isInitialized, checkWelcomeDialogState]);
|
||||||
// 예산 퍼센트 계산 함수
|
|
||||||
const calculatePercentage = (spent: number, target: number) => {
|
// 앱 시작시 예시 알림 추가 (실제 앱에서는 필요한 이벤트에 따라 알림 추가)
|
||||||
if (target <= 0) return 0;
|
useEffect(() => {
|
||||||
return Math.min(100, Math.round((spent / target) * 100));
|
// 환영 메시지가 이미 표시되었는지 확인하는 키
|
||||||
};
|
const welcomeNotificationSent = sessionStorage.getItem('welcomeNotificationSent');
|
||||||
|
|
||||||
|
if (isInitialized && user && !welcomeNotificationSent) {
|
||||||
|
// 사용자 로그인 시 알림 예시 (한 번만 실행)
|
||||||
|
const timeoutId = setTimeout(() => {
|
||||||
|
addNotification(
|
||||||
|
'환영합니다!',
|
||||||
|
'젤리의 적자탈출에 오신 것을 환영합니다. 예산을 설정하고 지출을 기록해보세요.'
|
||||||
|
);
|
||||||
|
// 세션 스토리지에 환영 메시지 표시 여부 저장
|
||||||
|
sessionStorage.setItem('welcomeNotificationSent', 'true');
|
||||||
|
}, 2000);
|
||||||
|
|
||||||
|
return () => clearTimeout(timeoutId);
|
||||||
|
}
|
||||||
|
}, [isInitialized, user, addNotification]);
|
||||||
|
|
||||||
|
// 페이지가 처음 로드될 때 데이터 로딩 확인
|
||||||
|
useEffect(() => {
|
||||||
|
console.log('Index 페이지 마운트, 현재 데이터 상태:');
|
||||||
|
console.log('트랜잭션:', transactions.length);
|
||||||
|
console.log('예산 데이터:', budgetData);
|
||||||
|
|
||||||
|
// 페이지 마운트 시 데이터 동기화 이벤트 수동 발생
|
||||||
|
try {
|
||||||
|
window.dispatchEvent(new Event('transactionUpdated'));
|
||||||
|
window.dispatchEvent(new Event('budgetDataUpdated'));
|
||||||
|
window.dispatchEvent(new Event('categoryBudgetsUpdated'));
|
||||||
|
} catch (e) {
|
||||||
|
console.error('이벤트 발생 오류:', e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 백업된 데이터 복구 확인 (메인 데이터가 없는 경우만)
|
||||||
|
try {
|
||||||
|
if (!localStorage.getItem('budgetData')) {
|
||||||
|
const budgetBackup = localStorage.getItem('budgetData_backup');
|
||||||
|
if (budgetBackup) {
|
||||||
|
console.log('예산 데이터 백업에서 복구');
|
||||||
|
localStorage.setItem('budgetData', budgetBackup);
|
||||||
|
window.dispatchEvent(new Event('budgetDataUpdated'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!localStorage.getItem('categoryBudgets')) {
|
||||||
|
const categoryBackup = localStorage.getItem('categoryBudgets_backup');
|
||||||
|
if (categoryBackup) {
|
||||||
|
console.log('카테고리 예산 백업에서 복구');
|
||||||
|
localStorage.setItem('categoryBudgets', categoryBackup);
|
||||||
|
window.dispatchEvent(new Event('categoryBudgetsUpdated'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!localStorage.getItem('transactions')) {
|
||||||
|
const transactionBackup = localStorage.getItem('transactions_backup');
|
||||||
|
if (transactionBackup) {
|
||||||
|
console.log('트랜잭션 백업에서 복구');
|
||||||
|
localStorage.setItem('transactions', transactionBackup);
|
||||||
|
window.dispatchEvent(new Event('transactionUpdated'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('백업 복구 시도 중 오류:', error);
|
||||||
|
}
|
||||||
|
}, [transactions.length, budgetData]);
|
||||||
|
|
||||||
|
// 앱이 포커스를 얻었을 때 데이터를 새로고침
|
||||||
|
useEffect(() => {
|
||||||
|
const handleFocus = () => {
|
||||||
|
console.log('창이 포커스를 얻음 - 데이터 새로고침');
|
||||||
|
// 이벤트 발생시켜 데이터 새로고침
|
||||||
|
try {
|
||||||
|
window.dispatchEvent(new Event('storage'));
|
||||||
|
window.dispatchEvent(new Event('transactionUpdated'));
|
||||||
|
window.dispatchEvent(new Event('budgetDataUpdated'));
|
||||||
|
window.dispatchEvent(new Event('categoryBudgetsUpdated'));
|
||||||
|
} catch (e) {
|
||||||
|
console.error('이벤트 발생 오류:', e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 포커스 이벤트
|
||||||
|
window.addEventListener('focus', handleFocus);
|
||||||
|
|
||||||
|
// 가시성 변경 이벤트 (백그라운드에서 전경으로 돌아올 때)
|
||||||
|
document.addEventListener('visibilitychange', () => {
|
||||||
|
if (document.visibilityState === 'visible') {
|
||||||
|
console.log('페이지가 다시 보임 - 데이터 새로고침');
|
||||||
|
handleFocus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 정기적인 데이터 새로고침 (10초마다)
|
||||||
|
const refreshInterval = setInterval(() => {
|
||||||
|
if (document.visibilityState === 'visible') {
|
||||||
|
console.log('정기 새로고침 - 데이터 업데이트');
|
||||||
|
handleFocus();
|
||||||
|
}
|
||||||
|
}, 10000);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('focus', handleFocus);
|
||||||
|
document.removeEventListener('visibilitychange', () => {});
|
||||||
|
clearInterval(refreshInterval);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeAreaContainer className="min-h-screen bg-neuro-background">
|
<div className="min-h-screen bg-neuro-background pb-24">
|
||||||
<div className="max-w-md mx-auto px-6">
|
<div className="max-w-md mx-auto px-6">
|
||||||
<Header />
|
<Header />
|
||||||
|
|
||||||
<Tabs defaultValue="budget" className="w-full mt-4">
|
<HomeContent
|
||||||
<TabsList className="grid w-full grid-cols-2 mb-8">
|
transactions={transactions}
|
||||||
<TabsTrigger value="budget">예산</TabsTrigger>
|
budgetData={budgetData}
|
||||||
<TabsTrigger value="recent">최근 거래</TabsTrigger>
|
selectedTab={selectedTab}
|
||||||
</TabsList>
|
setSelectedTab={setSelectedTab}
|
||||||
|
handleBudgetGoalUpdate={handleBudgetGoalUpdate}
|
||||||
<TabsContent value="budget" className="focus-visible:outline-none">
|
updateTransaction={updateTransaction}
|
||||||
{budgetData && budgetData.monthly && (
|
getCategorySpending={getCategorySpending}
|
||||||
<BudgetTabContent
|
/>
|
||||||
data={budgetData.monthly}
|
|
||||||
formatCurrency={formatCurrency}
|
|
||||||
calculatePercentage={calculatePercentage}
|
|
||||||
onSaveBudget={(amount, categoryBudgets) =>
|
|
||||||
handleBudgetGoalUpdate('monthly', amount, categoryBudgets)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</TabsContent>
|
|
||||||
|
|
||||||
<TabsContent value="recent" className="focus-visible:outline-none">
|
|
||||||
<RecentTransactionsSection
|
|
||||||
transactions={transactions || []}
|
|
||||||
onUpdateTransaction={transaction => console.log('트랜잭션 업데이트', transaction)}
|
|
||||||
/>
|
|
||||||
</TabsContent>
|
|
||||||
</Tabs>
|
|
||||||
</div>
|
</div>
|
||||||
|
<AddTransactionButton />
|
||||||
<NavBar />
|
<NavBar />
|
||||||
</SafeAreaContainer>
|
|
||||||
|
{/* 첫 사용자 안내 팝업 */}
|
||||||
|
<WelcomeDialog open={showWelcome} onClose={handleCloseWelcome} />
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import NavBar from '@/components/NavBar';
|
import NavBar from '@/components/NavBar';
|
||||||
@@ -7,7 +8,6 @@ import { User, CreditCard, Bell, Lock, HelpCircle, LogOut, ChevronRight } from '
|
|||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { useAuth } from '@/contexts/auth';
|
import { useAuth } from '@/contexts/auth';
|
||||||
import { useToast } from '@/hooks/useToast.wrapper';
|
import { useToast } from '@/hooks/useToast.wrapper';
|
||||||
import SafeAreaContainer from '@/components/SafeAreaContainer';
|
|
||||||
|
|
||||||
const SettingsOption = ({
|
const SettingsOption = ({
|
||||||
icon: Icon,
|
icon: Icon,
|
||||||
@@ -57,8 +57,7 @@ const Settings = () => {
|
|||||||
navigate(path);
|
navigate(path);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return <div className="min-h-screen bg-neuro-background pb-24">
|
||||||
<SafeAreaContainer className="min-h-screen bg-neuro-background">
|
|
||||||
<div className="max-w-md mx-auto px-6">
|
<div className="max-w-md mx-auto px-6">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<header className="py-4">
|
<header className="py-4">
|
||||||
@@ -122,8 +121,7 @@ const Settings = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<NavBar />
|
<NavBar />
|
||||||
</SafeAreaContainer>
|
</div>;
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Settings;
|
export default Settings;
|
||||||
|
|||||||
Reference in New Issue
Block a user