feat: Enhance error handling with showErrorToast utility
- Replaced existing toast error messages with the new `showErrorToast` utility across multiple components, improving consistency in error reporting. - Updated error messages to provide more specific guidance for users, enhancing the overall user experience during error scenarios. - Ensured that all relevant error handling in batch management, external call configurations, cascading management, and screen management components now utilizes the new utility for better maintainability.
This commit is contained in:
parent
ce8b4ed688
commit
f04d224b09
|
|
@ -10,6 +10,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@
|
|||
import { Badge } from "@/components/ui/badge";
|
||||
import { ArrowLeft, Save, RefreshCw, ArrowRight, Trash2 } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
||||
import { useRouter } from "next/navigation";
|
||||
import {
|
||||
BatchAPI,
|
||||
|
|
@ -133,7 +134,7 @@ export default function BatchCreatePage() {
|
|||
setFromColumns(Array.isArray(columns) ? columns : []);
|
||||
} catch (error) {
|
||||
console.error("FROM 컬럼 목록 로드 실패:", error);
|
||||
toast.error("컬럼 목록을 불러오는데 실패했습니다.");
|
||||
showErrorToast("컬럼 목록을 불러오는 데 실패했습니다", error, { guidance: "테이블 정보를 확인해 주세요." });
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -242,7 +243,7 @@ export default function BatchCreatePage() {
|
|||
router.push("/admin/batchmng");
|
||||
} catch (error) {
|
||||
console.error("배치 설정 저장 실패:", error);
|
||||
toast.error("배치 설정 저장에 실패했습니다.");
|
||||
showErrorToast("배치 설정 저장에 실패했습니다", error, { guidance: "입력 데이터를 확인하고 다시 시도해 주세요." });
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import {
|
|||
Database
|
||||
} from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
||||
import { useRouter } from "next/navigation";
|
||||
import {
|
||||
BatchAPI,
|
||||
|
|
@ -75,7 +76,9 @@ export default function BatchManagementPage() {
|
|||
}
|
||||
} catch (error) {
|
||||
console.error("배치 실행 실패:", error);
|
||||
toast.error("배치 실행 중 오류가 발생했습니다.");
|
||||
showErrorToast("배치 실행에 실패했습니다", error, {
|
||||
guidance: "배치 설정을 확인하고 다시 시도해 주세요.",
|
||||
});
|
||||
} finally {
|
||||
setExecutingBatch(null);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { Badge } from "@/components/ui/badge";
|
|||
import { Plus, Search, Edit, Trash2, TestTube, Filter } from "lucide-react";
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
||||
import { toast } from "sonner";
|
||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
||||
import {
|
||||
ExternalCallConfigAPI,
|
||||
ExternalCallConfig,
|
||||
|
|
@ -57,11 +58,15 @@ export default function ExternalCallConfigsPage() {
|
|||
if (response.success) {
|
||||
setConfigs(response.data || []);
|
||||
} else {
|
||||
toast.error(response.message || "외부 호출 설정 조회 실패");
|
||||
showErrorToast("외부 호출 설정 조회에 실패했습니다", response.message, {
|
||||
guidance: "네트워크 연결을 확인하고 다시 시도해 주세요.",
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("외부 호출 설정 조회 오류:", error);
|
||||
toast.error("외부 호출 설정 조회 중 오류가 발생했습니다.");
|
||||
showErrorToast("외부 호출 설정 조회에 실패했습니다", error, {
|
||||
guidance: "네트워크 연결을 확인하고 다시 시도해 주세요.",
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
|
@ -113,11 +118,15 @@ export default function ExternalCallConfigsPage() {
|
|||
toast.success("외부 호출 설정이 삭제되었습니다.");
|
||||
fetchConfigs();
|
||||
} else {
|
||||
toast.error(response.message || "외부 호출 설정 삭제 실패");
|
||||
showErrorToast("외부 호출 설정 삭제에 실패했습니다", response.message, {
|
||||
guidance: "잠시 후 다시 시도해 주세요.",
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("외부 호출 설정 삭제 오류:", error);
|
||||
toast.error("외부 호출 설정 삭제 중 오류가 발생했습니다.");
|
||||
showErrorToast("외부 호출 설정 삭제에 실패했습니다", error, {
|
||||
guidance: "잠시 후 다시 시도해 주세요.",
|
||||
});
|
||||
} finally {
|
||||
setDeleteDialogOpen(false);
|
||||
setConfigToDelete(null);
|
||||
|
|
@ -138,7 +147,9 @@ export default function ExternalCallConfigsPage() {
|
|||
}
|
||||
} catch (error) {
|
||||
console.error("외부 호출 설정 테스트 오류:", error);
|
||||
toast.error("외부 호출 설정 테스트 중 오류가 발생했습니다.");
|
||||
showErrorToast("외부 호출 테스트 실행에 실패했습니다", error, {
|
||||
guidance: "URL과 설정을 확인해 주세요.",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import { Textarea } from "@/components/ui/textarea";
|
|||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { Trash2, Plus, ArrowLeft, Save, RefreshCw, Globe, Database, Eye } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
||||
import { BatchManagementAPI } from "@/lib/api/batchManagement";
|
||||
|
||||
// 타입 정의
|
||||
|
|
@ -469,7 +470,9 @@ export default function BatchManagementNewPage() {
|
|||
}
|
||||
} catch (error) {
|
||||
console.error("배치 저장 오류:", error);
|
||||
toast.error("배치 저장 중 오류가 발생했습니다.");
|
||||
showErrorToast("배치 설정 저장에 실패했습니다", error, {
|
||||
guidance: "입력 데이터를 확인하고 다시 시도해 주세요.",
|
||||
});
|
||||
}
|
||||
return;
|
||||
} else if (batchType === "db-to-restapi") {
|
||||
|
|
@ -558,7 +561,9 @@ export default function BatchManagementNewPage() {
|
|||
}
|
||||
} catch (error) {
|
||||
console.error("배치 저장 오류:", error);
|
||||
toast.error("배치 저장 중 오류가 발생했습니다.");
|
||||
showErrorToast("배치 설정 저장에 실패했습니다", error, {
|
||||
guidance: "입력 데이터를 확인하고 다시 시도해 주세요.",
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ export default function BatchManagementPage() {
|
|||
setJobs(data);
|
||||
} catch (error) {
|
||||
console.error("배치 작업 목록 조회 오류:", error);
|
||||
toast.error("배치 작업 목록을 불러오는데 실패했습니다.");
|
||||
showErrorToast("배치 작업 목록을 불러오는 데 실패했습니다", error, { guidance: "네트워크 연결을 확인해 주세요." });
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
|
|
@ -150,7 +150,7 @@ export default function BatchManagementPage() {
|
|||
loadJobs();
|
||||
} catch (error) {
|
||||
console.error("배치 작업 삭제 오류:", error);
|
||||
toast.error("배치 작업 삭제에 실패했습니다.");
|
||||
showErrorToast("배치 작업 삭제에 실패했습니다", error, { guidance: "잠시 후 다시 시도해 주세요." });
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -160,7 +160,7 @@ export default function BatchManagementPage() {
|
|||
toast.success(`"${job.job_name}" 배치 작업을 실행했습니다.`);
|
||||
} catch (error) {
|
||||
console.error("배치 작업 실행 오류:", error);
|
||||
toast.error("배치 작업 실행에 실패했습니다.");
|
||||
showErrorToast("배치 작업 실행에 실패했습니다", error, { guidance: "배치 설정을 확인해 주세요." });
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ import {
|
|||
GripVertical,
|
||||
} from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { cascadingAutoFillApi, AutoFillGroup, AutoFillMapping } from "@/lib/api/cascadingAutoFill";
|
||||
import { tableManagementApi } from "@/lib/api/tableManagement";
|
||||
|
|
@ -97,7 +98,7 @@ export default function AutoFillTab() {
|
|||
}
|
||||
} catch (error) {
|
||||
console.error("그룹 목록 로드 실패:", error);
|
||||
toast.error("그룹 목록을 불러오는데 실패했습니다.");
|
||||
showErrorToast("그룹 목록을 불러오는 데 실패했습니다", error, { guidance: "네트워크 연결을 확인해 주세요." });
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
|
@ -269,7 +270,7 @@ export default function AutoFillTab() {
|
|||
toast.error(response.error || "저장에 실패했습니다.");
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error("저장 중 오류가 발생했습니다.");
|
||||
showErrorToast("자동입력 설정 저장에 실패했습니다", error, { guidance: "입력 데이터를 확인하고 다시 시도해 주세요." });
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import {
|
|||
Loader2,
|
||||
} from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { cascadingRelationApi, CascadingRelation, CascadingRelationCreateInput } from "@/lib/api/cascadingRelation";
|
||||
import { tableManagementApi } from "@/lib/api/tableManagement";
|
||||
|
|
@ -102,7 +103,7 @@ export default function CascadingRelationsTab() {
|
|||
setRelations(response.data);
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error("연쇄 관계 목록 조회에 실패했습니다.");
|
||||
showErrorToast("연쇄 관계 목록을 불러오는 데 실패했습니다", error, { guidance: "네트워크 연결을 확인해 주세요." });
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
|
@ -431,7 +432,7 @@ export default function CascadingRelationsTab() {
|
|||
toast.error(response.message || "저장에 실패했습니다.");
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error("저장 중 오류가 발생했습니다.");
|
||||
showErrorToast("연쇄 관계 저장에 실패했습니다", error, { guidance: "입력 데이터를 확인하고 다시 시도해 주세요." });
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
|
|
@ -452,7 +453,7 @@ export default function CascadingRelationsTab() {
|
|||
toast.error(response.message || "삭제에 실패했습니다.");
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error("삭제 중 오류가 발생했습니다.");
|
||||
showErrorToast("연쇄 관계 삭제에 실패했습니다", error, { guidance: "잠시 후 다시 시도해 주세요." });
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ import {
|
|||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import { toast } from "sonner";
|
||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
||||
import {
|
||||
cascadingConditionApi,
|
||||
CascadingCondition,
|
||||
|
|
@ -170,7 +171,7 @@ export default function ConditionTab() {
|
|||
toast.error(response.error || "삭제에 실패했습니다.");
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error("삭제 중 오류가 발생했습니다.");
|
||||
showErrorToast("조건 삭제에 실패했습니다", error, { guidance: "잠시 후 다시 시도해 주세요." });
|
||||
} finally {
|
||||
setIsDeleteDialogOpen(false);
|
||||
setDeletingConditionId(null);
|
||||
|
|
@ -206,7 +207,7 @@ export default function ConditionTab() {
|
|||
toast.error(response.error || "저장에 실패했습니다.");
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error("저장 중 오류가 발생했습니다.");
|
||||
showErrorToast("조건 저장에 실패했습니다", error, { guidance: "입력 데이터를 확인하고 다시 시도해 주세요." });
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import {
|
|||
} from "@/components/ui/card";
|
||||
import { Plus, Pencil, Trash2, Database, RefreshCw, Layers } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
||||
import { LoadingSpinner } from "@/components/common/LoadingSpinner";
|
||||
import {
|
||||
hierarchyColumnApi,
|
||||
|
|
@ -300,7 +301,7 @@ export default function HierarchyColumnTab() {
|
|||
}
|
||||
} catch (error) {
|
||||
console.error("저장 에러:", error);
|
||||
toast.error("저장 중 오류가 발생했습니다.");
|
||||
showErrorToast("계층구조 설정 저장에 실패했습니다", error, { guidance: "입력 데이터를 확인하고 다시 시도해 주세요." });
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -319,7 +320,7 @@ export default function HierarchyColumnTab() {
|
|||
}
|
||||
} catch (error) {
|
||||
console.error("삭제 에러:", error);
|
||||
toast.error("삭제 중 오류가 발생했습니다.");
|
||||
showErrorToast("계층구조 설정 삭제에 실패했습니다", error, { guidance: "잠시 후 다시 시도해 주세요." });
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/com
|
|||
import { Switch } from "@/components/ui/switch";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { toast } from "sonner";
|
||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
||||
import { ArrowLeft, Save, RotateCcw, Eye } from "lucide-react";
|
||||
import { useWebTypes, type WebTypeFormData } from "@/hooks/admin/useWebTypes";
|
||||
import { AVAILABLE_COMPONENTS, getComponentInfo } from "@/lib/utils/availableComponents";
|
||||
|
|
@ -148,7 +149,7 @@ export default function EditWebTypePage() {
|
|||
toast.success("웹타입이 성공적으로 수정되었습니다.");
|
||||
router.push(`/admin/standards/${webType}`);
|
||||
} catch (error) {
|
||||
toast.error(error instanceof Error ? error.message : "수정 중 오류가 발생했습니다.");
|
||||
showErrorToast("웹타입 수정에 실패했습니다", error, { guidance: "입력 데이터를 확인하고 다시 시도해 주세요." });
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import {
|
|||
AlertDialogTrigger,
|
||||
} from "@/components/ui/alert-dialog";
|
||||
import { toast } from "sonner";
|
||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
||||
import { Plus, Search, Edit, Trash2, Eye, Filter, RotateCcw, Settings, SortAsc, SortDesc } from "lucide-react";
|
||||
import { useWebTypes } from "@/hooks/admin/useWebTypes";
|
||||
import Link from "next/link";
|
||||
|
|
@ -90,7 +91,7 @@ export default function WebTypesManagePage() {
|
|||
await deleteWebType(webType);
|
||||
toast.success(`웹타입 '${typeName}'이 삭제되었습니다.`);
|
||||
} catch (error) {
|
||||
toast.error(error instanceof Error ? error.message : "삭제 중 오류가 발생했습니다.");
|
||||
showErrorToast("웹타입 삭제에 실패했습니다", error, { guidance: "잠시 후 다시 시도해 주세요." });
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ import {
|
|||
RefreshCw
|
||||
} from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
||||
import { CollectionAPI, DataCollectionConfig } from "@/lib/api/collection";
|
||||
import CollectionConfigModal from "@/components/admin/CollectionConfigModal";
|
||||
|
||||
|
|
@ -69,7 +70,7 @@ export default function CollectionManagementPage() {
|
|||
setConfigs(data);
|
||||
} catch (error) {
|
||||
console.error("수집 설정 목록 조회 오류:", error);
|
||||
toast.error("수집 설정 목록을 불러오는데 실패했습니다.");
|
||||
showErrorToast("수집 설정 목록을 불러오는 데 실패했습니다", error, { guidance: "네트워크 연결을 확인해 주세요." });
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
|
|
@ -131,7 +132,7 @@ export default function CollectionManagementPage() {
|
|||
toast.success(`"${config.config_name}" 수집 작업을 시작했습니다.`);
|
||||
} catch (error) {
|
||||
console.error("수집 작업 실행 오류:", error);
|
||||
toast.error("수집 작업 실행에 실패했습니다.");
|
||||
showErrorToast("수집 작업 실행에 실패했습니다", error, { guidance: "수집 설정을 확인해 주세요." });
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import DataFlowList from "@/components/dataflow/DataFlowList";
|
|||
import { FlowEditor } from "@/components/dataflow/node-editor/FlowEditor";
|
||||
import { useAuth } from "@/hooks/useAuth";
|
||||
import { toast } from "sonner";
|
||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ScrollToTop } from "@/components/common/ScrollToTop";
|
||||
import { ArrowLeft } from "lucide-react";
|
||||
|
|
@ -35,7 +36,7 @@ export default function DataFlowPage() {
|
|||
toast.success("플로우를 불러왔습니다.");
|
||||
} catch (error: any) {
|
||||
console.error("❌ 플로우 불러오기 실패:", error);
|
||||
toast.error(error.message || "플로우를 불러오는데 실패했습니다.");
|
||||
showErrorToast("플로우 목록을 불러오는 데 실패했습니다", error, { guidance: "네트워크 연결을 확인해 주세요." });
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import {
|
|||
import { cn } from "@/lib/utils";
|
||||
import { LoadingSpinner } from "@/components/common/LoadingSpinner";
|
||||
import { toast } from "sonner";
|
||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
||||
import { useMultiLang } from "@/hooks/useMultiLang";
|
||||
import { useAuth } from "@/hooks/useAuth";
|
||||
import { TABLE_MANAGEMENT_KEYS } from "@/constants/tableManagement";
|
||||
|
|
@ -331,11 +332,15 @@ export default function TableManagementPage() {
|
|||
setTables(response.data.data);
|
||||
toast.success("테이블 목록을 성공적으로 로드했습니다.");
|
||||
} else {
|
||||
toast.error(response.data.message || "테이블 목록 로드에 실패했습니다.");
|
||||
showErrorToast("테이블 목록을 불러오는 데 실패했습니다", response.data.message, {
|
||||
guidance: "네트워크 연결을 확인해 주세요.",
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
// console.error("테이블 목록 로드 실패:", error);
|
||||
toast.error("테이블 목록 로드 중 오류가 발생했습니다.");
|
||||
showErrorToast("테이블 목록을 불러오는 데 실패했습니다", error, {
|
||||
guidance: "네트워크 연결을 확인해 주세요.",
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
|
@ -403,11 +408,15 @@ export default function TableManagementPage() {
|
|||
setTotalColumns(data.total || processedColumns.length);
|
||||
toast.success("컬럼 정보를 성공적으로 로드했습니다.");
|
||||
} else {
|
||||
toast.error(response.data.message || "컬럼 정보 로드에 실패했습니다.");
|
||||
showErrorToast("컬럼 정보를 불러오는 데 실패했습니다", response.data.message, {
|
||||
guidance: "네트워크 연결을 확인해 주세요.",
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
// console.error("컬럼 타입 정보 로드 실패:", error);
|
||||
toast.error("컬럼 정보 로드 중 오류가 발생했습니다.");
|
||||
showErrorToast("컬럼 정보를 불러오는 데 실패했습니다", error, {
|
||||
guidance: "네트워크 연결을 확인해 주세요.",
|
||||
});
|
||||
} finally {
|
||||
setColumnsLoading(false);
|
||||
}
|
||||
|
|
@ -777,11 +786,15 @@ export default function TableManagementPage() {
|
|||
loadColumnTypes(selectedTable);
|
||||
}, 1000);
|
||||
} else {
|
||||
toast.error(response.data.message || "컬럼 설정 저장에 실패했습니다.");
|
||||
showErrorToast("컬럼 설정 저장에 실패했습니다", response.data.message, {
|
||||
guidance: "입력 데이터를 확인하고 다시 시도해 주세요.",
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
// console.error("컬럼 설정 저장 실패:", error);
|
||||
toast.error("컬럼 설정 저장 중 오류가 발생했습니다.");
|
||||
showErrorToast("컬럼 설정 저장에 실패했습니다", error, {
|
||||
guidance: "입력 데이터를 확인하고 다시 시도해 주세요.",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -980,12 +993,16 @@ export default function TableManagementPage() {
|
|||
loadColumnTypes(selectedTable, 1, pageSize);
|
||||
}, 1000);
|
||||
} else {
|
||||
toast.error(response.data.message || "설정 저장에 실패했습니다.");
|
||||
showErrorToast("설정 저장에 실패했습니다", response.data.message, {
|
||||
guidance: "잠시 후 다시 시도해 주세요.",
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// console.error("설정 저장 실패:", error);
|
||||
toast.error("설정 저장 중 오류가 발생했습니다.");
|
||||
showErrorToast("설정 저장에 실패했습니다", error, {
|
||||
guidance: "잠시 후 다시 시도해 주세요.",
|
||||
});
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
|
|
@ -1091,7 +1108,9 @@ export default function TableManagementPage() {
|
|||
toast.error(response.data.message || "PK 설정 실패");
|
||||
}
|
||||
} catch (error: any) {
|
||||
toast.error(error?.response?.data?.message || "PK 설정 중 오류가 발생했습니다.");
|
||||
showErrorToast("PK 설정에 실패했습니다", error, {
|
||||
guidance: "컬럼 정보를 확인하고 다시 시도해 주세요.",
|
||||
});
|
||||
} finally {
|
||||
setPkDialogOpen(false);
|
||||
}
|
||||
|
|
@ -1115,7 +1134,9 @@ export default function TableManagementPage() {
|
|||
toast.error(response.data.message || "인덱스 설정 실패");
|
||||
}
|
||||
} catch (error: any) {
|
||||
toast.error(error?.response?.data?.message || error?.response?.data?.error || "인덱스 설정 중 오류가 발생했습니다.");
|
||||
showErrorToast("인덱스 설정에 실패했습니다", error, {
|
||||
guidance: "컬럼 정보를 확인하고 다시 시도해 주세요.",
|
||||
});
|
||||
}
|
||||
},
|
||||
[selectedTable, loadConstraints],
|
||||
|
|
@ -1154,10 +1175,14 @@ export default function TableManagementPage() {
|
|||
),
|
||||
);
|
||||
} else {
|
||||
toast.error(response.data.message || "UNIQUE 설정 실패");
|
||||
showErrorToast("UNIQUE 제약 조건 설정에 실패했습니다", response.data.message, {
|
||||
guidance: "해당 컬럼에 중복 데이터가 없는지 확인해 주세요.",
|
||||
});
|
||||
}
|
||||
} catch (error: any) {
|
||||
toast.error(error?.response?.data?.message || "UNIQUE 설정 중 오류가 발생했습니다.");
|
||||
showErrorToast("UNIQUE 제약 조건 설정에 실패했습니다", error, {
|
||||
guidance: "해당 컬럼에 중복 데이터가 없는지 확인해 주세요.",
|
||||
});
|
||||
}
|
||||
},
|
||||
[selectedTable],
|
||||
|
|
@ -1188,12 +1213,14 @@ export default function TableManagementPage() {
|
|||
),
|
||||
);
|
||||
} else {
|
||||
toast.error(response.data.message || "NOT NULL 설정 실패");
|
||||
showErrorToast("NOT NULL 제약 조건 설정에 실패했습니다", response.data.message, {
|
||||
guidance: "해당 컬럼에 NULL 값이 없는지 확인해 주세요.",
|
||||
});
|
||||
}
|
||||
} catch (error: any) {
|
||||
toast.error(
|
||||
error?.response?.data?.message || "NOT NULL 설정 중 오류가 발생했습니다.",
|
||||
);
|
||||
showErrorToast("NOT NULL 제약 조건 설정에 실패했습니다", error, {
|
||||
guidance: "해당 컬럼에 NULL 값이 없는지 확인해 주세요.",
|
||||
});
|
||||
}
|
||||
},
|
||||
[selectedTable],
|
||||
|
|
@ -1225,10 +1252,14 @@ export default function TableManagementPage() {
|
|||
// 테이블 목록 새로고침
|
||||
await loadTables();
|
||||
} else {
|
||||
toast.error(result.message || "테이블 삭제에 실패했습니다.");
|
||||
showErrorToast("테이블 삭제에 실패했습니다", result.message, {
|
||||
guidance: "테이블에 종속된 데이터가 있는지 확인해 주세요.",
|
||||
});
|
||||
}
|
||||
} catch (error: any) {
|
||||
toast.error(error?.response?.data?.message || "테이블 삭제 중 오류가 발생했습니다.");
|
||||
showErrorToast("테이블 삭제에 실패했습니다", error, {
|
||||
guidance: "테이블에 종속된 데이터가 있는지 확인해 주세요.",
|
||||
});
|
||||
} finally {
|
||||
setIsDeleting(false);
|
||||
setDeleteDialogOpen(false);
|
||||
|
|
@ -1308,7 +1339,9 @@ export default function TableManagementPage() {
|
|||
setSelectedTableIds(new Set());
|
||||
await loadTables();
|
||||
} catch (error: any) {
|
||||
toast.error("테이블 삭제 중 오류가 발생했습니다.");
|
||||
showErrorToast("테이블 삭제에 실패했습니다", error, {
|
||||
guidance: "테이블에 종속된 데이터가 있는지 확인해 주세요.",
|
||||
});
|
||||
} finally {
|
||||
setIsDeleting(false);
|
||||
setDeleteDialogOpen(false);
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { ScreenDefinition, LayoutData, ComponentData } from "@/types/screen";
|
|||
import { LayerDefinition } from "@/types/screen-management";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { toast } from "sonner";
|
||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
||||
import { initializeComponents } from "@/lib/registry/components";
|
||||
import { EditModal } from "@/components/screen/EditModal";
|
||||
import { RealtimePreview } from "@/components/screen/RealtimePreviewDynamic";
|
||||
|
|
@ -219,7 +220,7 @@ function ScreenViewPage() {
|
|||
} catch (error) {
|
||||
console.error("화면 로드 실패:", error);
|
||||
setError("화면을 불러오는데 실패했습니다.");
|
||||
toast.error("화면을 불러오는데 실패했습니다.");
|
||||
showErrorToast("화면을 불러오는 데 실패했습니다", error, { guidance: "화면 설정을 확인하거나 잠시 후 다시 시도해 주세요." });
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { screenApi } from "@/lib/api/screen";
|
|||
import { ScreenDefinition } from "@/types/screen";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { toast } from "sonner";
|
||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
||||
import { ScreenPreviewProvider } from "@/contexts/ScreenPreviewContext";
|
||||
import { useAuth } from "@/hooks/useAuth";
|
||||
import { TableOptionsProvider } from "@/contexts/TableOptionsContext";
|
||||
|
|
@ -135,7 +136,7 @@ function PopScreenViewPage() {
|
|||
} catch (error) {
|
||||
console.error("[POP] 화면 로드 실패:", error);
|
||||
setError("화면을 불러오는데 실패했습니다.");
|
||||
toast.error("화면을 불러오는데 실패했습니다.");
|
||||
showErrorToast("POP 화면을 불러오는 데 실패했습니다", error, { guidance: "화면 설정을 확인하거나 잠시 후 다시 시도해 주세요." });
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import { downloadFile } from "@/lib/api/file";
|
|||
import { FileViewerModal } from "@/lib/registry/components/file-upload/FileViewerModal";
|
||||
import { formatFileSize } from "@/lib/utils";
|
||||
import { toast } from "sonner";
|
||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
||||
import {
|
||||
File,
|
||||
FileText,
|
||||
|
|
@ -134,7 +135,7 @@ export const GlobalFileViewer: React.FC<GlobalFileViewerProps> = ({
|
|||
toast.success(`파일 다운로드 시작: ${file.realFileName}`);
|
||||
} catch (error) {
|
||||
console.error("파일 다운로드 오류:", error);
|
||||
toast.error("파일 다운로드에 실패했습니다.");
|
||||
showErrorToast("파일 다운로드에 실패했습니다", error, { guidance: "파일이 존재하는지 확인하고 다시 시도해 주세요." });
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import {
|
|||
import { Switch } from "@/components/ui/switch";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { toast } from "sonner";
|
||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
||||
import { BatchAPI, BatchJob, BatchConfig } from "@/lib/api/batch";
|
||||
import { ExternalDbConnectionAPI } from "@/lib/api/externalDbConnection";
|
||||
|
||||
|
|
@ -123,7 +124,7 @@ export default function AdvancedBatchModal({
|
|||
setConnections(list);
|
||||
} catch (error) {
|
||||
console.error("연결 목록 조회 오류:", error);
|
||||
toast.error("연결 목록을 불러오는데 실패했습니다.");
|
||||
showErrorToast("연결 목록을 불러오는 데 실패했습니다", error, { guidance: "네트워크 연결을 확인해 주세요." });
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -190,7 +191,7 @@ export default function AdvancedBatchModal({
|
|||
onClose();
|
||||
} catch (error) {
|
||||
console.error("배치 저장 오류:", error);
|
||||
toast.error(error instanceof Error ? error.message : "저장에 실패했습니다.");
|
||||
showErrorToast("배치 설정 저장에 실패했습니다", error, { guidance: "입력 데이터를 확인하고 다시 시도해 주세요." });
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import {
|
|||
Trash2,
|
||||
} from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
||||
import { format } from "date-fns";
|
||||
import { ko } from "date-fns/locale";
|
||||
import { ddlApi } from "../../lib/api/ddl";
|
||||
|
|
@ -71,7 +72,7 @@ export function DDLLogViewer({ isOpen, onClose }: DDLLogViewerProps) {
|
|||
setStatistics(statsResult);
|
||||
} catch (error) {
|
||||
// console.error("DDL 로그 로드 실패:", error);
|
||||
toast.error("DDL 로그를 불러오는데 실패했습니다.");
|
||||
showErrorToast("DDL 로그를 불러오는 데 실패했습니다", error, { guidance: "네트워크 연결을 확인해 주세요." });
|
||||
} finally {
|
||||
if (showLoading) setLoading(false);
|
||||
setRefreshing(false);
|
||||
|
|
@ -108,7 +109,7 @@ export function DDLLogViewer({ isOpen, onClose }: DDLLogViewerProps) {
|
|||
loadData(false);
|
||||
} catch (error) {
|
||||
// console.error("로그 정리 실패:", error);
|
||||
toast.error("로그 정리에 실패했습니다.");
|
||||
showErrorToast("로그 정리에 실패했습니다", error, { guidance: "잠시 후 다시 시도해 주세요." });
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import {
|
|||
} from "@/components/ui/dialog";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { toast } from "sonner";
|
||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
||||
import {
|
||||
ExternalCallConfigAPI,
|
||||
ExternalCallConfig,
|
||||
|
|
@ -259,7 +260,7 @@ export function ExternalCallConfigModal({ isOpen, onClose, onSave, editingConfig
|
|||
}
|
||||
} catch (error) {
|
||||
console.error("외부 호출 설정 저장 오류:", error);
|
||||
toast.error("저장 중 오류가 발생했습니다.");
|
||||
showErrorToast("외부 호출 설정 저장에 실패했습니다", error, { guidance: "입력 데이터를 확인하고 다시 시도해 주세요." });
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ import {
|
|||
} from "lucide-react";
|
||||
import { LayoutCategory } from "@/types/layout";
|
||||
import { toast } from "sonner";
|
||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
||||
|
||||
interface LayoutFormModalProps {
|
||||
open: boolean;
|
||||
|
|
@ -210,7 +211,7 @@ export const LayoutFormModal: React.FC<LayoutFormModalProps> = ({ open, onOpenCh
|
|||
success: false,
|
||||
message: result.message || "레이아웃 생성에 실패했습니다.",
|
||||
});
|
||||
toast.error("레이아웃 생성 실패");
|
||||
showErrorToast("레이아웃 생성에 실패했습니다", result.message, { guidance: "입력 데이터를 확인하고 다시 시도해 주세요." });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("레이아웃 생성 오류:", error);
|
||||
|
|
@ -218,7 +219,7 @@ export const LayoutFormModal: React.FC<LayoutFormModalProps> = ({ open, onOpenCh
|
|||
success: false,
|
||||
message: "서버 오류가 발생했습니다.",
|
||||
});
|
||||
toast.error("서버 오류");
|
||||
showErrorToast("레이아웃 생성에 실패했습니다", error, { guidance: "입력 데이터를 확인하고 다시 시도해 주세요." });
|
||||
} finally {
|
||||
setIsGenerating(false);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import { useState, useEffect } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
||||
import { Loader2 } from "lucide-react";
|
||||
import {
|
||||
Dialog,
|
||||
|
|
@ -94,7 +95,7 @@ export function MenuCopyDialog({
|
|||
}
|
||||
} catch (error) {
|
||||
console.error("회사 목록 조회 실패:", error);
|
||||
toast.error("회사 목록을 불러올 수 없습니다");
|
||||
showErrorToast("회사 목록을 불러오는 데 실패했습니다", error, { guidance: "네트워크 연결을 확인해 주세요." });
|
||||
} finally {
|
||||
setLoadingCompanies(false);
|
||||
}
|
||||
|
|
@ -160,7 +161,7 @@ export function MenuCopyDialog({
|
|||
}
|
||||
} catch (error: any) {
|
||||
console.error("메뉴 복사 오류:", error);
|
||||
toast.error(error.message || "메뉴 복사 중 오류가 발생했습니다");
|
||||
showErrorToast("메뉴 복사에 실패했습니다", error, { guidance: "복사 대상과 설정을 확인해 주세요." });
|
||||
} finally {
|
||||
setCopying(false);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import { InteractiveScreenViewerDynamic } from "@/components/screen/InteractiveS
|
|||
import { screenApi } from "@/lib/api/screen";
|
||||
import { ComponentData } from "@/types/screen";
|
||||
import { toast } from "sonner";
|
||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
||||
import { useAuth } from "@/hooks/useAuth";
|
||||
import { TableOptionsProvider } from "@/contexts/TableOptionsContext";
|
||||
import { TableSearchWidgetHeightProvider } from "@/contexts/TableSearchWidgetHeightContext";
|
||||
|
|
@ -548,11 +549,15 @@ export const ScreenModal: React.FC<ScreenModalProps> = ({ className }) => {
|
|||
setOriginalData(normalizedData); // 🆕 원본 데이터 저장 (UPDATE 판단용)
|
||||
}
|
||||
} else {
|
||||
toast.error("데이터를 불러올 수 없습니다.");
|
||||
toast.error("수정할 데이터를 불러올 수 없습니다.", {
|
||||
description: "해당 항목이 삭제되었거나 접근 권한이 없을 수 있습니다.",
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("수정 데이터 조회 오류:", error);
|
||||
toast.error("데이터를 불러오는 중 오류가 발생했습니다.");
|
||||
showErrorToast("수정 데이터 조회에 실패했습니다", error, {
|
||||
guidance: "네트워크 연결을 확인하고 다시 시도해 주세요.",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -604,7 +609,9 @@ export const ScreenModal: React.FC<ScreenModalProps> = ({ className }) => {
|
|||
}
|
||||
} catch (error) {
|
||||
console.error("화면 데이터 로딩 오류:", error);
|
||||
toast.error("화면을 불러오는 중 오류가 발생했습니다.");
|
||||
showErrorToast("화면 데이터를 불러오는 데 실패했습니다", error, {
|
||||
guidance: "화면 설정을 확인하거나 잠시 후 다시 시도해 주세요.",
|
||||
});
|
||||
handleClose();
|
||||
} finally {
|
||||
setLoading(false);
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import {
|
|||
} from "@/components/ui/dialog";
|
||||
import { MoreHorizontal, Trash2, Copy, Plus, Search, Network, Database, Calendar, User } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
||||
import { useAuth } from "@/hooks/useAuth";
|
||||
import { apiClient } from "@/lib/api/client";
|
||||
|
||||
|
|
@ -61,7 +62,7 @@ export default function DataFlowList({ onLoadFlow }: DataFlowListProps) {
|
|||
}
|
||||
} catch (error) {
|
||||
console.error("플로우 목록 조회 실패", error);
|
||||
toast.error("플로우 목록을 불러오는데 실패했습니다.");
|
||||
showErrorToast("플로우 목록을 불러오는 데 실패했습니다", error, { guidance: "네트워크 연결을 확인해 주세요." });
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
|
@ -107,7 +108,7 @@ export default function DataFlowList({ onLoadFlow }: DataFlowListProps) {
|
|||
}
|
||||
} catch (error) {
|
||||
console.error("플로우 복사 실패:", error);
|
||||
toast.error("플로우 복사에 실패했습니다.");
|
||||
showErrorToast("플로우 복사에 실패했습니다", error, { guidance: "잠시 후 다시 시도해 주세요." });
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
|
@ -129,7 +130,7 @@ export default function DataFlowList({ onLoadFlow }: DataFlowListProps) {
|
|||
}
|
||||
} catch (error) {
|
||||
console.error("플로우 삭제 실패:", error);
|
||||
toast.error("플로우 삭제에 실패했습니다.");
|
||||
showErrorToast("플로우 삭제에 실패했습니다", error, { guidance: "잠시 후 다시 시도해 주세요." });
|
||||
} finally {
|
||||
setLoading(false);
|
||||
setShowDeleteModal(false);
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import {
|
|||
Timer,
|
||||
} from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
||||
|
||||
// 타입 import
|
||||
import {
|
||||
|
|
@ -144,7 +145,7 @@ const ExternalCallTestPanel: React.FC<ExternalCallTestPanelProps> = ({
|
|||
toast.success("API 테스트가 성공했습니다!");
|
||||
setActiveTab("response");
|
||||
} else {
|
||||
toast.error("API 호출이 실패했습니다.");
|
||||
showErrorToast("API 호출이 실패했습니다", null, { guidance: "URL과 요청 설정을 확인해 주세요." });
|
||||
setActiveTab("response");
|
||||
}
|
||||
} else {
|
||||
|
|
@ -156,7 +157,7 @@ const ExternalCallTestPanel: React.FC<ExternalCallTestPanelProps> = ({
|
|||
};
|
||||
setTestResult(errorResult);
|
||||
onTestResult(errorResult);
|
||||
toast.error(response.error || "테스트 실행 중 오류가 발생했습니다.");
|
||||
showErrorToast("API 테스트 실행에 실패했습니다", response.error, { guidance: "URL과 요청 설정을 확인해 주세요." });
|
||||
}
|
||||
} catch (error) {
|
||||
const errorResult: ApiTestResult = {
|
||||
|
|
@ -167,7 +168,7 @@ const ExternalCallTestPanel: React.FC<ExternalCallTestPanelProps> = ({
|
|||
};
|
||||
setTestResult(errorResult);
|
||||
onTestResult(errorResult);
|
||||
toast.error("테스트 실행 중 오류가 발생했습니다.");
|
||||
showErrorToast("API 테스트 실행에 실패했습니다", error, { guidance: "네트워크 연결과 URL을 확인해 주세요." });
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
|
|
@ -179,7 +180,7 @@ const ExternalCallTestPanel: React.FC<ExternalCallTestPanelProps> = ({
|
|||
await navigator.clipboard.writeText(text);
|
||||
toast.success("클립보드에 복사되었습니다.");
|
||||
} catch (error) {
|
||||
toast.error("복사에 실패했습니다.");
|
||||
showErrorToast("클립보드 복사에 실패했습니다", error, { guidance: "브라우저 권한을 확인해 주세요." });
|
||||
}
|
||||
}, []);
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { Label } from "@/components/ui/label";
|
|||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { Plus, Save, Edit2, Trash2, FolderTree, Check, ChevronsUpDown } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
||||
import { NumberingRuleConfig, NumberingRulePart, SEPARATOR_OPTIONS, SeparatorType } from "@/types/numbering-rule";
|
||||
import { NumberingRuleCard } from "./NumberingRuleCard";
|
||||
import { NumberingRulePreview } from "./NumberingRulePreview";
|
||||
|
|
@ -399,10 +400,10 @@ export const NumberingRuleDesigner: React.FC<NumberingRuleDesignerProps> = ({
|
|||
await onSave?.(response.data);
|
||||
toast.success("채번 규칙이 저장되었습니다");
|
||||
} else {
|
||||
toast.error(response.error || "저장 실패");
|
||||
showErrorToast("채번 규칙 저장에 실패했습니다", response.error, { guidance: "설정을 확인하고 다시 시도해 주세요." });
|
||||
}
|
||||
} catch (error: any) {
|
||||
toast.error(`저장 실패: ${error.message}`);
|
||||
showErrorToast("채번 규칙 저장에 실패했습니다", error, { guidance: "설정을 확인하고 다시 시도해 주세요." });
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
|
@ -446,10 +447,10 @@ export const NumberingRuleDesigner: React.FC<NumberingRuleDesignerProps> = ({
|
|||
|
||||
toast.success("규칙이 삭제되었습니다");
|
||||
} else {
|
||||
toast.error(response.error || "삭제 실패");
|
||||
showErrorToast("채번 규칙 삭제에 실패했습니다", response.error, { guidance: "잠시 후 다시 시도해 주세요." });
|
||||
}
|
||||
} catch (error: any) {
|
||||
toast.error(`삭제 실패: ${error.message}`);
|
||||
showErrorToast("채번 규칙 삭제에 실패했습니다", error, { guidance: "잠시 후 다시 시도해 주세요." });
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import {
|
|||
Save,
|
||||
} from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
||||
import { ScreenDefinition } from "@/types/screen";
|
||||
import { screenApi } from "@/lib/api/screen";
|
||||
import { PopScreenGroup, getPopScreenGroups } from "@/lib/api/popScreenGroup";
|
||||
|
|
@ -191,7 +192,7 @@ export function PopScreenSettingModal({
|
|||
onOpenChange(false);
|
||||
} catch (error) {
|
||||
console.error("저장 실패:", error);
|
||||
toast.error("저장에 실패했습니다.");
|
||||
showErrorToast("POP 화면 설정 저장에 실패했습니다", error, { guidance: "입력 데이터를 확인하고 다시 시도해 주세요." });
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import { ko } from "date-fns/locale";
|
|||
import { useAuth } from "@/hooks/useAuth";
|
||||
import { uploadFilesAndCreateData } from "@/lib/api/file";
|
||||
import { toast } from "sonner";
|
||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
||||
import { useCascadingDropdown } from "@/hooks/useCascadingDropdown";
|
||||
import { CascadingDropdownConfig, LayerDefinition } from "@/types/screen-management";
|
||||
import {
|
||||
|
|
@ -1265,7 +1266,7 @@ export const InteractiveScreenViewer: React.FC<InteractiveScreenViewerProps> = (
|
|||
}
|
||||
} catch (error) {
|
||||
// console.error("파일 업로드 오류:", error);
|
||||
toast.error("파일 업로드에 실패했습니다.");
|
||||
showErrorToast("파일 업로드에 실패했습니다", error, { guidance: "파일 크기와 형식을 확인하고 다시 시도해 주세요." });
|
||||
|
||||
// 파일 입력 초기화
|
||||
e.target.value = "";
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import { Badge } from "@/components/ui/badge";
|
|||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { toast } from "sonner";
|
||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
||||
import { Search, Monitor, Settings, X, Plus } from "lucide-react";
|
||||
import { menuScreenApi } from "@/lib/api/screen";
|
||||
import { apiClient } from "@/lib/api/client";
|
||||
|
|
@ -93,7 +94,7 @@ export const MenuAssignmentModal: React.FC<MenuAssignmentModalProps> = ({
|
|||
setMenus(allMenus);
|
||||
} catch (error) {
|
||||
// console.error("메뉴 목록 로드 실패:", error);
|
||||
toast.error("메뉴 목록을 불러오는데 실패했습니다.");
|
||||
showErrorToast("메뉴 목록을 불러오는 데 실패했습니다", error, { guidance: "네트워크 연결을 확인해 주세요." });
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ import {
|
|||
CommandList,
|
||||
} from "@/components/ui/command";
|
||||
import { toast } from "sonner";
|
||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import {
|
||||
|
|
@ -403,7 +404,7 @@ export default function NodeSettingModal({
|
|||
]);
|
||||
toast.success("데이터가 새로고침되었습니다.");
|
||||
} catch (error) {
|
||||
toast.error("새로고침 실패");
|
||||
showErrorToast("새로고침에 실패했습니다", error, { guidance: "잠시 후 다시 시도해 주세요." });
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
|
@ -635,10 +636,10 @@ function TableRelationTab({
|
|||
onReload();
|
||||
onRefreshVisualization?.();
|
||||
} else {
|
||||
toast.error(response.message || "저장에 실패했습니다.");
|
||||
showErrorToast("노드 설정 저장에 실패했습니다", response.message, { guidance: "입력 데이터를 확인하고 다시 시도해 주세요." });
|
||||
}
|
||||
} catch (error: any) {
|
||||
toast.error(error.message || "저장 중 오류가 발생했습니다.");
|
||||
showErrorToast("노드 설정 저장에 실패했습니다", error, { guidance: "입력 데이터를 확인하고 다시 시도해 주세요." });
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -653,10 +654,10 @@ function TableRelationTab({
|
|||
onReload();
|
||||
onRefreshVisualization?.();
|
||||
} else {
|
||||
toast.error(response.message || "삭제에 실패했습니다.");
|
||||
showErrorToast("노드 설정 삭제에 실패했습니다", response.message, { guidance: "잠시 후 다시 시도해 주세요." });
|
||||
}
|
||||
} catch (error: any) {
|
||||
toast.error(error.message || "삭제 중 오류가 발생했습니다.");
|
||||
showErrorToast("노드 설정 삭제에 실패했습니다", error, { guidance: "잠시 후 다시 시도해 주세요." });
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -1178,10 +1179,10 @@ function JoinSettingTab({
|
|||
onReload();
|
||||
onRefreshVisualization?.();
|
||||
} else {
|
||||
toast.error(response.message || "저장에 실패했습니다.");
|
||||
showErrorToast("노드 설정 저장에 실패했습니다", response.message, { guidance: "입력 데이터를 확인하고 다시 시도해 주세요." });
|
||||
}
|
||||
} catch (error: any) {
|
||||
toast.error(error.message || "저장 중 오류가 발생했습니다.");
|
||||
showErrorToast("노드 설정 저장에 실패했습니다", error, { guidance: "입력 데이터를 확인하고 다시 시도해 주세요." });
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -1196,10 +1197,10 @@ function JoinSettingTab({
|
|||
onReload();
|
||||
onRefreshVisualization?.();
|
||||
} else {
|
||||
toast.error(response.message || "삭제에 실패했습니다.");
|
||||
showErrorToast("노드 설정 삭제에 실패했습니다", response.message, { guidance: "잠시 후 다시 시도해 주세요." });
|
||||
}
|
||||
} catch (error: any) {
|
||||
toast.error(error.message || "삭제 중 오류가 발생했습니다.");
|
||||
showErrorToast("노드 설정 삭제에 실패했습니다", error, { guidance: "잠시 후 다시 시도해 주세요." });
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -1586,10 +1587,10 @@ function DataFlowTab({
|
|||
onReload();
|
||||
onRefreshVisualization?.();
|
||||
} else {
|
||||
toast.error(response.message || "저장에 실패했습니다.");
|
||||
showErrorToast("노드 설정 저장에 실패했습니다", response.message, { guidance: "입력 데이터를 확인하고 다시 시도해 주세요." });
|
||||
}
|
||||
} catch (error: any) {
|
||||
toast.error(error.message || "저장 중 오류가 발생했습니다.");
|
||||
showErrorToast("노드 설정 저장에 실패했습니다", error, { guidance: "입력 데이터를 확인하고 다시 시도해 주세요." });
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -1604,10 +1605,10 @@ function DataFlowTab({
|
|||
onReload();
|
||||
onRefreshVisualization?.();
|
||||
} else {
|
||||
toast.error(response.message || "삭제에 실패했습니다.");
|
||||
showErrorToast("노드 설정 삭제에 실패했습니다", response.message, { guidance: "잠시 후 다시 시도해 주세요." });
|
||||
}
|
||||
} catch (error: any) {
|
||||
toast.error(error.message || "삭제 중 오류가 발생했습니다.");
|
||||
showErrorToast("노드 설정 삭제에 실패했습니다", error, { guidance: "잠시 후 다시 시도해 주세요." });
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/u
|
|||
import { Button } from "@/components/ui/button";
|
||||
import { X, Save, Loader2 } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
||||
import { DynamicComponentRenderer } from "@/lib/registry/DynamicComponentRenderer";
|
||||
import { InteractiveScreenViewer } from "./InteractiveScreenViewer";
|
||||
import { screenApi } from "@/lib/api/screen";
|
||||
|
|
@ -76,7 +77,9 @@ export const SaveModal: React.FC<SaveModalProps> = ({
|
|||
}
|
||||
} catch (error) {
|
||||
console.error("화면 로드 실패:", error);
|
||||
toast.error("화면을 불러오는데 실패했습니다.");
|
||||
showErrorToast("화면 구성 정보를 불러오는 데 실패했습니다", error, {
|
||||
guidance: "화면 설정을 확인하거나 잠시 후 다시 시도해 주세요.",
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
|
@ -264,7 +267,9 @@ export const SaveModal: React.FC<SaveModalProps> = ({
|
|||
} catch (error: any) {
|
||||
// ❌ 저장 실패 - 모달은 닫히지 않음
|
||||
console.error("저장 실패:", error);
|
||||
toast.error(`저장 중 오류가 발생했습니다: ${error.message || "알 수 없는 오류"}`);
|
||||
showErrorToast("데이터 저장에 실패했습니다", error, {
|
||||
guidance: "입력 값을 확인하고 다시 시도해 주세요.",
|
||||
});
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import {
|
|||
} from "@/components/ui/select";
|
||||
import { ScreenGroup, createScreenGroup, updateScreenGroup } from "@/lib/api/screenGroup";
|
||||
import { toast } from "sonner";
|
||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
||||
import { apiClient } from "@/lib/api/client";
|
||||
import {
|
||||
Command,
|
||||
|
|
@ -225,11 +226,11 @@ export function ScreenGroupModal({
|
|||
onSuccess();
|
||||
onClose();
|
||||
} else {
|
||||
toast.error(response.message || "작업에 실패했습니다");
|
||||
showErrorToast("그룹 저장에 실패했습니다", response.message, { guidance: "잠시 후 다시 시도해 주세요." });
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error("그룹 저장 실패:", error);
|
||||
toast.error("그룹 저장에 실패했습니다");
|
||||
showErrorToast("그룹 저장에 실패했습니다", error, { guidance: "잠시 후 다시 시도해 주세요." });
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -89,6 +89,7 @@ import { Check, ChevronsUpDown } from "lucide-react";
|
|||
import { ScreenGroupModal } from "./ScreenGroupModal";
|
||||
import CopyScreenModal from "./CopyScreenModal";
|
||||
import { toast } from "sonner";
|
||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
||||
import { screenApi } from "@/lib/api/screen";
|
||||
|
||||
interface ScreenGroupTreeViewProps {
|
||||
|
|
@ -581,11 +582,11 @@ export function ScreenGroupTreeView({
|
|||
await loadGroupsData();
|
||||
window.dispatchEvent(new CustomEvent("screen-list-refresh"));
|
||||
} else {
|
||||
toast.error(response.message || "그룹 삭제에 실패했습니다");
|
||||
showErrorToast("그룹 삭제에 실패했습니다", response.message, { guidance: "하위 항목이 있는 경우 먼저 삭제해 주세요." });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("그룹 삭제 실패:", error);
|
||||
toast.error("그룹 삭제에 실패했습니다");
|
||||
showErrorToast("그룹 삭제에 실패했습니다", error, { guidance: "잠시 후 다시 시도해 주세요." });
|
||||
} finally {
|
||||
setIsDeleting(false);
|
||||
setDeleteProgress({ current: 0, total: 0, message: "" });
|
||||
|
|
@ -614,7 +615,7 @@ export function ScreenGroupTreeView({
|
|||
window.dispatchEvent(new CustomEvent("screen-list-refresh"));
|
||||
} catch (error) {
|
||||
console.error("화면 삭제 실패:", error);
|
||||
toast.error("화면 삭제에 실패했습니다");
|
||||
showErrorToast("화면 삭제에 실패했습니다", error, { guidance: "잠시 후 다시 시도해 주세요." });
|
||||
} finally {
|
||||
setIsScreenDeleting(false);
|
||||
setIsScreenDeleteDialogOpen(false);
|
||||
|
|
@ -765,7 +766,7 @@ export function ScreenGroupTreeView({
|
|||
window.dispatchEvent(new CustomEvent("screen-list-refresh"));
|
||||
} catch (error) {
|
||||
console.error("화면 수정 실패:", error);
|
||||
toast.error("화면 수정에 실패했습니다");
|
||||
showErrorToast("화면 정보 수정에 실패했습니다", error, { guidance: "입력 데이터를 확인하고 다시 시도해 주세요." });
|
||||
} finally {
|
||||
setIsEditScreenModalOpen(false);
|
||||
setEditingScreen(null);
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ import { isFileComponent, getComponentWebType } from "@/lib/utils/componentTypeU
|
|||
import { TableOptionsProvider } from "@/contexts/TableOptionsContext";
|
||||
import { RealtimePreview } from "./RealtimePreviewDynamic";
|
||||
import { ScreenPreviewProvider } from "@/contexts/ScreenPreviewContext";
|
||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
||||
|
||||
// InteractiveScreenViewer를 동적으로 import (SSR 비활성화)
|
||||
const InteractiveScreenViewer = dynamic(
|
||||
|
|
@ -683,7 +684,7 @@ export default function ScreenList({ onScreenSelect, selectedScreen, onDesignScr
|
|||
setPreviewLayout(layoutData);
|
||||
} catch (error) {
|
||||
console.error("❌ 레이아웃 로드 실패:", error);
|
||||
toast.error("화면 레이아웃을 불러오는데 실패했습니다.");
|
||||
showErrorToast("화면 레이아웃을 불러오는 데 실패했습니다", error, { guidance: "화면 설정을 확인하거나 잠시 후 다시 시도해 주세요." });
|
||||
} finally {
|
||||
setIsLoadingPreview(false);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import {
|
|||
import { generateComponentId } from "@/lib/utils/generateId";
|
||||
import { screenApi } from "@/lib/api/screen";
|
||||
import { toast } from "sonner";
|
||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
||||
import { RealtimePreview } from "./RealtimePreviewDynamic";
|
||||
import DesignerToolbar from "./DesignerToolbar";
|
||||
|
||||
|
|
@ -53,7 +54,7 @@ export default function SimpleScreenDesigner({ selectedScreen, onBackToList }: S
|
|||
toast.success("화면이 저장되었습니다.");
|
||||
} catch (error) {
|
||||
// console.error("저장 실패:", error);
|
||||
toast.error("저장 중 오류가 발생했습니다.");
|
||||
showErrorToast("화면 설정 저장에 실패했습니다", error, { guidance: "잠시 후 다시 시도해 주세요." });
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import {
|
|||
} from "@/components/ui/table";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { toast } from "sonner";
|
||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
||||
import { Plus, Pencil, Trash2, Link2, Database } from "lucide-react";
|
||||
import {
|
||||
getFieldJoins,
|
||||
|
|
@ -155,7 +156,7 @@ export default function FieldJoinPanel({ screenId, componentId, layoutId }: Fiel
|
|||
toast.error(response.message || "저장에 실패했습니다.");
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error("저장 중 오류가 발생했습니다.");
|
||||
showErrorToast("필드 조인 설정 저장에 실패했습니다", error, { guidance: "입력 데이터를 확인하고 다시 시도해 주세요." });
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -172,7 +173,7 @@ export default function FieldJoinPanel({ screenId, componentId, layoutId }: Fiel
|
|||
toast.error(response.message || "삭제에 실패했습니다.");
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error("삭제 중 오류가 발생했습니다.");
|
||||
showErrorToast("필드 조인 설정 삭제에 실패했습니다", error, { guidance: "잠시 후 다시 시도해 주세요." });
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import { FileInfo, FileUploadResponse } from "@/lib/registry/components/file-upl
|
|||
import { uploadFiles, downloadFile, deleteFile } from "@/lib/api/file";
|
||||
import { formatFileSize, cn } from "@/lib/utils";
|
||||
import { toast } from "sonner";
|
||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
||||
|
||||
interface FileComponentConfigPanelProps {
|
||||
component: FileComponent;
|
||||
|
|
@ -536,7 +537,7 @@ export const FileComponentConfigPanel: React.FC<FileComponentConfigPanelProps> =
|
|||
// fieldName
|
||||
// });
|
||||
toast.dismiss();
|
||||
toast.error(`파일 업로드에 실패했습니다: ${error?.message || '알 수 없는 오류'}`);
|
||||
showErrorToast("파일 업로드에 실패했습니다", error, { guidance: "파일 크기와 형식을 확인하고 다시 시도해 주세요." });
|
||||
} finally {
|
||||
// console.log("🏁 파일 업로드 완료, 로딩 상태 해제");
|
||||
setUploading(false);
|
||||
|
|
@ -554,7 +555,7 @@ export const FileComponentConfigPanel: React.FC<FileComponentConfigPanelProps> =
|
|||
toast.success(`${file.realFileName || file.name} 다운로드가 완료되었습니다.`);
|
||||
} catch (error) {
|
||||
// console.error('파일 다운로드 오류:', error);
|
||||
toast.error('파일 다운로드에 실패했습니다.');
|
||||
showErrorToast("파일 다운로드에 실패했습니다", error, { guidance: "파일이 존재하는지 확인하고 다시 시도해 주세요." });
|
||||
}
|
||||
}, []);
|
||||
|
||||
|
|
@ -677,7 +678,7 @@ export const FileComponentConfigPanel: React.FC<FileComponentConfigPanelProps> =
|
|||
toast.success('파일이 삭제되었습니다.');
|
||||
} catch (error) {
|
||||
// console.error('파일 삭제 오류:', error);
|
||||
toast.error('파일 삭제에 실패했습니다.');
|
||||
showErrorToast("파일 삭제에 실패했습니다", error, { guidance: "잠시 후 다시 시도해 주세요." });
|
||||
}
|
||||
}, [uploadedFiles, onUpdateProperty, component.id]);
|
||||
|
||||
|
|
@ -713,7 +714,7 @@ export const FileComponentConfigPanel: React.FC<FileComponentConfigPanelProps> =
|
|||
toast.success(`${uploadedFiles.length}개 파일이 영구 저장되었습니다.`);
|
||||
} catch (error) {
|
||||
// console.error('파일 저장 오류:', error);
|
||||
toast.error('파일 저장에 실패했습니다.');
|
||||
showErrorToast("파일 저장에 실패했습니다", error, { guidance: "파일 크기와 형식을 확인하고 다시 시도해 주세요." });
|
||||
}
|
||||
}, [uploadedFiles, onUpdateProperty, component.id, setGlobalFileState]);
|
||||
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ import { SingleTableWithSticky } from "@/lib/registry/components/table-list/Sing
|
|||
import type { ColumnConfig } from "@/lib/registry/components/table-list/types";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { toast } from "sonner";
|
||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
||||
import {
|
||||
Pagination,
|
||||
PaginationContent,
|
||||
|
|
@ -265,7 +266,7 @@ export function FlowWidget({
|
|||
setSearchValues({});
|
||||
} catch (error) {
|
||||
console.error("필터 설정 저장 실패:", error);
|
||||
toast.error("설정 저장에 실패했습니다");
|
||||
showErrorToast("필터 설정 저장에 실패했습니다", error, { guidance: "잠시 후 다시 시도해 주세요." });
|
||||
}
|
||||
}, [filterSettingKey, searchFilterColumns]);
|
||||
|
||||
|
|
@ -309,7 +310,7 @@ export function FlowWidget({
|
|||
toast.success("그룹 설정이 저장되었습니다");
|
||||
} catch (error) {
|
||||
console.error("그룹 설정 저장 실패:", error);
|
||||
toast.error("설정 저장에 실패했습니다");
|
||||
showErrorToast("그룹 설정 저장에 실패했습니다", error, { guidance: "잠시 후 다시 시도해 주세요." });
|
||||
}
|
||||
}, [groupSettingKey, groupByColumns]);
|
||||
|
||||
|
|
@ -514,7 +515,7 @@ export function FlowWidget({
|
|||
}
|
||||
} catch (err: any) {
|
||||
console.error("❌ 플로우 새로고침 실패:", err);
|
||||
toast.error(err.message || "데이터를 새로고치는데 실패했습니다");
|
||||
showErrorToast("데이터 새로고침에 실패했습니다", err, { guidance: "네트워크 연결을 확인하고 다시 시도해 주세요." });
|
||||
} finally {
|
||||
if (selectedStepId) {
|
||||
setStepDataLoading(false);
|
||||
|
|
@ -747,7 +748,7 @@ export function FlowWidget({
|
|||
}
|
||||
} catch (err: any) {
|
||||
console.error("Failed to load step data:", err);
|
||||
toast.error(err.message || "데이터를 불러오는데 실패했습니다");
|
||||
showErrorToast("스텝 데이터를 불러오는 데 실패했습니다", err, { guidance: "네트워크 연결을 확인해 주세요." });
|
||||
} finally {
|
||||
setStepDataLoading(false);
|
||||
}
|
||||
|
|
@ -1023,7 +1024,7 @@ export function FlowWidget({
|
|||
toast.success(`${exportData.length}개 행이 Excel로 내보내기 되었습니다.`);
|
||||
} catch (error) {
|
||||
console.error("Excel 내보내기 오류:", error);
|
||||
toast.error("Excel 내보내기에 실패했습니다.");
|
||||
showErrorToast("Excel 파일 내보내기에 실패했습니다", error, { guidance: "데이터를 확인하고 다시 시도해 주세요." });
|
||||
}
|
||||
}, [sortedDisplayData, selectedRows, stepDataColumns, columnLabels, flowName, getRowKey]);
|
||||
|
||||
|
|
@ -1188,7 +1189,7 @@ export function FlowWidget({
|
|||
toast.success(`${exportData.length}개 행이 PDF로 내보내기 되었습니다.`, { id: "pdf-export" });
|
||||
} catch (error) {
|
||||
console.error("PDF 내보내기 오류:", error);
|
||||
toast.error("PDF 내보내기에 실패했습니다.", { id: "pdf-export" });
|
||||
showErrorToast("PDF 파일 내보내기에 실패했습니다", error, { guidance: "데이터를 확인하고 다시 시도해 주세요." });
|
||||
}
|
||||
}, [sortedDisplayData, selectedRows, stepDataColumns, columnLabels, flowName, getRowKey]);
|
||||
|
||||
|
|
@ -1216,7 +1217,7 @@ export function FlowWidget({
|
|||
toast.success(`${copyData.length}개 행이 클립보드에 복사되었습니다.`);
|
||||
} catch (error) {
|
||||
console.error("복사 오류:", error);
|
||||
toast.error("복사에 실패했습니다.");
|
||||
showErrorToast("클립보드 복사에 실패했습니다", error, { guidance: "브라우저 권한을 확인해 주세요." });
|
||||
}
|
||||
}, [sortedDisplayData, selectedRows, stepDataColumns, columnLabels, getRowKey]);
|
||||
|
||||
|
|
@ -1318,7 +1319,7 @@ export function FlowWidget({
|
|||
toast.success("데이터를 새로고침했습니다.");
|
||||
} catch (error) {
|
||||
console.error("새로고침 오류:", error);
|
||||
toast.error("새로고침에 실패했습니다.");
|
||||
showErrorToast("데이터 새로고침에 실패했습니다", error, { guidance: "잠시 후 다시 시도해 주세요." });
|
||||
} finally {
|
||||
setStepDataLoading(false);
|
||||
}
|
||||
|
|
@ -1399,7 +1400,7 @@ export function FlowWidget({
|
|||
}
|
||||
} catch (error) {
|
||||
console.error("편집 저장 오류:", error);
|
||||
toast.error("저장 중 오류가 발생했습니다.");
|
||||
showErrorToast("데이터 저장에 실패했습니다", error, { guidance: "입력 데이터를 확인하고 다시 시도해 주세요." });
|
||||
}
|
||||
|
||||
cancelEditing();
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import { usePopEvent } from "./usePopEvent";
|
|||
import { executePopAction } from "./executePopAction";
|
||||
import type { ActionResult } from "./executePopAction";
|
||||
import { toast } from "sonner";
|
||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
||||
|
||||
// ========================================
|
||||
// 타입 정의
|
||||
|
|
@ -107,7 +108,7 @@ export function usePopAction(screenId: string) {
|
|||
const msg = ACTION_SUCCESS_MESSAGES[action.type];
|
||||
if (msg) toast.success(msg);
|
||||
} else {
|
||||
toast.error(result.error || "작업에 실패했습니다.");
|
||||
showErrorToast("작업에 실패했습니다", result.error, { guidance: "잠시 후 다시 시도해 주세요." });
|
||||
}
|
||||
|
||||
// 성공 시 후속 액션 실행
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import {
|
|||
AlertDialogTitle,
|
||||
} from "@/components/ui/alert-dialog";
|
||||
import { toast } from "sonner";
|
||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
||||
import { filterDOMProps } from "@/lib/utils/domPropsFilter";
|
||||
import { useCurrentFlowStep } from "@/stores/flowStepStore";
|
||||
import { useScreenPreview } from "@/contexts/ScreenPreviewContext";
|
||||
|
|
@ -955,7 +956,7 @@ export const ButtonPrimaryComponent: React.FC<ButtonPrimaryComponentProps> = ({
|
|||
}
|
||||
} catch (error: any) {
|
||||
console.error("❌ 데이터 전달 실패:", error);
|
||||
toast.error(error.message || "데이터 전달 중 오류가 발생했습니다.");
|
||||
showErrorToast("데이터 전달에 실패했습니다", error, { guidance: "대상 화면 설정과 데이터를 확인해 주세요." });
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { Card, CardContent } from "@/components/ui/card";
|
|||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { toast } from "sonner";
|
||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
||||
import { uploadFiles, downloadFile, deleteFile, getComponentFiles } from "@/lib/api/file";
|
||||
import { GlobalFileManager } from "@/lib/api/globalFile";
|
||||
import { formatFileSize } from "@/lib/utils";
|
||||
|
|
@ -881,7 +882,7 @@ const FileUploadComponent: React.FC<FileUploadComponentProps> = ({
|
|||
console.error("파일 업로드 오류:", error);
|
||||
setUploadStatus("error");
|
||||
toast.dismiss("file-upload");
|
||||
toast.error(`파일 업로드 오류: ${error instanceof Error ? error.message : "알 수 없는 오류"}`);
|
||||
showErrorToast("파일 업로드에 실패했습니다", error, { guidance: "파일 크기와 형식을 확인하고 다시 시도해 주세요." });
|
||||
}
|
||||
},
|
||||
[safeComponentConfig, uploadedFiles, onFormDataChange, component.columnName, component.id, formData],
|
||||
|
|
@ -1006,7 +1007,7 @@ const FileUploadComponent: React.FC<FileUploadComponentProps> = ({
|
|||
toast.success(`${fileName} 삭제 완료`);
|
||||
} catch (error) {
|
||||
console.error("파일 삭제 오류:", error);
|
||||
toast.error("파일 삭제에 실패했습니다.");
|
||||
showErrorToast("파일 삭제에 실패했습니다", error, { guidance: "잠시 후 다시 시도해 주세요." });
|
||||
}
|
||||
},
|
||||
[uploadedFiles, onUpdate, component.id, isRecordMode, onFormDataChange, recordTableName, recordId, columnName, getUniqueKey],
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ import { FileText, ChevronRightIcon, Search } from "lucide-react";
|
|||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { toast } from "sonner";
|
||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
||||
import { tableDisplayStore } from "@/stores/tableDisplayStore";
|
||||
import {
|
||||
Dialog,
|
||||
|
|
@ -2491,7 +2492,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
|||
console.log("✅ 배치 저장 완료:", pendingChanges.size, "개");
|
||||
} catch (error) {
|
||||
console.error("❌ 배치 저장 실패:", error);
|
||||
toast.error("저장 중 오류가 발생했습니다.");
|
||||
showErrorToast("데이터 저장에 실패했습니다", error, { guidance: "입력 데이터를 확인하고 다시 시도해 주세요." });
|
||||
}
|
||||
}, [pendingChanges, tableConfig.selectedTable, tableConfig.primaryKey]);
|
||||
|
||||
|
|
@ -2709,7 +2710,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
|||
console.log("✅ Excel 내보내기 완료:", fileName);
|
||||
} catch (error) {
|
||||
console.error("❌ Excel 내보내기 실패:", error);
|
||||
toast.error("Excel 내보내기 중 오류가 발생했습니다.");
|
||||
showErrorToast("Excel 파일 내보내기에 실패했습니다", error, { guidance: "데이터를 확인하고 다시 시도해 주세요." });
|
||||
}
|
||||
},
|
||||
[
|
||||
|
|
@ -3623,7 +3624,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
|||
console.log("✅ 행 순서 변경:", { from: draggedRowIndex, to: targetIndex });
|
||||
} catch (error) {
|
||||
console.error("❌ 행 순서 변경 실패:", error);
|
||||
toast.error("순서 변경 중 오류가 발생했습니다.");
|
||||
showErrorToast("행 순서 변경에 실패했습니다", error, { guidance: "잠시 후 다시 시도해 주세요." });
|
||||
}
|
||||
|
||||
handleRowDragEnd();
|
||||
|
|
@ -3737,7 +3738,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
|||
}
|
||||
} catch (error) {
|
||||
console.error("❌ PDF 내보내기 실패:", error);
|
||||
toast.error("PDF 내보내기 중 오류가 발생했습니다.");
|
||||
showErrorToast("PDF 파일 내보내기에 실패했습니다", error, { guidance: "데이터를 확인하고 다시 시도해 주세요." });
|
||||
}
|
||||
},
|
||||
[
|
||||
|
|
@ -6644,7 +6645,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
|||
handleRefresh();
|
||||
} catch (error) {
|
||||
console.error("삭제 오류:", error);
|
||||
toast.error("삭제 중 오류가 발생했습니다");
|
||||
showErrorToast("데이터 삭제에 실패했습니다", error, { guidance: "삭제 대상을 확인하고 다시 시도해 주세요." });
|
||||
}
|
||||
}
|
||||
closeContextMenu();
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import {
|
|||
AlertDialogTitle,
|
||||
} from "@/components/ui/alert-dialog";
|
||||
import { toast } from "sonner";
|
||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
||||
import { filterDOMProps } from "@/lib/utils/domPropsFilter";
|
||||
import { useCurrentFlowStep } from "@/stores/flowStepStore";
|
||||
import { useScreenPreview } from "@/contexts/ScreenPreviewContext";
|
||||
|
|
@ -1069,7 +1070,7 @@ export const ButtonPrimaryComponent: React.FC<ButtonPrimaryComponentProps> = ({
|
|||
}
|
||||
} catch (error: any) {
|
||||
console.error("❌ 데이터 전달 실패:", error);
|
||||
toast.error(error.message || "데이터 전달 중 오류가 발생했습니다.");
|
||||
showErrorToast("데이터 전달에 실패했습니다", error, { guidance: "대상 화면 설정과 데이터를 확인해 주세요." });
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -181,6 +181,7 @@ import { FileText, ChevronRightIcon } from "lucide-react";
|
|||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { toast } from "sonner";
|
||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
||||
import { tableDisplayStore } from "@/stores/tableDisplayStore";
|
||||
import {
|
||||
Dialog,
|
||||
|
|
@ -2577,7 +2578,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
|||
|
||||
toast.success(`${pendingChanges.size}개의 변경사항이 저장되었습니다.`);
|
||||
} catch (error) {
|
||||
toast.error("저장 중 오류가 발생했습니다.");
|
||||
showErrorToast("데이터 저장에 실패했습니다", error, { guidance: "입력 데이터를 확인하고 다시 시도해 주세요." });
|
||||
}
|
||||
}, [pendingChanges, tableConfig.selectedTable, tableConfig.primaryKey]);
|
||||
|
||||
|
|
@ -2765,7 +2766,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
|||
|
||||
toast.success(`${exportData.length}개 행이 Excel로 내보내기 되었습니다.`);
|
||||
} catch (error) {
|
||||
toast.error("Excel 내보내기 중 오류가 발생했습니다.");
|
||||
showErrorToast("Excel 파일 내보내기에 실패했습니다", error, { guidance: "데이터를 확인하고 다시 시도해 주세요." });
|
||||
}
|
||||
},
|
||||
[
|
||||
|
|
@ -3216,7 +3217,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
|||
|
||||
toast.success(`${copyData.length}행 복사됨`);
|
||||
} catch (error) {
|
||||
toast.error("복사 실패");
|
||||
showErrorToast("클립보드 복사에 실패했습니다", error, { guidance: "브라우저 권한을 확인해 주세요." });
|
||||
}
|
||||
}, [selectedRows, filteredData, focusedCell, visibleColumns, columnLabels, getRowKey]);
|
||||
|
||||
|
|
@ -3771,7 +3772,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
|||
}
|
||||
} catch (error) {
|
||||
console.error("❌ PDF 내보내기 실패:", error);
|
||||
toast.error("PDF 내보내기 중 오류가 발생했습니다.");
|
||||
showErrorToast("PDF 파일 내보내기에 실패했습니다", error, { guidance: "데이터를 확인하고 다시 시도해 주세요." });
|
||||
}
|
||||
},
|
||||
[
|
||||
|
|
@ -4519,7 +4520,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
|||
setSearchValues({});
|
||||
} catch (error) {
|
||||
console.error("필터 설정 저장 실패:", error);
|
||||
toast.error("설정 저장에 실패했습니다");
|
||||
showErrorToast("필터 설정 저장에 실패했습니다", error, { guidance: "잠시 후 다시 시도해 주세요." });
|
||||
}
|
||||
}, [filterSettingKey, visibleFilterColumns]);
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import { dataApi } from "@/lib/api/data";
|
|||
import { executePopAction } from "@/hooks/pop/executePopAction";
|
||||
import { usePopEvent } from "@/hooks/pop/usePopEvent";
|
||||
import { toast } from "sonner";
|
||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
||||
import type {
|
||||
PopStringListConfig,
|
||||
CardGridConfig,
|
||||
|
|
@ -146,10 +147,10 @@ export function PopStringListComponent({
|
|||
if (result.success) {
|
||||
toast.success("작업이 완료되었습니다.");
|
||||
} else {
|
||||
toast.error(result.error || "작업에 실패했습니다.");
|
||||
showErrorToast("작업에 실패했습니다", result.error, { guidance: "잠시 후 다시 시도해 주세요." });
|
||||
}
|
||||
} catch {
|
||||
toast.error("알 수 없는 오류가 발생했습니다.");
|
||||
showErrorToast("예기치 않은 오류가 발생했습니다", null, { guidance: "잠시 후 다시 시도해 주세요." });
|
||||
} finally {
|
||||
setLoadingRowIdx(-1);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import React from "react";
|
||||
import { toast } from "sonner";
|
||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
||||
import { screenApi } from "@/lib/api/screen";
|
||||
import { DynamicFormApi } from "@/lib/api/dynamicForm";
|
||||
import { ImprovedButtonActionExecutor } from "@/lib/utils/improvedButtonActionExecutor";
|
||||
|
|
@ -456,7 +457,11 @@ export class ButtonActionExecutor {
|
|||
}
|
||||
} catch (error) {
|
||||
console.error("버튼 액션 실행 오류:", error);
|
||||
toast.error(config.errorMessage || "작업 중 오류가 발생했습니다.");
|
||||
showErrorToast(
|
||||
config.errorMessage || `'${config.label || config.type}' 버튼 실행에 실패했습니다`,
|
||||
error,
|
||||
{ guidance: "설정을 확인하거나 잠시 후 다시 시도해 주세요." }
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -2652,7 +2657,9 @@ export class ButtonActionExecutor {
|
|||
return { handled: true, success: true };
|
||||
} catch (error: any) {
|
||||
console.error("❌ [handleUniversalFormModalTableSectionSave] 저장 오류:", error);
|
||||
toast.error(error.message || "저장 중 오류가 발생했습니다.");
|
||||
showErrorToast("테이블 섹션 데이터 저장에 실패했습니다", error, {
|
||||
guidance: "입력값을 확인하고 다시 시도해 주세요.",
|
||||
});
|
||||
return { handled: true, success: false };
|
||||
}
|
||||
}
|
||||
|
|
@ -2894,7 +2901,9 @@ export class ButtonActionExecutor {
|
|||
if (failCount === 0) {
|
||||
toast.success(`${successCount}개 항목이 저장되었습니다.`);
|
||||
} else if (successCount === 0) {
|
||||
toast.error(`저장 실패: ${errors.join(", ")}`);
|
||||
showErrorToast(`${errors.length}개 항목 저장에 모두 실패했습니다`, errors.join("\n"), {
|
||||
guidance: "입력 데이터를 확인하고 다시 시도해 주세요.",
|
||||
});
|
||||
return false;
|
||||
} else {
|
||||
toast.warning(`${successCount}개 성공, ${failCount}개 실패: ${errors.join(", ")}`);
|
||||
|
|
@ -2911,7 +2920,9 @@ export class ButtonActionExecutor {
|
|||
return true;
|
||||
} catch (error: any) {
|
||||
console.error("배치 저장 오류:", error);
|
||||
toast.error(`저장 오류: ${error.message}`);
|
||||
showErrorToast("배치 저장 중 오류가 발생했습니다", error, {
|
||||
guidance: "저장 대상 데이터를 확인하고 다시 시도해 주세요.",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -3263,7 +3274,9 @@ export class ButtonActionExecutor {
|
|||
}
|
||||
} catch (error) {
|
||||
console.error("❌ 데이터 확인 실패:", error);
|
||||
toast.error("데이터 확인 중 오류가 발생했습니다.");
|
||||
showErrorToast("상위 데이터 조회에 실패했습니다", error, {
|
||||
guidance: "데이터 소스 연결 상태를 확인해 주세요.",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
|
|
@ -3436,7 +3449,9 @@ export class ButtonActionExecutor {
|
|||
}
|
||||
} catch (error) {
|
||||
console.error("❌ 데이터 확인 실패:", error);
|
||||
toast.error("데이터 확인 중 오류가 발생했습니다.");
|
||||
showErrorToast("모달 데이터 확인에 실패했습니다", error, {
|
||||
guidance: "데이터 소스를 확인하고 다시 시도해 주세요.",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -3997,7 +4012,9 @@ export class ButtonActionExecutor {
|
|||
return true;
|
||||
} catch (error: any) {
|
||||
console.error("❌ 복사 액션 실행 중 오류:", error);
|
||||
toast.error(`복사 중 오류가 발생했습니다: ${error.message || "알 수 없는 오류"}`);
|
||||
showErrorToast("데이터 복사에 실패했습니다", error, {
|
||||
guidance: "복사 대상 데이터를 확인하고 다시 시도해 주세요.",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -4228,12 +4245,18 @@ export class ButtonActionExecutor {
|
|||
return true;
|
||||
} else {
|
||||
console.error("❌ 노드 플로우 실행 실패:", result);
|
||||
toast.error(config.errorMessage || result.message || "플로우 실행 중 오류가 발생했습니다.");
|
||||
showErrorToast(
|
||||
config.errorMessage || "플로우 실행에 실패했습니다",
|
||||
result.message,
|
||||
{ guidance: "플로우 설정과 데이터를 확인해 주세요." }
|
||||
);
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("❌ 노드 플로우 실행 오류:", error);
|
||||
toast.error("플로우 실행 중 오류가 발생했습니다.");
|
||||
showErrorToast("플로우 실행 중 오류가 발생했습니다", error, {
|
||||
guidance: "플로우 연결 상태와 데이터를 확인해 주세요.",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
} else if (config.dataflowConfig?.controlMode === "relationship" && config.dataflowConfig?.relationshipConfig) {
|
||||
|
|
@ -4277,7 +4300,11 @@ export class ButtonActionExecutor {
|
|||
return true;
|
||||
} else {
|
||||
console.error("❌ 관계 실행 실패:", executionResult);
|
||||
toast.error(config.errorMessage || "관계 실행 중 오류가 발생했습니다.");
|
||||
showErrorToast(
|
||||
config.errorMessage || "관계 실행에 실패했습니다",
|
||||
executionResult.message || executionResult.error,
|
||||
{ guidance: "관계 설정과 데이터를 확인해 주세요." }
|
||||
);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
|
|
@ -4292,7 +4319,9 @@ export class ButtonActionExecutor {
|
|||
}
|
||||
} catch (error) {
|
||||
console.error("제어 조건 검증 중 오류:", error);
|
||||
toast.error("제어 조건 검증 중 오류가 발생했습니다.");
|
||||
showErrorToast("제어 조건 검증에 실패했습니다", error, {
|
||||
guidance: "제어 설정을 확인해 주세요.",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -4420,7 +4449,12 @@ export class ButtonActionExecutor {
|
|||
if (allSuccess) {
|
||||
toast.success(`${successCount}개 제어 실행 완료`);
|
||||
} else {
|
||||
toast.error(`제어 실행 중 오류 발생 (${successCount}/${results.length} 성공)`);
|
||||
const failedNames = results.filter((r) => !r.success).map((r) => r.flowName || r.flowId).join(", ");
|
||||
showErrorToast(
|
||||
`제어 실행 중 일부 실패 (${successCount}/${results.length} 성공)`,
|
||||
failedNames ? `실패 항목: ${failedNames}` : undefined,
|
||||
{ guidance: "실패한 제어 플로우 설정을 확인해 주세요." }
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
|
|
@ -4486,11 +4520,15 @@ export class ButtonActionExecutor {
|
|||
toast.success("제어 로직 실행이 완료되었습니다.");
|
||||
} else {
|
||||
console.error("❌ 저장 후 노드 플로우 실행 실패:", result);
|
||||
toast.error("저장은 완료되었으나 제어 실행 중 오류가 발생했습니다.");
|
||||
showErrorToast("저장은 완료되었으나 후속 제어 실행에 실패했습니다", result.message, {
|
||||
guidance: "제어 플로우 설정을 확인해 주세요. 데이터는 정상 저장되었습니다.",
|
||||
});
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error("❌ 저장 후 노드 플로우 실행 오류:", error);
|
||||
toast.error(`제어 실행 오류: ${error.message || "알 수 없는 오류"}`);
|
||||
showErrorToast("저장은 완료되었으나 후속 제어 실행에 실패했습니다", error, {
|
||||
guidance: "제어 플로우 설정을 확인해 주세요. 데이터는 정상 저장되었습니다.",
|
||||
});
|
||||
}
|
||||
|
||||
return; // 노드 플로우 실행 후 종료
|
||||
|
|
@ -4521,7 +4559,9 @@ export class ButtonActionExecutor {
|
|||
// 성공 토스트는 save 액션에서 이미 표시했으므로 추가로 표시하지 않음
|
||||
} else {
|
||||
console.error("❌ 저장 후 제어 실행 실패:", executionResult);
|
||||
toast.error("저장은 완료되었으나 연결된 제어 실행 중 오류가 발생했습니다.");
|
||||
showErrorToast("저장은 완료되었으나 연결된 제어 실행에 실패했습니다", executionResult.message || executionResult.error, {
|
||||
guidance: "제어 관계 설정을 확인해 주세요. 데이터는 정상 저장되었습니다.",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4565,9 +4605,10 @@ export class ButtonActionExecutor {
|
|||
const actionType = action.actionType || action.type;
|
||||
console.error(`❌ 액션 ${i + 1}/${actions.length} 실행 실패:`, action.name, error);
|
||||
|
||||
// 실패 토스트
|
||||
toast.error(
|
||||
`${action.name || `액션 ${i + 1}`} 실행 실패: ${error instanceof Error ? error.message : "알 수 없는 오류"}`,
|
||||
showErrorToast(
|
||||
`'${action.name || `액션 ${i + 1}`}' 실행에 실패했습니다`,
|
||||
error,
|
||||
{ guidance: `전체 ${actions.length}개 액션 중 ${i + 1}번째에서 중단되었습니다.` }
|
||||
);
|
||||
|
||||
// 🚨 순차 실행 중단: 하나라도 실패하면 전체 중단
|
||||
|
|
@ -4635,9 +4676,11 @@ export class ButtonActionExecutor {
|
|||
} else {
|
||||
throw new Error(result.message || "저장 실패");
|
||||
}
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
console.error("❌ 저장 실패:", error);
|
||||
toast.error(`저장 실패: ${error.message}`);
|
||||
showErrorToast(`'${context.tableName}' 테이블 저장에 실패했습니다`, error, {
|
||||
guidance: "입력 데이터를 확인하고 다시 시도해 주세요.",
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
|
@ -4724,9 +4767,11 @@ export class ButtonActionExecutor {
|
|||
} else {
|
||||
throw new Error(result.message || "업데이트 실패");
|
||||
}
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
console.error("❌ 업데이트 실패:", error);
|
||||
toast.error(`업데이트 실패: ${error.message}`);
|
||||
showErrorToast(`'${context.tableName}' 데이터 수정에 실패했습니다`, error, {
|
||||
guidance: "수정 데이터를 확인하고 다시 시도해 주세요.",
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
|
@ -4780,9 +4825,11 @@ export class ButtonActionExecutor {
|
|||
} else {
|
||||
throw new Error(result.message || "삭제 실패");
|
||||
}
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
console.error("❌ 삭제 실패:", error);
|
||||
toast.error(`삭제 실패: ${error.message}`);
|
||||
showErrorToast(`'${context.tableName}' 데이터 삭제에 실패했습니다`, error, {
|
||||
guidance: "삭제 대상을 확인하고 다시 시도해 주세요.",
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
|
@ -4863,7 +4910,9 @@ export class ButtonActionExecutor {
|
|||
}
|
||||
} catch (error) {
|
||||
console.error("❌ 삽입 실패:", error);
|
||||
toast.error(`삽입 실패: ${error instanceof Error ? error.message : "알 수 없는 오류"}`);
|
||||
showErrorToast("데이터 삽입에 실패했습니다", error, {
|
||||
guidance: "필수 입력 항목과 데이터 형식을 확인해 주세요.",
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
|
@ -4964,7 +5013,9 @@ export class ButtonActionExecutor {
|
|||
return true;
|
||||
} catch (error) {
|
||||
console.error("❌ 이력 모달 열기 실패:", error);
|
||||
toast.error("이력 조회 중 오류가 발생했습니다.");
|
||||
showErrorToast("이력 조회에 실패했습니다", error, {
|
||||
guidance: "이력 테이블 설정을 확인해 주세요.",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -5004,7 +5055,9 @@ export class ButtonActionExecutor {
|
|||
columnLabels![col] = downloadResponse.data.headers[index] || col;
|
||||
});
|
||||
} else {
|
||||
toast.error("마스터-디테일 데이터 조회에 실패했습니다.");
|
||||
showErrorToast("마스터-디테일 데이터 조회에 실패했습니다", null, {
|
||||
guidance: "데이터 소스 설정을 확인해 주세요.",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -5094,12 +5147,16 @@ export class ButtonActionExecutor {
|
|||
dataToExport = response.data;
|
||||
} else {
|
||||
console.error("❌ 예상치 못한 응답 형식:", response);
|
||||
toast.error("데이터를 가져오는데 실패했습니다.");
|
||||
showErrorToast("엑셀 데이터 조회에 실패했습니다", null, {
|
||||
guidance: "서버 응답 형식이 예상과 다릅니다. 관리자에게 문의해 주세요.",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("엑셀 다운로드: 데이터 조회 실패:", error);
|
||||
toast.error("데이터를 가져오는데 실패했습니다.");
|
||||
showErrorToast("엑셀 다운로드용 데이터 조회에 실패했습니다", error, {
|
||||
guidance: "네트워크 연결을 확인하고 다시 시도해 주세요.",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -5109,7 +5166,9 @@ export class ButtonActionExecutor {
|
|||
}
|
||||
// 테이블명도 없고 폼 데이터도 없으면 에러
|
||||
else {
|
||||
toast.error("다운로드할 데이터 소스가 없습니다.");
|
||||
toast.error("다운로드할 데이터 소스가 없습니다.", {
|
||||
description: "테이블 또는 폼 데이터가 설정되어 있지 않습니다. 버튼 설정을 확인해 주세요.",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -5118,13 +5177,17 @@ export class ButtonActionExecutor {
|
|||
if (typeof dataToExport === "object" && dataToExport !== null) {
|
||||
dataToExport = [dataToExport];
|
||||
} else {
|
||||
toast.error("다운로드할 데이터 형식이 올바르지 않습니다.");
|
||||
toast.error("다운로드할 데이터 형식이 올바르지 않습니다.", {
|
||||
description: "서버에서 받은 데이터 형식이 예상과 다릅니다. 관리자에게 문의해 주세요.",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (dataToExport.length === 0) {
|
||||
toast.error("다운로드할 데이터가 없습니다.");
|
||||
toast.error("다운로드할 데이터가 없습니다.", {
|
||||
description: "조회 조건에 맞는 데이터가 없습니다. 검색 조건을 변경해 보세요.",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -5399,7 +5462,9 @@ export class ButtonActionExecutor {
|
|||
return true;
|
||||
} catch (error) {
|
||||
console.error("❌ 엑셀 다운로드 실패:", error);
|
||||
toast.error(config.errorMessage || "엑셀 다운로드 중 오류가 발생했습니다.");
|
||||
showErrorToast(config.errorMessage || "엑셀 파일 다운로드에 실패했습니다", error, {
|
||||
guidance: "데이터를 확인하고 다시 시도해 주세요.",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -5503,7 +5568,9 @@ export class ButtonActionExecutor {
|
|||
return true;
|
||||
} catch (error) {
|
||||
console.error("❌ 엑셀 업로드 모달 열기 실패:", error);
|
||||
toast.error(config.errorMessage || "엑셀 업로드 중 오류가 발생했습니다.");
|
||||
showErrorToast(config.errorMessage || "엑셀 업로드 화면을 열 수 없습니다", error, {
|
||||
guidance: "잠시 후 다시 시도해 주세요.",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -5564,7 +5631,9 @@ export class ButtonActionExecutor {
|
|||
return true;
|
||||
} catch (error) {
|
||||
console.error("❌ 바코드 스캔 모달 열기 실패:", error);
|
||||
toast.error("바코드 스캔 중 오류가 발생했습니다.");
|
||||
showErrorToast("바코드 스캔 화면을 열 수 없습니다", error, {
|
||||
guidance: "카메라 권한을 확인하고 다시 시도해 주세요.",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -5744,13 +5813,15 @@ export class ButtonActionExecutor {
|
|||
|
||||
return true;
|
||||
} else {
|
||||
toast.error(response.data.message || "코드 병합에 실패했습니다.");
|
||||
showErrorToast("코드 병합에 실패했습니다", response.data.message, {
|
||||
guidance: "병합 대상 코드를 확인해 주세요.",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error("❌ 코드 병합 실패:", error);
|
||||
toast.dismiss();
|
||||
toast.error(error.response?.data?.message || "코드 병합 중 오류가 발생했습니다.");
|
||||
showErrorToast("코드 병합 중 오류가 발생했습니다", error.response?.data?.message || error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -5877,7 +5948,9 @@ export class ButtonActionExecutor {
|
|||
return true;
|
||||
} catch (error: any) {
|
||||
console.error("❌ 위치 추적 시작 실패:", error);
|
||||
toast.error(config.errorMessage || "위치 추적 시작 중 오류가 발생했습니다.");
|
||||
showErrorToast(config.errorMessage || "위치 추적을 시작할 수 없습니다", error, {
|
||||
guidance: "위치 권한 설정과 GPS 상태를 확인해 주세요.",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -6131,7 +6204,9 @@ export class ButtonActionExecutor {
|
|||
return true;
|
||||
} catch (error: any) {
|
||||
console.error("❌ 위치 추적 종료 실패:", error);
|
||||
toast.error(config.errorMessage || "위치 추적 종료 중 오류가 발생했습니다.");
|
||||
showErrorToast(config.errorMessage || "위치 추적 종료에 실패했습니다", error, {
|
||||
guidance: "잠시 후 다시 시도해 주세요.",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -6425,7 +6500,9 @@ export class ButtonActionExecutor {
|
|||
}
|
||||
} catch (error: any) {
|
||||
console.error("❌ 데이터 전달 실패:", error);
|
||||
toast.error(error.message || "데이터 전달 중 오류가 발생했습니다.");
|
||||
showErrorToast("데이터 전달에 실패했습니다", error, {
|
||||
guidance: "대상 화면 설정과 데이터를 확인해 주세요.",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -6555,7 +6632,9 @@ export class ButtonActionExecutor {
|
|||
toast.success(config.successMessage || "공차 등록이 완료되었습니다. 위치 추적을 시작합니다.");
|
||||
} catch (saveError) {
|
||||
console.error("❌ 위치정보 자동 저장 실패:", saveError);
|
||||
toast.error("위치 정보 저장에 실패했습니다.");
|
||||
showErrorToast("위치 정보 저장에 실패했습니다", saveError, {
|
||||
guidance: "네트워크 연결을 확인해 주세요.",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
|
|
@ -6585,10 +6664,14 @@ export class ButtonActionExecutor {
|
|||
toast.error("위치 정보 요청 시간이 초과되었습니다.\n다시 시도해주세요.");
|
||||
break;
|
||||
default:
|
||||
toast.error(config.errorMessage || "위치 정보를 가져오는 중 오류가 발생했습니다.");
|
||||
showErrorToast(config.errorMessage || "위치 정보를 가져올 수 없습니다", null, {
|
||||
guidance: "브라우저 설정에서 위치 권한을 확인해 주세요.",
|
||||
});
|
||||
}
|
||||
} else {
|
||||
toast.error(config.errorMessage || "위치 정보를 가져오는 중 오류가 발생했습니다.");
|
||||
showErrorToast(config.errorMessage || "위치 정보를 가져올 수 없습니다", error, {
|
||||
guidance: "브라우저 설정에서 위치 권한을 확인해 주세요.",
|
||||
});
|
||||
}
|
||||
|
||||
return false;
|
||||
|
|
@ -6760,7 +6843,9 @@ export class ButtonActionExecutor {
|
|||
return true;
|
||||
} catch (error) {
|
||||
console.error("❌ 필드 값 교환 오류:", error);
|
||||
toast.error(config.errorMessage || "값 교환 중 오류가 발생했습니다.");
|
||||
showErrorToast(config.errorMessage || "필드 값 교환에 실패했습니다", error, {
|
||||
guidance: "교환 대상 필드 설정을 확인해 주세요.",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
*/
|
||||
|
||||
import { toast } from "sonner";
|
||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
||||
import { apiClient } from "@/lib/api/client";
|
||||
import { ExtendedButtonTypeConfig, ButtonDataflowConfig } from "@/types/control-management";
|
||||
import { ButtonActionType } from "@/types/v2-core";
|
||||
|
|
@ -383,7 +384,9 @@ export class ImprovedButtonActionExecutor {
|
|||
if (result.success) {
|
||||
toast.success(`관계 '${config.relationshipName}' 실행 완료`);
|
||||
} else {
|
||||
toast.error(`관계 '${config.relationshipName}' 실행 실패: ${result.message}`);
|
||||
showErrorToast(`관계 '${config.relationshipName}' 실행에 실패했습니다`, result.message, {
|
||||
guidance: "관계 설정과 대상 데이터를 확인해 주세요.",
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
|
|
@ -396,7 +399,9 @@ export class ImprovedButtonActionExecutor {
|
|||
error: error.message,
|
||||
};
|
||||
|
||||
toast.error(errorResult.message);
|
||||
showErrorToast(`관계 '${config.relationshipName}' 실행에 실패했습니다`, error, {
|
||||
guidance: "관계 설정과 연결 상태를 확인해 주세요.",
|
||||
});
|
||||
return errorResult;
|
||||
}
|
||||
}
|
||||
|
|
@ -1057,7 +1062,8 @@ export class ImprovedButtonActionExecutor {
|
|||
}
|
||||
}
|
||||
|
||||
// 오류 토스트 표시
|
||||
toast.error(error.message || "작업 중 오류가 발생했습니다.");
|
||||
showErrorToast("버튼 액션 실행 중 오류가 발생했습니다", error, {
|
||||
guidance: "잠시 후 다시 시도해 주세요. 문제가 계속되면 관리자에게 문의해 주세요.",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,82 @@
|
|||
import { toast } from "sonner";
|
||||
|
||||
/**
|
||||
* 서버/catch 에러에서 사용자에게 보여줄 메시지를 추출
|
||||
*/
|
||||
function extractErrorMessage(error: unknown): string | null {
|
||||
if (!error) return null;
|
||||
|
||||
if (error instanceof Error) {
|
||||
return error.message;
|
||||
}
|
||||
|
||||
if (typeof error === "string") {
|
||||
return error;
|
||||
}
|
||||
|
||||
if (typeof error === "object" && error !== null) {
|
||||
const obj = error as Record<string, any>;
|
||||
return (
|
||||
obj.response?.data?.message ||
|
||||
obj.response?.data?.error ||
|
||||
obj.message ||
|
||||
obj.error ||
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 친절한 에러 토스트를 표시합니다.
|
||||
*
|
||||
* @param title - 어떤 작업에서 문제가 발생했는지 (예: "메뉴 저장에 실패했습니다")
|
||||
* @param error - catch 블록의 error 객체 또는 에러 메시지 문자열
|
||||
* @param options - 추가 옵션
|
||||
* @param options.guidance - 사용자에게 안내할 해결 방법 (예: "네트워크 연결을 확인해 주세요")
|
||||
* @param options.duration - 토스트 표시 시간 (ms)
|
||||
*/
|
||||
export function showErrorToast(
|
||||
title: string,
|
||||
error?: unknown,
|
||||
options?: {
|
||||
guidance?: string;
|
||||
duration?: number;
|
||||
}
|
||||
) {
|
||||
const errorMessage = extractErrorMessage(error);
|
||||
const guidance = options?.guidance;
|
||||
|
||||
const descriptionParts: string[] = [];
|
||||
if (errorMessage) descriptionParts.push(errorMessage);
|
||||
if (guidance) descriptionParts.push(guidance);
|
||||
|
||||
const description =
|
||||
descriptionParts.length > 0 ? descriptionParts.join("\n") : undefined;
|
||||
|
||||
toast.error(title, {
|
||||
description,
|
||||
duration: options?.duration || 5000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* API 응답 기반 에러 토스트
|
||||
* API 호출 실패 시 응답 메시지를 포함하여 친절하게 표시합니다.
|
||||
*/
|
||||
export function showApiErrorToast(
|
||||
action: string,
|
||||
response?: { message?: string; error?: string } | null,
|
||||
fallbackError?: unknown
|
||||
) {
|
||||
const apiMessage = response?.message || response?.error;
|
||||
const errorMessage = apiMessage || extractErrorMessage(fallbackError);
|
||||
|
||||
const description = errorMessage || "잠시 후 다시 시도해 주세요.";
|
||||
|
||||
toast.error(`${action}에 실패했습니다`, {
|
||||
description,
|
||||
duration: 5000,
|
||||
});
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ import { V2_EVENTS } from "../events/types";
|
|||
import type { ScheduleType, V2ScheduleGenerateRequestEvent, V2ScheduleGenerateApplyEvent } from "../events/types";
|
||||
import { apiClient } from "@/lib/api/client";
|
||||
import { toast } from "sonner";
|
||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
||||
|
||||
// ============================================================================
|
||||
// 타입 정의
|
||||
|
|
@ -230,7 +231,8 @@ export function useScheduleGenerator(scheduleConfig?: ScheduleGenerationConfig |
|
|||
});
|
||||
} catch (error: any) {
|
||||
console.error("[ScheduleGeneratorService] 미리보기 오류:", error);
|
||||
toast.error("스케줄 생성 중 오류가 발생했습니다.", { id: "schedule-generate" });
|
||||
toast.dismiss("schedule-generate");
|
||||
showErrorToast("스케줄 미리보기 생성에 실패했습니다", error, { guidance: "스케줄 설정을 확인하고 다시 시도해 주세요." });
|
||||
v2EventBus.emit(V2_EVENTS.SCHEDULE_GENERATE_ERROR, {
|
||||
requestId: payload.requestId,
|
||||
error: error.message,
|
||||
|
|
@ -295,7 +297,8 @@ export function useScheduleGenerator(scheduleConfig?: ScheduleGenerationConfig |
|
|||
setPreviewResult(null);
|
||||
} catch (error: any) {
|
||||
console.error("[ScheduleGeneratorService] 적용 오류:", error);
|
||||
toast.error("스케줄 적용 중 오류가 발생했습니다.", { id: "schedule-apply" });
|
||||
toast.dismiss("schedule-apply");
|
||||
showErrorToast("스케줄 적용에 실패했습니다", error, { guidance: "스케줄 설정과 데이터를 확인하고 다시 시도해 주세요." });
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue