Compare commits
No commits in common. "dfc495d32b2a8f68b6c61769e8134b42a2bfeb82" and "52c6af472de00a31b62ac1bc40050bcf7dc158c3" have entirely different histories.
dfc495d32b
...
52c6af472d
|
|
@ -1120,8 +1120,8 @@ export async function saveMenu(
|
||||||
`INSERT INTO menu_info (
|
`INSERT INTO menu_info (
|
||||||
objid, menu_type, parent_obj_id, menu_name_kor, menu_name_eng,
|
objid, menu_type, parent_obj_id, menu_name_kor, menu_name_eng,
|
||||||
seq, menu_url, menu_desc, writer, regdate, status,
|
seq, menu_url, menu_desc, writer, regdate, status,
|
||||||
system_name, company_code, lang_key, lang_key_desc, screen_code, menu_icon
|
system_name, company_code, lang_key, lang_key_desc, screen_code
|
||||||
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17)
|
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16)
|
||||||
RETURNING *`,
|
RETURNING *`,
|
||||||
[
|
[
|
||||||
objid,
|
objid,
|
||||||
|
|
@ -1140,7 +1140,6 @@ export async function saveMenu(
|
||||||
menuData.langKey || null,
|
menuData.langKey || null,
|
||||||
menuData.langKeyDesc || null,
|
menuData.langKeyDesc || null,
|
||||||
screenCode,
|
screenCode,
|
||||||
menuData.menuIcon || null,
|
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -1324,9 +1323,8 @@ export async function updateMenu(
|
||||||
company_code = $10,
|
company_code = $10,
|
||||||
lang_key = $11,
|
lang_key = $11,
|
||||||
lang_key_desc = $12,
|
lang_key_desc = $12,
|
||||||
screen_code = $13,
|
screen_code = $13
|
||||||
menu_icon = $14
|
WHERE objid = $14
|
||||||
WHERE objid = $15
|
|
||||||
RETURNING *`,
|
RETURNING *`,
|
||||||
[
|
[
|
||||||
menuData.menuType ? Number(menuData.menuType) : null,
|
menuData.menuType ? Number(menuData.menuType) : null,
|
||||||
|
|
@ -1342,7 +1340,6 @@ export async function updateMenu(
|
||||||
menuData.langKey || null,
|
menuData.langKey || null,
|
||||||
menuData.langKeyDesc || null,
|
menuData.langKeyDesc || null,
|
||||||
screenCode,
|
screenCode,
|
||||||
menuData.menuIcon || null,
|
|
||||||
Number(menuId),
|
Number(menuId),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -227,8 +227,7 @@ export class AdminService {
|
||||||
PATH,
|
PATH,
|
||||||
CYCLE,
|
CYCLE,
|
||||||
TRANSLATED_NAME,
|
TRANSLATED_NAME,
|
||||||
TRANSLATED_DESC,
|
TRANSLATED_DESC
|
||||||
MENU_ICON
|
|
||||||
) AS (
|
) AS (
|
||||||
SELECT
|
SELECT
|
||||||
1 AS LEVEL,
|
1 AS LEVEL,
|
||||||
|
|
@ -283,8 +282,7 @@ export class AdminService {
|
||||||
AND MLT.lang_code = $1
|
AND MLT.lang_code = $1
|
||||||
LIMIT 1),
|
LIMIT 1),
|
||||||
MENU.MENU_DESC
|
MENU.MENU_DESC
|
||||||
),
|
)
|
||||||
MENU.MENU_ICON
|
|
||||||
FROM MENU_INFO MENU
|
FROM MENU_INFO MENU
|
||||||
WHERE ${menuTypeCondition}
|
WHERE ${menuTypeCondition}
|
||||||
AND ${statusCondition}
|
AND ${statusCondition}
|
||||||
|
|
@ -350,8 +348,7 @@ export class AdminService {
|
||||||
AND MLT.lang_code = $1
|
AND MLT.lang_code = $1
|
||||||
LIMIT 1),
|
LIMIT 1),
|
||||||
MENU_SUB.MENU_DESC
|
MENU_SUB.MENU_DESC
|
||||||
),
|
)
|
||||||
MENU_SUB.MENU_ICON
|
|
||||||
FROM MENU_INFO MENU_SUB
|
FROM MENU_INFO MENU_SUB
|
||||||
JOIN V_MENU ON MENU_SUB.PARENT_OBJ_ID = V_MENU.OBJID
|
JOIN V_MENU ON MENU_SUB.PARENT_OBJ_ID = V_MENU.OBJID
|
||||||
WHERE MENU_SUB.OBJID != ANY(V_MENU.PATH)
|
WHERE MENU_SUB.OBJID != ANY(V_MENU.PATH)
|
||||||
|
|
@ -377,7 +374,6 @@ export class AdminService {
|
||||||
COALESCE(CM.COMPANY_NAME, '미지정') AS COMPANY_NAME,
|
COALESCE(CM.COMPANY_NAME, '미지정') AS COMPANY_NAME,
|
||||||
A.TRANSLATED_NAME,
|
A.TRANSLATED_NAME,
|
||||||
A.TRANSLATED_DESC,
|
A.TRANSLATED_DESC,
|
||||||
A.MENU_ICON,
|
|
||||||
CASE UPPER(A.STATUS)
|
CASE UPPER(A.STATUS)
|
||||||
WHEN 'ACTIVE' THEN '활성화'
|
WHEN 'ACTIVE' THEN '활성화'
|
||||||
WHEN 'INACTIVE' THEN '비활성화'
|
WHEN 'INACTIVE' THEN '비활성화'
|
||||||
|
|
@ -518,8 +514,7 @@ export class AdminService {
|
||||||
LANG_KEY,
|
LANG_KEY,
|
||||||
LANG_KEY_DESC,
|
LANG_KEY_DESC,
|
||||||
PATH,
|
PATH,
|
||||||
CYCLE,
|
CYCLE
|
||||||
MENU_ICON
|
|
||||||
) AS (
|
) AS (
|
||||||
SELECT
|
SELECT
|
||||||
1 AS LEVEL,
|
1 AS LEVEL,
|
||||||
|
|
@ -537,8 +532,7 @@ export class AdminService {
|
||||||
LANG_KEY,
|
LANG_KEY,
|
||||||
LANG_KEY_DESC,
|
LANG_KEY_DESC,
|
||||||
ARRAY [MENU.OBJID],
|
ARRAY [MENU.OBJID],
|
||||||
FALSE,
|
FALSE
|
||||||
MENU.MENU_ICON
|
|
||||||
FROM MENU_INFO MENU
|
FROM MENU_INFO MENU
|
||||||
WHERE PARENT_OBJ_ID = 0
|
WHERE PARENT_OBJ_ID = 0
|
||||||
AND MENU_TYPE = 1
|
AND MENU_TYPE = 1
|
||||||
|
|
@ -564,8 +558,7 @@ export class AdminService {
|
||||||
MENU_SUB.LANG_KEY,
|
MENU_SUB.LANG_KEY,
|
||||||
MENU_SUB.LANG_KEY_DESC,
|
MENU_SUB.LANG_KEY_DESC,
|
||||||
PATH || MENU_SUB.SEQ::numeric,
|
PATH || MENU_SUB.SEQ::numeric,
|
||||||
MENU_SUB.OBJID = ANY(PATH),
|
MENU_SUB.OBJID = ANY(PATH)
|
||||||
MENU_SUB.MENU_ICON
|
|
||||||
FROM MENU_INFO MENU_SUB
|
FROM MENU_INFO MENU_SUB
|
||||||
JOIN V_MENU ON MENU_SUB.PARENT_OBJ_ID = V_MENU.OBJID
|
JOIN V_MENU ON MENU_SUB.PARENT_OBJ_ID = V_MENU.OBJID
|
||||||
WHERE MENU_SUB.STATUS = 'active'
|
WHERE MENU_SUB.STATUS = 'active'
|
||||||
|
|
@ -591,9 +584,10 @@ export class AdminService {
|
||||||
A.COMPANY_CODE,
|
A.COMPANY_CODE,
|
||||||
A.LANG_KEY,
|
A.LANG_KEY,
|
||||||
A.LANG_KEY_DESC,
|
A.LANG_KEY_DESC,
|
||||||
A.MENU_ICON,
|
|
||||||
COALESCE(CM.COMPANY_NAME, '미지정') AS COMPANY_NAME,
|
COALESCE(CM.COMPANY_NAME, '미지정') AS COMPANY_NAME,
|
||||||
|
-- 번역된 메뉴명 (우선순위: 번역 > 기본명)
|
||||||
COALESCE(MLT_NAME.lang_text, A.MENU_NAME_KOR) AS TRANSLATED_NAME,
|
COALESCE(MLT_NAME.lang_text, A.MENU_NAME_KOR) AS TRANSLATED_NAME,
|
||||||
|
-- 번역된 설명 (우선순위: 번역 > 기본명)
|
||||||
COALESCE(MLT_DESC.lang_text, A.MENU_DESC) AS TRANSLATED_DESC,
|
COALESCE(MLT_DESC.lang_text, A.MENU_DESC) AS TRANSLATED_DESC,
|
||||||
CASE UPPER(A.STATUS)
|
CASE UPPER(A.STATUS)
|
||||||
WHEN 'ACTIVE' THEN '활성화'
|
WHEN 'ACTIVE' THEN '활성화'
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { ArrowLeft, Save, RefreshCw, ArrowRight, Trash2 } from "lucide-react";
|
import { ArrowLeft, Save, RefreshCw, ArrowRight, Trash2 } from "lucide-react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import {
|
import {
|
||||||
BatchAPI,
|
BatchAPI,
|
||||||
|
|
@ -134,7 +133,7 @@ export default function BatchCreatePage() {
|
||||||
setFromColumns(Array.isArray(columns) ? columns : []);
|
setFromColumns(Array.isArray(columns) ? columns : []);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("FROM 컬럼 목록 로드 실패:", error);
|
console.error("FROM 컬럼 목록 로드 실패:", error);
|
||||||
showErrorToast("컬럼 목록을 불러오는 데 실패했습니다", error, { guidance: "테이블 정보를 확인해 주세요." });
|
toast.error("컬럼 목록을 불러오는데 실패했습니다.");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -243,7 +242,7 @@ export default function BatchCreatePage() {
|
||||||
router.push("/admin/batchmng");
|
router.push("/admin/batchmng");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("배치 설정 저장 실패:", error);
|
console.error("배치 설정 저장 실패:", error);
|
||||||
showErrorToast("배치 설정 저장에 실패했습니다", error, { guidance: "입력 데이터를 확인하고 다시 시도해 주세요." });
|
toast.error("배치 설정 저장에 실패했습니다.");
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ import {
|
||||||
Database
|
Database
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import {
|
import {
|
||||||
BatchAPI,
|
BatchAPI,
|
||||||
|
|
@ -76,9 +75,7 @@ export default function BatchManagementPage() {
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("배치 실행 실패:", error);
|
console.error("배치 실행 실패:", error);
|
||||||
showErrorToast("배치 실행에 실패했습니다", error, {
|
toast.error("배치 실행 중 오류가 발생했습니다.");
|
||||||
guidance: "배치 설정을 확인하고 다시 시도해 주세요.",
|
|
||||||
});
|
|
||||||
} finally {
|
} finally {
|
||||||
setExecutingBatch(null);
|
setExecutingBatch(null);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ import { Badge } from "@/components/ui/badge";
|
||||||
import { Plus, Search, Edit, Trash2, TestTube, Filter } from "lucide-react";
|
import { Plus, Search, Edit, Trash2, TestTube, Filter } from "lucide-react";
|
||||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
|
||||||
import {
|
import {
|
||||||
ExternalCallConfigAPI,
|
ExternalCallConfigAPI,
|
||||||
ExternalCallConfig,
|
ExternalCallConfig,
|
||||||
|
|
@ -58,15 +57,11 @@ export default function ExternalCallConfigsPage() {
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
setConfigs(response.data || []);
|
setConfigs(response.data || []);
|
||||||
} else {
|
} else {
|
||||||
showErrorToast("외부 호출 설정 조회에 실패했습니다", response.message, {
|
toast.error(response.message || "외부 호출 설정 조회 실패");
|
||||||
guidance: "네트워크 연결을 확인하고 다시 시도해 주세요.",
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("외부 호출 설정 조회 오류:", error);
|
console.error("외부 호출 설정 조회 오류:", error);
|
||||||
showErrorToast("외부 호출 설정 조회에 실패했습니다", error, {
|
toast.error("외부 호출 설정 조회 중 오류가 발생했습니다.");
|
||||||
guidance: "네트워크 연결을 확인하고 다시 시도해 주세요.",
|
|
||||||
});
|
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|
@ -118,15 +113,11 @@ export default function ExternalCallConfigsPage() {
|
||||||
toast.success("외부 호출 설정이 삭제되었습니다.");
|
toast.success("외부 호출 설정이 삭제되었습니다.");
|
||||||
fetchConfigs();
|
fetchConfigs();
|
||||||
} else {
|
} else {
|
||||||
showErrorToast("외부 호출 설정 삭제에 실패했습니다", response.message, {
|
toast.error(response.message || "외부 호출 설정 삭제 실패");
|
||||||
guidance: "잠시 후 다시 시도해 주세요.",
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("외부 호출 설정 삭제 오류:", error);
|
console.error("외부 호출 설정 삭제 오류:", error);
|
||||||
showErrorToast("외부 호출 설정 삭제에 실패했습니다", error, {
|
toast.error("외부 호출 설정 삭제 중 오류가 발생했습니다.");
|
||||||
guidance: "잠시 후 다시 시도해 주세요.",
|
|
||||||
});
|
|
||||||
} finally {
|
} finally {
|
||||||
setDeleteDialogOpen(false);
|
setDeleteDialogOpen(false);
|
||||||
setConfigToDelete(null);
|
setConfigToDelete(null);
|
||||||
|
|
@ -147,9 +138,7 @@ export default function ExternalCallConfigsPage() {
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("외부 호출 설정 테스트 오류:", error);
|
console.error("외부 호출 설정 테스트 오류:", error);
|
||||||
showErrorToast("외부 호출 테스트 실행에 실패했습니다", error, {
|
toast.error("외부 호출 설정 테스트 중 오류가 발생했습니다.");
|
||||||
guidance: "URL과 설정을 확인해 주세요.",
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ import { Textarea } from "@/components/ui/textarea";
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||||
import { Trash2, Plus, ArrowLeft, Save, RefreshCw, Globe, Database, Eye } from "lucide-react";
|
import { Trash2, Plus, ArrowLeft, Save, RefreshCw, Globe, Database, Eye } from "lucide-react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
|
||||||
import { BatchManagementAPI } from "@/lib/api/batchManagement";
|
import { BatchManagementAPI } from "@/lib/api/batchManagement";
|
||||||
|
|
||||||
// 타입 정의
|
// 타입 정의
|
||||||
|
|
@ -470,9 +469,7 @@ export default function BatchManagementNewPage() {
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("배치 저장 오류:", error);
|
console.error("배치 저장 오류:", error);
|
||||||
showErrorToast("배치 설정 저장에 실패했습니다", error, {
|
toast.error("배치 저장 중 오류가 발생했습니다.");
|
||||||
guidance: "입력 데이터를 확인하고 다시 시도해 주세요.",
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
} else if (batchType === "db-to-restapi") {
|
} else if (batchType === "db-to-restapi") {
|
||||||
|
|
@ -561,9 +558,7 @@ export default function BatchManagementNewPage() {
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("배치 저장 오류:", error);
|
console.error("배치 저장 오류:", error);
|
||||||
showErrorToast("배치 설정 저장에 실패했습니다", error, {
|
toast.error("배치 저장 중 오류가 발생했습니다.");
|
||||||
guidance: "입력 데이터를 확인하고 다시 시도해 주세요.",
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,7 @@ export default function BatchManagementPage() {
|
||||||
setJobs(data);
|
setJobs(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("배치 작업 목록 조회 오류:", error);
|
console.error("배치 작업 목록 조회 오류:", error);
|
||||||
showErrorToast("배치 작업 목록을 불러오는 데 실패했습니다", error, { guidance: "네트워크 연결을 확인해 주세요." });
|
toast.error("배치 작업 목록을 불러오는데 실패했습니다.");
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
|
|
@ -150,7 +150,7 @@ export default function BatchManagementPage() {
|
||||||
loadJobs();
|
loadJobs();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("배치 작업 삭제 오류:", error);
|
console.error("배치 작업 삭제 오류:", error);
|
||||||
showErrorToast("배치 작업 삭제에 실패했습니다", error, { guidance: "잠시 후 다시 시도해 주세요." });
|
toast.error("배치 작업 삭제에 실패했습니다.");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -160,7 +160,7 @@ export default function BatchManagementPage() {
|
||||||
toast.success(`"${job.job_name}" 배치 작업을 실행했습니다.`);
|
toast.success(`"${job.job_name}" 배치 작업을 실행했습니다.`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("배치 작업 실행 오류:", error);
|
console.error("배치 작업 실행 오류:", error);
|
||||||
showErrorToast("배치 작업 실행에 실패했습니다", error, { guidance: "배치 설정을 확인해 주세요." });
|
toast.error("배치 작업 실행에 실패했습니다.");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,6 @@ import {
|
||||||
GripVertical,
|
GripVertical,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { cascadingAutoFillApi, AutoFillGroup, AutoFillMapping } from "@/lib/api/cascadingAutoFill";
|
import { cascadingAutoFillApi, AutoFillGroup, AutoFillMapping } from "@/lib/api/cascadingAutoFill";
|
||||||
import { tableManagementApi } from "@/lib/api/tableManagement";
|
import { tableManagementApi } from "@/lib/api/tableManagement";
|
||||||
|
|
@ -98,7 +97,7 @@ export default function AutoFillTab() {
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("그룹 목록 로드 실패:", error);
|
console.error("그룹 목록 로드 실패:", error);
|
||||||
showErrorToast("그룹 목록을 불러오는 데 실패했습니다", error, { guidance: "네트워크 연결을 확인해 주세요." });
|
toast.error("그룹 목록을 불러오는데 실패했습니다.");
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|
@ -270,7 +269,7 @@ export default function AutoFillTab() {
|
||||||
toast.error(response.error || "저장에 실패했습니다.");
|
toast.error(response.error || "저장에 실패했습니다.");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showErrorToast("자동입력 설정 저장에 실패했습니다", error, { guidance: "입력 데이터를 확인하고 다시 시도해 주세요." });
|
toast.error("저장 중 오류가 발생했습니다.");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,6 @@ import {
|
||||||
Loader2,
|
Loader2,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { cascadingRelationApi, CascadingRelation, CascadingRelationCreateInput } from "@/lib/api/cascadingRelation";
|
import { cascadingRelationApi, CascadingRelation, CascadingRelationCreateInput } from "@/lib/api/cascadingRelation";
|
||||||
import { tableManagementApi } from "@/lib/api/tableManagement";
|
import { tableManagementApi } from "@/lib/api/tableManagement";
|
||||||
|
|
@ -103,7 +102,7 @@ export default function CascadingRelationsTab() {
|
||||||
setRelations(response.data);
|
setRelations(response.data);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showErrorToast("연쇄 관계 목록을 불러오는 데 실패했습니다", error, { guidance: "네트워크 연결을 확인해 주세요." });
|
toast.error("연쇄 관계 목록 조회에 실패했습니다.");
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|
@ -432,7 +431,7 @@ export default function CascadingRelationsTab() {
|
||||||
toast.error(response.message || "저장에 실패했습니다.");
|
toast.error(response.message || "저장에 실패했습니다.");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showErrorToast("연쇄 관계 저장에 실패했습니다", error, { guidance: "입력 데이터를 확인하고 다시 시도해 주세요." });
|
toast.error("저장 중 오류가 발생했습니다.");
|
||||||
} finally {
|
} finally {
|
||||||
setSaving(false);
|
setSaving(false);
|
||||||
}
|
}
|
||||||
|
|
@ -453,7 +452,7 @@ export default function CascadingRelationsTab() {
|
||||||
toast.error(response.message || "삭제에 실패했습니다.");
|
toast.error(response.message || "삭제에 실패했습니다.");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showErrorToast("연쇄 관계 삭제에 실패했습니다", error, { guidance: "잠시 후 다시 시도해 주세요." });
|
toast.error("삭제 중 오류가 발생했습니다.");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,6 @@ import {
|
||||||
TableRow,
|
TableRow,
|
||||||
} from "@/components/ui/table";
|
} from "@/components/ui/table";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
|
||||||
import {
|
import {
|
||||||
cascadingConditionApi,
|
cascadingConditionApi,
|
||||||
CascadingCondition,
|
CascadingCondition,
|
||||||
|
|
@ -171,7 +170,7 @@ export default function ConditionTab() {
|
||||||
toast.error(response.error || "삭제에 실패했습니다.");
|
toast.error(response.error || "삭제에 실패했습니다.");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showErrorToast("조건 삭제에 실패했습니다", error, { guidance: "잠시 후 다시 시도해 주세요." });
|
toast.error("삭제 중 오류가 발생했습니다.");
|
||||||
} finally {
|
} finally {
|
||||||
setIsDeleteDialogOpen(false);
|
setIsDeleteDialogOpen(false);
|
||||||
setDeletingConditionId(null);
|
setDeletingConditionId(null);
|
||||||
|
|
@ -207,7 +206,7 @@ export default function ConditionTab() {
|
||||||
toast.error(response.error || "저장에 실패했습니다.");
|
toast.error(response.error || "저장에 실패했습니다.");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showErrorToast("조건 저장에 실패했습니다", error, { guidance: "입력 데이터를 확인하고 다시 시도해 주세요." });
|
toast.error("저장 중 오류가 발생했습니다.");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,6 @@ import {
|
||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
import { Plus, Pencil, Trash2, Database, RefreshCw, Layers } from "lucide-react";
|
import { Plus, Pencil, Trash2, Database, RefreshCw, Layers } from "lucide-react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
|
||||||
import { LoadingSpinner } from "@/components/common/LoadingSpinner";
|
import { LoadingSpinner } from "@/components/common/LoadingSpinner";
|
||||||
import {
|
import {
|
||||||
hierarchyColumnApi,
|
hierarchyColumnApi,
|
||||||
|
|
@ -301,7 +300,7 @@ export default function HierarchyColumnTab() {
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("저장 에러:", error);
|
console.error("저장 에러:", error);
|
||||||
showErrorToast("계층구조 설정 저장에 실패했습니다", error, { guidance: "입력 데이터를 확인하고 다시 시도해 주세요." });
|
toast.error("저장 중 오류가 발생했습니다.");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -320,7 +319,7 @@ export default function HierarchyColumnTab() {
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("삭제 에러:", error);
|
console.error("삭제 에러:", error);
|
||||||
showErrorToast("계층구조 설정 삭제에 실패했습니다", error, { guidance: "잠시 후 다시 시도해 주세요." });
|
toast.error("삭제 중 오류가 발생했습니다.");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/com
|
||||||
import { Switch } from "@/components/ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
|
||||||
import { ArrowLeft, Save, RotateCcw, Eye } from "lucide-react";
|
import { ArrowLeft, Save, RotateCcw, Eye } from "lucide-react";
|
||||||
import { useWebTypes, type WebTypeFormData } from "@/hooks/admin/useWebTypes";
|
import { useWebTypes, type WebTypeFormData } from "@/hooks/admin/useWebTypes";
|
||||||
import { AVAILABLE_COMPONENTS, getComponentInfo } from "@/lib/utils/availableComponents";
|
import { AVAILABLE_COMPONENTS, getComponentInfo } from "@/lib/utils/availableComponents";
|
||||||
|
|
@ -149,7 +148,7 @@ export default function EditWebTypePage() {
|
||||||
toast.success("웹타입이 성공적으로 수정되었습니다.");
|
toast.success("웹타입이 성공적으로 수정되었습니다.");
|
||||||
router.push(`/admin/standards/${webType}`);
|
router.push(`/admin/standards/${webType}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showErrorToast("웹타입 수정에 실패했습니다", error, { guidance: "입력 데이터를 확인하고 다시 시도해 주세요." });
|
toast.error(error instanceof Error ? error.message : "수정 중 오류가 발생했습니다.");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ import {
|
||||||
AlertDialogTrigger,
|
AlertDialogTrigger,
|
||||||
} from "@/components/ui/alert-dialog";
|
} from "@/components/ui/alert-dialog";
|
||||||
import { toast } from "sonner";
|
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 { Plus, Search, Edit, Trash2, Eye, Filter, RotateCcw, Settings, SortAsc, SortDesc } from "lucide-react";
|
||||||
import { useWebTypes } from "@/hooks/admin/useWebTypes";
|
import { useWebTypes } from "@/hooks/admin/useWebTypes";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
|
@ -91,7 +90,7 @@ export default function WebTypesManagePage() {
|
||||||
await deleteWebType(webType);
|
await deleteWebType(webType);
|
||||||
toast.success(`웹타입 '${typeName}'이 삭제되었습니다.`);
|
toast.success(`웹타입 '${typeName}'이 삭제되었습니다.`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showErrorToast("웹타입 삭제에 실패했습니다", error, { guidance: "잠시 후 다시 시도해 주세요." });
|
toast.error(error instanceof Error ? error.message : "삭제 중 오류가 발생했습니다.");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,6 @@ import {
|
||||||
RefreshCw
|
RefreshCw
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
|
||||||
import { CollectionAPI, DataCollectionConfig } from "@/lib/api/collection";
|
import { CollectionAPI, DataCollectionConfig } from "@/lib/api/collection";
|
||||||
import CollectionConfigModal from "@/components/admin/CollectionConfigModal";
|
import CollectionConfigModal from "@/components/admin/CollectionConfigModal";
|
||||||
|
|
||||||
|
|
@ -70,7 +69,7 @@ export default function CollectionManagementPage() {
|
||||||
setConfigs(data);
|
setConfigs(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("수집 설정 목록 조회 오류:", error);
|
console.error("수집 설정 목록 조회 오류:", error);
|
||||||
showErrorToast("수집 설정 목록을 불러오는 데 실패했습니다", error, { guidance: "네트워크 연결을 확인해 주세요." });
|
toast.error("수집 설정 목록을 불러오는데 실패했습니다.");
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
|
|
@ -132,7 +131,7 @@ export default function CollectionManagementPage() {
|
||||||
toast.success(`"${config.config_name}" 수집 작업을 시작했습니다.`);
|
toast.success(`"${config.config_name}" 수집 작업을 시작했습니다.`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("수집 작업 실행 오류:", error);
|
console.error("수집 작업 실행 오류:", error);
|
||||||
showErrorToast("수집 작업 실행에 실패했습니다", error, { guidance: "수집 설정을 확인해 주세요." });
|
toast.error("수집 작업 실행에 실패했습니다.");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ import DataFlowList from "@/components/dataflow/DataFlowList";
|
||||||
import { FlowEditor } from "@/components/dataflow/node-editor/FlowEditor";
|
import { FlowEditor } from "@/components/dataflow/node-editor/FlowEditor";
|
||||||
import { useAuth } from "@/hooks/useAuth";
|
import { useAuth } from "@/hooks/useAuth";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { ScrollToTop } from "@/components/common/ScrollToTop";
|
import { ScrollToTop } from "@/components/common/ScrollToTop";
|
||||||
import { ArrowLeft } from "lucide-react";
|
import { ArrowLeft } from "lucide-react";
|
||||||
|
|
@ -36,7 +35,7 @@ export default function DataFlowPage() {
|
||||||
toast.success("플로우를 불러왔습니다.");
|
toast.success("플로우를 불러왔습니다.");
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error("❌ 플로우 불러오기 실패:", error);
|
console.error("❌ 플로우 불러오기 실패:", error);
|
||||||
showErrorToast("플로우 목록을 불러오는 데 실패했습니다", error, { guidance: "네트워크 연결을 확인해 주세요." });
|
toast.error(error.message || "플로우를 불러오는데 실패했습니다.");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,6 @@ import {
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { LoadingSpinner } from "@/components/common/LoadingSpinner";
|
import { LoadingSpinner } from "@/components/common/LoadingSpinner";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
|
||||||
import { useMultiLang } from "@/hooks/useMultiLang";
|
import { useMultiLang } from "@/hooks/useMultiLang";
|
||||||
import { useAuth } from "@/hooks/useAuth";
|
import { useAuth } from "@/hooks/useAuth";
|
||||||
import { TABLE_MANAGEMENT_KEYS } from "@/constants/tableManagement";
|
import { TABLE_MANAGEMENT_KEYS } from "@/constants/tableManagement";
|
||||||
|
|
@ -332,15 +331,11 @@ export default function TableManagementPage() {
|
||||||
setTables(response.data.data);
|
setTables(response.data.data);
|
||||||
toast.success("테이블 목록을 성공적으로 로드했습니다.");
|
toast.success("테이블 목록을 성공적으로 로드했습니다.");
|
||||||
} else {
|
} else {
|
||||||
showErrorToast("테이블 목록을 불러오는 데 실패했습니다", response.data.message, {
|
toast.error(response.data.message || "테이블 목록 로드에 실패했습니다.");
|
||||||
guidance: "네트워크 연결을 확인해 주세요.",
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// console.error("테이블 목록 로드 실패:", error);
|
// console.error("테이블 목록 로드 실패:", error);
|
||||||
showErrorToast("테이블 목록을 불러오는 데 실패했습니다", error, {
|
toast.error("테이블 목록 로드 중 오류가 발생했습니다.");
|
||||||
guidance: "네트워크 연결을 확인해 주세요.",
|
|
||||||
});
|
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|
@ -408,15 +403,11 @@ export default function TableManagementPage() {
|
||||||
setTotalColumns(data.total || processedColumns.length);
|
setTotalColumns(data.total || processedColumns.length);
|
||||||
toast.success("컬럼 정보를 성공적으로 로드했습니다.");
|
toast.success("컬럼 정보를 성공적으로 로드했습니다.");
|
||||||
} else {
|
} else {
|
||||||
showErrorToast("컬럼 정보를 불러오는 데 실패했습니다", response.data.message, {
|
toast.error(response.data.message || "컬럼 정보 로드에 실패했습니다.");
|
||||||
guidance: "네트워크 연결을 확인해 주세요.",
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// console.error("컬럼 타입 정보 로드 실패:", error);
|
// console.error("컬럼 타입 정보 로드 실패:", error);
|
||||||
showErrorToast("컬럼 정보를 불러오는 데 실패했습니다", error, {
|
toast.error("컬럼 정보 로드 중 오류가 발생했습니다.");
|
||||||
guidance: "네트워크 연결을 확인해 주세요.",
|
|
||||||
});
|
|
||||||
} finally {
|
} finally {
|
||||||
setColumnsLoading(false);
|
setColumnsLoading(false);
|
||||||
}
|
}
|
||||||
|
|
@ -786,15 +777,11 @@ export default function TableManagementPage() {
|
||||||
loadColumnTypes(selectedTable);
|
loadColumnTypes(selectedTable);
|
||||||
}, 1000);
|
}, 1000);
|
||||||
} else {
|
} else {
|
||||||
showErrorToast("컬럼 설정 저장에 실패했습니다", response.data.message, {
|
toast.error(response.data.message || "컬럼 설정 저장에 실패했습니다.");
|
||||||
guidance: "입력 데이터를 확인하고 다시 시도해 주세요.",
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// console.error("컬럼 설정 저장 실패:", error);
|
// console.error("컬럼 설정 저장 실패:", error);
|
||||||
showErrorToast("컬럼 설정 저장에 실패했습니다", error, {
|
toast.error("컬럼 설정 저장 중 오류가 발생했습니다.");
|
||||||
guidance: "입력 데이터를 확인하고 다시 시도해 주세요.",
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -993,16 +980,12 @@ export default function TableManagementPage() {
|
||||||
loadColumnTypes(selectedTable, 1, pageSize);
|
loadColumnTypes(selectedTable, 1, pageSize);
|
||||||
}, 1000);
|
}, 1000);
|
||||||
} else {
|
} else {
|
||||||
showErrorToast("설정 저장에 실패했습니다", response.data.message, {
|
toast.error(response.data.message || "설정 저장에 실패했습니다.");
|
||||||
guidance: "잠시 후 다시 시도해 주세요.",
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// console.error("설정 저장 실패:", error);
|
// console.error("설정 저장 실패:", error);
|
||||||
showErrorToast("설정 저장에 실패했습니다", error, {
|
toast.error("설정 저장 중 오류가 발생했습니다.");
|
||||||
guidance: "잠시 후 다시 시도해 주세요.",
|
|
||||||
});
|
|
||||||
} finally {
|
} finally {
|
||||||
setIsSaving(false);
|
setIsSaving(false);
|
||||||
}
|
}
|
||||||
|
|
@ -1108,9 +1091,7 @@ export default function TableManagementPage() {
|
||||||
toast.error(response.data.message || "PK 설정 실패");
|
toast.error(response.data.message || "PK 설정 실패");
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
showErrorToast("PK 설정에 실패했습니다", error, {
|
toast.error(error?.response?.data?.message || "PK 설정 중 오류가 발생했습니다.");
|
||||||
guidance: "컬럼 정보를 확인하고 다시 시도해 주세요.",
|
|
||||||
});
|
|
||||||
} finally {
|
} finally {
|
||||||
setPkDialogOpen(false);
|
setPkDialogOpen(false);
|
||||||
}
|
}
|
||||||
|
|
@ -1134,9 +1115,7 @@ export default function TableManagementPage() {
|
||||||
toast.error(response.data.message || "인덱스 설정 실패");
|
toast.error(response.data.message || "인덱스 설정 실패");
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
showErrorToast("인덱스 설정에 실패했습니다", error, {
|
toast.error(error?.response?.data?.message || error?.response?.data?.error || "인덱스 설정 중 오류가 발생했습니다.");
|
||||||
guidance: "컬럼 정보를 확인하고 다시 시도해 주세요.",
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[selectedTable, loadConstraints],
|
[selectedTable, loadConstraints],
|
||||||
|
|
@ -1175,14 +1154,10 @@ export default function TableManagementPage() {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
showErrorToast("UNIQUE 제약 조건 설정에 실패했습니다", response.data.message, {
|
toast.error(response.data.message || "UNIQUE 설정 실패");
|
||||||
guidance: "해당 컬럼에 중복 데이터가 없는지 확인해 주세요.",
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
showErrorToast("UNIQUE 제약 조건 설정에 실패했습니다", error, {
|
toast.error(error?.response?.data?.message || "UNIQUE 설정 중 오류가 발생했습니다.");
|
||||||
guidance: "해당 컬럼에 중복 데이터가 없는지 확인해 주세요.",
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[selectedTable],
|
[selectedTable],
|
||||||
|
|
@ -1213,14 +1188,12 @@ export default function TableManagementPage() {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
showErrorToast("NOT NULL 제약 조건 설정에 실패했습니다", response.data.message, {
|
toast.error(response.data.message || "NOT NULL 설정 실패");
|
||||||
guidance: "해당 컬럼에 NULL 값이 없는지 확인해 주세요.",
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
showErrorToast("NOT NULL 제약 조건 설정에 실패했습니다", error, {
|
toast.error(
|
||||||
guidance: "해당 컬럼에 NULL 값이 없는지 확인해 주세요.",
|
error?.response?.data?.message || "NOT NULL 설정 중 오류가 발생했습니다.",
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[selectedTable],
|
[selectedTable],
|
||||||
|
|
@ -1252,14 +1225,10 @@ export default function TableManagementPage() {
|
||||||
// 테이블 목록 새로고침
|
// 테이블 목록 새로고침
|
||||||
await loadTables();
|
await loadTables();
|
||||||
} else {
|
} else {
|
||||||
showErrorToast("테이블 삭제에 실패했습니다", result.message, {
|
toast.error(result.message || "테이블 삭제에 실패했습니다.");
|
||||||
guidance: "테이블에 종속된 데이터가 있는지 확인해 주세요.",
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
showErrorToast("테이블 삭제에 실패했습니다", error, {
|
toast.error(error?.response?.data?.message || "테이블 삭제 중 오류가 발생했습니다.");
|
||||||
guidance: "테이블에 종속된 데이터가 있는지 확인해 주세요.",
|
|
||||||
});
|
|
||||||
} finally {
|
} finally {
|
||||||
setIsDeleting(false);
|
setIsDeleting(false);
|
||||||
setDeleteDialogOpen(false);
|
setDeleteDialogOpen(false);
|
||||||
|
|
@ -1339,9 +1308,7 @@ export default function TableManagementPage() {
|
||||||
setSelectedTableIds(new Set());
|
setSelectedTableIds(new Set());
|
||||||
await loadTables();
|
await loadTables();
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
showErrorToast("테이블 삭제에 실패했습니다", error, {
|
toast.error("테이블 삭제 중 오류가 발생했습니다.");
|
||||||
guidance: "테이블에 종속된 데이터가 있는지 확인해 주세요.",
|
|
||||||
});
|
|
||||||
} finally {
|
} finally {
|
||||||
setIsDeleting(false);
|
setIsDeleting(false);
|
||||||
setDeleteDialogOpen(false);
|
setDeleteDialogOpen(false);
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ import { ScreenDefinition, LayoutData, ComponentData } from "@/types/screen";
|
||||||
import { LayerDefinition } from "@/types/screen-management";
|
import { LayerDefinition } from "@/types/screen-management";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
|
||||||
import { initializeComponents } from "@/lib/registry/components";
|
import { initializeComponents } from "@/lib/registry/components";
|
||||||
import { EditModal } from "@/components/screen/EditModal";
|
import { EditModal } from "@/components/screen/EditModal";
|
||||||
import { RealtimePreview } from "@/components/screen/RealtimePreviewDynamic";
|
import { RealtimePreview } from "@/components/screen/RealtimePreviewDynamic";
|
||||||
|
|
@ -230,7 +229,7 @@ function ScreenViewPage({ screenIdProp, menuObjidProp }: ScreenViewPageProps = {
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("화면 로드 실패:", error);
|
console.error("화면 로드 실패:", error);
|
||||||
setError("화면을 불러오는데 실패했습니다.");
|
setError("화면을 불러오는데 실패했습니다.");
|
||||||
showErrorToast("화면을 불러오는 데 실패했습니다", error, { guidance: "화면 설정을 확인하거나 잠시 후 다시 시도해 주세요." });
|
toast.error("화면을 불러오는데 실패했습니다.");
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ import { screenApi } from "@/lib/api/screen";
|
||||||
import { ScreenDefinition } from "@/types/screen";
|
import { ScreenDefinition } from "@/types/screen";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
|
||||||
import { ScreenPreviewProvider } from "@/contexts/ScreenPreviewContext";
|
import { ScreenPreviewProvider } from "@/contexts/ScreenPreviewContext";
|
||||||
import { useAuth } from "@/hooks/useAuth";
|
import { useAuth } from "@/hooks/useAuth";
|
||||||
import { TableOptionsProvider } from "@/contexts/TableOptionsContext";
|
import { TableOptionsProvider } from "@/contexts/TableOptionsContext";
|
||||||
|
|
@ -136,7 +135,7 @@ function PopScreenViewPage() {
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("[POP] 화면 로드 실패:", error);
|
console.error("[POP] 화면 로드 실패:", error);
|
||||||
setError("화면을 불러오는데 실패했습니다.");
|
setError("화면을 불러오는데 실패했습니다.");
|
||||||
showErrorToast("POP 화면을 불러오는 데 실패했습니다", error, { guidance: "화면 설정을 확인하거나 잠시 후 다시 시도해 주세요." });
|
toast.error("화면을 불러오는데 실패했습니다.");
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ import { downloadFile } from "@/lib/api/file";
|
||||||
import { FileViewerModal } from "@/lib/registry/components/file-upload/FileViewerModal";
|
import { FileViewerModal } from "@/lib/registry/components/file-upload/FileViewerModal";
|
||||||
import { formatFileSize } from "@/lib/utils";
|
import { formatFileSize } from "@/lib/utils";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
|
||||||
import {
|
import {
|
||||||
File,
|
File,
|
||||||
FileText,
|
FileText,
|
||||||
|
|
@ -135,7 +134,7 @@ export const GlobalFileViewer: React.FC<GlobalFileViewerProps> = ({
|
||||||
toast.success(`파일 다운로드 시작: ${file.realFileName}`);
|
toast.success(`파일 다운로드 시작: ${file.realFileName}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("파일 다운로드 오류:", error);
|
console.error("파일 다운로드 오류:", error);
|
||||||
showErrorToast("파일 다운로드에 실패했습니다", error, { guidance: "파일이 존재하는지 확인하고 다시 시도해 주세요." });
|
toast.error("파일 다운로드에 실패했습니다.");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,6 @@ import {
|
||||||
import { Switch } from "@/components/ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
|
||||||
import { BatchAPI, BatchJob, BatchConfig } from "@/lib/api/batch";
|
import { BatchAPI, BatchJob, BatchConfig } from "@/lib/api/batch";
|
||||||
import { ExternalDbConnectionAPI } from "@/lib/api/externalDbConnection";
|
import { ExternalDbConnectionAPI } from "@/lib/api/externalDbConnection";
|
||||||
|
|
||||||
|
|
@ -124,7 +123,7 @@ export default function AdvancedBatchModal({
|
||||||
setConnections(list);
|
setConnections(list);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("연결 목록 조회 오류:", error);
|
console.error("연결 목록 조회 오류:", error);
|
||||||
showErrorToast("연결 목록을 불러오는 데 실패했습니다", error, { guidance: "네트워크 연결을 확인해 주세요." });
|
toast.error("연결 목록을 불러오는데 실패했습니다.");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -191,7 +190,7 @@ export default function AdvancedBatchModal({
|
||||||
onClose();
|
onClose();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("배치 저장 오류:", error);
|
console.error("배치 저장 오류:", error);
|
||||||
showErrorToast("배치 설정 저장에 실패했습니다", error, { guidance: "입력 데이터를 확인하고 다시 시도해 주세요." });
|
toast.error(error instanceof Error ? error.message : "저장에 실패했습니다.");
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,6 @@ import {
|
||||||
Trash2,
|
Trash2,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
|
||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
import { ko } from "date-fns/locale";
|
import { ko } from "date-fns/locale";
|
||||||
import { ddlApi } from "../../lib/api/ddl";
|
import { ddlApi } from "../../lib/api/ddl";
|
||||||
|
|
@ -72,7 +71,7 @@ export function DDLLogViewer({ isOpen, onClose }: DDLLogViewerProps) {
|
||||||
setStatistics(statsResult);
|
setStatistics(statsResult);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// console.error("DDL 로그 로드 실패:", error);
|
// console.error("DDL 로그 로드 실패:", error);
|
||||||
showErrorToast("DDL 로그를 불러오는 데 실패했습니다", error, { guidance: "네트워크 연결을 확인해 주세요." });
|
toast.error("DDL 로그를 불러오는데 실패했습니다.");
|
||||||
} finally {
|
} finally {
|
||||||
if (showLoading) setLoading(false);
|
if (showLoading) setLoading(false);
|
||||||
setRefreshing(false);
|
setRefreshing(false);
|
||||||
|
|
@ -109,7 +108,7 @@ export function DDLLogViewer({ isOpen, onClose }: DDLLogViewerProps) {
|
||||||
loadData(false);
|
loadData(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// console.error("로그 정리 실패:", error);
|
// console.error("로그 정리 실패:", error);
|
||||||
showErrorToast("로그 정리에 실패했습니다", error, { guidance: "잠시 후 다시 시도해 주세요." });
|
toast.error("로그 정리에 실패했습니다.");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,6 @@ import {
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
|
||||||
import {
|
import {
|
||||||
ExternalCallConfigAPI,
|
ExternalCallConfigAPI,
|
||||||
ExternalCallConfig,
|
ExternalCallConfig,
|
||||||
|
|
@ -260,7 +259,7 @@ export function ExternalCallConfigModal({ isOpen, onClose, onSave, editingConfig
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("외부 호출 설정 저장 오류:", error);
|
console.error("외부 호출 설정 저장 오류:", error);
|
||||||
showErrorToast("외부 호출 설정 저장에 실패했습니다", error, { guidance: "입력 데이터를 확인하고 다시 시도해 주세요." });
|
toast.error("저장 중 오류가 발생했습니다.");
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,6 @@ import {
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { LayoutCategory } from "@/types/layout";
|
import { LayoutCategory } from "@/types/layout";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
|
||||||
|
|
||||||
interface LayoutFormModalProps {
|
interface LayoutFormModalProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
|
|
@ -211,7 +210,7 @@ export const LayoutFormModal: React.FC<LayoutFormModalProps> = ({ open, onOpenCh
|
||||||
success: false,
|
success: false,
|
||||||
message: result.message || "레이아웃 생성에 실패했습니다.",
|
message: result.message || "레이아웃 생성에 실패했습니다.",
|
||||||
});
|
});
|
||||||
showErrorToast("레이아웃 생성에 실패했습니다", result.message, { guidance: "입력 데이터를 확인하고 다시 시도해 주세요." });
|
toast.error("레이아웃 생성 실패");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("레이아웃 생성 오류:", error);
|
console.error("레이아웃 생성 오류:", error);
|
||||||
|
|
@ -219,7 +218,7 @@ export const LayoutFormModal: React.FC<LayoutFormModalProps> = ({ open, onOpenCh
|
||||||
success: false,
|
success: false,
|
||||||
message: "서버 오류가 발생했습니다.",
|
message: "서버 오류가 발생했습니다.",
|
||||||
});
|
});
|
||||||
showErrorToast("레이아웃 생성에 실패했습니다", error, { guidance: "입력 데이터를 확인하고 다시 시도해 주세요." });
|
toast.error("서버 오류");
|
||||||
} finally {
|
} finally {
|
||||||
setIsGenerating(false);
|
setIsGenerating(false);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
|
||||||
import { Loader2 } from "lucide-react";
|
import { Loader2 } from "lucide-react";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
|
|
@ -95,7 +94,7 @@ export function MenuCopyDialog({
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("회사 목록 조회 실패:", error);
|
console.error("회사 목록 조회 실패:", error);
|
||||||
showErrorToast("회사 목록을 불러오는 데 실패했습니다", error, { guidance: "네트워크 연결을 확인해 주세요." });
|
toast.error("회사 목록을 불러올 수 없습니다");
|
||||||
} finally {
|
} finally {
|
||||||
setLoadingCompanies(false);
|
setLoadingCompanies(false);
|
||||||
}
|
}
|
||||||
|
|
@ -161,7 +160,7 @@ export function MenuCopyDialog({
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error("메뉴 복사 오류:", error);
|
console.error("메뉴 복사 오류:", error);
|
||||||
showErrorToast("메뉴 복사에 실패했습니다", error, { guidance: "복사 대상과 설정을 확인해 주세요." });
|
toast.error(error.message || "메뉴 복사 중 오류가 발생했습니다");
|
||||||
} finally {
|
} finally {
|
||||||
setCopying(false);
|
setCopying(false);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,6 @@ import { toast } from "sonner";
|
||||||
import { ChevronDown, Search } from "lucide-react";
|
import { ChevronDown, Search } from "lucide-react";
|
||||||
import { MENU_MANAGEMENT_KEYS } from "@/lib/utils/multilang";
|
import { MENU_MANAGEMENT_KEYS } from "@/lib/utils/multilang";
|
||||||
import { ScreenDefinition } from "@/types/screen";
|
import { ScreenDefinition } from "@/types/screen";
|
||||||
import { MenuIconPicker } from "./MenuIconPicker";
|
|
||||||
|
|
||||||
interface Company {
|
interface Company {
|
||||||
company_code: string;
|
company_code: string;
|
||||||
|
|
@ -78,7 +77,6 @@ export const MenuFormModal: React.FC<MenuFormModalProps> = ({
|
||||||
status: "ACTIVE",
|
status: "ACTIVE",
|
||||||
companyCode: parentCompanyCode || "none",
|
companyCode: parentCompanyCode || "none",
|
||||||
langKey: "",
|
langKey: "",
|
||||||
menuIcon: "",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 화면 할당 관련 상태
|
// 화면 할당 관련 상태
|
||||||
|
|
@ -277,7 +275,6 @@ export const MenuFormModal: React.FC<MenuFormModalProps> = ({
|
||||||
const status = menu.status || menu.STATUS || "active";
|
const status = menu.status || menu.STATUS || "active";
|
||||||
const companyCode = menu.company_code || menu.COMPANY_CODE || "";
|
const companyCode = menu.company_code || menu.COMPANY_CODE || "";
|
||||||
const langKey = menu.lang_key || menu.LANG_KEY || "";
|
const langKey = menu.lang_key || menu.LANG_KEY || "";
|
||||||
const menuIcon = menu.menu_icon || menu.MENU_ICON || "";
|
|
||||||
|
|
||||||
// 메뉴 타입 변환 (admin/user -> 0/1)
|
// 메뉴 타입 변환 (admin/user -> 0/1)
|
||||||
let convertedMenuType = menuType;
|
let convertedMenuType = menuType;
|
||||||
|
|
@ -310,8 +307,7 @@ export const MenuFormModal: React.FC<MenuFormModalProps> = ({
|
||||||
menuType: convertedMenuType,
|
menuType: convertedMenuType,
|
||||||
status: convertedStatus,
|
status: convertedStatus,
|
||||||
companyCode: companyCode,
|
companyCode: companyCode,
|
||||||
langKey: langKey,
|
langKey: langKey, // 다국어 키 설정
|
||||||
menuIcon: menuIcon,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// URL 타입 설정
|
// URL 타입 설정
|
||||||
|
|
@ -424,10 +420,9 @@ export const MenuFormModal: React.FC<MenuFormModalProps> = ({
|
||||||
menuDesc: "",
|
menuDesc: "",
|
||||||
seq: 1,
|
seq: 1,
|
||||||
menuType: defaultMenuType,
|
menuType: defaultMenuType,
|
||||||
status: "ACTIVE",
|
status: "ACTIVE", // 기본값은 활성화
|
||||||
companyCode: parentCompanyCode || "none",
|
companyCode: parentCompanyCode || "none", // 상위 메뉴의 회사 코드를 기본값으로 설정
|
||||||
langKey: "",
|
langKey: "", // 다국어 키 초기화
|
||||||
menuIcon: "",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// console.log("메뉴 등록 기본값 설정:", {
|
// console.log("메뉴 등록 기본값 설정:", {
|
||||||
|
|
@ -844,11 +839,6 @@ export const MenuFormModal: React.FC<MenuFormModalProps> = ({
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<MenuIconPicker
|
|
||||||
value={formData.menuIcon || ""}
|
|
||||||
onChange={(iconName) => handleInputChange("menuIcon", iconName)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="menuUrl">{getText(MENU_MANAGEMENT_KEYS.FORM_MENU_URL)}</Label>
|
<Label htmlFor="menuUrl">{getText(MENU_MANAGEMENT_KEYS.FORM_MENU_URL)}</Label>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,553 +0,0 @@
|
||||||
"use client";
|
|
||||||
|
|
||||||
import React, { useState, useMemo, useRef, useEffect, useCallback } from "react";
|
|
||||||
import { Input } from "@/components/ui/input";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { Label } from "@/components/ui/label";
|
|
||||||
import { cn } from "@/lib/utils";
|
|
||||||
import { Search, X, ChevronDown } from "lucide-react";
|
|
||||||
import * as LucideIcons from "lucide-react";
|
|
||||||
|
|
||||||
type IconComponent = React.FC<{ className?: string }>;
|
|
||||||
|
|
||||||
// lucide-react에서 아이콘 컴포넌트만 필터링 (유틸 함수, 타입 등 제외)
|
|
||||||
const EXCLUDED_EXPORTS = new Set([
|
|
||||||
"createLucideIcon",
|
|
||||||
"defaultAttributes",
|
|
||||||
"Icon",
|
|
||||||
"icons",
|
|
||||||
"default",
|
|
||||||
]);
|
|
||||||
|
|
||||||
// PascalCase인지 확인 (아이콘 컴포넌트는 모두 PascalCase)
|
|
||||||
const isPascalCase = (str: string): boolean => /^[A-Z][a-zA-Z0-9]*$/.test(str);
|
|
||||||
|
|
||||||
// 한글 키워드 매핑 (자주 쓰는 아이콘에 한글 검색어 추가)
|
|
||||||
const KOREAN_KEYWORDS: Record<string, string[]> = {
|
|
||||||
Home: ["홈", "메인", "대시보드"],
|
|
||||||
FileText: ["문서", "파일", "텍스트"],
|
|
||||||
Users: ["사용자", "회원", "인사", "팀"],
|
|
||||||
User: ["사용자", "회원", "개인"],
|
|
||||||
Settings: ["설정", "관리", "시스템"],
|
|
||||||
Shield: ["보안", "권한", "관리자"],
|
|
||||||
Package: ["제품", "품목", "패키지", "상품"],
|
|
||||||
BarChart3: ["통계", "차트", "분석", "리포트"],
|
|
||||||
BarChart2: ["통계", "차트", "분석"],
|
|
||||||
BarChart: ["통계", "차트"],
|
|
||||||
Building2: ["회사", "조직", "건물", "부서"],
|
|
||||||
Building: ["회사", "건물"],
|
|
||||||
ShoppingCart: ["영업", "판매", "주문", "장바구니"],
|
|
||||||
ShoppingBag: ["쇼핑", "가방", "구매"],
|
|
||||||
Truck: ["물류", "배송", "운송", "출하"],
|
|
||||||
Warehouse: ["창고", "재고", "입고"],
|
|
||||||
Factory: ["생산", "공장", "제조"],
|
|
||||||
Wrench: ["설비", "유지보수", "수리", "도구"],
|
|
||||||
ClipboardCheck: ["품질", "검사", "체크리스트"],
|
|
||||||
ClipboardList: ["작업지시", "지시서", "할일"],
|
|
||||||
Clipboard: ["클립보드", "복사"],
|
|
||||||
DollarSign: ["회계", "금액", "비용", "가격"],
|
|
||||||
Receipt: ["영수증", "청구", "전표"],
|
|
||||||
Calendar: ["일정", "캘린더", "날짜"],
|
|
||||||
CalendarDays: ["일정", "캘린더", "날짜", "일"],
|
|
||||||
Clock: ["시간", "이력", "히스토리"],
|
|
||||||
FolderOpen: ["폴더", "분류", "카테고리"],
|
|
||||||
Folder: ["폴더", "분류", "그룹"],
|
|
||||||
FolderPlus: ["폴더추가", "분류추가"],
|
|
||||||
Database: ["데이터", "DB", "저장소"],
|
|
||||||
Globe: ["글로벌", "다국어", "웹", "세계"],
|
|
||||||
Mail: ["메일", "이메일"],
|
|
||||||
Bell: ["알림", "벨", "통지"],
|
|
||||||
BellRing: ["알림", "벨", "울림"],
|
|
||||||
Search: ["검색", "조회", "찾기"],
|
|
||||||
ListOrdered: ["목록", "리스트", "순서"],
|
|
||||||
List: ["목록", "리스트"],
|
|
||||||
LayoutGrid: ["그리드", "레이아웃", "화면"],
|
|
||||||
LayoutDashboard: ["대시보드", "레이아웃"],
|
|
||||||
Tag: ["태그", "라벨", "분류"],
|
|
||||||
Tags: ["태그", "라벨", "분류", "복수"],
|
|
||||||
BookOpen: ["문서", "매뉴얼", "가이드"],
|
|
||||||
Book: ["책", "문서"],
|
|
||||||
Boxes: ["BOM", "자재", "부품", "구성"],
|
|
||||||
Box: ["박스", "상자", "제품"],
|
|
||||||
GitBranch: ["흐름", "분기", "프로세스"],
|
|
||||||
Workflow: ["워크플로우", "플로우", "프로세스"],
|
|
||||||
ArrowRightLeft: ["이동", "전환", "교환"],
|
|
||||||
ArrowRight: ["오른쪽", "다음", "진행"],
|
|
||||||
ArrowLeft: ["왼쪽", "이전", "뒤로"],
|
|
||||||
ArrowUp: ["위", "상승", "업"],
|
|
||||||
ArrowDown: ["아래", "하강", "다운"],
|
|
||||||
Layers: ["레이어", "계층", "구조"],
|
|
||||||
PieChart: ["파이차트", "통계", "비율"],
|
|
||||||
TrendingUp: ["추세", "성장", "상승"],
|
|
||||||
TrendingDown: ["추세", "하락", "하강"],
|
|
||||||
AlertTriangle: ["경고", "주의"],
|
|
||||||
AlertCircle: ["경고", "주의", "원"],
|
|
||||||
CheckCircle: ["완료", "승인", "확인"],
|
|
||||||
CheckCircle2: ["완료", "승인", "확인"],
|
|
||||||
Check: ["확인", "체크"],
|
|
||||||
Cog: ["톱니바퀴", "설정", "옵션"],
|
|
||||||
Map: ["지도", "위치", "경로"],
|
|
||||||
MapPin: ["지도핀", "위치", "장소"],
|
|
||||||
Printer: ["프린터", "인쇄", "출력"],
|
|
||||||
UserCog: ["사용자설정", "계정", "프로필"],
|
|
||||||
UserPlus: ["사용자추가", "회원가입"],
|
|
||||||
UserCheck: ["사용자확인", "인증"],
|
|
||||||
Key: ["키", "권한", "인증", "보안"],
|
|
||||||
Lock: ["잠금", "보안", "비밀번호"],
|
|
||||||
LockOpen: ["잠금해제", "열기"],
|
|
||||||
Unlock: ["잠금해제"],
|
|
||||||
Hammer: ["작업", "공구", "수리"],
|
|
||||||
Ruler: ["측정", "규격", "사양"],
|
|
||||||
Scan: ["스캔", "바코드", "QR"],
|
|
||||||
QrCode: ["QR코드", "큐알"],
|
|
||||||
ScrollText: ["계약", "문서", "스크롤"],
|
|
||||||
HandCoins: ["구매", "발주", "거래"],
|
|
||||||
CircleDollarSign: ["매출", "수익", "원가"],
|
|
||||||
FileSpreadsheet: ["엑셀", "스프레드시트", "표"],
|
|
||||||
FilePlus2: ["신규", "추가", "등록"],
|
|
||||||
FilePlus: ["파일추가", "신규"],
|
|
||||||
FileCheck2: ["승인", "결재", "확인"],
|
|
||||||
FileCheck: ["파일확인"],
|
|
||||||
Zap: ["전기", "에너지", "빠른"],
|
|
||||||
Gauge: ["게이지", "성능", "속도"],
|
|
||||||
HardDrive: ["저장", "서버", "디스크"],
|
|
||||||
Monitor: ["모니터", "화면", "디스플레이"],
|
|
||||||
Smartphone: ["모바일", "스마트폰", "앱"],
|
|
||||||
Lightbulb: ["아이디어", "제안", "개선"],
|
|
||||||
Star: ["별", "즐겨찾기", "중요"],
|
|
||||||
Heart: ["좋아요", "관심", "찜"],
|
|
||||||
Bookmark: ["북마크", "저장", "즐겨찾기"],
|
|
||||||
Flag: ["플래그", "깃발", "표시"],
|
|
||||||
Award: ["수상", "인증", "포상"],
|
|
||||||
Trophy: ["트로피", "우승", "성과"],
|
|
||||||
Target: ["목표", "타겟", "대상"],
|
|
||||||
Crosshair: ["크로스헤어", "조준", "정확"],
|
|
||||||
Eye: ["보기", "조회", "미리보기"],
|
|
||||||
EyeOff: ["숨기기", "비공개"],
|
|
||||||
Image: ["이미지", "사진", "그림"],
|
|
||||||
Camera: ["카메라", "사진", "촬영"],
|
|
||||||
Video: ["비디오", "영상", "동영상"],
|
|
||||||
Music: ["음악", "오디오", "사운드"],
|
|
||||||
Mic: ["마이크", "음성", "녹음"],
|
|
||||||
Phone: ["전화", "연락", "콜"],
|
|
||||||
PhoneCall: ["통화", "전화"],
|
|
||||||
MessageSquare: ["메시지", "채팅", "대화"],
|
|
||||||
MessageCircle: ["메시지", "채팅"],
|
|
||||||
Send: ["보내기", "전송", "발송"],
|
|
||||||
Share2: ["공유", "전달"],
|
|
||||||
Link: ["링크", "연결", "URL"],
|
|
||||||
ExternalLink: ["외부링크", "새창"],
|
|
||||||
Download: ["다운로드", "내려받기"],
|
|
||||||
Upload: ["업로드", "올리기"],
|
|
||||||
CloudUpload: ["클라우드업로드", "올리기"],
|
|
||||||
CloudDownload: ["클라우드다운로드", "내려받기"],
|
|
||||||
Cloud: ["클라우드", "구름"],
|
|
||||||
Server: ["서버", "시스템"],
|
|
||||||
Cpu: ["CPU", "프로세서", "처리"],
|
|
||||||
Wifi: ["와이파이", "네트워크", "무선"],
|
|
||||||
Activity: ["활동", "모니터링", "심박"],
|
|
||||||
Thermometer: ["온도", "온도계", "측정"],
|
|
||||||
Droplets: ["물", "수질", "액체"],
|
|
||||||
Wind: ["바람", "공기", "환기"],
|
|
||||||
Sun: ["태양", "밝기", "낮"],
|
|
||||||
Moon: ["달", "야간", "다크모드"],
|
|
||||||
Umbrella: ["우산", "보호", "보험"],
|
|
||||||
Compass: ["나침반", "방향", "가이드"],
|
|
||||||
Navigation: ["네비게이션", "안내"],
|
|
||||||
RotateCcw: ["되돌리기", "새로고침", "초기화"],
|
|
||||||
RefreshCw: ["새로고침", "갱신", "동기화"],
|
|
||||||
Repeat: ["반복", "되풀이"],
|
|
||||||
Shuffle: ["셔플", "무작위", "랜덤"],
|
|
||||||
Filter: ["필터", "거르기", "조건"],
|
|
||||||
SlidersHorizontal: ["슬라이더", "조정", "필터"],
|
|
||||||
Maximize2: ["최대화", "전체화면"],
|
|
||||||
Minimize2: ["최소화", "축소"],
|
|
||||||
Move: ["이동", "옮기기"],
|
|
||||||
Copy: ["복사", "복제"],
|
|
||||||
Scissors: ["가위", "잘라내기"],
|
|
||||||
Trash2: ["삭제", "쓰레기통", "휴지통"],
|
|
||||||
Trash: ["삭제", "쓰레기"],
|
|
||||||
Archive: ["보관", "아카이브", "저장"],
|
|
||||||
ArchiveRestore: ["복원", "복구"],
|
|
||||||
Plus: ["추가", "더하기", "플러스"],
|
|
||||||
Minus: ["빼기", "마이너스", "제거"],
|
|
||||||
PlusCircle: ["추가", "원형추가"],
|
|
||||||
MinusCircle: ["제거", "원형제거"],
|
|
||||||
XCircle: ["닫기", "취소", "제거"],
|
|
||||||
Info: ["정보", "안내", "도움말"],
|
|
||||||
HelpCircle: ["도움말", "질문", "안내"],
|
|
||||||
CircleAlert: ["경고", "주의", "원형경고"],
|
|
||||||
Ban: ["금지", "차단", "비허용"],
|
|
||||||
ShieldCheck: ["보안확인", "인증완료"],
|
|
||||||
ShieldAlert: ["보안경고", "위험"],
|
|
||||||
LogIn: ["로그인", "접속"],
|
|
||||||
LogOut: ["로그아웃", "종료"],
|
|
||||||
Power: ["전원", "켜기/끄기"],
|
|
||||||
ToggleLeft: ["토글", "스위치", "끄기"],
|
|
||||||
ToggleRight: ["토글", "스위치", "켜기"],
|
|
||||||
Percent: ["퍼센트", "비율", "할인"],
|
|
||||||
Hash: ["해시", "번호", "코드"],
|
|
||||||
AtSign: ["앳", "이메일", "골뱅이"],
|
|
||||||
Code: ["코드", "개발", "프로그래밍"],
|
|
||||||
Terminal: ["터미널", "명령어", "콘솔"],
|
|
||||||
Table: ["테이블", "표", "데이터"],
|
|
||||||
Table2: ["테이블", "표"],
|
|
||||||
Columns: ["컬럼", "열", "항목"],
|
|
||||||
Rows: ["행", "줄"],
|
|
||||||
Grid3x3: ["그리드", "격자", "표"],
|
|
||||||
PanelLeft: ["패널", "사이드바", "왼쪽"],
|
|
||||||
PanelRight: ["패널", "사이드바", "오른쪽"],
|
|
||||||
Split: ["분할", "나누기"],
|
|
||||||
Combine: ["결합", "합치기"],
|
|
||||||
Network: ["네트워크", "연결망"],
|
|
||||||
Radio: ["라디오", "옵션"],
|
|
||||||
CircleDot: ["원형점", "선택"],
|
|
||||||
SquareCheck: ["체크박스", "선택"],
|
|
||||||
Square: ["사각형", "상자"],
|
|
||||||
Circle: ["원", "동그라미"],
|
|
||||||
Triangle: ["삼각형", "세모"],
|
|
||||||
Hexagon: ["육각형", "벌집"],
|
|
||||||
Diamond: ["다이아몬드", "마름모"],
|
|
||||||
Pen: ["펜", "작성", "편집"],
|
|
||||||
Pencil: ["연필", "수정", "편집"],
|
|
||||||
PenLine: ["펜라인", "서명"],
|
|
||||||
Eraser: ["지우개", "삭제", "초기화"],
|
|
||||||
Palette: ["팔레트", "색상", "디자인"],
|
|
||||||
Paintbrush: ["브러시", "페인트", "디자인"],
|
|
||||||
Figma: ["피그마", "디자인"],
|
|
||||||
Type: ["타입", "글꼴", "폰트"],
|
|
||||||
Bold: ["굵게", "볼드"],
|
|
||||||
Italic: ["기울임", "이탤릭"],
|
|
||||||
AlignLeft: ["왼쪽정렬"],
|
|
||||||
AlignCenter: ["가운데정렬"],
|
|
||||||
AlignRight: ["오른쪽정렬"],
|
|
||||||
Footprints: ["발자국", "추적", "이력"],
|
|
||||||
Fingerprint: ["지문", "인증", "보안"],
|
|
||||||
ScanLine: ["스캔라인", "인식"],
|
|
||||||
Barcode: ["바코드"],
|
|
||||||
CreditCard: ["신용카드", "결제", "카드"],
|
|
||||||
Wallet: ["지갑", "결제", "자금"],
|
|
||||||
Banknote: ["지폐", "현금", "돈"],
|
|
||||||
Coins: ["동전", "코인"],
|
|
||||||
PiggyBank: ["저금통", "저축", "예산"],
|
|
||||||
Landmark: ["랜드마크", "은행", "기관"],
|
|
||||||
Store: ["매장", "상점", "가게"],
|
|
||||||
GraduationCap: ["졸업", "교육", "학습"],
|
|
||||||
School: ["학교", "교육", "훈련"],
|
|
||||||
Library: ["도서관", "라이브러리"],
|
|
||||||
BookMarked: ["북마크", "표시된책"],
|
|
||||||
Notebook: ["노트북", "공책", "메모"],
|
|
||||||
NotebookPen: ["노트작성", "메모"],
|
|
||||||
FileArchive: ["압축파일", "아카이브"],
|
|
||||||
FileAudio: ["오디오파일", "음악파일"],
|
|
||||||
FileVideo: ["비디오파일", "영상파일"],
|
|
||||||
FileImage: ["이미지파일", "사진파일"],
|
|
||||||
FileCode: ["코드파일", "소스파일"],
|
|
||||||
FileJson: ["JSON파일", "데이터파일"],
|
|
||||||
FileCog: ["파일설정", "환경설정"],
|
|
||||||
FileSearch: ["파일검색", "문서검색"],
|
|
||||||
FileWarning: ["파일경고", "주의파일"],
|
|
||||||
FileX: ["파일삭제", "파일제거"],
|
|
||||||
Files: ["파일들", "다중파일"],
|
|
||||||
FolderSearch: ["폴더검색"],
|
|
||||||
FolderCog: ["폴더설정"],
|
|
||||||
FolderInput: ["입력폴더", "수신"],
|
|
||||||
FolderOutput: ["출력폴더", "발신"],
|
|
||||||
FolderSync: ["폴더동기화"],
|
|
||||||
FolderTree: ["폴더트리", "계층구조"],
|
|
||||||
Inbox: ["받은편지함", "수신"],
|
|
||||||
MailOpen: ["메일열기", "읽음"],
|
|
||||||
MailPlus: ["메일추가", "새메일"],
|
|
||||||
CalendarCheck: ["일정확인", "예약확인"],
|
|
||||||
CalendarPlus: ["일정추가", "새일정"],
|
|
||||||
CalendarX: ["일정취소", "일정삭제"],
|
|
||||||
Timer: ["타이머", "시간측정"],
|
|
||||||
Hourglass: ["모래시계", "대기", "로딩"],
|
|
||||||
AlarmClock: ["알람", "시계"],
|
|
||||||
Watch: ["시계", "손목시계"],
|
|
||||||
Rocket: ["로켓", "출시", "배포"],
|
|
||||||
Plane: ["비행기", "항공", "운송"],
|
|
||||||
Ship: ["배", "선박", "해운"],
|
|
||||||
Car: ["자동차", "차량"],
|
|
||||||
Bus: ["버스", "대중교통"],
|
|
||||||
Train: ["기차", "열차", "철도"],
|
|
||||||
Bike: ["자전거", "이동"],
|
|
||||||
Fuel: ["연료", "주유"],
|
|
||||||
Construction: ["공사", "건설", "설치"],
|
|
||||||
HardHat: ["안전모", "건설", "안전"],
|
|
||||||
Shovel: ["삽", "건설", "시공"],
|
|
||||||
Drill: ["드릴", "공구"],
|
|
||||||
Nut: ["너트", "부품", "볼트"],
|
|
||||||
Plug: ["플러그", "전원", "연결"],
|
|
||||||
Cable: ["케이블", "선", "연결"],
|
|
||||||
Battery: ["배터리", "충전"],
|
|
||||||
BatteryCharging: ["충전중", "배터리"],
|
|
||||||
Signal: ["신호", "강도"],
|
|
||||||
Antenna: ["안테나", "수신"],
|
|
||||||
Bluetooth: ["블루투스", "무선"],
|
|
||||||
Usb: ["USB", "연결"],
|
|
||||||
SquareStack: ["스택", "쌓기", "레이어"],
|
|
||||||
Component: ["컴포넌트", "부품", "구성요소"],
|
|
||||||
Puzzle: ["퍼즐", "조각", "모듈"],
|
|
||||||
Blocks: ["블록", "구성요소"],
|
|
||||||
GitCommit: ["커밋", "변경"],
|
|
||||||
GitMerge: ["병합", "머지"],
|
|
||||||
GitPullRequest: ["풀리퀘스트", "요청"],
|
|
||||||
GitCompare: ["비교", "차이"],
|
|
||||||
CirclePlay: ["재생", "플레이"],
|
|
||||||
CirclePause: ["일시정지", "멈춤"],
|
|
||||||
CircleStop: ["정지", "중지"],
|
|
||||||
SkipForward: ["다음", "건너뛰기"],
|
|
||||||
SkipBack: ["이전", "뒤로"],
|
|
||||||
Volume2: ["볼륨", "소리"],
|
|
||||||
VolumeX: ["음소거"],
|
|
||||||
Headphones: ["헤드폰", "오디오"],
|
|
||||||
Speaker: ["스피커", "소리"],
|
|
||||||
Projector: ["프로젝터", "발표"],
|
|
||||||
Presentation: ["프레젠테이션", "발표"],
|
|
||||||
GanttChart: ["간트차트", "일정관리", "프로젝트"],
|
|
||||||
KanbanSquare: ["칸반", "보드", "프로젝트"],
|
|
||||||
ListTodo: ["할일목록", "체크리스트"],
|
|
||||||
ListChecks: ["체크목록", "확인목록"],
|
|
||||||
ListFilter: ["필터목록", "조건목록"],
|
|
||||||
ListTree: ["트리목록", "계층목록"],
|
|
||||||
StretchHorizontal: ["가로확장"],
|
|
||||||
StretchVertical: ["세로확장"],
|
|
||||||
Maximize: ["최대화"],
|
|
||||||
Minimize: ["최소화"],
|
|
||||||
Expand: ["확장", "펼치기"],
|
|
||||||
Shrink: ["축소", "줄이기"],
|
|
||||||
ZoomIn: ["확대"],
|
|
||||||
ZoomOut: ["축소"],
|
|
||||||
Focus: ["포커스", "집중"],
|
|
||||||
Crosshairs: ["조준", "대상"],
|
|
||||||
Locate: ["위치찾기", "현재위치"],
|
|
||||||
LocateFixed: ["위치고정"],
|
|
||||||
LocateOff: ["위치끄기"],
|
|
||||||
Spline: ["스플라인", "곡선"],
|
|
||||||
BrainCircuit: ["AI", "인공지능", "두뇌"],
|
|
||||||
Brain: ["두뇌", "지능", "생각"],
|
|
||||||
Bot: ["봇", "로봇", "자동화"],
|
|
||||||
Sparkles: ["반짝", "AI", "마법"],
|
|
||||||
Wand2: ["마법봉", "자동", "AI"],
|
|
||||||
FlaskConical: ["실험", "연구", "시험"],
|
|
||||||
TestTube: ["시험관", "검사", "테스트"],
|
|
||||||
Microscope: ["현미경", "분석", "연구"],
|
|
||||||
Stethoscope: ["청진기", "의료", "진단"],
|
|
||||||
Syringe: ["주사기", "의료"],
|
|
||||||
Pill: ["약", "의약품"],
|
|
||||||
HeartPulse: ["심박", "건강", "의료"],
|
|
||||||
Dna: ["DNA", "유전", "생명과학"],
|
|
||||||
Atom: ["원자", "과학", "화학"],
|
|
||||||
Beaker: ["비커", "실험", "화학"],
|
|
||||||
Scale: ["저울", "무게", "측정"],
|
|
||||||
Weight: ["무게", "중량"],
|
|
||||||
Ratio: ["비율", "비교"],
|
|
||||||
Calculator: ["계산기", "계산"],
|
|
||||||
Binary: ["이진수", "코드"],
|
|
||||||
Regex: ["정규식", "패턴"],
|
|
||||||
Variable: ["변수", "값"],
|
|
||||||
FunctionSquare: ["함수", "기능"],
|
|
||||||
Braces: ["중괄호", "코드"],
|
|
||||||
Brackets: ["대괄호", "배열"],
|
|
||||||
Parentheses: ["소괄호", "그룹"],
|
|
||||||
Tally5: ["집계", "카운트", "합계"],
|
|
||||||
Sigma: ["시그마", "합계", "총합"],
|
|
||||||
Infinity: ["무한", "반복"],
|
|
||||||
Pi: ["파이", "수학"],
|
|
||||||
Omega: ["오메가", "마지막"],
|
|
||||||
};
|
|
||||||
|
|
||||||
interface IconEntry {
|
|
||||||
name: string;
|
|
||||||
component: IconComponent;
|
|
||||||
keywords: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
// 모든 Lucide 아이콘을 동적으로 가져오기
|
|
||||||
const ALL_ICONS: IconEntry[] = (() => {
|
|
||||||
const entries: IconEntry[] = [];
|
|
||||||
for (const [name, maybeComponent] of Object.entries(LucideIcons)) {
|
|
||||||
if (EXCLUDED_EXPORTS.has(name)) continue;
|
|
||||||
if (!isPascalCase(name)) continue;
|
|
||||||
// lucide-react 아이콘은 forwardRef + memo로 감싸진 React 컴포넌트 (object)
|
|
||||||
const comp = maybeComponent as any;
|
|
||||||
const isReactComponent =
|
|
||||||
typeof comp === "function" ||
|
|
||||||
(typeof comp === "object" && comp !== null && comp.$$typeof);
|
|
||||||
if (!isReactComponent) continue;
|
|
||||||
|
|
||||||
const koreanKw = KOREAN_KEYWORDS[name] || [];
|
|
||||||
entries.push({
|
|
||||||
name,
|
|
||||||
component: comp as IconComponent,
|
|
||||||
keywords: [...koreanKw, name.toLowerCase()],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
||||||
})();
|
|
||||||
|
|
||||||
export function getIconComponent(iconName: string | null | undefined): IconComponent | null {
|
|
||||||
if (!iconName) return null;
|
|
||||||
const entry = ALL_ICONS.find((e) => e.name === iconName);
|
|
||||||
return entry?.component || null;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface MenuIconPickerProps {
|
|
||||||
value: string;
|
|
||||||
onChange: (iconName: string) => void;
|
|
||||||
label?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const MenuIconPicker: React.FC<MenuIconPickerProps> = ({
|
|
||||||
value,
|
|
||||||
onChange,
|
|
||||||
label = "메뉴 아이콘",
|
|
||||||
}) => {
|
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
|
||||||
const [searchText, setSearchText] = useState("");
|
|
||||||
const [visibleCount, setVisibleCount] = useState(120);
|
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
|
||||||
const scrollRef = useRef<HTMLDivElement>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const handleClickOutside = (event: MouseEvent) => {
|
|
||||||
if (containerRef.current && !containerRef.current.contains(event.target as Node)) {
|
|
||||||
setIsOpen(false);
|
|
||||||
setSearchText("");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if (isOpen) {
|
|
||||||
document.addEventListener("mousedown", handleClickOutside);
|
|
||||||
}
|
|
||||||
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
||||||
}, [isOpen]);
|
|
||||||
|
|
||||||
// 드롭다운 열릴 때 표시 개수 초기화
|
|
||||||
useEffect(() => {
|
|
||||||
if (isOpen) setVisibleCount(120);
|
|
||||||
}, [isOpen]);
|
|
||||||
|
|
||||||
// 검색어 변경 시 표시 개수 초기화
|
|
||||||
useEffect(() => {
|
|
||||||
setVisibleCount(120);
|
|
||||||
}, [searchText]);
|
|
||||||
|
|
||||||
const filteredIcons = useMemo(() => {
|
|
||||||
if (!searchText) return ALL_ICONS;
|
|
||||||
const lower = searchText.toLowerCase();
|
|
||||||
return ALL_ICONS.filter(
|
|
||||||
(entry) =>
|
|
||||||
entry.name.toLowerCase().includes(lower) ||
|
|
||||||
entry.keywords.some((kw) => kw.includes(lower))
|
|
||||||
);
|
|
||||||
}, [searchText]);
|
|
||||||
|
|
||||||
// 스크롤 끝에 도달하면 더 로드
|
|
||||||
const handleScroll = useCallback(() => {
|
|
||||||
const el = scrollRef.current;
|
|
||||||
if (!el) return;
|
|
||||||
if (el.scrollTop + el.clientHeight >= el.scrollHeight - 40) {
|
|
||||||
setVisibleCount((prev) => Math.min(prev + 120, filteredIcons.length));
|
|
||||||
}
|
|
||||||
}, [filteredIcons.length]);
|
|
||||||
|
|
||||||
const selectedIcon = ALL_ICONS.find((e) => e.name === value);
|
|
||||||
const SelectedIconComponent = selectedIcon?.component;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label>{label}</Label>
|
|
||||||
<div className="relative" ref={containerRef}>
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant="outline"
|
|
||||||
onClick={() => setIsOpen(!isOpen)}
|
|
||||||
className="h-10 w-full justify-between text-sm"
|
|
||||||
>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
{SelectedIconComponent ? (
|
|
||||||
<>
|
|
||||||
<SelectedIconComponent className="h-4 w-4" />
|
|
||||||
<span>{selectedIcon?.name}</span>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<span className="text-muted-foreground">아이콘을 선택하세요 (선택사항)</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{value ? (
|
|
||||||
<X
|
|
||||||
className="h-4 w-4 opacity-50 hover:opacity-100"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
onChange("");
|
|
||||||
setIsOpen(false);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<ChevronDown className="h-4 w-4 opacity-50" />
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
{isOpen && (
|
|
||||||
<div className="bg-popover text-popover-foreground absolute top-full left-0 z-50 mt-1 w-full rounded-md border shadow-lg">
|
|
||||||
<div className="border-b p-2">
|
|
||||||
<div className="relative">
|
|
||||||
<Search className="text-muted-foreground absolute top-1/2 left-2 h-4 w-4 -translate-y-1/2" />
|
|
||||||
<Input
|
|
||||||
placeholder="아이콘 검색 (한글/영문)..."
|
|
||||||
value={searchText}
|
|
||||||
onChange={(e) => setSearchText(e.target.value)}
|
|
||||||
className="h-8 pl-8 text-sm"
|
|
||||||
onClick={(e) => e.stopPropagation()}
|
|
||||||
autoFocus
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
ref={scrollRef}
|
|
||||||
onScroll={handleScroll}
|
|
||||||
className="max-h-72 overflow-y-auto p-2"
|
|
||||||
>
|
|
||||||
{!searchText && (
|
|
||||||
<p className="text-muted-foreground mb-2 text-center text-xs">
|
|
||||||
총 {ALL_ICONS.length}개 아이콘
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
<div className="grid grid-cols-6 gap-1">
|
|
||||||
{filteredIcons.slice(0, visibleCount).map((entry) => {
|
|
||||||
const IconComp = entry.component;
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
key={entry.name}
|
|
||||||
type="button"
|
|
||||||
title={entry.name}
|
|
||||||
onClick={() => {
|
|
||||||
onChange(entry.name);
|
|
||||||
setIsOpen(false);
|
|
||||||
setSearchText("");
|
|
||||||
}}
|
|
||||||
className={cn(
|
|
||||||
"flex flex-col items-center justify-center rounded-md p-2 transition-colors hover:bg-accent",
|
|
||||||
value === entry.name && "bg-primary/10 ring-primary ring-1"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<IconComp className="h-5 w-5" />
|
|
||||||
<span className="mt-1 max-w-full truncate text-[9px] leading-tight">{entry.name}</span>
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
{filteredIcons.length === 0 && (
|
|
||||||
<p className="text-muted-foreground py-4 text-center text-sm">
|
|
||||||
검색 결과가 없습니다.
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
@ -18,7 +18,6 @@ import { InteractiveScreenViewerDynamic } from "@/components/screen/InteractiveS
|
||||||
import { screenApi } from "@/lib/api/screen";
|
import { screenApi } from "@/lib/api/screen";
|
||||||
import { ComponentData } from "@/types/screen";
|
import { ComponentData } from "@/types/screen";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
|
||||||
import { useAuth } from "@/hooks/useAuth";
|
import { useAuth } from "@/hooks/useAuth";
|
||||||
import { TableOptionsProvider } from "@/contexts/TableOptionsContext";
|
import { TableOptionsProvider } from "@/contexts/TableOptionsContext";
|
||||||
import { TableSearchWidgetHeightProvider } from "@/contexts/TableSearchWidgetHeightContext";
|
import { TableSearchWidgetHeightProvider } from "@/contexts/TableSearchWidgetHeightContext";
|
||||||
|
|
@ -558,15 +557,11 @@ export const ScreenModal: React.FC<ScreenModalProps> = ({ className }) => {
|
||||||
setOriginalData(normalizedData); // 🆕 원본 데이터 저장 (UPDATE 판단용)
|
setOriginalData(normalizedData); // 🆕 원본 데이터 저장 (UPDATE 판단용)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
toast.error("수정할 데이터를 불러올 수 없습니다.", {
|
toast.error("데이터를 불러올 수 없습니다.");
|
||||||
description: "해당 항목이 삭제되었거나 접근 권한이 없을 수 있습니다.",
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("수정 데이터 조회 오류:", error);
|
console.error("수정 데이터 조회 오류:", error);
|
||||||
showErrorToast("수정 데이터 조회에 실패했습니다", error, {
|
toast.error("데이터를 불러오는 중 오류가 발생했습니다.");
|
||||||
guidance: "네트워크 연결을 확인하고 다시 시도해 주세요.",
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -618,9 +613,7 @@ export const ScreenModal: React.FC<ScreenModalProps> = ({ className }) => {
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("화면 데이터 로딩 오류:", error);
|
console.error("화면 데이터 로딩 오류:", error);
|
||||||
showErrorToast("화면 데이터를 불러오는 데 실패했습니다", error, {
|
toast.error("화면을 불러오는 중 오류가 발생했습니다.");
|
||||||
guidance: "화면 설정을 확인하거나 잠시 후 다시 시도해 주세요.",
|
|
||||||
});
|
|
||||||
handleClose();
|
handleClose();
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ import {
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
import { MoreHorizontal, Trash2, Copy, Plus, Search, Network, Database, Calendar, User } from "lucide-react";
|
import { MoreHorizontal, Trash2, Copy, Plus, Search, Network, Database, Calendar, User } from "lucide-react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
|
||||||
import { useAuth } from "@/hooks/useAuth";
|
import { useAuth } from "@/hooks/useAuth";
|
||||||
import { apiClient } from "@/lib/api/client";
|
import { apiClient } from "@/lib/api/client";
|
||||||
|
|
||||||
|
|
@ -62,7 +61,7 @@ export default function DataFlowList({ onLoadFlow }: DataFlowListProps) {
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("플로우 목록 조회 실패", error);
|
console.error("플로우 목록 조회 실패", error);
|
||||||
showErrorToast("플로우 목록을 불러오는 데 실패했습니다", error, { guidance: "네트워크 연결을 확인해 주세요." });
|
toast.error("플로우 목록을 불러오는데 실패했습니다.");
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|
@ -108,7 +107,7 @@ export default function DataFlowList({ onLoadFlow }: DataFlowListProps) {
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("플로우 복사 실패:", error);
|
console.error("플로우 복사 실패:", error);
|
||||||
showErrorToast("플로우 복사에 실패했습니다", error, { guidance: "잠시 후 다시 시도해 주세요." });
|
toast.error("플로우 복사에 실패했습니다.");
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|
@ -130,7 +129,7 @@ export default function DataFlowList({ onLoadFlow }: DataFlowListProps) {
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("플로우 삭제 실패:", error);
|
console.error("플로우 삭제 실패:", error);
|
||||||
showErrorToast("플로우 삭제에 실패했습니다", error, { guidance: "잠시 후 다시 시도해 주세요." });
|
toast.error("플로우 삭제에 실패했습니다.");
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
setShowDeleteModal(false);
|
setShowDeleteModal(false);
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,6 @@ import {
|
||||||
Timer,
|
Timer,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
|
||||||
|
|
||||||
// 타입 import
|
// 타입 import
|
||||||
import {
|
import {
|
||||||
|
|
@ -145,7 +144,7 @@ const ExternalCallTestPanel: React.FC<ExternalCallTestPanelProps> = ({
|
||||||
toast.success("API 테스트가 성공했습니다!");
|
toast.success("API 테스트가 성공했습니다!");
|
||||||
setActiveTab("response");
|
setActiveTab("response");
|
||||||
} else {
|
} else {
|
||||||
showErrorToast("API 호출이 실패했습니다", null, { guidance: "URL과 요청 설정을 확인해 주세요." });
|
toast.error("API 호출이 실패했습니다.");
|
||||||
setActiveTab("response");
|
setActiveTab("response");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -157,7 +156,7 @@ const ExternalCallTestPanel: React.FC<ExternalCallTestPanelProps> = ({
|
||||||
};
|
};
|
||||||
setTestResult(errorResult);
|
setTestResult(errorResult);
|
||||||
onTestResult(errorResult);
|
onTestResult(errorResult);
|
||||||
showErrorToast("API 테스트 실행에 실패했습니다", response.error, { guidance: "URL과 요청 설정을 확인해 주세요." });
|
toast.error(response.error || "테스트 실행 중 오류가 발생했습니다.");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorResult: ApiTestResult = {
|
const errorResult: ApiTestResult = {
|
||||||
|
|
@ -168,7 +167,7 @@ const ExternalCallTestPanel: React.FC<ExternalCallTestPanelProps> = ({
|
||||||
};
|
};
|
||||||
setTestResult(errorResult);
|
setTestResult(errorResult);
|
||||||
onTestResult(errorResult);
|
onTestResult(errorResult);
|
||||||
showErrorToast("API 테스트 실행에 실패했습니다", error, { guidance: "네트워크 연결과 URL을 확인해 주세요." });
|
toast.error("테스트 실행 중 오류가 발생했습니다.");
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
|
|
@ -180,7 +179,7 @@ const ExternalCallTestPanel: React.FC<ExternalCallTestPanelProps> = ({
|
||||||
await navigator.clipboard.writeText(text);
|
await navigator.clipboard.writeText(text);
|
||||||
toast.success("클립보드에 복사되었습니다.");
|
toast.success("클립보드에 복사되었습니다.");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showErrorToast("클립보드 복사에 실패했습니다", error, { guidance: "브라우저 권한을 확인해 주세요." });
|
toast.error("복사에 실패했습니다.");
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,6 @@ import {
|
||||||
DialogDescription,
|
DialogDescription,
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
import { CompanySwitcher } from "@/components/admin/CompanySwitcher";
|
import { CompanySwitcher } from "@/components/admin/CompanySwitcher";
|
||||||
import { getIconComponent } from "@/components/admin/MenuIconPicker";
|
|
||||||
|
|
||||||
// useAuth의 UserInfo 타입을 확장
|
// useAuth의 UserInfo 타입을 확장
|
||||||
interface ExtendedUserInfo {
|
interface ExtendedUserInfo {
|
||||||
|
|
@ -78,13 +77,8 @@ interface AppLayoutProps {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 메뉴 아이콘 매핑 함수 (DB 아이콘 우선, 없으면 키워드 기반 fallback)
|
// 메뉴 아이콘 매핑 함수
|
||||||
const getMenuIcon = (menuName: string, dbIconName?: string | null) => {
|
const getMenuIcon = (menuName: string) => {
|
||||||
if (dbIconName) {
|
|
||||||
const DbIcon = getIconComponent(dbIconName);
|
|
||||||
if (DbIcon) return <DbIcon className="h-4 w-4" />;
|
|
||||||
}
|
|
||||||
|
|
||||||
const name = menuName.toLowerCase();
|
const name = menuName.toLowerCase();
|
||||||
if (name.includes("대시보드") || name.includes("dashboard")) return <Home className="h-4 w-4" />;
|
if (name.includes("대시보드") || name.includes("dashboard")) return <Home className="h-4 w-4" />;
|
||||||
if (name.includes("관리자") || name.includes("admin")) return <Shield className="h-4 w-4" />;
|
if (name.includes("관리자") || name.includes("admin")) return <Shield className="h-4 w-4" />;
|
||||||
|
|
@ -211,7 +205,7 @@ const convertSingleMenu = (menu: MenuItem, allMenus: MenuItem[], userInfo: Exten
|
||||||
id: menuId,
|
id: menuId,
|
||||||
name: displayName,
|
name: displayName,
|
||||||
tabTitle,
|
tabTitle,
|
||||||
icon: getMenuIcon(menu.menu_name_kor || menu.MENU_NAME_KOR || "", menu.menu_icon || menu.MENU_ICON),
|
icon: getMenuIcon(menu.menu_name_kor || menu.MENU_NAME_KOR || ""),
|
||||||
url: menu.menu_url || menu.MENU_URL || "#",
|
url: menu.menu_url || menu.MENU_URL || "#",
|
||||||
children: children.length > 0 ? children : undefined,
|
children: children.length > 0 ? children : undefined,
|
||||||
hasChildren: children.length > 0,
|
hasChildren: children.length > 0,
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ import { ChevronDown, ChevronRight, Home, FileText, Users, BarChart3, Cog, GitBr
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { MenuItem } from "@/types/menu";
|
import { MenuItem } from "@/types/menu";
|
||||||
import { MENU_ICONS, MESSAGES } from "@/constants/layout";
|
import { MENU_ICONS, MESSAGES } from "@/constants/layout";
|
||||||
import { getIconComponent } from "@/components/admin/MenuIconPicker";
|
|
||||||
|
|
||||||
interface MainSidebarProps {
|
interface MainSidebarProps {
|
||||||
menuList: MenuItem[];
|
menuList: MenuItem[];
|
||||||
|
|
@ -12,14 +11,9 @@ interface MainSidebarProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 메뉴 아이콘 선택 함수 (DB 아이콘 우선, 없으면 키워드 기반 fallback)
|
* 메뉴 아이콘 선택 함수
|
||||||
*/
|
*/
|
||||||
const getMenuIcon = (menuName: string, dbIconName?: string | null) => {
|
const getMenuIcon = (menuName: string) => {
|
||||||
if (dbIconName) {
|
|
||||||
const DbIcon = getIconComponent(dbIconName);
|
|
||||||
if (DbIcon) return <DbIcon className="h-4 w-4" />;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (MENU_ICONS.HOME.some((keyword) => menuName.includes(keyword))) {
|
if (MENU_ICONS.HOME.some((keyword) => menuName.includes(keyword))) {
|
||||||
return <Home className="h-4 w-4" />;
|
return <Home className="h-4 w-4" />;
|
||||||
}
|
}
|
||||||
|
|
@ -63,7 +57,7 @@ export function MainSidebar({ menuList, expandedMenus, onMenuClick, className =
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{getMenuIcon(menu.MENU_NAME_KOR || menu.menuNameKor || "", menu.MENU_ICON || menu.menu_icon)}
|
{getMenuIcon(menu.MENU_NAME_KOR || menu.menuNameKor || "")}
|
||||||
<span>{menu.MENU_NAME_KOR || menu.menuNameKor || "메뉴"}</span>
|
<span>{menu.MENU_NAME_KOR || menu.menuNameKor || "메뉴"}</span>
|
||||||
</div>
|
</div>
|
||||||
{hasChildren && (isExpanded ? <ChevronDown className="h-4 w-4" /> : <ChevronRight className="h-4 w-4" />)}
|
{hasChildren && (isExpanded ? <ChevronDown className="h-4 w-4" /> : <ChevronRight className="h-4 w-4" />)}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ import { Label } from "@/components/ui/label";
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||||
import { Plus, Save, Edit2, Trash2, FolderTree, Check, ChevronsUpDown } from "lucide-react";
|
import { Plus, Save, Edit2, Trash2, FolderTree, Check, ChevronsUpDown } from "lucide-react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
|
||||||
import { NumberingRuleConfig, NumberingRulePart, SEPARATOR_OPTIONS, SeparatorType } from "@/types/numbering-rule";
|
import { NumberingRuleConfig, NumberingRulePart, SEPARATOR_OPTIONS, SeparatorType } from "@/types/numbering-rule";
|
||||||
import { NumberingRuleCard } from "./NumberingRuleCard";
|
import { NumberingRuleCard } from "./NumberingRuleCard";
|
||||||
import { NumberingRulePreview } from "./NumberingRulePreview";
|
import { NumberingRulePreview } from "./NumberingRulePreview";
|
||||||
|
|
@ -400,10 +399,10 @@ export const NumberingRuleDesigner: React.FC<NumberingRuleDesignerProps> = ({
|
||||||
await onSave?.(response.data);
|
await onSave?.(response.data);
|
||||||
toast.success("채번 규칙이 저장되었습니다");
|
toast.success("채번 규칙이 저장되었습니다");
|
||||||
} else {
|
} else {
|
||||||
showErrorToast("채번 규칙 저장에 실패했습니다", response.error, { guidance: "설정을 확인하고 다시 시도해 주세요." });
|
toast.error(response.error || "저장 실패");
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
showErrorToast("채번 규칙 저장에 실패했습니다", error, { guidance: "설정을 확인하고 다시 시도해 주세요." });
|
toast.error(`저장 실패: ${error.message}`);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|
@ -447,10 +446,10 @@ export const NumberingRuleDesigner: React.FC<NumberingRuleDesignerProps> = ({
|
||||||
|
|
||||||
toast.success("규칙이 삭제되었습니다");
|
toast.success("규칙이 삭제되었습니다");
|
||||||
} else {
|
} else {
|
||||||
showErrorToast("채번 규칙 삭제에 실패했습니다", response.error, { guidance: "잠시 후 다시 시도해 주세요." });
|
toast.error(response.error || "삭제 실패");
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
showErrorToast("채번 규칙 삭제에 실패했습니다", error, { guidance: "잠시 후 다시 시도해 주세요." });
|
toast.error(`삭제 실패: ${error.message}`);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,6 @@ import {
|
||||||
Save,
|
Save,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
|
||||||
import { ScreenDefinition } from "@/types/screen";
|
import { ScreenDefinition } from "@/types/screen";
|
||||||
import { screenApi } from "@/lib/api/screen";
|
import { screenApi } from "@/lib/api/screen";
|
||||||
import { PopScreenGroup, getPopScreenGroups } from "@/lib/api/popScreenGroup";
|
import { PopScreenGroup, getPopScreenGroups } from "@/lib/api/popScreenGroup";
|
||||||
|
|
@ -192,7 +191,7 @@ export function PopScreenSettingModal({
|
||||||
onOpenChange(false);
|
onOpenChange(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("저장 실패:", error);
|
console.error("저장 실패:", error);
|
||||||
showErrorToast("POP 화면 설정 저장에 실패했습니다", error, { guidance: "입력 데이터를 확인하고 다시 시도해 주세요." });
|
toast.error("저장에 실패했습니다.");
|
||||||
} finally {
|
} finally {
|
||||||
setSaving(false);
|
setSaving(false);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,6 @@ import { ko } from "date-fns/locale";
|
||||||
import { useAuth } from "@/hooks/useAuth";
|
import { useAuth } from "@/hooks/useAuth";
|
||||||
import { uploadFilesAndCreateData } from "@/lib/api/file";
|
import { uploadFilesAndCreateData } from "@/lib/api/file";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
|
||||||
import { useCascadingDropdown } from "@/hooks/useCascadingDropdown";
|
import { useCascadingDropdown } from "@/hooks/useCascadingDropdown";
|
||||||
import { CascadingDropdownConfig, LayerDefinition } from "@/types/screen-management";
|
import { CascadingDropdownConfig, LayerDefinition } from "@/types/screen-management";
|
||||||
import {
|
import {
|
||||||
|
|
@ -1266,7 +1265,7 @@ export const InteractiveScreenViewer: React.FC<InteractiveScreenViewerProps> = (
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// console.error("파일 업로드 오류:", error);
|
// console.error("파일 업로드 오류:", error);
|
||||||
showErrorToast("파일 업로드에 실패했습니다", error, { guidance: "파일 크기와 형식을 확인하고 다시 시도해 주세요." });
|
toast.error("파일 업로드에 실패했습니다.");
|
||||||
|
|
||||||
// 파일 입력 초기화
|
// 파일 입력 초기화
|
||||||
e.target.value = "";
|
e.target.value = "";
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@ import { Badge } from "@/components/ui/badge";
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
|
||||||
import { Search, Monitor, Settings, X, Plus } from "lucide-react";
|
import { Search, Monitor, Settings, X, Plus } from "lucide-react";
|
||||||
import { menuScreenApi } from "@/lib/api/screen";
|
import { menuScreenApi } from "@/lib/api/screen";
|
||||||
import { apiClient } from "@/lib/api/client";
|
import { apiClient } from "@/lib/api/client";
|
||||||
|
|
@ -94,7 +93,7 @@ export const MenuAssignmentModal: React.FC<MenuAssignmentModalProps> = ({
|
||||||
setMenus(allMenus);
|
setMenus(allMenus);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// console.error("메뉴 목록 로드 실패:", error);
|
// console.error("메뉴 목록 로드 실패:", error);
|
||||||
showErrorToast("메뉴 목록을 불러오는 데 실패했습니다", error, { guidance: "네트워크 연결을 확인해 주세요." });
|
toast.error("메뉴 목록을 불러오는데 실패했습니다.");
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,6 @@ import {
|
||||||
CommandList,
|
CommandList,
|
||||||
} from "@/components/ui/command";
|
} from "@/components/ui/command";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import {
|
import {
|
||||||
|
|
@ -404,7 +403,7 @@ export default function NodeSettingModal({
|
||||||
]);
|
]);
|
||||||
toast.success("데이터가 새로고침되었습니다.");
|
toast.success("데이터가 새로고침되었습니다.");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showErrorToast("새로고침에 실패했습니다", error, { guidance: "잠시 후 다시 시도해 주세요." });
|
toast.error("새로고침 실패");
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|
@ -636,10 +635,10 @@ function TableRelationTab({
|
||||||
onReload();
|
onReload();
|
||||||
onRefreshVisualization?.();
|
onRefreshVisualization?.();
|
||||||
} else {
|
} else {
|
||||||
showErrorToast("노드 설정 저장에 실패했습니다", response.message, { guidance: "입력 데이터를 확인하고 다시 시도해 주세요." });
|
toast.error(response.message || "저장에 실패했습니다.");
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
showErrorToast("노드 설정 저장에 실패했습니다", error, { guidance: "입력 데이터를 확인하고 다시 시도해 주세요." });
|
toast.error(error.message || "저장 중 오류가 발생했습니다.");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -654,10 +653,10 @@ function TableRelationTab({
|
||||||
onReload();
|
onReload();
|
||||||
onRefreshVisualization?.();
|
onRefreshVisualization?.();
|
||||||
} else {
|
} else {
|
||||||
showErrorToast("노드 설정 삭제에 실패했습니다", response.message, { guidance: "잠시 후 다시 시도해 주세요." });
|
toast.error(response.message || "삭제에 실패했습니다.");
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
showErrorToast("노드 설정 삭제에 실패했습니다", error, { guidance: "잠시 후 다시 시도해 주세요." });
|
toast.error(error.message || "삭제 중 오류가 발생했습니다.");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1179,10 +1178,10 @@ function JoinSettingTab({
|
||||||
onReload();
|
onReload();
|
||||||
onRefreshVisualization?.();
|
onRefreshVisualization?.();
|
||||||
} else {
|
} else {
|
||||||
showErrorToast("노드 설정 저장에 실패했습니다", response.message, { guidance: "입력 데이터를 확인하고 다시 시도해 주세요." });
|
toast.error(response.message || "저장에 실패했습니다.");
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
showErrorToast("노드 설정 저장에 실패했습니다", error, { guidance: "입력 데이터를 확인하고 다시 시도해 주세요." });
|
toast.error(error.message || "저장 중 오류가 발생했습니다.");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1197,10 +1196,10 @@ function JoinSettingTab({
|
||||||
onReload();
|
onReload();
|
||||||
onRefreshVisualization?.();
|
onRefreshVisualization?.();
|
||||||
} else {
|
} else {
|
||||||
showErrorToast("노드 설정 삭제에 실패했습니다", response.message, { guidance: "잠시 후 다시 시도해 주세요." });
|
toast.error(response.message || "삭제에 실패했습니다.");
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
showErrorToast("노드 설정 삭제에 실패했습니다", error, { guidance: "잠시 후 다시 시도해 주세요." });
|
toast.error(error.message || "삭제 중 오류가 발생했습니다.");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1587,10 +1586,10 @@ function DataFlowTab({
|
||||||
onReload();
|
onReload();
|
||||||
onRefreshVisualization?.();
|
onRefreshVisualization?.();
|
||||||
} else {
|
} else {
|
||||||
showErrorToast("노드 설정 저장에 실패했습니다", response.message, { guidance: "입력 데이터를 확인하고 다시 시도해 주세요." });
|
toast.error(response.message || "저장에 실패했습니다.");
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
showErrorToast("노드 설정 저장에 실패했습니다", error, { guidance: "입력 데이터를 확인하고 다시 시도해 주세요." });
|
toast.error(error.message || "저장 중 오류가 발생했습니다.");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1605,10 +1604,10 @@ function DataFlowTab({
|
||||||
onReload();
|
onReload();
|
||||||
onRefreshVisualization?.();
|
onRefreshVisualization?.();
|
||||||
} else {
|
} else {
|
||||||
showErrorToast("노드 설정 삭제에 실패했습니다", response.message, { guidance: "잠시 후 다시 시도해 주세요." });
|
toast.error(response.message || "삭제에 실패했습니다.");
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
showErrorToast("노드 설정 삭제에 실패했습니다", error, { guidance: "잠시 후 다시 시도해 주세요." });
|
toast.error(error.message || "삭제 중 오류가 발생했습니다.");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/u
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { X, Save, Loader2 } from "lucide-react";
|
import { X, Save, Loader2 } from "lucide-react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
|
||||||
import { DynamicComponentRenderer } from "@/lib/registry/DynamicComponentRenderer";
|
import { DynamicComponentRenderer } from "@/lib/registry/DynamicComponentRenderer";
|
||||||
import { InteractiveScreenViewer } from "./InteractiveScreenViewer";
|
import { InteractiveScreenViewer } from "./InteractiveScreenViewer";
|
||||||
import { screenApi } from "@/lib/api/screen";
|
import { screenApi } from "@/lib/api/screen";
|
||||||
|
|
@ -76,9 +75,7 @@ export const SaveModal: React.FC<SaveModalProps> = ({
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("화면 로드 실패:", error);
|
console.error("화면 로드 실패:", error);
|
||||||
showErrorToast("화면 구성 정보를 불러오는 데 실패했습니다", error, {
|
toast.error("화면을 불러오는데 실패했습니다.");
|
||||||
guidance: "화면 설정을 확인하거나 잠시 후 다시 시도해 주세요.",
|
|
||||||
});
|
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|
@ -219,9 +216,7 @@ export const SaveModal: React.FC<SaveModalProps> = ({
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
// ❌ 저장 실패 - 모달은 닫히지 않음
|
// ❌ 저장 실패 - 모달은 닫히지 않음
|
||||||
console.error("저장 실패:", error);
|
console.error("저장 실패:", error);
|
||||||
showErrorToast("데이터 저장에 실패했습니다", error, {
|
toast.error(`저장 중 오류가 발생했습니다: ${error.message || "알 수 없는 오류"}`);
|
||||||
guidance: "입력 값을 확인하고 다시 시도해 주세요.",
|
|
||||||
});
|
|
||||||
} finally {
|
} finally {
|
||||||
setIsSaving(false);
|
setIsSaving(false);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,6 @@ import {
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { ScreenGroup, createScreenGroup, updateScreenGroup } from "@/lib/api/screenGroup";
|
import { ScreenGroup, createScreenGroup, updateScreenGroup } from "@/lib/api/screenGroup";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
|
||||||
import { apiClient } from "@/lib/api/client";
|
import { apiClient } from "@/lib/api/client";
|
||||||
import {
|
import {
|
||||||
Command,
|
Command,
|
||||||
|
|
@ -226,11 +225,11 @@ export function ScreenGroupModal({
|
||||||
onSuccess();
|
onSuccess();
|
||||||
onClose();
|
onClose();
|
||||||
} else {
|
} else {
|
||||||
showErrorToast("그룹 저장에 실패했습니다", response.message, { guidance: "잠시 후 다시 시도해 주세요." });
|
toast.error(response.message || "작업에 실패했습니다");
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error("그룹 저장 실패:", error);
|
console.error("그룹 저장 실패:", error);
|
||||||
showErrorToast("그룹 저장에 실패했습니다", error, { guidance: "잠시 후 다시 시도해 주세요." });
|
toast.error("그룹 저장에 실패했습니다");
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,6 @@ import { Check, ChevronsUpDown } from "lucide-react";
|
||||||
import { ScreenGroupModal } from "./ScreenGroupModal";
|
import { ScreenGroupModal } from "./ScreenGroupModal";
|
||||||
import CopyScreenModal from "./CopyScreenModal";
|
import CopyScreenModal from "./CopyScreenModal";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
|
||||||
import { screenApi } from "@/lib/api/screen";
|
import { screenApi } from "@/lib/api/screen";
|
||||||
|
|
||||||
interface ScreenGroupTreeViewProps {
|
interface ScreenGroupTreeViewProps {
|
||||||
|
|
@ -582,11 +581,11 @@ export function ScreenGroupTreeView({
|
||||||
await loadGroupsData();
|
await loadGroupsData();
|
||||||
window.dispatchEvent(new CustomEvent("screen-list-refresh"));
|
window.dispatchEvent(new CustomEvent("screen-list-refresh"));
|
||||||
} else {
|
} else {
|
||||||
showErrorToast("그룹 삭제에 실패했습니다", response.message, { guidance: "하위 항목이 있는 경우 먼저 삭제해 주세요." });
|
toast.error(response.message || "그룹 삭제에 실패했습니다");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("그룹 삭제 실패:", error);
|
console.error("그룹 삭제 실패:", error);
|
||||||
showErrorToast("그룹 삭제에 실패했습니다", error, { guidance: "잠시 후 다시 시도해 주세요." });
|
toast.error("그룹 삭제에 실패했습니다");
|
||||||
} finally {
|
} finally {
|
||||||
setIsDeleting(false);
|
setIsDeleting(false);
|
||||||
setDeleteProgress({ current: 0, total: 0, message: "" });
|
setDeleteProgress({ current: 0, total: 0, message: "" });
|
||||||
|
|
@ -615,7 +614,7 @@ export function ScreenGroupTreeView({
|
||||||
window.dispatchEvent(new CustomEvent("screen-list-refresh"));
|
window.dispatchEvent(new CustomEvent("screen-list-refresh"));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("화면 삭제 실패:", error);
|
console.error("화면 삭제 실패:", error);
|
||||||
showErrorToast("화면 삭제에 실패했습니다", error, { guidance: "잠시 후 다시 시도해 주세요." });
|
toast.error("화면 삭제에 실패했습니다");
|
||||||
} finally {
|
} finally {
|
||||||
setIsScreenDeleting(false);
|
setIsScreenDeleting(false);
|
||||||
setIsScreenDeleteDialogOpen(false);
|
setIsScreenDeleteDialogOpen(false);
|
||||||
|
|
@ -766,7 +765,7 @@ export function ScreenGroupTreeView({
|
||||||
window.dispatchEvent(new CustomEvent("screen-list-refresh"));
|
window.dispatchEvent(new CustomEvent("screen-list-refresh"));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("화면 수정 실패:", error);
|
console.error("화면 수정 실패:", error);
|
||||||
showErrorToast("화면 정보 수정에 실패했습니다", error, { guidance: "입력 데이터를 확인하고 다시 시도해 주세요." });
|
toast.error("화면 수정에 실패했습니다");
|
||||||
} finally {
|
} finally {
|
||||||
setIsEditScreenModalOpen(false);
|
setIsEditScreenModalOpen(false);
|
||||||
setEditingScreen(null);
|
setEditingScreen(null);
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,6 @@ import { isFileComponent, getComponentWebType } from "@/lib/utils/componentTypeU
|
||||||
import { TableOptionsProvider } from "@/contexts/TableOptionsContext";
|
import { TableOptionsProvider } from "@/contexts/TableOptionsContext";
|
||||||
import { RealtimePreview } from "./RealtimePreviewDynamic";
|
import { RealtimePreview } from "./RealtimePreviewDynamic";
|
||||||
import { ScreenPreviewProvider } from "@/contexts/ScreenPreviewContext";
|
import { ScreenPreviewProvider } from "@/contexts/ScreenPreviewContext";
|
||||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
|
||||||
|
|
||||||
// InteractiveScreenViewer를 동적으로 import (SSR 비활성화)
|
// InteractiveScreenViewer를 동적으로 import (SSR 비활성화)
|
||||||
const InteractiveScreenViewer = dynamic(
|
const InteractiveScreenViewer = dynamic(
|
||||||
|
|
@ -684,7 +683,7 @@ export default function ScreenList({ onScreenSelect, selectedScreen, onDesignScr
|
||||||
setPreviewLayout(layoutData);
|
setPreviewLayout(layoutData);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("❌ 레이아웃 로드 실패:", error);
|
console.error("❌ 레이아웃 로드 실패:", error);
|
||||||
showErrorToast("화면 레이아웃을 불러오는 데 실패했습니다", error, { guidance: "화면 설정을 확인하거나 잠시 후 다시 시도해 주세요." });
|
toast.error("화면 레이아웃을 불러오는데 실패했습니다.");
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoadingPreview(false);
|
setIsLoadingPreview(false);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ import {
|
||||||
import { generateComponentId } from "@/lib/utils/generateId";
|
import { generateComponentId } from "@/lib/utils/generateId";
|
||||||
import { screenApi } from "@/lib/api/screen";
|
import { screenApi } from "@/lib/api/screen";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
|
||||||
import { RealtimePreview } from "./RealtimePreviewDynamic";
|
import { RealtimePreview } from "./RealtimePreviewDynamic";
|
||||||
import DesignerToolbar from "./DesignerToolbar";
|
import DesignerToolbar from "./DesignerToolbar";
|
||||||
|
|
||||||
|
|
@ -54,7 +53,7 @@ export default function SimpleScreenDesigner({ selectedScreen, onBackToList }: S
|
||||||
toast.success("화면이 저장되었습니다.");
|
toast.success("화면이 저장되었습니다.");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// console.error("저장 실패:", error);
|
// console.error("저장 실패:", error);
|
||||||
showErrorToast("화면 설정 저장에 실패했습니다", error, { guidance: "잠시 후 다시 시도해 주세요." });
|
toast.error("저장 중 오류가 발생했습니다.");
|
||||||
} finally {
|
} finally {
|
||||||
setIsSaving(false);
|
setIsSaving(false);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,6 @@ import {
|
||||||
} from "@/components/ui/table";
|
} from "@/components/ui/table";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
|
||||||
import { Plus, Pencil, Trash2, Link2, Database } from "lucide-react";
|
import { Plus, Pencil, Trash2, Link2, Database } from "lucide-react";
|
||||||
import {
|
import {
|
||||||
getFieldJoins,
|
getFieldJoins,
|
||||||
|
|
@ -156,7 +155,7 @@ export default function FieldJoinPanel({ screenId, componentId, layoutId }: Fiel
|
||||||
toast.error(response.message || "저장에 실패했습니다.");
|
toast.error(response.message || "저장에 실패했습니다.");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showErrorToast("필드 조인 설정 저장에 실패했습니다", error, { guidance: "입력 데이터를 확인하고 다시 시도해 주세요." });
|
toast.error("저장 중 오류가 발생했습니다.");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -173,7 +172,7 @@ export default function FieldJoinPanel({ screenId, componentId, layoutId }: Fiel
|
||||||
toast.error(response.message || "삭제에 실패했습니다.");
|
toast.error(response.message || "삭제에 실패했습니다.");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showErrorToast("필드 조인 설정 삭제에 실패했습니다", error, { guidance: "잠시 후 다시 시도해 주세요." });
|
toast.error("삭제 중 오류가 발생했습니다.");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@ import { FileInfo, FileUploadResponse } from "@/lib/registry/components/file-upl
|
||||||
import { uploadFiles, downloadFile, deleteFile } from "@/lib/api/file";
|
import { uploadFiles, downloadFile, deleteFile } from "@/lib/api/file";
|
||||||
import { formatFileSize, cn } from "@/lib/utils";
|
import { formatFileSize, cn } from "@/lib/utils";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
|
||||||
|
|
||||||
interface FileComponentConfigPanelProps {
|
interface FileComponentConfigPanelProps {
|
||||||
component: FileComponent;
|
component: FileComponent;
|
||||||
|
|
@ -537,7 +536,7 @@ export const FileComponentConfigPanel: React.FC<FileComponentConfigPanelProps> =
|
||||||
// fieldName
|
// fieldName
|
||||||
// });
|
// });
|
||||||
toast.dismiss();
|
toast.dismiss();
|
||||||
showErrorToast("파일 업로드에 실패했습니다", error, { guidance: "파일 크기와 형식을 확인하고 다시 시도해 주세요." });
|
toast.error(`파일 업로드에 실패했습니다: ${error?.message || '알 수 없는 오류'}`);
|
||||||
} finally {
|
} finally {
|
||||||
// console.log("🏁 파일 업로드 완료, 로딩 상태 해제");
|
// console.log("🏁 파일 업로드 완료, 로딩 상태 해제");
|
||||||
setUploading(false);
|
setUploading(false);
|
||||||
|
|
@ -555,7 +554,7 @@ export const FileComponentConfigPanel: React.FC<FileComponentConfigPanelProps> =
|
||||||
toast.success(`${file.realFileName || file.name} 다운로드가 완료되었습니다.`);
|
toast.success(`${file.realFileName || file.name} 다운로드가 완료되었습니다.`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// console.error('파일 다운로드 오류:', error);
|
// console.error('파일 다운로드 오류:', error);
|
||||||
showErrorToast("파일 다운로드에 실패했습니다", error, { guidance: "파일이 존재하는지 확인하고 다시 시도해 주세요." });
|
toast.error('파일 다운로드에 실패했습니다.');
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
@ -678,7 +677,7 @@ export const FileComponentConfigPanel: React.FC<FileComponentConfigPanelProps> =
|
||||||
toast.success('파일이 삭제되었습니다.');
|
toast.success('파일이 삭제되었습니다.');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// console.error('파일 삭제 오류:', error);
|
// console.error('파일 삭제 오류:', error);
|
||||||
showErrorToast("파일 삭제에 실패했습니다", error, { guidance: "잠시 후 다시 시도해 주세요." });
|
toast.error('파일 삭제에 실패했습니다.');
|
||||||
}
|
}
|
||||||
}, [uploadedFiles, onUpdateProperty, component.id]);
|
}, [uploadedFiles, onUpdateProperty, component.id]);
|
||||||
|
|
||||||
|
|
@ -714,7 +713,7 @@ export const FileComponentConfigPanel: React.FC<FileComponentConfigPanelProps> =
|
||||||
toast.success(`${uploadedFiles.length}개 파일이 영구 저장되었습니다.`);
|
toast.success(`${uploadedFiles.length}개 파일이 영구 저장되었습니다.`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// console.error('파일 저장 오류:', error);
|
// console.error('파일 저장 오류:', error);
|
||||||
showErrorToast("파일 저장에 실패했습니다", error, { guidance: "파일 크기와 형식을 확인하고 다시 시도해 주세요." });
|
toast.error('파일 저장에 실패했습니다.');
|
||||||
}
|
}
|
||||||
}, [uploadedFiles, onUpdateProperty, component.id, setGlobalFileState]);
|
}, [uploadedFiles, onUpdateProperty, component.id, setGlobalFileState]);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,6 @@ import { SingleTableWithSticky } from "@/lib/registry/components/table-list/Sing
|
||||||
import type { ColumnConfig } from "@/lib/registry/components/table-list/types";
|
import type { ColumnConfig } from "@/lib/registry/components/table-list/types";
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
|
||||||
import {
|
import {
|
||||||
Pagination,
|
Pagination,
|
||||||
PaginationContent,
|
PaginationContent,
|
||||||
|
|
@ -266,7 +265,7 @@ export function FlowWidget({
|
||||||
setSearchValues({});
|
setSearchValues({});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("필터 설정 저장 실패:", error);
|
console.error("필터 설정 저장 실패:", error);
|
||||||
showErrorToast("필터 설정 저장에 실패했습니다", error, { guidance: "잠시 후 다시 시도해 주세요." });
|
toast.error("설정 저장에 실패했습니다");
|
||||||
}
|
}
|
||||||
}, [filterSettingKey, searchFilterColumns]);
|
}, [filterSettingKey, searchFilterColumns]);
|
||||||
|
|
||||||
|
|
@ -310,7 +309,7 @@ export function FlowWidget({
|
||||||
toast.success("그룹 설정이 저장되었습니다");
|
toast.success("그룹 설정이 저장되었습니다");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("그룹 설정 저장 실패:", error);
|
console.error("그룹 설정 저장 실패:", error);
|
||||||
showErrorToast("그룹 설정 저장에 실패했습니다", error, { guidance: "잠시 후 다시 시도해 주세요." });
|
toast.error("설정 저장에 실패했습니다");
|
||||||
}
|
}
|
||||||
}, [groupSettingKey, groupByColumns]);
|
}, [groupSettingKey, groupByColumns]);
|
||||||
|
|
||||||
|
|
@ -515,7 +514,7 @@ export function FlowWidget({
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error("❌ 플로우 새로고침 실패:", err);
|
console.error("❌ 플로우 새로고침 실패:", err);
|
||||||
showErrorToast("데이터 새로고침에 실패했습니다", err, { guidance: "네트워크 연결을 확인하고 다시 시도해 주세요." });
|
toast.error(err.message || "데이터를 새로고치는데 실패했습니다");
|
||||||
} finally {
|
} finally {
|
||||||
if (selectedStepId) {
|
if (selectedStepId) {
|
||||||
setStepDataLoading(false);
|
setStepDataLoading(false);
|
||||||
|
|
@ -748,7 +747,7 @@ export function FlowWidget({
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error("Failed to load step data:", err);
|
console.error("Failed to load step data:", err);
|
||||||
showErrorToast("스텝 데이터를 불러오는 데 실패했습니다", err, { guidance: "네트워크 연결을 확인해 주세요." });
|
toast.error(err.message || "데이터를 불러오는데 실패했습니다");
|
||||||
} finally {
|
} finally {
|
||||||
setStepDataLoading(false);
|
setStepDataLoading(false);
|
||||||
}
|
}
|
||||||
|
|
@ -1024,7 +1023,7 @@ export function FlowWidget({
|
||||||
toast.success(`${exportData.length}개 행이 Excel로 내보내기 되었습니다.`);
|
toast.success(`${exportData.length}개 행이 Excel로 내보내기 되었습니다.`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Excel 내보내기 오류:", error);
|
console.error("Excel 내보내기 오류:", error);
|
||||||
showErrorToast("Excel 파일 내보내기에 실패했습니다", error, { guidance: "데이터를 확인하고 다시 시도해 주세요." });
|
toast.error("Excel 내보내기에 실패했습니다.");
|
||||||
}
|
}
|
||||||
}, [sortedDisplayData, selectedRows, stepDataColumns, columnLabels, flowName, getRowKey]);
|
}, [sortedDisplayData, selectedRows, stepDataColumns, columnLabels, flowName, getRowKey]);
|
||||||
|
|
||||||
|
|
@ -1189,7 +1188,7 @@ export function FlowWidget({
|
||||||
toast.success(`${exportData.length}개 행이 PDF로 내보내기 되었습니다.`, { id: "pdf-export" });
|
toast.success(`${exportData.length}개 행이 PDF로 내보내기 되었습니다.`, { id: "pdf-export" });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("PDF 내보내기 오류:", error);
|
console.error("PDF 내보내기 오류:", error);
|
||||||
showErrorToast("PDF 파일 내보내기에 실패했습니다", error, { guidance: "데이터를 확인하고 다시 시도해 주세요." });
|
toast.error("PDF 내보내기에 실패했습니다.", { id: "pdf-export" });
|
||||||
}
|
}
|
||||||
}, [sortedDisplayData, selectedRows, stepDataColumns, columnLabels, flowName, getRowKey]);
|
}, [sortedDisplayData, selectedRows, stepDataColumns, columnLabels, flowName, getRowKey]);
|
||||||
|
|
||||||
|
|
@ -1217,7 +1216,7 @@ export function FlowWidget({
|
||||||
toast.success(`${copyData.length}개 행이 클립보드에 복사되었습니다.`);
|
toast.success(`${copyData.length}개 행이 클립보드에 복사되었습니다.`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("복사 오류:", error);
|
console.error("복사 오류:", error);
|
||||||
showErrorToast("클립보드 복사에 실패했습니다", error, { guidance: "브라우저 권한을 확인해 주세요." });
|
toast.error("복사에 실패했습니다.");
|
||||||
}
|
}
|
||||||
}, [sortedDisplayData, selectedRows, stepDataColumns, columnLabels, getRowKey]);
|
}, [sortedDisplayData, selectedRows, stepDataColumns, columnLabels, getRowKey]);
|
||||||
|
|
||||||
|
|
@ -1319,7 +1318,7 @@ export function FlowWidget({
|
||||||
toast.success("데이터를 새로고침했습니다.");
|
toast.success("데이터를 새로고침했습니다.");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("새로고침 오류:", error);
|
console.error("새로고침 오류:", error);
|
||||||
showErrorToast("데이터 새로고침에 실패했습니다", error, { guidance: "잠시 후 다시 시도해 주세요." });
|
toast.error("새로고침에 실패했습니다.");
|
||||||
} finally {
|
} finally {
|
||||||
setStepDataLoading(false);
|
setStepDataLoading(false);
|
||||||
}
|
}
|
||||||
|
|
@ -1400,7 +1399,7 @@ export function FlowWidget({
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("편집 저장 오류:", error);
|
console.error("편집 저장 오류:", error);
|
||||||
showErrorToast("데이터 저장에 실패했습니다", error, { guidance: "입력 데이터를 확인하고 다시 시도해 주세요." });
|
toast.error("저장 중 오류가 발생했습니다.");
|
||||||
}
|
}
|
||||||
|
|
||||||
cancelEditing();
|
cancelEditing();
|
||||||
|
|
|
||||||
|
|
@ -38,9 +38,7 @@ export function MenuProvider({ children }: { children: ReactNode }) {
|
||||||
regdate: item.REGDATE || item.regdate,
|
regdate: item.REGDATE || item.regdate,
|
||||||
company_code: item.COMPANY_CODE || item.company_code,
|
company_code: item.COMPANY_CODE || item.company_code,
|
||||||
company_name: item.COMPANY_NAME || item.company_name,
|
company_name: item.COMPANY_NAME || item.company_name,
|
||||||
// 아이콘 필드
|
// 다국어 관련 필드 추가
|
||||||
menu_icon: item.MENU_ICON || item.menu_icon,
|
|
||||||
// 다국어 관련 필드
|
|
||||||
lang_key: item.LANG_KEY || item.lang_key,
|
lang_key: item.LANG_KEY || item.lang_key,
|
||||||
lang_key_desc: item.LANG_KEY_DESC || item.lang_key_desc,
|
lang_key_desc: item.LANG_KEY_DESC || item.lang_key_desc,
|
||||||
translated_name: item.TRANSLATED_NAME || item.translated_name,
|
translated_name: item.TRANSLATED_NAME || item.translated_name,
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,6 @@ import { usePopEvent } from "./usePopEvent";
|
||||||
import { executePopAction } from "./executePopAction";
|
import { executePopAction } from "./executePopAction";
|
||||||
import type { ActionResult } from "./executePopAction";
|
import type { ActionResult } from "./executePopAction";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// 타입 정의
|
// 타입 정의
|
||||||
|
|
@ -108,7 +107,7 @@ export function usePopAction(screenId: string) {
|
||||||
const msg = ACTION_SUCCESS_MESSAGES[action.type];
|
const msg = ACTION_SUCCESS_MESSAGES[action.type];
|
||||||
if (msg) toast.success(msg);
|
if (msg) toast.success(msg);
|
||||||
} else {
|
} else {
|
||||||
showErrorToast("작업에 실패했습니다", result.error, { guidance: "잠시 후 다시 시도해 주세요." });
|
toast.error(result.error || "작업에 실패했습니다.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 성공 시 후속 액션 실행
|
// 성공 시 후속 액션 실행
|
||||||
|
|
|
||||||
|
|
@ -40,8 +40,6 @@ export interface MenuItem {
|
||||||
TRANSLATED_NAME?: string;
|
TRANSLATED_NAME?: string;
|
||||||
translated_desc?: string;
|
translated_desc?: string;
|
||||||
TRANSLATED_DESC?: string;
|
TRANSLATED_DESC?: string;
|
||||||
menu_icon?: string;
|
|
||||||
MENU_ICON?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MenuFormData {
|
export interface MenuFormData {
|
||||||
|
|
@ -54,9 +52,8 @@ export interface MenuFormData {
|
||||||
menuType: string;
|
menuType: string;
|
||||||
status: string;
|
status: string;
|
||||||
companyCode: string;
|
companyCode: string;
|
||||||
langKey?: string;
|
langKey?: string; // 다국어 키 추가
|
||||||
screenCode?: string;
|
screenCode?: string; // 화면 코드 추가
|
||||||
menuIcon?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LangKey {
|
export interface LangKey {
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,6 @@ import {
|
||||||
AlertDialogTitle,
|
AlertDialogTitle,
|
||||||
} from "@/components/ui/alert-dialog";
|
} from "@/components/ui/alert-dialog";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
|
||||||
import { filterDOMProps } from "@/lib/utils/domPropsFilter";
|
import { filterDOMProps } from "@/lib/utils/domPropsFilter";
|
||||||
import { useCurrentFlowStep } from "@/stores/flowStepStore";
|
import { useCurrentFlowStep } from "@/stores/flowStepStore";
|
||||||
import { useScreenPreview } from "@/contexts/ScreenPreviewContext";
|
import { useScreenPreview } from "@/contexts/ScreenPreviewContext";
|
||||||
|
|
@ -955,7 +954,7 @@ export const ButtonPrimaryComponent: React.FC<ButtonPrimaryComponentProps> = ({
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error("❌ 데이터 전달 실패:", error);
|
console.error("❌ 데이터 전달 실패:", error);
|
||||||
showErrorToast("데이터 전달에 실패했습니다", error, { guidance: "대상 화면 설정과 데이터를 확인해 주세요." });
|
toast.error(error.message || "데이터 전달 중 오류가 발생했습니다.");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ import { Card, CardContent } from "@/components/ui/card";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
|
||||||
import { uploadFiles, downloadFile, deleteFile, getComponentFiles } from "@/lib/api/file";
|
import { uploadFiles, downloadFile, deleteFile, getComponentFiles } from "@/lib/api/file";
|
||||||
import { GlobalFileManager } from "@/lib/api/globalFile";
|
import { GlobalFileManager } from "@/lib/api/globalFile";
|
||||||
import { formatFileSize } from "@/lib/utils";
|
import { formatFileSize } from "@/lib/utils";
|
||||||
|
|
@ -882,7 +881,7 @@ const FileUploadComponent: React.FC<FileUploadComponentProps> = ({
|
||||||
console.error("파일 업로드 오류:", error);
|
console.error("파일 업로드 오류:", error);
|
||||||
setUploadStatus("error");
|
setUploadStatus("error");
|
||||||
toast.dismiss("file-upload");
|
toast.dismiss("file-upload");
|
||||||
showErrorToast("파일 업로드에 실패했습니다", error, { guidance: "파일 크기와 형식을 확인하고 다시 시도해 주세요." });
|
toast.error(`파일 업로드 오류: ${error instanceof Error ? error.message : "알 수 없는 오류"}`);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[safeComponentConfig, uploadedFiles, onFormDataChange, component.columnName, component.id, formData],
|
[safeComponentConfig, uploadedFiles, onFormDataChange, component.columnName, component.id, formData],
|
||||||
|
|
@ -1007,7 +1006,7 @@ const FileUploadComponent: React.FC<FileUploadComponentProps> = ({
|
||||||
toast.success(`${fileName} 삭제 완료`);
|
toast.success(`${fileName} 삭제 완료`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("파일 삭제 오류:", error);
|
console.error("파일 삭제 오류:", error);
|
||||||
showErrorToast("파일 삭제에 실패했습니다", error, { guidance: "잠시 후 다시 시도해 주세요." });
|
toast.error("파일 삭제에 실패했습니다.");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[uploadedFiles, onUpdate, component.id, isRecordMode, onFormDataChange, recordTableName, recordId, columnName, getUniqueKey],
|
[uploadedFiles, onUpdate, component.id, isRecordMode, onFormDataChange, recordTableName, recordId, columnName, getUniqueKey],
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,6 @@ import { FileText, ChevronRightIcon, Search } from "lucide-react";
|
||||||
import { Checkbox } from "@/components/ui/checkbox";
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
|
||||||
import { tableDisplayStore } from "@/stores/tableDisplayStore";
|
import { tableDisplayStore } from "@/stores/tableDisplayStore";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
|
|
@ -2492,7 +2491,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||||
console.log("✅ 배치 저장 완료:", pendingChanges.size, "개");
|
console.log("✅ 배치 저장 완료:", pendingChanges.size, "개");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("❌ 배치 저장 실패:", error);
|
console.error("❌ 배치 저장 실패:", error);
|
||||||
showErrorToast("데이터 저장에 실패했습니다", error, { guidance: "입력 데이터를 확인하고 다시 시도해 주세요." });
|
toast.error("저장 중 오류가 발생했습니다.");
|
||||||
}
|
}
|
||||||
}, [pendingChanges, tableConfig.selectedTable, tableConfig.primaryKey]);
|
}, [pendingChanges, tableConfig.selectedTable, tableConfig.primaryKey]);
|
||||||
|
|
||||||
|
|
@ -2710,7 +2709,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||||
console.log("✅ Excel 내보내기 완료:", fileName);
|
console.log("✅ Excel 내보내기 완료:", fileName);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("❌ Excel 내보내기 실패:", error);
|
console.error("❌ Excel 내보내기 실패:", error);
|
||||||
showErrorToast("Excel 파일 내보내기에 실패했습니다", error, { guidance: "데이터를 확인하고 다시 시도해 주세요." });
|
toast.error("Excel 내보내기 중 오류가 발생했습니다.");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
|
|
@ -3624,7 +3623,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||||
console.log("✅ 행 순서 변경:", { from: draggedRowIndex, to: targetIndex });
|
console.log("✅ 행 순서 변경:", { from: draggedRowIndex, to: targetIndex });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("❌ 행 순서 변경 실패:", error);
|
console.error("❌ 행 순서 변경 실패:", error);
|
||||||
showErrorToast("행 순서 변경에 실패했습니다", error, { guidance: "잠시 후 다시 시도해 주세요." });
|
toast.error("순서 변경 중 오류가 발생했습니다.");
|
||||||
}
|
}
|
||||||
|
|
||||||
handleRowDragEnd();
|
handleRowDragEnd();
|
||||||
|
|
@ -3738,7 +3737,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("❌ PDF 내보내기 실패:", error);
|
console.error("❌ PDF 내보내기 실패:", error);
|
||||||
showErrorToast("PDF 파일 내보내기에 실패했습니다", error, { guidance: "데이터를 확인하고 다시 시도해 주세요." });
|
toast.error("PDF 내보내기 중 오류가 발생했습니다.");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
|
|
@ -6645,7 +6644,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||||
handleRefresh();
|
handleRefresh();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("삭제 오류:", error);
|
console.error("삭제 오류:", error);
|
||||||
showErrorToast("데이터 삭제에 실패했습니다", error, { guidance: "삭제 대상을 확인하고 다시 시도해 주세요." });
|
toast.error("삭제 중 오류가 발생했습니다");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
closeContextMenu();
|
closeContextMenu();
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,6 @@ import {
|
||||||
AlertDialogTitle,
|
AlertDialogTitle,
|
||||||
} from "@/components/ui/alert-dialog";
|
} from "@/components/ui/alert-dialog";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
|
||||||
import { filterDOMProps } from "@/lib/utils/domPropsFilter";
|
import { filterDOMProps } from "@/lib/utils/domPropsFilter";
|
||||||
import { useCurrentFlowStep } from "@/stores/flowStepStore";
|
import { useCurrentFlowStep } from "@/stores/flowStepStore";
|
||||||
import { useScreenPreview } from "@/contexts/ScreenPreviewContext";
|
import { useScreenPreview } from "@/contexts/ScreenPreviewContext";
|
||||||
|
|
@ -1069,7 +1068,7 @@ export const ButtonPrimaryComponent: React.FC<ButtonPrimaryComponentProps> = ({
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error("❌ 데이터 전달 실패:", error);
|
console.error("❌ 데이터 전달 실패:", error);
|
||||||
showErrorToast("데이터 전달에 실패했습니다", error, { guidance: "대상 화면 설정과 데이터를 확인해 주세요." });
|
toast.error(error.message || "데이터 전달 중 오류가 발생했습니다.");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -181,7 +181,6 @@ import { FileText, ChevronRightIcon } from "lucide-react";
|
||||||
import { Checkbox } from "@/components/ui/checkbox";
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
|
||||||
import { tableDisplayStore } from "@/stores/tableDisplayStore";
|
import { tableDisplayStore } from "@/stores/tableDisplayStore";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
|
|
@ -2578,7 +2577,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||||
|
|
||||||
toast.success(`${pendingChanges.size}개의 변경사항이 저장되었습니다.`);
|
toast.success(`${pendingChanges.size}개의 변경사항이 저장되었습니다.`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showErrorToast("데이터 저장에 실패했습니다", error, { guidance: "입력 데이터를 확인하고 다시 시도해 주세요." });
|
toast.error("저장 중 오류가 발생했습니다.");
|
||||||
}
|
}
|
||||||
}, [pendingChanges, tableConfig.selectedTable, tableConfig.primaryKey]);
|
}, [pendingChanges, tableConfig.selectedTable, tableConfig.primaryKey]);
|
||||||
|
|
||||||
|
|
@ -2766,7 +2765,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||||
|
|
||||||
toast.success(`${exportData.length}개 행이 Excel로 내보내기 되었습니다.`);
|
toast.success(`${exportData.length}개 행이 Excel로 내보내기 되었습니다.`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showErrorToast("Excel 파일 내보내기에 실패했습니다", error, { guidance: "데이터를 확인하고 다시 시도해 주세요." });
|
toast.error("Excel 내보내기 중 오류가 발생했습니다.");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
|
|
@ -3217,7 +3216,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||||
|
|
||||||
toast.success(`${copyData.length}행 복사됨`);
|
toast.success(`${copyData.length}행 복사됨`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showErrorToast("클립보드 복사에 실패했습니다", error, { guidance: "브라우저 권한을 확인해 주세요." });
|
toast.error("복사 실패");
|
||||||
}
|
}
|
||||||
}, [selectedRows, filteredData, focusedCell, visibleColumns, columnLabels, getRowKey]);
|
}, [selectedRows, filteredData, focusedCell, visibleColumns, columnLabels, getRowKey]);
|
||||||
|
|
||||||
|
|
@ -3772,7 +3771,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("❌ PDF 내보내기 실패:", error);
|
console.error("❌ PDF 내보내기 실패:", error);
|
||||||
showErrorToast("PDF 파일 내보내기에 실패했습니다", error, { guidance: "데이터를 확인하고 다시 시도해 주세요." });
|
toast.error("PDF 내보내기 중 오류가 발생했습니다.");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
|
|
@ -4520,7 +4519,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||||
setSearchValues({});
|
setSearchValues({});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("필터 설정 저장 실패:", error);
|
console.error("필터 설정 저장 실패:", error);
|
||||||
showErrorToast("필터 설정 저장에 실패했습니다", error, { guidance: "잠시 후 다시 시도해 주세요." });
|
toast.error("설정 저장에 실패했습니다");
|
||||||
}
|
}
|
||||||
}, [filterSettingKey, visibleFilterColumns]);
|
}, [filterSettingKey, visibleFilterColumns]);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ import { dataApi } from "@/lib/api/data";
|
||||||
import { executePopAction } from "@/hooks/pop/executePopAction";
|
import { executePopAction } from "@/hooks/pop/executePopAction";
|
||||||
import { usePopEvent } from "@/hooks/pop/usePopEvent";
|
import { usePopEvent } from "@/hooks/pop/usePopEvent";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
|
||||||
import type {
|
import type {
|
||||||
PopStringListConfig,
|
PopStringListConfig,
|
||||||
CardGridConfig,
|
CardGridConfig,
|
||||||
|
|
@ -147,10 +146,10 @@ export function PopStringListComponent({
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
toast.success("작업이 완료되었습니다.");
|
toast.success("작업이 완료되었습니다.");
|
||||||
} else {
|
} else {
|
||||||
showErrorToast("작업에 실패했습니다", result.error, { guidance: "잠시 후 다시 시도해 주세요." });
|
toast.error(result.error || "작업에 실패했습니다.");
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
showErrorToast("예기치 않은 오류가 발생했습니다", null, { guidance: "잠시 후 다시 시도해 주세요." });
|
toast.error("알 수 없는 오류가 발생했습니다.");
|
||||||
} finally {
|
} finally {
|
||||||
setLoadingRowIdx(-1);
|
setLoadingRowIdx(-1);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
|
||||||
import { screenApi } from "@/lib/api/screen";
|
import { screenApi } from "@/lib/api/screen";
|
||||||
import { DynamicFormApi } from "@/lib/api/dynamicForm";
|
import { DynamicFormApi } from "@/lib/api/dynamicForm";
|
||||||
import { ImprovedButtonActionExecutor } from "@/lib/utils/improvedButtonActionExecutor";
|
import { ImprovedButtonActionExecutor } from "@/lib/utils/improvedButtonActionExecutor";
|
||||||
|
|
@ -457,11 +456,7 @@ export class ButtonActionExecutor {
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("버튼 액션 실행 오류:", error);
|
console.error("버튼 액션 실행 오류:", error);
|
||||||
showErrorToast(
|
toast.error(config.errorMessage || "작업 중 오류가 발생했습니다.");
|
||||||
config.errorMessage || `'${config.label || config.type}' 버튼 실행에 실패했습니다`,
|
|
||||||
error,
|
|
||||||
{ guidance: "설정을 확인하거나 잠시 후 다시 시도해 주세요." }
|
|
||||||
);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2657,9 +2652,7 @@ export class ButtonActionExecutor {
|
||||||
return { handled: true, success: true };
|
return { handled: true, success: true };
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error("❌ [handleUniversalFormModalTableSectionSave] 저장 오류:", error);
|
console.error("❌ [handleUniversalFormModalTableSectionSave] 저장 오류:", error);
|
||||||
showErrorToast("테이블 섹션 데이터 저장에 실패했습니다", error, {
|
toast.error(error.message || "저장 중 오류가 발생했습니다.");
|
||||||
guidance: "입력값을 확인하고 다시 시도해 주세요.",
|
|
||||||
});
|
|
||||||
return { handled: true, success: false };
|
return { handled: true, success: false };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2901,9 +2894,7 @@ export class ButtonActionExecutor {
|
||||||
if (failCount === 0) {
|
if (failCount === 0) {
|
||||||
toast.success(`${successCount}개 항목이 저장되었습니다.`);
|
toast.success(`${successCount}개 항목이 저장되었습니다.`);
|
||||||
} else if (successCount === 0) {
|
} else if (successCount === 0) {
|
||||||
showErrorToast(`${errors.length}개 항목 저장에 모두 실패했습니다`, errors.join("\n"), {
|
toast.error(`저장 실패: ${errors.join(", ")}`);
|
||||||
guidance: "입력 데이터를 확인하고 다시 시도해 주세요.",
|
|
||||||
});
|
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
toast.warning(`${successCount}개 성공, ${failCount}개 실패: ${errors.join(", ")}`);
|
toast.warning(`${successCount}개 성공, ${failCount}개 실패: ${errors.join(", ")}`);
|
||||||
|
|
@ -2920,9 +2911,7 @@ export class ButtonActionExecutor {
|
||||||
return true;
|
return true;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error("배치 저장 오류:", error);
|
console.error("배치 저장 오류:", error);
|
||||||
showErrorToast("배치 저장 중 오류가 발생했습니다", error, {
|
toast.error(`저장 오류: ${error.message}`);
|
||||||
guidance: "저장 대상 데이터를 확인하고 다시 시도해 주세요.",
|
|
||||||
});
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3274,9 +3263,7 @@ export class ButtonActionExecutor {
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("❌ 데이터 확인 실패:", error);
|
console.error("❌ 데이터 확인 실패:", error);
|
||||||
showErrorToast("상위 데이터 조회에 실패했습니다", error, {
|
toast.error("데이터 확인 중 오류가 발생했습니다.");
|
||||||
guidance: "데이터 소스 연결 상태를 확인해 주세요.",
|
|
||||||
});
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -3449,9 +3436,7 @@ export class ButtonActionExecutor {
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("❌ 데이터 확인 실패:", error);
|
console.error("❌ 데이터 확인 실패:", error);
|
||||||
showErrorToast("모달 데이터 확인에 실패했습니다", error, {
|
toast.error("데이터 확인 중 오류가 발생했습니다.");
|
||||||
guidance: "데이터 소스를 확인하고 다시 시도해 주세요.",
|
|
||||||
});
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -4012,9 +3997,7 @@ export class ButtonActionExecutor {
|
||||||
return true;
|
return true;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error("❌ 복사 액션 실행 중 오류:", error);
|
console.error("❌ 복사 액션 실행 중 오류:", error);
|
||||||
showErrorToast("데이터 복사에 실패했습니다", error, {
|
toast.error(`복사 중 오류가 발생했습니다: ${error.message || "알 수 없는 오류"}`);
|
||||||
guidance: "복사 대상 데이터를 확인하고 다시 시도해 주세요.",
|
|
||||||
});
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4245,18 +4228,12 @@ export class ButtonActionExecutor {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
console.error("❌ 노드 플로우 실행 실패:", result);
|
console.error("❌ 노드 플로우 실행 실패:", result);
|
||||||
showErrorToast(
|
toast.error(config.errorMessage || result.message || "플로우 실행 중 오류가 발생했습니다.");
|
||||||
config.errorMessage || "플로우 실행에 실패했습니다",
|
|
||||||
result.message,
|
|
||||||
{ guidance: "플로우 설정과 데이터를 확인해 주세요." }
|
|
||||||
);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("❌ 노드 플로우 실행 오류:", error);
|
console.error("❌ 노드 플로우 실행 오류:", error);
|
||||||
showErrorToast("플로우 실행 중 오류가 발생했습니다", error, {
|
toast.error("플로우 실행 중 오류가 발생했습니다.");
|
||||||
guidance: "플로우 연결 상태와 데이터를 확인해 주세요.",
|
|
||||||
});
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else if (config.dataflowConfig?.controlMode === "relationship" && config.dataflowConfig?.relationshipConfig) {
|
} else if (config.dataflowConfig?.controlMode === "relationship" && config.dataflowConfig?.relationshipConfig) {
|
||||||
|
|
@ -4300,11 +4277,7 @@ export class ButtonActionExecutor {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
console.error("❌ 관계 실행 실패:", executionResult);
|
console.error("❌ 관계 실행 실패:", executionResult);
|
||||||
showErrorToast(
|
toast.error(config.errorMessage || "관계 실행 중 오류가 발생했습니다.");
|
||||||
config.errorMessage || "관계 실행에 실패했습니다",
|
|
||||||
executionResult.message || executionResult.error,
|
|
||||||
{ guidance: "관계 설정과 데이터를 확인해 주세요." }
|
|
||||||
);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -4319,9 +4292,7 @@ export class ButtonActionExecutor {
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("제어 조건 검증 중 오류:", error);
|
console.error("제어 조건 검증 중 오류:", error);
|
||||||
showErrorToast("제어 조건 검증에 실패했습니다", error, {
|
toast.error("제어 조건 검증 중 오류가 발생했습니다.");
|
||||||
guidance: "제어 설정을 확인해 주세요.",
|
|
||||||
});
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4449,12 +4420,7 @@ export class ButtonActionExecutor {
|
||||||
if (allSuccess) {
|
if (allSuccess) {
|
||||||
toast.success(`${successCount}개 제어 실행 완료`);
|
toast.success(`${successCount}개 제어 실행 완료`);
|
||||||
} else {
|
} else {
|
||||||
const failedNames = results.filter((r) => !r.success).map((r) => r.flowName || r.flowId).join(", ");
|
toast.error(`제어 실행 중 오류 발생 (${successCount}/${results.length} 성공)`);
|
||||||
showErrorToast(
|
|
||||||
`제어 실행 중 일부 실패 (${successCount}/${results.length} 성공)`,
|
|
||||||
failedNames ? `실패 항목: ${failedNames}` : undefined,
|
|
||||||
{ guidance: "실패한 제어 플로우 설정을 확인해 주세요." }
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|
@ -4520,15 +4486,11 @@ export class ButtonActionExecutor {
|
||||||
toast.success("제어 로직 실행이 완료되었습니다.");
|
toast.success("제어 로직 실행이 완료되었습니다.");
|
||||||
} else {
|
} else {
|
||||||
console.error("❌ 저장 후 노드 플로우 실행 실패:", result);
|
console.error("❌ 저장 후 노드 플로우 실행 실패:", result);
|
||||||
showErrorToast("저장은 완료되었으나 후속 제어 실행에 실패했습니다", result.message, {
|
toast.error("저장은 완료되었으나 제어 실행 중 오류가 발생했습니다.");
|
||||||
guidance: "제어 플로우 설정을 확인해 주세요. 데이터는 정상 저장되었습니다.",
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error("❌ 저장 후 노드 플로우 실행 오류:", error);
|
console.error("❌ 저장 후 노드 플로우 실행 오류:", error);
|
||||||
showErrorToast("저장은 완료되었으나 후속 제어 실행에 실패했습니다", error, {
|
toast.error(`제어 실행 오류: ${error.message || "알 수 없는 오류"}`);
|
||||||
guidance: "제어 플로우 설정을 확인해 주세요. 데이터는 정상 저장되었습니다.",
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return; // 노드 플로우 실행 후 종료
|
return; // 노드 플로우 실행 후 종료
|
||||||
|
|
@ -4559,9 +4521,7 @@ export class ButtonActionExecutor {
|
||||||
// 성공 토스트는 save 액션에서 이미 표시했으므로 추가로 표시하지 않음
|
// 성공 토스트는 save 액션에서 이미 표시했으므로 추가로 표시하지 않음
|
||||||
} else {
|
} else {
|
||||||
console.error("❌ 저장 후 제어 실행 실패:", executionResult);
|
console.error("❌ 저장 후 제어 실행 실패:", executionResult);
|
||||||
showErrorToast("저장은 완료되었으나 연결된 제어 실행에 실패했습니다", executionResult.message || executionResult.error, {
|
toast.error("저장은 완료되었으나 연결된 제어 실행 중 오류가 발생했습니다.");
|
||||||
guidance: "제어 관계 설정을 확인해 주세요. 데이터는 정상 저장되었습니다.",
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4605,10 +4565,9 @@ export class ButtonActionExecutor {
|
||||||
const actionType = action.actionType || action.type;
|
const actionType = action.actionType || action.type;
|
||||||
console.error(`❌ 액션 ${i + 1}/${actions.length} 실행 실패:`, action.name, error);
|
console.error(`❌ 액션 ${i + 1}/${actions.length} 실행 실패:`, action.name, error);
|
||||||
|
|
||||||
showErrorToast(
|
// 실패 토스트
|
||||||
`'${action.name || `액션 ${i + 1}`}' 실행에 실패했습니다`,
|
toast.error(
|
||||||
error,
|
`${action.name || `액션 ${i + 1}`} 실행 실패: ${error instanceof Error ? error.message : "알 수 없는 오류"}`,
|
||||||
{ guidance: `전체 ${actions.length}개 액션 중 ${i + 1}번째에서 중단되었습니다.` }
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// 🚨 순차 실행 중단: 하나라도 실패하면 전체 중단
|
// 🚨 순차 실행 중단: 하나라도 실패하면 전체 중단
|
||||||
|
|
@ -4676,11 +4635,9 @@ export class ButtonActionExecutor {
|
||||||
} else {
|
} else {
|
||||||
throw new Error(result.message || "저장 실패");
|
throw new Error(result.message || "저장 실패");
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error) {
|
||||||
console.error("❌ 저장 실패:", error);
|
console.error("❌ 저장 실패:", error);
|
||||||
showErrorToast(`'${context.tableName}' 테이블 저장에 실패했습니다`, error, {
|
toast.error(`저장 실패: ${error.message}`);
|
||||||
guidance: "입력 데이터를 확인하고 다시 시도해 주세요.",
|
|
||||||
});
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4767,11 +4724,9 @@ export class ButtonActionExecutor {
|
||||||
} else {
|
} else {
|
||||||
throw new Error(result.message || "업데이트 실패");
|
throw new Error(result.message || "업데이트 실패");
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error) {
|
||||||
console.error("❌ 업데이트 실패:", error);
|
console.error("❌ 업데이트 실패:", error);
|
||||||
showErrorToast(`'${context.tableName}' 데이터 수정에 실패했습니다`, error, {
|
toast.error(`업데이트 실패: ${error.message}`);
|
||||||
guidance: "수정 데이터를 확인하고 다시 시도해 주세요.",
|
|
||||||
});
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4825,11 +4780,9 @@ export class ButtonActionExecutor {
|
||||||
} else {
|
} else {
|
||||||
throw new Error(result.message || "삭제 실패");
|
throw new Error(result.message || "삭제 실패");
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error) {
|
||||||
console.error("❌ 삭제 실패:", error);
|
console.error("❌ 삭제 실패:", error);
|
||||||
showErrorToast(`'${context.tableName}' 데이터 삭제에 실패했습니다`, error, {
|
toast.error(`삭제 실패: ${error.message}`);
|
||||||
guidance: "삭제 대상을 확인하고 다시 시도해 주세요.",
|
|
||||||
});
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4910,9 +4863,7 @@ export class ButtonActionExecutor {
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("❌ 삽입 실패:", error);
|
console.error("❌ 삽입 실패:", error);
|
||||||
showErrorToast("데이터 삽입에 실패했습니다", error, {
|
toast.error(`삽입 실패: ${error instanceof Error ? error.message : "알 수 없는 오류"}`);
|
||||||
guidance: "필수 입력 항목과 데이터 형식을 확인해 주세요.",
|
|
||||||
});
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -5013,9 +4964,7 @@ export class ButtonActionExecutor {
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("❌ 이력 모달 열기 실패:", error);
|
console.error("❌ 이력 모달 열기 실패:", error);
|
||||||
showErrorToast("이력 조회에 실패했습니다", error, {
|
toast.error("이력 조회 중 오류가 발생했습니다.");
|
||||||
guidance: "이력 테이블 설정을 확인해 주세요.",
|
|
||||||
});
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -5055,9 +5004,7 @@ export class ButtonActionExecutor {
|
||||||
columnLabels![col] = downloadResponse.data.headers[index] || col;
|
columnLabels![col] = downloadResponse.data.headers[index] || col;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
showErrorToast("마스터-디테일 데이터 조회에 실패했습니다", null, {
|
toast.error("마스터-디테일 데이터 조회에 실패했습니다.");
|
||||||
guidance: "데이터 소스 설정을 확인해 주세요.",
|
|
||||||
});
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -5147,16 +5094,12 @@ export class ButtonActionExecutor {
|
||||||
dataToExport = response.data;
|
dataToExport = response.data;
|
||||||
} else {
|
} else {
|
||||||
console.error("❌ 예상치 못한 응답 형식:", response);
|
console.error("❌ 예상치 못한 응답 형식:", response);
|
||||||
showErrorToast("엑셀 데이터 조회에 실패했습니다", null, {
|
toast.error("데이터를 가져오는데 실패했습니다.");
|
||||||
guidance: "서버 응답 형식이 예상과 다릅니다. 관리자에게 문의해 주세요.",
|
|
||||||
});
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("엑셀 다운로드: 데이터 조회 실패:", error);
|
console.error("엑셀 다운로드: 데이터 조회 실패:", error);
|
||||||
showErrorToast("엑셀 다운로드용 데이터 조회에 실패했습니다", error, {
|
toast.error("데이터를 가져오는데 실패했습니다.");
|
||||||
guidance: "네트워크 연결을 확인하고 다시 시도해 주세요.",
|
|
||||||
});
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -5166,9 +5109,7 @@ export class ButtonActionExecutor {
|
||||||
}
|
}
|
||||||
// 테이블명도 없고 폼 데이터도 없으면 에러
|
// 테이블명도 없고 폼 데이터도 없으면 에러
|
||||||
else {
|
else {
|
||||||
toast.error("다운로드할 데이터 소스가 없습니다.", {
|
toast.error("다운로드할 데이터 소스가 없습니다.");
|
||||||
description: "테이블 또는 폼 데이터가 설정되어 있지 않습니다. 버튼 설정을 확인해 주세요.",
|
|
||||||
});
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -5177,17 +5118,13 @@ export class ButtonActionExecutor {
|
||||||
if (typeof dataToExport === "object" && dataToExport !== null) {
|
if (typeof dataToExport === "object" && dataToExport !== null) {
|
||||||
dataToExport = [dataToExport];
|
dataToExport = [dataToExport];
|
||||||
} else {
|
} else {
|
||||||
toast.error("다운로드할 데이터 형식이 올바르지 않습니다.", {
|
toast.error("다운로드할 데이터 형식이 올바르지 않습니다.");
|
||||||
description: "서버에서 받은 데이터 형식이 예상과 다릅니다. 관리자에게 문의해 주세요.",
|
|
||||||
});
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dataToExport.length === 0) {
|
if (dataToExport.length === 0) {
|
||||||
toast.error("다운로드할 데이터가 없습니다.", {
|
toast.error("다운로드할 데이터가 없습니다.");
|
||||||
description: "조회 조건에 맞는 데이터가 없습니다. 검색 조건을 변경해 보세요.",
|
|
||||||
});
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -5462,9 +5399,7 @@ export class ButtonActionExecutor {
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("❌ 엑셀 다운로드 실패:", error);
|
console.error("❌ 엑셀 다운로드 실패:", error);
|
||||||
showErrorToast(config.errorMessage || "엑셀 파일 다운로드에 실패했습니다", error, {
|
toast.error(config.errorMessage || "엑셀 다운로드 중 오류가 발생했습니다.");
|
||||||
guidance: "데이터를 확인하고 다시 시도해 주세요.",
|
|
||||||
});
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -5568,9 +5503,7 @@ export class ButtonActionExecutor {
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("❌ 엑셀 업로드 모달 열기 실패:", error);
|
console.error("❌ 엑셀 업로드 모달 열기 실패:", error);
|
||||||
showErrorToast(config.errorMessage || "엑셀 업로드 화면을 열 수 없습니다", error, {
|
toast.error(config.errorMessage || "엑셀 업로드 중 오류가 발생했습니다.");
|
||||||
guidance: "잠시 후 다시 시도해 주세요.",
|
|
||||||
});
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -5631,9 +5564,7 @@ export class ButtonActionExecutor {
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("❌ 바코드 스캔 모달 열기 실패:", error);
|
console.error("❌ 바코드 스캔 모달 열기 실패:", error);
|
||||||
showErrorToast("바코드 스캔 화면을 열 수 없습니다", error, {
|
toast.error("바코드 스캔 중 오류가 발생했습니다.");
|
||||||
guidance: "카메라 권한을 확인하고 다시 시도해 주세요.",
|
|
||||||
});
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -5813,15 +5744,13 @@ export class ButtonActionExecutor {
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
showErrorToast("코드 병합에 실패했습니다", response.data.message, {
|
toast.error(response.data.message || "코드 병합에 실패했습니다.");
|
||||||
guidance: "병합 대상 코드를 확인해 주세요.",
|
|
||||||
});
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error("❌ 코드 병합 실패:", error);
|
console.error("❌ 코드 병합 실패:", error);
|
||||||
toast.dismiss();
|
toast.dismiss();
|
||||||
showErrorToast("코드 병합 중 오류가 발생했습니다", error.response?.data?.message || error);
|
toast.error(error.response?.data?.message || "코드 병합 중 오류가 발생했습니다.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -5948,9 +5877,7 @@ export class ButtonActionExecutor {
|
||||||
return true;
|
return true;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error("❌ 위치 추적 시작 실패:", error);
|
console.error("❌ 위치 추적 시작 실패:", error);
|
||||||
showErrorToast(config.errorMessage || "위치 추적을 시작할 수 없습니다", error, {
|
toast.error(config.errorMessage || "위치 추적 시작 중 오류가 발생했습니다.");
|
||||||
guidance: "위치 권한 설정과 GPS 상태를 확인해 주세요.",
|
|
||||||
});
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -6204,9 +6131,7 @@ export class ButtonActionExecutor {
|
||||||
return true;
|
return true;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error("❌ 위치 추적 종료 실패:", error);
|
console.error("❌ 위치 추적 종료 실패:", error);
|
||||||
showErrorToast(config.errorMessage || "위치 추적 종료에 실패했습니다", error, {
|
toast.error(config.errorMessage || "위치 추적 종료 중 오류가 발생했습니다.");
|
||||||
guidance: "잠시 후 다시 시도해 주세요.",
|
|
||||||
});
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -6500,9 +6425,7 @@ export class ButtonActionExecutor {
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error("❌ 데이터 전달 실패:", error);
|
console.error("❌ 데이터 전달 실패:", error);
|
||||||
showErrorToast("데이터 전달에 실패했습니다", error, {
|
toast.error(error.message || "데이터 전달 중 오류가 발생했습니다.");
|
||||||
guidance: "대상 화면 설정과 데이터를 확인해 주세요.",
|
|
||||||
});
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -6632,9 +6555,7 @@ export class ButtonActionExecutor {
|
||||||
toast.success(config.successMessage || "공차 등록이 완료되었습니다. 위치 추적을 시작합니다.");
|
toast.success(config.successMessage || "공차 등록이 완료되었습니다. 위치 추적을 시작합니다.");
|
||||||
} catch (saveError) {
|
} catch (saveError) {
|
||||||
console.error("❌ 위치정보 자동 저장 실패:", saveError);
|
console.error("❌ 위치정보 자동 저장 실패:", saveError);
|
||||||
showErrorToast("위치 정보 저장에 실패했습니다", saveError, {
|
toast.error("위치 정보 저장에 실패했습니다.");
|
||||||
guidance: "네트워크 연결을 확인해 주세요.",
|
|
||||||
});
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -6664,14 +6585,10 @@ export class ButtonActionExecutor {
|
||||||
toast.error("위치 정보 요청 시간이 초과되었습니다.\n다시 시도해주세요.");
|
toast.error("위치 정보 요청 시간이 초과되었습니다.\n다시 시도해주세요.");
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
showErrorToast(config.errorMessage || "위치 정보를 가져올 수 없습니다", null, {
|
toast.error(config.errorMessage || "위치 정보를 가져오는 중 오류가 발생했습니다.");
|
||||||
guidance: "브라우저 설정에서 위치 권한을 확인해 주세요.",
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
showErrorToast(config.errorMessage || "위치 정보를 가져올 수 없습니다", error, {
|
toast.error(config.errorMessage || "위치 정보를 가져오는 중 오류가 발생했습니다.");
|
||||||
guidance: "브라우저 설정에서 위치 권한을 확인해 주세요.",
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -6843,9 +6760,7 @@ export class ButtonActionExecutor {
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("❌ 필드 값 교환 오류:", error);
|
console.error("❌ 필드 값 교환 오류:", error);
|
||||||
showErrorToast(config.errorMessage || "필드 값 교환에 실패했습니다", error, {
|
toast.error(config.errorMessage || "값 교환 중 오류가 발생했습니다.");
|
||||||
guidance: "교환 대상 필드 설정을 확인해 주세요.",
|
|
||||||
});
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
|
||||||
import { apiClient } from "@/lib/api/client";
|
import { apiClient } from "@/lib/api/client";
|
||||||
import { ExtendedButtonTypeConfig, ButtonDataflowConfig } from "@/types/control-management";
|
import { ExtendedButtonTypeConfig, ButtonDataflowConfig } from "@/types/control-management";
|
||||||
import { ButtonActionType } from "@/types/v2-core";
|
import { ButtonActionType } from "@/types/v2-core";
|
||||||
|
|
@ -384,9 +383,7 @@ export class ImprovedButtonActionExecutor {
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
toast.success(`관계 '${config.relationshipName}' 실행 완료`);
|
toast.success(`관계 '${config.relationshipName}' 실행 완료`);
|
||||||
} else {
|
} else {
|
||||||
showErrorToast(`관계 '${config.relationshipName}' 실행에 실패했습니다`, result.message, {
|
toast.error(`관계 '${config.relationshipName}' 실행 실패: ${result.message}`);
|
||||||
guidance: "관계 설정과 대상 데이터를 확인해 주세요.",
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|
@ -399,9 +396,7 @@ export class ImprovedButtonActionExecutor {
|
||||||
error: error.message,
|
error: error.message,
|
||||||
};
|
};
|
||||||
|
|
||||||
showErrorToast(`관계 '${config.relationshipName}' 실행에 실패했습니다`, error, {
|
toast.error(errorResult.message);
|
||||||
guidance: "관계 설정과 연결 상태를 확인해 주세요.",
|
|
||||||
});
|
|
||||||
return errorResult;
|
return errorResult;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1062,8 +1057,7 @@ export class ImprovedButtonActionExecutor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
showErrorToast("버튼 액션 실행 중 오류가 발생했습니다", error, {
|
// 오류 토스트 표시
|
||||||
guidance: "잠시 후 다시 시도해 주세요. 문제가 계속되면 관리자에게 문의해 주세요.",
|
toast.error(error.message || "작업 중 오류가 발생했습니다.");
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,82 +0,0 @@
|
||||||
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,7 +13,6 @@ import { V2_EVENTS } from "../events/types";
|
||||||
import type { ScheduleType, V2ScheduleGenerateRequestEvent, V2ScheduleGenerateApplyEvent } from "../events/types";
|
import type { ScheduleType, V2ScheduleGenerateRequestEvent, V2ScheduleGenerateApplyEvent } from "../events/types";
|
||||||
import { apiClient } from "@/lib/api/client";
|
import { apiClient } from "@/lib/api/client";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { showErrorToast } from "@/lib/utils/toastUtils";
|
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// 타입 정의
|
// 타입 정의
|
||||||
|
|
@ -231,8 +230,7 @@ export function useScheduleGenerator(scheduleConfig?: ScheduleGenerationConfig |
|
||||||
});
|
});
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error("[ScheduleGeneratorService] 미리보기 오류:", error);
|
console.error("[ScheduleGeneratorService] 미리보기 오류:", error);
|
||||||
toast.dismiss("schedule-generate");
|
toast.error("스케줄 생성 중 오류가 발생했습니다.", { id: "schedule-generate" });
|
||||||
showErrorToast("스케줄 미리보기 생성에 실패했습니다", error, { guidance: "스케줄 설정을 확인하고 다시 시도해 주세요." });
|
|
||||||
v2EventBus.emit(V2_EVENTS.SCHEDULE_GENERATE_ERROR, {
|
v2EventBus.emit(V2_EVENTS.SCHEDULE_GENERATE_ERROR, {
|
||||||
requestId: payload.requestId,
|
requestId: payload.requestId,
|
||||||
error: error.message,
|
error: error.message,
|
||||||
|
|
@ -297,8 +295,7 @@ export function useScheduleGenerator(scheduleConfig?: ScheduleGenerationConfig |
|
||||||
setPreviewResult(null);
|
setPreviewResult(null);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error("[ScheduleGeneratorService] 적용 오류:", error);
|
console.error("[ScheduleGeneratorService] 적용 오류:", error);
|
||||||
toast.dismiss("schedule-apply");
|
toast.error("스케줄 적용 중 오류가 발생했습니다.", { id: "schedule-apply" });
|
||||||
showErrorToast("스케줄 적용에 실패했습니다", error, { guidance: "스케줄 설정과 데이터를 확인하고 다시 시도해 주세요." });
|
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,6 @@ export interface MenuItem {
|
||||||
// 계층적 메뉴 구조를 위한 필드들
|
// 계층적 메뉴 구조를 위한 필드들
|
||||||
children?: MenuItem[];
|
children?: MenuItem[];
|
||||||
|
|
||||||
// 아이콘 필드
|
|
||||||
menu_icon?: string;
|
|
||||||
|
|
||||||
// 번역 관련 필드들
|
// 번역 관련 필드들
|
||||||
translated_name?: string;
|
translated_name?: string;
|
||||||
translated_desc?: string;
|
translated_desc?: string;
|
||||||
|
|
@ -50,9 +47,6 @@ export interface MenuItem {
|
||||||
COMPANY_CODE?: string;
|
COMPANY_CODE?: string;
|
||||||
COMPANY_NAME?: string;
|
COMPANY_NAME?: string;
|
||||||
|
|
||||||
// 아이콘 대문자 키
|
|
||||||
MENU_ICON?: string;
|
|
||||||
|
|
||||||
// 번역 관련 대문자 키들
|
// 번역 관련 대문자 키들
|
||||||
TRANSLATED_NAME?: string;
|
TRANSLATED_NAME?: string;
|
||||||
TRANSLATED_DESC?: string;
|
TRANSLATED_DESC?: string;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue