[agent-pipeline] pipe-20260329080516-zyud round-1

This commit is contained in:
DDD1542
2026-03-29 17:48:45 +09:00
parent 9277c93ddc
commit 0fc2101331
32 changed files with 213 additions and 189 deletions
+22 -22
View File
@@ -646,10 +646,10 @@ function ReceivedTab() {
// ============================================================
interface UserSearchResult {
userId: string;
userName: string;
positionName?: string;
deptName?: string;
user_id: string;
user_name: string;
position_name?: string;
dept_name?: string;
}
function formatDateOnly(dateStr?: string) {
@@ -722,10 +722,10 @@ function ProxyTab() {
const data = res?.data || res || [];
const rawUsers: any[] = Array.isArray(data) ? data : [];
const users: UserSearchResult[] = rawUsers.map((u: any) => ({
userId: u.user_id || "",
userName: u.user_name || "",
positionName: u.position_name || "",
deptName: u.dept_name || "",
user_id: u.user_id || "",
user_name: u.user_name || "",
position_name: u.position_name || "",
dept_name: u.dept_name || "",
}));
setResults(users);
} catch {
@@ -876,16 +876,16 @@ function ProxyTab() {
};
const selectOrigUser = (user: UserSearchResult) => {
setFormOriginalUserId(user.userId);
setFormOriginalUserLabel(`${user.userName}${user.deptName ? ` (${user.deptName})` : ""}`);
setFormOriginalUserId(user.user_id);
setFormOriginalUserLabel(`${user.user_name}${user.dept_name ? ` (${user.dept_name})` : ""}`);
setOrigSearchOpen(false);
setOrigSearchQuery("");
setOrigSearchResults([]);
};
const selectProxyUser = (user: UserSearchResult) => {
setFormProxyUserId(user.userId);
setFormProxyUserLabel(`${user.userName}${user.deptName ? ` (${user.deptName})` : ""}`);
setFormProxyUserId(user.user_id);
setFormProxyUserLabel(`${user.user_name}${user.dept_name ? ` (${user.dept_name})` : ""}`);
setProxySearchOpen(false);
setProxySearchQuery("");
setProxySearchResults([]);
@@ -1124,15 +1124,15 @@ function ProxyTab() {
<div className="max-h-48 overflow-y-auto">
{origSearchResults.map((user) => (
<div
key={user.userId}
key={user.user_id}
className="hover:bg-accent hover:text-accent-foreground flex cursor-pointer items-center gap-2 px-3 py-2 text-sm"
onClick={() => selectOrigUser(user)}
>
<span className="font-medium">{user.userName}</span>
<span className="font-medium">{user.user_name}</span>
<span className="text-muted-foreground text-xs">
{user.userId}
{user.deptName ? ` / ${user.deptName}` : ""}
{user.positionName ? ` / ${user.positionName}` : ""}
{user.user_id}
{user.dept_name ? ` / ${user.dept_name}` : ""}
{user.position_name ? ` / ${user.position_name}` : ""}
</span>
</div>
))}
@@ -1178,15 +1178,15 @@ function ProxyTab() {
<div className="max-h-48 overflow-y-auto">
{proxySearchResults.map((user) => (
<div
key={user.userId}
key={user.user_id}
className="hover:bg-accent hover:text-accent-foreground flex cursor-pointer items-center gap-2 px-3 py-2 text-sm"
onClick={() => selectProxyUser(user)}
>
<span className="font-medium">{user.userName}</span>
<span className="font-medium">{user.user_name}</span>
<span className="text-muted-foreground text-xs">
{user.userId}
{user.deptName ? ` / ${user.deptName}` : ""}
{user.positionName ? ` / ${user.positionName}` : ""}
{user.user_id}
{user.dept_name ? ` / ${user.dept_name}` : ""}
{user.position_name ? ` / ${user.position_name}` : ""}
</span>
</div>
))}
+22 -22
View File
@@ -320,14 +320,14 @@ export default function AuditLogPage() {
const fetchAuditUsers = useCallback(async () => {
try {
const result = await getAuditLogUsers(filters.companyCode);
const result = await getAuditLogUsers(filters.company_code);
if (result.success) {
setAuditUsers(result.data);
}
} catch (error) {
console.error("사용자 목록 조회 실패:", error);
}
}, [filters.companyCode]);
}, [filters.company_code]);
const fetchLogs = useCallback(async () => {
setLoading(true);
@@ -346,14 +346,14 @@ export default function AuditLogPage() {
const fetchStats = useCallback(async () => {
try {
const result = await getAuditLogStats(filters.companyCode, 30);
const result = await getAuditLogStats(filters.company_code, 30);
if (result.success) {
setStats(result.data);
}
} catch (error) {
console.error("통계 조회 실패:", error);
}
}, [filters.companyCode]);
}, [filters.company_code]);
useEffect(() => {
fetchCompanies();
@@ -377,8 +377,8 @@ export default function AuditLogPage() {
const handleFilterChange = (key: keyof AuditLogFilters, value: string) => {
const updates: Partial<AuditLogFilters> = { [key]: value || undefined, page: 1 };
if (key === "companyCode") {
updates.userId = undefined;
if (key === "company_code") {
updates.user_id = undefined;
}
setFilters((prev) => ({ ...prev, ...updates }));
};
@@ -529,9 +529,9 @@ export default function AuditLogPage() {
aria-expanded={companyComboOpen}
className="h-9 w-full justify-between text-xs"
>
{filters.companyCode
? companies.find((c) => c.company_code === filters.companyCode)
?.company_name || filters.companyCode
{filters.company_code
? companies.find((c) => c.company_code === filters.company_code)
?.company_name || filters.company_code
: "전체 회사"}
<ChevronsUpDown className="ml-2 h-3 w-3 shrink-0 opacity-50" />
</Button>
@@ -551,7 +551,7 @@ export default function AuditLogPage() {
<CommandItem
value="__all_companies__"
onSelect={() => {
handleFilterChange("companyCode", "");
handleFilterChange("company_code", "");
setCompanyComboOpen(false);
}}
className="text-xs"
@@ -559,7 +559,7 @@ export default function AuditLogPage() {
<Check
className={cn(
"mr-2 h-3 w-3",
!filters.companyCode ? "opacity-100" : "opacity-0"
!filters.company_code ? "opacity-100" : "opacity-0"
)}
/>
@@ -570,8 +570,8 @@ export default function AuditLogPage() {
value={`${company.company_name} ${company.company_code}`}
onSelect={() => {
handleFilterChange(
"companyCode",
filters.companyCode === company.company_code
"company_code",
filters.company_code === company.company_code
? ""
: company.company_code
);
@@ -582,7 +582,7 @@ export default function AuditLogPage() {
<Check
className={cn(
"mr-2 h-3 w-3",
filters.companyCode === company.company_code
filters.company_code === company.company_code
? "opacity-100"
: "opacity-0"
)}
@@ -613,9 +613,9 @@ export default function AuditLogPage() {
aria-expanded={userComboOpen}
className="h-9 w-full justify-between text-xs"
>
{filters.userId
? auditUsers.find((u) => u.user_id === filters.userId)
?.user_name || filters.userId
{filters.user_id
? auditUsers.find((u) => u.user_id === filters.user_id)
?.user_name || filters.user_id
: "전체"}
<ChevronsUpDown className="ml-2 h-3 w-3 shrink-0 opacity-50" />
</Button>
@@ -635,7 +635,7 @@ export default function AuditLogPage() {
<CommandItem
value="__all_users__"
onSelect={() => {
handleFilterChange("userId", "");
handleFilterChange("user_id", "");
setUserComboOpen(false);
}}
className="text-xs"
@@ -643,7 +643,7 @@ export default function AuditLogPage() {
<Check
className={cn(
"mr-2 h-3 w-3",
!filters.userId ? "opacity-100" : "opacity-0"
!filters.user_id ? "opacity-100" : "opacity-0"
)}
/>
@@ -654,8 +654,8 @@ export default function AuditLogPage() {
value={`${u.user_name} ${u.user_id}`}
onSelect={() => {
handleFilterChange(
"userId",
filters.userId === u.user_id ? "" : u.user_id
"user_id",
filters.user_id === u.user_id ? "" : u.user_id
);
setUserComboOpen(false);
}}
@@ -664,7 +664,7 @@ export default function AuditLogPage() {
<Check
className={cn(
"mr-2 h-3 w-3",
filters.userId === u.user_id
filters.user_id === u.user_id
? "opacity-100"
: "opacity-0"
)}
+2 -2
View File
@@ -456,8 +456,8 @@ export default function MenuPage() {
if (response.data.success) {
// console.log("🏢 회사 목록 응답:", response.data);
const companyList = response.data.data.map((company: any) => ({
code: company.company_code ?? company.companyCode,
name: company.company_name ?? company.companyName,
code: company.company_code,
name: company.company_name,
}));
// console.log("🏢 변환된 회사 목록:", companyList);
setCompanies(companyList);
@@ -428,7 +428,7 @@ export default function DashboardDesignerPage({ params }: { params: Promise<{ id
title: string;
description: string;
assignToMenu: boolean;
menuType?: "admin" | "user";
menu_type?: "admin" | "user";
menuId?: string;
}) => {
try {
@@ -514,7 +514,7 @@ export default function DashboardDesignerPage({ params }: { params: Promise<{ id
// 대시보드 URL 생성 (관리자 메뉴면 mode=admin 추가)
let dashboardUrl = `/dashboard/${savedDashboard.id}`;
if (data.menuType === "admin") {
if (data.menu_type === "admin") {
dashboardUrl += "?mode=admin";
}
@@ -116,7 +116,7 @@ export default function PopScreenManagementPage() {
const openDesignerId = searchParams.get("openDesigner");
if (openDesignerId && screens.length > 0) {
const screenId = parseInt(openDesignerId, 10);
const targetScreen = screens.find((s) => s.screenId === screenId);
const targetScreen = screens.find((s) => s.screen_id === screenId);
if (targetScreen) {
setSelectedScreen(targetScreen);
setCurrentStep("design");
@@ -162,7 +162,7 @@ export default function PopScreenManagementPage() {
// POP 화면 미리보기 (새 탭에서 열기)
const handlePreviewScreen = (screen: ScreenDefinition) => {
const previewUrl = `/pop/screens/${screen.screenId}?preview=true&device=${devicePreview}`;
const previewUrl = `/pop/screens/${screen.screen_id}?preview=true&device=${devicePreview}`;
window.open(previewUrl, "_blank", "width=800,height=900");
};
@@ -178,7 +178,7 @@ export default function PopScreenManagementPage() {
// ============================================================
// POP 레이아웃이 있는 화면만 필터링
const popScreens = screens.filter((screen) => popLayoutScreenIds.has(screen.screenId));
const popScreens = screens.filter((screen) => popLayoutScreenIds.has(screen.screen_id));
// 검색어 필터링
const filteredScreens = popScreens.filter((screen) => {
@@ -210,7 +210,7 @@ export default function PopScreenManagementPage() {
});
setScreens((prev) =>
prev.map((s) =>
s.screenId === selectedScreen.screenId
s.screen_id === selectedScreen.screen_id
? { ...s, ...updatedFields }
: s
)
@@ -83,7 +83,7 @@ export default function ScreenManagementPage() {
const openDesignerId = searchParams.get("openDesigner");
if (openDesignerId && screens.length > 0) {
const screenId = parseInt(openDesignerId, 10);
const targetScreen = screens.find((s) => s.screenId === screenId);
const targetScreen = screens.find((s) => s.screen_id === screenId);
if (targetScreen) {
setSelectedScreen(targetScreen);
setCurrentStep("design");
@@ -147,7 +147,7 @@ export default function ScreenManagementPage() {
setSelectedScreen(updated);
setScreens((prev) =>
prev.map((s) =>
s.screenId === selectedScreen.screenId
s.screen_id === selectedScreen.screen_id
? { ...s, ...updatedFields }
: s
)
@@ -326,7 +326,7 @@ export default function ScreenManagementPage() {
<div className="grid grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4 gap-3">
{filteredScreens.map((screen) => {
const screenType = (screen as { screenType?: string }).screenType || "form";
const isSelected = selectedScreen?.screenId === screen.screenId;
const isSelected = selectedScreen?.screen_id === screen.screen_id;
const isRecentlyModified = screen.updatedDate && (Date.now() - new Date(screen.updatedDate).getTime()) < 7 * 24 * 60 * 60 * 1000;
const typeColorClass = screenType === "grid"
@@ -349,7 +349,7 @@ export default function ScreenManagementPage() {
return (
<div
key={screen.screenId}
key={screen.screen_id}
className={`group relative overflow-hidden rounded-[12px] cursor-pointer transition-all duration-250 ease-[cubic-bezier(0.4,0,0.2,1)] ${
isSelected
? "border border-primary bg-primary/5 dark:bg-primary/8 shadow-[0_0_0_2px_hsl(var(--primary)/0.22),0_1px_3px_rgba(0,0,0,0.06)] dark:shadow-[0_0_0_2px_hsl(var(--primary)/0.3),0_1px_4px_rgba(0,0,0,0.3)]"
@@ -422,7 +422,7 @@ export default function ScreenManagementPage() {
</div>
<div className="flex items-center justify-between">
<span className="text-xs text-muted-foreground"> ID</span>
<span className="text-xs font-mono">{selectedScreen.screenId}</span>
<span className="text-xs font-mono">{selectedScreen.screen_id}</span>
</div>
</div>
<div className="flex gap-2 pt-4 border-t border-border/50">
@@ -889,8 +889,8 @@ export default function I18nPage() {
isOpen={isGenerateModalOpen}
onClose={() => setIsGenerateModalOpen(false)}
selectedCategory={selectedCategory}
companyCode={user?.companyCode || ""}
isSuperAdmin={user?.companyCode === "*"}
companyCode={user?.company_code || ""}
isSuperAdmin={user?.company_code === "*"}
onSuccess={() => {
fetchLangKeys(selectedCategory?.categoryId);
}}
@@ -17,7 +17,7 @@ import { getCompanyList } from "@/lib/api/company";
export default function DepartmentManagementPage() {
const params = useParams();
const router = useRouter();
const companyCode = params.companyCode as string;
const { companyCode } = params as { companyCode: string };
const [selectedDepartment, setSelectedDepartment] = useState<Department | null>(null);
const [activeTab, setActiveTab] = useState<string>("structure");
const [companyName, setCompanyName] = useState<string>("");
@@ -45,8 +45,8 @@ export default function UserMngPage() {
// 비밀번호 초기화 모달 상태
const [passwordResetModal, setPasswordResetModal] = useState({
isOpen: false,
userId: null as string | null,
userName: null as string | null,
user_id: null as string | null,
user_name: null as string | null,
});
// 사용자 등록/수정 모달 상태
@@ -89,8 +89,8 @@ export default function UserMngPage() {
const handlePasswordReset = (userId: string, userName: string) => {
setPasswordResetModal({
isOpen: true,
userId,
userName,
user_id: userId,
user_name: userName,
});
};
@@ -98,8 +98,8 @@ export default function UserMngPage() {
const handlePasswordResetClose = () => {
setPasswordResetModal({
isOpen: false,
userId: null,
userName: null,
user_id: null,
user_name: null,
});
};
@@ -177,8 +177,8 @@ export default function UserMngPage() {
<UserPasswordResetModal
isOpen={passwordResetModal.isOpen}
onClose={handlePasswordResetClose}
userId={passwordResetModal.userId}
userName={passwordResetModal.userName}
userId={passwordResetModal.user_id}
userName={passwordResetModal.user_name}
onSuccess={handlePasswordResetSuccess}
/>
</div>
@@ -44,7 +44,8 @@ function ScreenViewPage({ screenIdProp, menuObjidProp }: ScreenViewPageProps = {
const params = useParams();
const searchParams = useSearchParams();
const router = useRouter();
const screenId = screenIdProp ?? parseInt(params.screenId as string);
const { screenId: screenIdFromParams } = params as { screenId: string };
const screenId = screenIdProp ?? parseInt(screenIdFromParams);
// props 우선, 없으면 URL 쿼리에서 menuObjid 가져오기
const menuObjid =
@@ -99,7 +100,7 @@ function ScreenViewPage({ screenIdProp, menuObjidProp }: ScreenViewPageProps = {
// 편집 모달 상태
const [editModalOpen, setEditModalOpen] = useState(false);
const [editModalConfig, setEditModalConfig] = useState<{
screenId?: number;
screen_id?: number;
modalSize?: "sm" | "md" | "lg" | "xl" | "full";
editData?: Record<string, unknown>;
onSave?: () => void;
@@ -132,13 +133,21 @@ function ScreenViewPage({ screenIdProp, menuObjidProp }: ScreenViewPageProps = {
const currentActiveTabId = state[state.mode].activeTabId;
if (tabId && tabId !== currentActiveTabId) return;
const { screenId: detailScreenId, modalSize, editData, onSave, modalTitle, modalDescription } = event.detail as {
screenId?: number;
modalSize?: "sm" | "md" | "lg" | "xl" | "full";
editData?: Record<string, unknown>;
onSave?: () => void;
modalTitle?: string;
modalDescription?: string;
};
setEditModalConfig({
screenId: event.detail.screenId,
modalSize: event.detail.modalSize,
editData: event.detail.editData,
onSave: event.detail.onSave,
modalTitle: event.detail.modalTitle,
modalDescription: event.detail.modalDescription,
screen_id: detailScreenId,
modalSize,
editData,
onSave,
modalTitle,
modalDescription,
});
setEditModalOpen(true);
};
@@ -787,7 +796,7 @@ function ScreenViewPage({ screenIdProp, menuObjidProp }: ScreenViewPageProps = {
setEditModalOpen(false);
setEditModalConfig({});
}}
screenId={editModalConfig.screenId}
screenId={editModalConfig.screen_id}
modalSize={editModalConfig.modalSize}
editData={editModalConfig.editData}
onSave={editModalConfig.onSave}
@@ -64,7 +64,8 @@ function PopScreenViewPage() {
const params = useParams();
const searchParams = useSearchParams();
const router = useRouter();
const screenId = parseInt(params.screenId as string);
const { screenId: screenIdFromParams } = params as { screenId: string };
const screenId = parseInt(screenIdFromParams);
const isPreviewMode = searchParams.get("preview") === "true";
+15 -15
View File
@@ -24,9 +24,9 @@ interface LangKeyModalProps {
export default function LangKeyModal({ isOpen, onClose, onSave, keyData, companies }: LangKeyModalProps) {
const [formData, setFormData] = useState({
companyCode: "",
menuName: "",
langKey: "",
company_code: "",
menu_name: "",
lang_key: "",
description: "",
});
@@ -34,9 +34,9 @@ export default function LangKeyModal({ isOpen, onClose, onSave, keyData, compani
if (keyData) {
// 수정 모드
setFormData({
companyCode: keyData.company_code || "",
menuName: keyData.menu_name || "",
langKey: keyData.lang_key || "",
company_code: keyData.company_code || "",
menu_name: keyData.menu_name || "",
lang_key: keyData.lang_key || "",
description: keyData.description || "",
});
} else {
@@ -57,9 +57,9 @@ export default function LangKeyModal({ isOpen, onClose, onSave, keyData, compani
const handleClose = () => {
setFormData({
companyCode: "",
menuName: "",
langKey: "",
company_code: "",
menu_name: "",
lang_key: "",
description: "",
});
onClose();
@@ -75,8 +75,8 @@ export default function LangKeyModal({ isOpen, onClose, onSave, keyData, compani
<div>
<Label htmlFor="companyCode"></Label>
<Select
value={formData.companyCode}
onValueChange={(value) => setFormData({ ...formData, companyCode: value })}
value={formData.company_code}
onValueChange={(value) => setFormData({ ...formData, company_code: value })}
>
<SelectTrigger>
<SelectValue placeholder="회사 선택" />
@@ -95,8 +95,8 @@ export default function LangKeyModal({ isOpen, onClose, onSave, keyData, compani
<Label htmlFor="menuName"></Label>
<Input
id="menuName"
value={formData.menuName}
onChange={(e) => setFormData({ ...formData, menuName: e.target.value })}
value={formData.menu_name}
onChange={(e) => setFormData({ ...formData, menu_name: e.target.value })}
placeholder="메뉴명 입력"
required
/>
@@ -106,8 +106,8 @@ export default function LangKeyModal({ isOpen, onClose, onSave, keyData, compani
<Label htmlFor="langKey"> </Label>
<Input
id="langKey"
value={formData.langKey}
onChange={(e) => setFormData({ ...formData, langKey: e.target.value })}
value={formData.lang_key}
onChange={(e) => setFormData({ ...formData, lang_key: e.target.value })}
placeholder="언어 키 입력"
required
/>
+17 -17
View File
@@ -32,7 +32,7 @@ interface RoleFormModalProps {
* 권한 그룹 생성/수정 모달
*
* 기능:
* - 권한 그룹 생성 (authName, authCode, companyCode)
* - 권한 그룹 생성 (authName, authCode, company_code)
* - 권한 그룹 수정 (authName, authCode, status)
* - 유효성 검사
*
@@ -49,7 +49,7 @@ export function RoleFormModal({ isOpen, onClose, onSuccess, editingRole }: RoleF
const [formData, setFormData] = useState({
authName: "",
authCode: "",
companyCode: currentUser?.company_code || "",
company_code: currentUser?.company_code || "",
status: "active",
});
@@ -66,7 +66,7 @@ export function RoleFormModal({ isOpen, onClose, onSuccess, editingRole }: RoleF
// 폼 유효성 검사
const isFormValid = useMemo(() => {
return formData.authName.trim() !== "" && formData.authCode.trim() !== "" && formData.companyCode.trim() !== "";
return formData.authName.trim() !== "" && formData.authCode.trim() !== "" && formData.company_code.trim() !== "";
}, [formData]);
// 알림 표시
@@ -108,7 +108,7 @@ export function RoleFormModal({ isOpen, onClose, onSuccess, editingRole }: RoleF
setFormData({
authName: editingRole.auth_name || "",
authCode: editingRole.auth_code || "",
companyCode: editingRole.company_code || "",
company_code: editingRole.company_code || "",
status: editingRole.status || "active",
});
} else {
@@ -116,7 +116,7 @@ export function RoleFormModal({ isOpen, onClose, onSuccess, editingRole }: RoleF
setFormData({
authName: "",
authCode: "",
companyCode: currentUser?.company_code || "",
company_code: currentUser?.company_code || "",
status: "active",
});
}
@@ -152,7 +152,7 @@ export function RoleFormModal({ isOpen, onClose, onSuccess, editingRole }: RoleF
response = await roleAPI.create({
auth_name: formData.authName,
auth_code: formData.authCode,
company_code: formData.companyCode,
company_code: formData.company_code,
});
}
@@ -229,12 +229,12 @@ export function RoleFormModal({ isOpen, onClose, onSuccess, editingRole }: RoleF
{/* 회사 (수정 모드에서는 비활성화) */}
{isEditMode ? (
<div>
<Label htmlFor="companyCode" className="text-xs sm:text-sm">
<Label htmlFor="company_code" className="text-xs sm:text-sm">
</Label>
<Input
id="companyCode"
value={formData.companyCode}
id="company_code"
value={formData.company_code}
disabled
className="bg-muted h-8 cursor-not-allowed text-xs sm:h-10 sm:text-sm"
/>
@@ -242,7 +242,7 @@ export function RoleFormModal({ isOpen, onClose, onSuccess, editingRole }: RoleF
</div>
) : (
<div>
<Label htmlFor="companyCode" className="text-xs sm:text-sm">
<Label htmlFor="company_code" className="text-xs sm:text-sm">
<span className="text-destructive">*</span>
</Label>
{isSuperAdmin ? (
@@ -256,9 +256,9 @@ export function RoleFormModal({ isOpen, onClose, onSuccess, editingRole }: RoleF
className="h-8 w-full justify-between text-xs sm:h-10 sm:text-sm"
disabled={isLoading || isLoadingCompanies}
>
{formData.companyCode
? companies.find((company) => company.company_code === formData.companyCode)?.company_name ||
formData.companyCode
{formData.company_code
? companies.find((company) => company.company_code === formData.company_code)?.company_name ||
formData.company_code
: "회사 선택..."}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
@@ -280,7 +280,7 @@ export function RoleFormModal({ isOpen, onClose, onSuccess, editingRole }: RoleF
key={company.company_code}
value={`${company.company_code} ${company.company_name}`}
onSelect={() => {
handleInputChange("companyCode", company.company_code);
handleInputChange("company_code", company.company_code);
setCompanyComboOpen(false);
}}
className="text-xs sm:text-sm"
@@ -288,7 +288,7 @@ export function RoleFormModal({ isOpen, onClose, onSuccess, editingRole }: RoleF
<Check
className={cn(
"mr-2 h-4 w-4",
formData.companyCode === company.company_code ? "opacity-100" : "opacity-0",
formData.company_code === company.company_code ? "opacity-100" : "opacity-0",
)}
/>
<div className="flex flex-col">
@@ -309,8 +309,8 @@ export function RoleFormModal({ isOpen, onClose, onSuccess, editingRole }: RoleF
) : (
<>
<Input
id="companyCode"
value={formData.companyCode}
id="company_code"
value={formData.company_code}
disabled
className="bg-muted h-8 cursor-not-allowed text-xs sm:h-10 sm:text-sm"
/>
@@ -41,7 +41,7 @@ interface DashboardSaveModalProps {
title: string;
description: string;
assignToMenu: boolean;
menuType?: "admin" | "user";
menu_type?: "admin" | "user";
menuId?: string;
}) => Promise<void>;
initialTitle?: string;
@@ -158,7 +158,7 @@ export function DashboardSaveModal({
title: title.trim(),
description: description.trim(),
assignToMenu,
menuType: assignToMenu ? menuType : undefined,
menu_type: assignToMenu ? menuType : undefined,
menuId: assignToMenu ? selectedMenuId : undefined,
});
onClose();
@@ -250,6 +250,7 @@ export const ApprovalRequestModal: React.FC<ApprovalRequestModalProps> = ({
// 혼합형 여부: approvers에 step_type이 설정된 경우
const hasMixedStepTypes = approvers.some((a) => a.step_type);
const { screenId: eventScreenId } = eventDetail ?? {};
const res = await createApprovalRequest({
title: title.trim(),
@@ -260,7 +261,7 @@ export const ApprovalRequestModal: React.FC<ApprovalRequestModalProps> = ({
target_record_data: eventDetail.targetRecordData,
approval_mode: approvalType === "consensus" ? "parallel" : approvalMode,
approval_type: approvalType,
screen_id: eventDetail.screenId,
screen_id: eventScreenId,
button_component_id: eventDetail.buttonComponentId,
approvers: approvalType === "self"
? []
@@ -11,9 +11,9 @@ import { DynamicComponentRenderer } from "@/lib/registry/DynamicComponentRendere
import { screenApi } from "@/lib/api/screen";
import { ResponsiveGridRenderer } from "@/components/screen/ResponsiveGridRenderer";
// 확장된 TabItem 타입 (screenId 지원)
// 확장된 TabItem 타입 (screen_id 지원)
interface ExtendedTabItem extends TabItem {
screenId?: number;
screen_id?: number;
screenName?: string;
}
@@ -147,9 +147,9 @@ export function TabsWidget({
(c) => c.componentType === "v2-table-list" || c.componentType === "table-list",
);
const selectedTable = tableComp?.componentConfig?.selectedTable;
if (selectedTable || tab.screenId) {
if (selectedTable || tab.screen_id) {
map[tab.id] = {
id: tab.screenId,
id: tab.screen_id,
tableName: selectedTable,
};
}
@@ -170,14 +170,14 @@ export function TabsWidget({
const extTab = tab as ExtendedTabItem;
// screenId가 있고, 아직 로드하지 않았으며, 인라인 컴포넌트가 없는 경우만 로드
if (
extTab.screenId &&
extTab.screen_id &&
!screenLayouts[tab.id] &&
!screenLoadingStates[tab.id] &&
(!extTab.components || extTab.components.length === 0)
) {
setScreenLoadingStates((prev) => ({ ...prev, [tab.id]: true }));
try {
const layoutData = await screenApi.getLayout(extTab.screenId);
const layoutData = await screenApi.getLayout(extTab.screen_id);
if (layoutData && layoutData.components) {
setScreenLayouts((prev) => ({ ...prev, [tab.id]: layoutData.components }));
}
@@ -254,8 +254,8 @@ export function TabsWidget({
const extTab = tab as ExtendedTabItem;
const inlineComponents = tab.components || [];
// 1. screenId가 있고 인라인 컴포넌트가 없는 경우 -> 화면 로드 방식
if (extTab.screenId && inlineComponents.length === 0) {
// 1. screen_id가 있고 인라인 컴포넌트가 없는 경우 -> 화면 로드 방식
if (extTab.screen_id && inlineComponents.length === 0) {
// 로딩 중
if (screenLoadingStates[tab.id]) {
return (
+1 -1
View File
@@ -362,7 +362,7 @@ export const V2Layout = forwardRef<HTMLDivElement, V2LayoutProps>(
case "screen-embed":
return (
<ScreenEmbedLayout
screenId={config.screenId}
screenId={config.screen_id}
/>
);
@@ -28,7 +28,7 @@ interface V2ItemRoutingConfigPanelProps {
interface TableInfo { tableName: string; displayName?: string; }
interface ColumnInfo { columnName: string; displayName?: string; dataType?: string; }
interface ScreenInfo { screenId: number; screenName: string; screenCode: string; }
interface ScreenInfo { screen_id: number; screenName: string; screenCode: string; }
// ─── 공용: 테이블 Combobox ───
function TableCombobox({ value, onChange, tables, loading }: {
@@ -144,7 +144,7 @@ function ScreenCombobox({ value, onChange }: { value?: number; onChange: (v?: nu
const res = await screenApi.getScreens({ page: 1, size: 1000 });
if (res.data) {
setScreens(res.data.map((s: any) => ({
screenId: s.screen_id, screenName: s.screen_name || `화면 ${s.screen_id}`, screenCode: s.screen_code || "",
screen_id: s.screen_id, screenName: s.screen_name || `화면 ${s.screen_id}`, screenCode: s.screen_code || "",
})));
}
} catch { /* ignore */ } finally { setLoading(false); }
@@ -152,7 +152,7 @@ function ScreenCombobox({ value, onChange }: { value?: number; onChange: (v?: nu
load();
}, []);
const selected = screens.find((s) => s.screenId === value);
const selected = screens.find((s) => s.screen_id === value);
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
@@ -168,12 +168,12 @@ function ScreenCombobox({ value, onChange }: { value?: number; onChange: (v?: nu
<CommandEmpty className="py-4 text-center text-xs"> .</CommandEmpty>
<CommandGroup className="max-h-[200px] overflow-auto">
{screens.map((s) => (
<CommandItem key={s.screenId} value={`${s.screenName} ${s.screenCode} ${s.screenId}`}
onSelect={() => { onChange(s.screenId); setOpen(false); }} className="text-xs">
<Check className={cn("mr-2 h-3 w-3", value === s.screenId ? "opacity-100" : "opacity-0")} />
<CommandItem key={s.screen_id} value={`${s.screenName} ${s.screenCode} ${s.screen_id}`}
onSelect={() => { onChange(s.screen_id); setOpen(false); }} className="text-xs">
<Check className={cn("mr-2 h-3 w-3", value === s.screen_id ? "opacity-100" : "opacity-0")} />
<div className="flex flex-col">
<span className="font-medium">{s.screenName}</span>
<span className="text-[10px] text-muted-foreground">ID: {s.screenId}</span>
<span className="text-[10px] text-muted-foreground">ID: {s.screen_id}</span>
</div>
</CommandItem>
))}
@@ -344,8 +344,8 @@ export const V2LayoutConfigPanel: React.FC<V2LayoutConfigPanelProps> = ({
<span className="text-xs text-muted-foreground"> ID</span>
<Input
type="number"
value={config.screenId || ""}
onChange={(e) => updateConfig("screenId", e.target.value ? Number(e.target.value) : undefined)}
value={config.screen_id || ""}
onChange={(e) => updateConfig("screen_id", e.target.value ? Number(e.target.value) : undefined)}
placeholder="화면 ID 입력"
className="h-8 w-[180px] text-sm"
/>
+2 -2
View File
@@ -25,9 +25,9 @@ export const notifyLanguageChange = (newLang: string) => {
console.log("🔄 모든 컴포넌트에 언어 변경 알림:", newLang);
};
export const useMultiLang = (options: { companyCode?: string } = {}) => {
export const useMultiLang = (options: { company_code?: string } = {}) => {
const [userLang, setUserLang] = useState<string | null>(null); // null로 시작
const companyCode = options.companyCode || "*";
const companyCode = options.company_code || "*";
// 🎯 효율적인 언어 변경 감지 (폴링 대신 콜백 등록)
useEffect(() => {
+3 -3
View File
@@ -54,8 +54,8 @@ export const useProfile = (user: any, refreshUserData: () => Promise<void>, refr
// 부서 목록 상태
const [departments, setDepartments] = useState<
Array<{
deptCode: string;
deptName: string;
dept_code: string;
dept_name: string;
}>
>([]);
@@ -99,7 +99,7 @@ export const useProfile = (user: any, refreshUserData: () => Promise<void>, refr
try {
const response = await apiCall("GET", "/admin/departments");
if (response.success && response.data) {
setDepartments(response.data as Array<{ deptCode: string; deptName: string }>);
setDepartments(response.data as Array<{ dept_code: string; dept_name: string }>);
}
} catch (error) {
console.error("부서 목록 로드 실패:", error);
+1 -1
View File
@@ -89,7 +89,7 @@ export const entityJoinApi = {
} = {
enabled: true,
filterColumn: "company_code",
userField: "companyCode",
userField: "company_code",
};
// 🆕 프리뷰 모드에서 회사 코드 오버라이드 (최고 관리자만 백엔드에서 허용)
+8 -8
View File
@@ -42,7 +42,7 @@ export interface TripDetail {
}
export interface TripListFilters {
userId?: string;
user_id?: string;
vehicleId?: number;
status?: string;
startDate?: string;
@@ -126,7 +126,7 @@ export async function getTripList(filters?: TripListFilters) {
const params = new URLSearchParams();
if (filters) {
if (filters.userId) params.append("userId", filters.userId);
if (filters.user_id) params.append("userId", filters.user_id);
if (filters.vehicleId) params.append("vehicleId", String(filters.vehicleId));
if (filters.status) params.append("status", filters.status);
if (filters.startDate) params.append("startDate", filters.startDate);
@@ -290,11 +290,11 @@ export async function getSummaryReport(period?: string) {
/**
*
*/
export async function getDailyReport(filters?: { startDate?: string; endDate?: string; userId?: string }) {
export async function getDailyReport(filters?: { startDate?: string; endDate?: string; user_id?: string }) {
const params = new URLSearchParams();
if (filters?.startDate) params.append("startDate", filters.startDate);
if (filters?.endDate) params.append("endDate", filters.endDate);
if (filters?.userId) params.append("userId", filters.userId);
if (filters?.user_id) params.append("userId", filters.user_id);
const queryString = params.toString();
const url = queryString ? `/vehicle/reports/daily?${queryString}` : "/vehicle/reports/daily";
@@ -306,11 +306,11 @@ export async function getDailyReport(filters?: { startDate?: string; endDate?: s
/**
*
*/
export async function getWeeklyReport(filters?: { year?: number; month?: number; userId?: string }) {
export async function getWeeklyReport(filters?: { year?: number; month?: number; user_id?: string }) {
const params = new URLSearchParams();
if (filters?.year) params.append("year", String(filters.year));
if (filters?.month) params.append("month", String(filters.month));
if (filters?.userId) params.append("userId", filters.userId);
if (filters?.user_id) params.append("userId", filters.user_id);
const queryString = params.toString();
const url = queryString ? `/vehicle/reports/weekly?${queryString}` : "/vehicle/reports/weekly";
@@ -322,10 +322,10 @@ export async function getWeeklyReport(filters?: { year?: number; month?: number;
/**
*
*/
export async function getMonthlyReport(filters?: { year?: number; userId?: string }) {
export async function getMonthlyReport(filters?: { year?: number; user_id?: string }) {
const params = new URLSearchParams();
if (filters?.year) params.append("year", String(filters.year));
if (filters?.userId) params.append("userId", filters.userId);
if (filters?.user_id) params.append("userId", filters.user_id);
const queryString = params.toString();
const url = queryString ? `/vehicle/reports/monthly?${queryString}` : "/vehicle/reports/monthly";
@@ -308,6 +308,10 @@ export const DynamicComponentRenderer: React.FC<DynamicComponentRendererProps> =
onDragStart,
onDragEnd,
children,
screenId,
userId,
userName,
companyCode,
...props
}) => {
// 컬럼 메타데이터 로드 트리거 (TTL 기반 자동 갱신)
@@ -652,7 +656,7 @@ export const DynamicComponentRenderer: React.FC<DynamicComponentRendererProps> =
isInteractive={props.isInteractive}
formData={props.formData}
onFormDataChange={props.onFormDataChange}
screenId={props.screenId}
screenId={screenId}
tableName={props.tableName}
onRefresh={props.onRefresh}
onClose={props.onClose}
@@ -1093,11 +1097,11 @@ export const DynamicComponentRenderer: React.FC<DynamicComponentRendererProps> =
isInteractive: props.isInteractive,
formData: props.formData,
onFormDataChange: props.onFormDataChange,
screenId: props.screenId,
screenId,
tableName: props.tableName,
userId: props.userId, // 🆕 사용자 ID
userName: props.userName, // 🆕 사용자 이름
companyCode: props.companyCode, // 🆕 회사 코드
userId, // 🆕 사용자 ID
userName, // 🆕 사용자 이름
companyCode, // 🆕 회사 코드
onRefresh: props.onRefresh,
onClose: props.onClose,
mode: props.mode,
@@ -112,7 +112,8 @@ export const ButtonPrimaryComponent: React.FC<ButtonPrimaryComponentProps> = ({
// 🆕 tableName이 props로 전달되지 않으면 ScreenContext에서 가져오기
const effectiveTableName = tableName || screenContext?.tableName;
const effectiveScreenId = screenId || screenContext?.screenId;
const { screenId: contextScreenId } = screenContext ?? {};
const effectiveScreenId = screenId || contextScreenId;
// 🆕 props에서 onSave 추출 (명시적으로 선언되지 않은 경우 ...props에서 추출)
const propsOnSave = (props as any).onSave as (() => Promise<void>) | undefined;
@@ -213,13 +213,15 @@ export function ConditionalContainerComponent({
{isDesignMode ? (
// 디자인 모드: 모든 섹션 표시
<div className={spacingClass}>
{sections.map((section) => (
{sections.map((section) => {
const { screenId: sectionScreenId } = section;
return (
<ConditionalSectionViewer
key={section.id}
sectionId={section.id}
condition={section.condition}
label={section.label}
screenId={section.screenId}
screenId={sectionScreenId}
screenName={section.screenName}
isActive={selectedValue === section.condition}
isDesignMode={isDesignMode}
@@ -232,18 +234,20 @@ export function ConditionalContainerComponent({
selectedCondition={selectedValue}
initialData={initialData}
/>
))}
);
})}
</div>
) : (
// 실행 모드: 활성 섹션만 표시
sections.map((section) =>
selectedValue === section.condition ? (
sections.map((section) => {
const { screenId: sectionScreenId } = section;
return selectedValue === section.condition ? (
<ConditionalSectionViewer
key={section.id}
sectionId={section.id}
condition={section.condition}
label={section.label}
screenId={section.screenId}
screenId={sectionScreenId}
screenName={section.screenName}
isActive={true}
isDesignMode={false}
@@ -256,8 +260,8 @@ export function ConditionalContainerComponent({
selectedCondition={selectedValue}
initialData={initialData}
/>
) : null
)
) : null;
})
)}
{/* 섹션이 없는 경우 안내 */}
@@ -503,7 +503,9 @@ export function ConditionalContainerConfigPanel({
</div>
) : (
<div className="space-y-3">
{localConfig.sections.map((section, index) => (
{localConfig.sections.map((section, index) => {
const { screenId: sectionScreenId } = section;
return (
<div
key={section.id}
className="p-3 border rounded-lg space-y-3 bg-muted/20"
@@ -568,7 +570,7 @@ export function ConditionalContainerConfigPanel({
</div>
) : (
<Select
value={section.screenId?.toString() || "none"}
value={sectionScreenId?.toString() || "none"}
onValueChange={(value) => {
if (value === "none") {
updateSection(section.id, {
@@ -608,14 +610,15 @@ export function ConditionalContainerConfigPanel({
</SelectContent>
</Select>
)}
{section.screenId && (
{sectionScreenId && (
<div className="text-[10px] text-muted-foreground">
ID: {section.screenId}
ID: {sectionScreenId}
</div>
)}
</div>
</div>
))}
);
})}
</div>
)}
</div>
@@ -339,7 +339,8 @@ export function ModalRepeaterTableComponent({
: rawUniqueField;
const filterCondition = componentConfig?.filterCondition || propFilterCondition || {};
const companyCode = componentConfig?.companyCode || propCompanyCode;
const { companyCode: configCompanyCode } = componentConfig ?? {};
const companyCode = configCompanyCode || propCompanyCode;
const [modalOpen, setModalOpen] = useState(false);
// 체크박스 선택 상태
@@ -16,7 +16,7 @@ import type { RelatedDataButtonsConfig } from "./types";
// 화면 정보 타입
interface ScreenInfo {
screenId: number;
screen_id: number;
screenName: string;
tableName?: string;
}
@@ -40,7 +40,7 @@ const ScreenSelector: React.FC<ScreenSelectorProps> = ({ value, onChange, placeh
const response = await screenApi.getScreens({ size: 500 });
if (response.data) {
setScreens(response.data.map((s: any) => ({
screenId: s.screen_id,
screen_id: s.screen_id,
screenName: s.screen_name || s.name || `화면 ${s.screen_id}`,
tableName: s.table_name,
})));
@@ -54,13 +54,13 @@ const ScreenSelector: React.FC<ScreenSelectorProps> = ({ value, onChange, placeh
loadScreens();
}, []);
const selectedScreen = screens.find(s => s.screenId === value);
const selectedScreen = screens.find(s => s.screen_id === value);
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button variant="outline" role="combobox" className="w-full justify-between text-xs h-9">
{loading ? "로딩중..." : selectedScreen ? `${selectedScreen.screenName} (${selectedScreen.screenId})` : placeholder}
{loading ? "로딩중..." : selectedScreen ? `${selectedScreen.screenName} (${selectedScreen.screen_id})` : placeholder}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
@@ -72,17 +72,17 @@ const ScreenSelector: React.FC<ScreenSelectorProps> = ({ value, onChange, placeh
<CommandGroup className="max-h-[200px] overflow-auto">
{screens.map((screen) => (
<CommandItem
key={screen.screenId}
value={`${screen.screenName} ${screen.screenId}`}
key={screen.screen_id}
value={`${screen.screenName} ${screen.screen_id}`}
onSelect={() => {
onChange(screen.screenId, screen.tableName);
onChange(screen.screen_id, screen.tableName);
setOpen(false);
}}
className="text-xs"
>
<Check className={cn("mr-2 h-4 w-4", value === screen.screenId ? "opacity-100" : "opacity-0")} />
<Check className={cn("mr-2 h-4 w-4", value === screen.screen_id ? "opacity-100" : "opacity-0")} />
<span className="truncate">{screen.screenName}</span>
<span className="ml-auto text-muted-foreground">({screen.screenId})</span>
<span className="ml-auto text-muted-foreground">({screen.screen_id})</span>
</CommandItem>
))}
</CommandGroup>
+2 -2
View File
@@ -99,7 +99,7 @@ export class AutoGenerationUtils {
if (token) {
try {
const payload = JSON.parse(atob(token.split(".")[1]));
return payload.userId || payload.id || "unknown";
return payload.user_id || payload.id || "unknown";
} catch {
// JWT 파싱 실패 시 fallback
}
@@ -118,7 +118,7 @@ export class AutoGenerationUtils {
if (companyInfo) {
try {
const parsed = JSON.parse(companyInfo);
return parsed.companyCode || parsed.code || "COMPANY";
return parsed.company_code || parsed.code || "COMPANY";
} catch {
return "COMPANY";
}
@@ -17,9 +17,9 @@ import { ButtonActionType } from "@/types/v2-core";
export interface ButtonExecutionContext {
buttonId: string;
screenId: string;
userId: string;
companyCode: string;
screen_id: string;
user_id: string;
company_code: string;
startTime: number;
formData?: Record<string, any>;
selectedRows?: any[];
@@ -300,9 +300,9 @@ export class ImprovedButtonActionExecutor {
contextData: config.contextData || {},
selectedRows: context.selectedRows || [],
flowSelectedData: context.flowSelectedData || [],
screen_id: context.screenId,
company_code: context.companyCode,
user_id: context.userId,
screen_id: context.screen_id,
company_code: context.company_code,
user_id: context.user_id,
});
const executionTime = Date.now() - startTime;
@@ -991,7 +991,7 @@ export class ImprovedButtonActionExecutor {
targetRecordId: targetRecordId ? String(targetRecordId) : "",
targetRecordData: Object.keys(selectedRow).length > 0 ? selectedRow : undefined,
definitionId: actionConfig.approvalDefinitionId || undefined,
screenId: context.screenId ? Number(context.screenId) : undefined,
screenId: context.screen_id ? Number(context.screen_id) : undefined,
buttonComponentId: context.buttonId,
},
}),
+6 -6
View File
@@ -9,9 +9,9 @@ import type { ButtonDataflowConfig, ExtendedControlContext } from "@/types/contr
export interface ButtonExecutionContext {
buttonId: string;
screenId?: number;
companyCode?: string;
userId?: string;
screen_id?: number;
company_code?: string;
user_id?: string;
formData: Record<string, any>;
selectedRows?: any[];
selectedRowsData?: Record<string, any>[];
@@ -193,9 +193,9 @@ function prepareContextData(context: ButtonExecutionContext): Record<string, any
const baseContext = {
buttonId: context.buttonId,
screenId: context.screenId,
companyCode: context.companyCode,
userId: context.userId,
screen_id: context.screen_id,
company_code: context.company_code,
user_id: context.user_id,
controlDataSource: dataSource,
};