Fix toast and data reset issues
- Resolved issue where budget creation toast appeared before expense creation toast. - Fixed data reset causing a redirect to the login screen.
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
import React, { useState } from 'react';
|
||||
import { PlusIcon } from 'lucide-react';
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from './ui/dialog';
|
||||
import { toast } from '@/hooks/use-toast';
|
||||
import { toast } from '@/hooks/useToast.wrapper';
|
||||
import { useBudget } from '@/contexts/BudgetContext';
|
||||
import { supabase } from '@/lib/supabase';
|
||||
import { isSyncEnabled } from '@/utils/syncUtils';
|
||||
@@ -11,6 +11,7 @@ import { Transaction } from '@/components/TransactionCard';
|
||||
|
||||
const AddTransactionButton = () => {
|
||||
const [showExpenseDialog, setShowExpenseDialog] = useState(false);
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const { addTransaction } = useBudget();
|
||||
|
||||
// Format number with commas
|
||||
@@ -21,7 +22,12 @@ const AddTransactionButton = () => {
|
||||
};
|
||||
|
||||
const onSubmit = async (data: ExpenseFormValues) => {
|
||||
// 중복 제출 방지
|
||||
if (isSubmitting) return;
|
||||
|
||||
try {
|
||||
setIsSubmitting(true);
|
||||
|
||||
// Remove commas before processing the amount
|
||||
const numericAmount = data.amount.replace(/,/g, '');
|
||||
|
||||
@@ -68,11 +74,14 @@ const AddTransactionButton = () => {
|
||||
// 다이얼로그를 닫습니다
|
||||
setShowExpenseDialog(false);
|
||||
|
||||
// 사용자에게 알림을 표시합니다
|
||||
toast({
|
||||
title: "지출이 추가되었습니다",
|
||||
description: `${data.title} 항목이 ${formatWithCommas(numericAmount)}원으로 등록되었습니다.`,
|
||||
});
|
||||
// 사용자에게 알림을 표시합니다 (지연 추가하여 다른 토스트와 충돌 방지)
|
||||
setTimeout(() => {
|
||||
toast({
|
||||
title: "지출이 추가되었습니다",
|
||||
description: `${data.title} 항목이 ${formatWithCommas(numericAmount)}원으로 등록되었습니다.`,
|
||||
duration: 3000
|
||||
});
|
||||
}, 100);
|
||||
|
||||
// 브라우저 이벤트 발생시켜 다른 페이지에서도 업데이트되도록 함
|
||||
window.dispatchEvent(new Event('budgetDataUpdated'));
|
||||
@@ -84,7 +93,10 @@ const AddTransactionButton = () => {
|
||||
title: "지출 추가 실패",
|
||||
description: "지출을 추가하는 도중 오류가 발생했습니다.",
|
||||
variant: "destructive",
|
||||
duration: 4000
|
||||
});
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -95,19 +107,23 @@ const AddTransactionButton = () => {
|
||||
className="p-4 rounded-full transition-all duration-300 bg-neuro-income shadow-neuro-flat hover:shadow-neuro-convex text-white animate-pulse-subtle"
|
||||
onClick={() => setShowExpenseDialog(true)}
|
||||
aria-label="지출 추가"
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
<PlusIcon size={24} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<Dialog open={showExpenseDialog} onOpenChange={setShowExpenseDialog}>
|
||||
<Dialog open={showExpenseDialog} onOpenChange={(open) => {
|
||||
if (!isSubmitting) setShowExpenseDialog(open);
|
||||
}}>
|
||||
<DialogContent className="w-[90%] max-w-sm mx-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle>지출 추가</DialogTitle>
|
||||
</DialogHeader>
|
||||
<ExpenseForm
|
||||
onSubmit={onSubmit}
|
||||
onCancel={() => setShowExpenseDialog(false)}
|
||||
onCancel={() => !isSubmitting && setShowExpenseDialog(false)}
|
||||
isSubmitting={isSubmitting}
|
||||
/>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useForm } from 'react-hook-form';
|
||||
import { Form, FormField, FormItem, FormLabel } from '@/components/ui/form';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Loader2 } from 'lucide-react';
|
||||
import ExpenseCategorySelector from './ExpenseCategorySelector';
|
||||
|
||||
export interface ExpenseFormValues {
|
||||
@@ -15,9 +16,10 @@ export interface ExpenseFormValues {
|
||||
interface ExpenseFormProps {
|
||||
onSubmit: (data: ExpenseFormValues) => void;
|
||||
onCancel: () => void;
|
||||
isSubmitting?: boolean;
|
||||
}
|
||||
|
||||
const ExpenseForm: React.FC<ExpenseFormProps> = ({ onSubmit, onCancel }) => {
|
||||
const ExpenseForm: React.FC<ExpenseFormProps> = ({ onSubmit, onCancel, isSubmitting = false }) => {
|
||||
const form = useForm<ExpenseFormValues>({
|
||||
defaultValues: {
|
||||
title: '',
|
||||
@@ -47,7 +49,11 @@ const ExpenseForm: React.FC<ExpenseFormProps> = ({ onSubmit, onCancel }) => {
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>제목</FormLabel>
|
||||
<Input placeholder="지출 내역을 입력하세요" {...field} />
|
||||
<Input
|
||||
placeholder="지출 내역을 입력하세요"
|
||||
{...field}
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
@@ -62,6 +68,7 @@ const ExpenseForm: React.FC<ExpenseFormProps> = ({ onSubmit, onCancel }) => {
|
||||
placeholder="금액을 입력하세요"
|
||||
value={field.value}
|
||||
onChange={handleAmountChange}
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
</FormItem>
|
||||
)}
|
||||
@@ -75,7 +82,8 @@ const ExpenseForm: React.FC<ExpenseFormProps> = ({ onSubmit, onCancel }) => {
|
||||
<FormLabel>카테고리</FormLabel>
|
||||
<ExpenseCategorySelector
|
||||
value={field.value}
|
||||
onValueChange={(value) => field.onChange(value)}
|
||||
onValueChange={(value) => field.onChange(value)}
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
</FormItem>
|
||||
)}
|
||||
@@ -86,14 +94,21 @@ const ExpenseForm: React.FC<ExpenseFormProps> = ({ onSubmit, onCancel }) => {
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={onCancel}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
취소
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
className="bg-neuro-income text-white hover:bg-neuro-income/90"
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
저장
|
||||
{isSubmitting ? (
|
||||
<>
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
저장 중...
|
||||
</>
|
||||
) : '저장'}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { Trash2 } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import { useToast } from '@/hooks/useToast.wrapper';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { resetAllStorageData } from '@/utils/storageUtils';
|
||||
import {
|
||||
@@ -16,20 +17,38 @@ import {
|
||||
|
||||
const DataResetSection = () => {
|
||||
const [isResetDialogOpen, setIsResetDialogOpen] = useState(false);
|
||||
const [isResetting, setIsResetting] = useState(false);
|
||||
const { toast } = useToast();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleResetAllData = () => {
|
||||
// 데이터 초기화 수행
|
||||
try {
|
||||
setIsResetting(true);
|
||||
console.log('모든 데이터 초기화 시작');
|
||||
|
||||
// 초기화 실행 전에 사용자 설정 백업
|
||||
const dontShowWelcomeValue = localStorage.getItem('dontShowWelcome');
|
||||
const hasVisitedBefore = localStorage.getItem('hasVisitedBefore');
|
||||
// 로그인 관련 설정도 백업
|
||||
const authSession = localStorage.getItem('authSession');
|
||||
const sb_auth = localStorage.getItem('sb-auth-token');
|
||||
|
||||
// 로그인 관련 설정 백업 (supabase 관련 모든 설정)
|
||||
const authBackupItems: Record<string, string | null> = {};
|
||||
|
||||
// 로그인 관련 항목 수집
|
||||
for (let i = 0; i < localStorage.length; i++) {
|
||||
const key = localStorage.key(i);
|
||||
if (key && (
|
||||
key.includes('supabase') ||
|
||||
key.includes('auth') ||
|
||||
key.includes('sb-') ||
|
||||
key.includes('token') ||
|
||||
key.includes('user') ||
|
||||
key.includes('session')
|
||||
)) {
|
||||
authBackupItems[key] = localStorage.getItem(key);
|
||||
console.log(`백업 항목: ${key}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 데이터 초기화
|
||||
resetAllStorageData();
|
||||
@@ -44,13 +63,12 @@ const DataResetSection = () => {
|
||||
}
|
||||
|
||||
// 로그인 관련 설정 복원 (로그인 화면이 나타나지 않도록)
|
||||
if (authSession) {
|
||||
localStorage.setItem('authSession', authSession);
|
||||
}
|
||||
|
||||
if (sb_auth) {
|
||||
localStorage.setItem('sb-auth-token', sb_auth);
|
||||
}
|
||||
Object.entries(authBackupItems).forEach(([key, value]) => {
|
||||
if (value) {
|
||||
localStorage.setItem(key, value);
|
||||
console.log(`복원 항목: ${key}`);
|
||||
}
|
||||
});
|
||||
|
||||
// 스토리지 이벤트 트리거하여 다른 컴포넌트에 변경 알림
|
||||
window.dispatchEvent(new Event('transactionUpdated'));
|
||||
@@ -58,22 +76,28 @@ const DataResetSection = () => {
|
||||
window.dispatchEvent(new Event('categoryBudgetsUpdated'));
|
||||
window.dispatchEvent(new StorageEvent('storage'));
|
||||
|
||||
toast({
|
||||
title: "모든 데이터가 초기화되었습니다.",
|
||||
description: "모든 예산, 지출 내역, 설정이 초기화되었습니다.",
|
||||
});
|
||||
setIsResetDialogOpen(false);
|
||||
console.log('모든 데이터 초기화 완료');
|
||||
|
||||
// 초기화 후 설정 페이지로 이동
|
||||
setTimeout(() => navigate('/settings'), 1000);
|
||||
setTimeout(() => {
|
||||
toast({
|
||||
title: "모든 데이터가 초기화되었습니다.",
|
||||
description: "모든 예산, 지출 내역, 설정이 초기화되었습니다.",
|
||||
duration: 3000
|
||||
});
|
||||
setIsResetDialogOpen(false);
|
||||
console.log('모든 데이터 초기화 완료');
|
||||
|
||||
// 초기화 후 설정 페이지로 이동
|
||||
setTimeout(() => navigate('/settings'), 500);
|
||||
}, 500);
|
||||
} catch (error) {
|
||||
console.error('데이터 초기화 실패:', error);
|
||||
toast({
|
||||
title: "데이터 초기화 실패",
|
||||
description: "데이터를 초기화하는 중 문제가 발생했습니다.",
|
||||
variant: "destructive",
|
||||
duration: 4000
|
||||
});
|
||||
} finally {
|
||||
setIsResetting(false);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -93,6 +117,7 @@ const DataResetSection = () => {
|
||||
variant="destructive"
|
||||
className="w-full mt-4"
|
||||
onClick={() => setIsResetDialogOpen(true)}
|
||||
disabled={isResetting}
|
||||
>
|
||||
모든 데이터 초기화
|
||||
</Button>
|
||||
@@ -110,13 +135,19 @@ const DataResetSection = () => {
|
||||
</DialogHeader>
|
||||
<DialogFooter className="flex flex-col sm:flex-row gap-2 sm:gap-0">
|
||||
<DialogClose asChild>
|
||||
<Button variant="outline" className="sm:mr-2">취소</Button>
|
||||
<Button variant="outline" className="sm:mr-2" disabled={isResetting}>취소</Button>
|
||||
</DialogClose>
|
||||
<Button
|
||||
variant="destructive"
|
||||
onClick={handleResetAllData}
|
||||
disabled={isResetting}
|
||||
>
|
||||
확인, 모든 데이터 초기화
|
||||
{isResetting ? (
|
||||
<>
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
초기화 중...
|
||||
</>
|
||||
) : '확인, 모든 데이터 초기화'}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
||||
Reference in New Issue
Block a user