diff --git a/backend-node/src/controllers/tableManagementController.ts b/backend-node/src/controllers/tableManagementController.ts index c4c29503..8b1f859d 100644 --- a/backend-node/src/controllers/tableManagementController.ts +++ b/backend-node/src/controllers/tableManagementController.ts @@ -62,10 +62,10 @@ export async function getColumnList( try { const { tableName } = req.params; const { page = 1, size = 50 } = req.query; - + // πŸ”₯ νšŒμ‚¬ μ½”λ“œ μΆ”μΆœ (JWTμ—μ„œ λ˜λŠ” DBμ—μ„œ 쑰회) let companyCode = req.user?.companyCode; - + if (!companyCode && req.user?.userId) { // JWT에 μ—†μœΌλ©΄ DBμ—μ„œ 쑰회 const { query } = require("../database/db"); @@ -74,7 +74,9 @@ export async function getColumnList( [req.user.userId] ); companyCode = userResult[0]?.company_code; - logger.info(`DBμ—μ„œ νšŒμ‚¬ μ½”λ“œ 쑰회 (컬럼 λͺ©λ‘): ${req.user.userId} β†’ ${companyCode}`); + logger.info( + `DBμ—μ„œ νšŒμ‚¬ μ½”λ“œ 쑰회 (컬럼 λͺ©λ‘): ${req.user.userId} β†’ ${companyCode}` + ); } logger.info( @@ -139,10 +141,10 @@ export async function updateColumnSettings( try { const { tableName, columnName } = req.params; const settings: ColumnSettings = req.body; - + // πŸ”₯ νšŒμ‚¬ μ½”λ“œ μΆ”μΆœ (JWTμ—μ„œ λ˜λŠ” DBμ—μ„œ 쑰회) let companyCode = req.user?.companyCode; - + if (!companyCode && req.user?.userId) { // JWT에 μ—†μœΌλ©΄ DBμ—μ„œ 쑰회 const { query } = require("../database/db"); @@ -154,7 +156,9 @@ export async function updateColumnSettings( logger.info(`DBμ—μ„œ νšŒμ‚¬ μ½”λ“œ 쑰회: ${req.user.userId} β†’ ${companyCode}`); } - logger.info(`=== 컬럼 μ„€μ • μ—…λ°μ΄νŠΈ μ‹œμž‘: ${tableName}.${columnName}, company: ${companyCode} ===`); + logger.info( + `=== 컬럼 μ„€μ • μ—…λ°μ΄νŠΈ μ‹œμž‘: ${tableName}.${columnName}, company: ${companyCode} ===` + ); if (!tableName || !columnName) { const response: ApiResponse = { @@ -194,7 +198,8 @@ export async function updateColumnSettings( message: "νšŒμ‚¬ μ½”λ“œλ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€.", error: { code: "MISSING_COMPANY_CODE", - details: "μ‚¬μš©μž μ •λ³΄μ—μ„œ νšŒμ‚¬ μ½”λ“œλ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€. κ΄€λ¦¬μžμ—κ²Œ λ¬Έμ˜ν•˜μ„Έμš”.", + details: + "μ‚¬μš©μž μ •λ³΄μ—μ„œ νšŒμ‚¬ μ½”λ“œλ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€. κ΄€λ¦¬μžμ—κ²Œ λ¬Έμ˜ν•˜μ„Έμš”.", }, }; res.status(400).json(response); @@ -209,7 +214,9 @@ export async function updateColumnSettings( companyCode // πŸ”₯ νšŒμ‚¬ μ½”λ“œ 전달 ); - logger.info(`컬럼 μ„€μ • μ—…λ°μ΄νŠΈ μ™„λ£Œ: ${tableName}.${columnName}, company: ${companyCode}`); + logger.info( + `컬럼 μ„€μ • μ—…λ°μ΄νŠΈ μ™„λ£Œ: ${tableName}.${columnName}, company: ${companyCode}` + ); const response: ApiResponse = { success: true, @@ -243,10 +250,10 @@ export async function updateAllColumnSettings( try { const { tableName } = req.params; const columnSettings: ColumnSettings[] = req.body; - + // πŸ”₯ νšŒμ‚¬ μ½”λ“œ μΆ”μΆœ (JWTμ—μ„œ λ˜λŠ” DBμ—μ„œ 쑰회) let companyCode = req.user?.companyCode; - + if (!companyCode && req.user?.userId) { // JWT에 μ—†μœΌλ©΄ DBμ—μ„œ 쑰회 const { query } = require("../database/db"); @@ -264,7 +271,9 @@ export async function updateAllColumnSettings( logger.info(`[DEBUG] req.user?.userId: ${req.user?.userId}`); logger.info(`[DEBUG] companyCode μ΅œμ’…κ°’: ${companyCode}`); - logger.info(`=== 전체 컬럼 μ„€μ • 일괄 μ—…λ°μ΄νŠΈ μ‹œμž‘: ${tableName}, company: ${companyCode} ===`); + logger.info( + `=== 전체 컬럼 μ„€μ • 일괄 μ—…λ°μ΄νŠΈ μ‹œμž‘: ${tableName}, company: ${companyCode} ===` + ); if (!tableName) { const response: ApiResponse = { @@ -305,7 +314,8 @@ export async function updateAllColumnSettings( message: "νšŒμ‚¬ μ½”λ“œλ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€.", error: { code: "MISSING_COMPANY_CODE", - details: "μ‚¬μš©μž μ •λ³΄μ—μ„œ νšŒμ‚¬ μ½”λ“œλ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€. κ΄€λ¦¬μžμ—κ²Œ λ¬Έμ˜ν•˜μ„Έμš”.", + details: + "μ‚¬μš©μž μ •λ³΄μ—μ„œ νšŒμ‚¬ μ½”λ“œλ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€. κ΄€λ¦¬μžμ—κ²Œ λ¬Έμ˜ν•˜μ„Έμš”.", }, }; res.status(400).json(response); @@ -543,10 +553,10 @@ export async function updateColumnInputType( try { const { tableName, columnName } = req.params; const { inputType, detailSettings } = req.body; - + // πŸ”₯ νšŒμ‚¬ μ½”λ“œ μΆ”μΆœ (JWTμ—μ„œ λ˜λŠ” DBμ—μ„œ 쑰회) let companyCode = req.user?.companyCode; - + if (!companyCode && req.user?.userId) { // JWT에 μ—†μœΌλ©΄ DBμ—μ„œ 쑰회 const { query } = require("../database/db"); @@ -588,7 +598,8 @@ export async function updateColumnInputType( message: "νšŒμ‚¬ μ½”λ“œλ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€.", error: { code: "MISSING_COMPANY_CODE", - details: "μ‚¬μš©μž μ •λ³΄μ—μ„œ νšŒμ‚¬ μ½”λ“œλ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€. κ΄€λ¦¬μžμ—κ²Œ λ¬Έμ˜ν•˜μ„Έμš”.", + details: + "μ‚¬μš©μž μ •λ³΄μ—μ„œ νšŒμ‚¬ μ½”λ“œλ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€. κ΄€λ¦¬μžμ—κ²Œ λ¬Έμ˜ν•˜μ„Έμš”.", }, }; res.status(400).json(response); @@ -1085,10 +1096,10 @@ export async function getColumnWebTypes( ): Promise { try { const { tableName } = req.params; - + // πŸ”₯ νšŒμ‚¬ μ½”λ“œ μΆ”μΆœ (JWTμ—μ„œ λ˜λŠ” DBμ—μ„œ 쑰회) let companyCode = req.user?.companyCode; - + if (!companyCode && req.user?.userId) { // JWT에 μ—†μœΌλ©΄ DBμ—μ„œ 쑰회 const { query } = require("../database/db"); @@ -1097,7 +1108,9 @@ export async function getColumnWebTypes( [req.user.userId] ); companyCode = userResult[0]?.company_code; - logger.info(`DBμ—μ„œ νšŒμ‚¬ μ½”λ“œ 쑰회 (쑰회): ${req.user.userId} β†’ ${companyCode}`); + logger.info( + `DBμ—μ„œ νšŒμ‚¬ μ½”λ“œ 쑰회 (쑰회): ${req.user.userId} β†’ ${companyCode}` + ); } logger.info( @@ -1129,7 +1142,8 @@ export async function getColumnWebTypes( message: "νšŒμ‚¬ μ½”λ“œλ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€.", error: { code: "MISSING_COMPANY_CODE", - details: "μ‚¬μš©μž μ •λ³΄μ—μ„œ νšŒμ‚¬ μ½”λ“œλ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€. κ΄€λ¦¬μžμ—κ²Œ λ¬Έμ˜ν•˜μ„Έμš”.", + details: + "μ‚¬μš©μž μ •λ³΄μ—μ„œ νšŒμ‚¬ μ½”λ“œλ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€. κ΄€λ¦¬μžμ—κ²Œ λ¬Έμ˜ν•˜μ„Έμš”.", }, }; res.status(400).json(response); diff --git a/backend-node/src/routes/dataRoutes.ts b/backend-node/src/routes/dataRoutes.ts index 33413da3..344eb1a9 100644 --- a/backend-node/src/routes/dataRoutes.ts +++ b/backend-node/src/routes/dataRoutes.ts @@ -357,8 +357,25 @@ router.post( console.log(`βž• λ ˆμ½”λ“œ 생성: ${tableName}`, data); + // company_code와 company_name μžλ™ μΆ”κ°€ (λ©€ν‹°ν…Œλ„Œμ‹œ) + const enrichedData = { ...data }; + + // ν…Œμ΄λΈ”μ— company_code 컬럼이 μžˆλŠ”μ§€ ν™•μΈν•˜κ³  μžλ™μœΌλ‘œ μΆ”κ°€ + const hasCompanyCode = await dataService.checkColumnExists(tableName, "company_code"); + if (hasCompanyCode && req.user?.companyCode) { + enrichedData.company_code = req.user.companyCode; + console.log(`🏒 company_code μžλ™ μΆ”κ°€: ${req.user.companyCode}`); + } + + // ν…Œμ΄λΈ”μ— company_name 컬럼이 μžˆλŠ”μ§€ ν™•μΈν•˜κ³  μžλ™μœΌλ‘œ μΆ”κ°€ + const hasCompanyName = await dataService.checkColumnExists(tableName, "company_name"); + if (hasCompanyName && req.user?.companyName) { + enrichedData.company_name = req.user.companyName; + console.log(`🏒 company_name μžλ™ μΆ”κ°€: ${req.user.companyName}`); + } + // λ ˆμ½”λ“œ 생성 - const result = await dataService.createRecord(tableName, data); + const result = await dataService.createRecord(tableName, enrichedData); if (!result.success) { return res.status(400).json(result); diff --git a/backend-node/src/services/authService.ts b/backend-node/src/services/authService.ts index 7c4f4c8d..11e34576 100644 --- a/backend-node/src/services/authService.ts +++ b/backend-node/src/services/authService.ts @@ -165,12 +165,13 @@ export class AuthService { const authNames = authResult.map((row) => row.auth_name).join(","); // 3. νšŒμ‚¬ 정보 쑰회 (Raw Query μ „ν™˜) - // Note: ν˜„μž¬ νšŒμ‚¬ μ •λ³΄λŠ” PersonBean에 직접 μ‚¬μš©λ˜μ§€ μ•Šμ§€λ§Œ ν–₯ν›„ ν™•μž₯을 μœ„ν•΄ μœ μ§€ const companyResult = await query<{ company_name: string }>( "SELECT company_name FROM company_mng WHERE company_code = $1", [userInfo.company_code || "ILSHIN"] ); + const companyName = companyResult.length > 0 ? companyResult[0].company_name : undefined; + // DBμ—μ„œ μ‘°νšŒν•œ 원본 μ‚¬μš©μž 정보 상세 둜그 //console.log("πŸ” AuthService - DB 원본 μ‚¬μš©μž 정보:", { // userId: userInfo.user_id, @@ -205,6 +206,7 @@ export class AuthService { partnerObjid: userInfo.partner_objid || undefined, authName: authNames || undefined, companyCode: companyCode, + companyName: companyName, // νšŒμ‚¬λͺ… μΆ”κ°€ photo: userInfo.photo ? `data:image/jpeg;base64,${Buffer.from(userInfo.photo).toString("base64")}` : undefined, diff --git a/backend-node/src/services/dataService.ts b/backend-node/src/services/dataService.ts index eb69797e..c970878b 100644 --- a/backend-node/src/services/dataService.ts +++ b/backend-node/src/services/dataService.ts @@ -231,6 +231,9 @@ class DataService { const columns = await this.getTableColumnsSimple(tableName); + // PK 컬럼 정보 쑰회 + const pkColumns = await this.getPrimaryKeyColumns(tableName); + // 컬럼 라벨 정보 μΆ”κ°€ const columnsWithLabels = await Promise.all( columns.map(async (column) => { @@ -244,6 +247,7 @@ class DataService { dataType: column.data_type, isNullable: column.is_nullable === "YES", defaultValue: column.column_default, + isPrimaryKey: pkColumns.includes(column.column_name), // PK μ—¬λΆ€ μΆ”κ°€ }; }) ); @@ -262,6 +266,26 @@ class DataService { } } + /** + * ν…Œμ΄λΈ”μ˜ Primary Key 컬럼 λͺ©λ‘ 쑰회 + */ + private async getPrimaryKeyColumns(tableName: string): Promise { + try { + const result = await query<{ attname: string }>( + `SELECT a.attname + FROM pg_index i + JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = ANY(i.indkey) + WHERE i.indrelid = $1::regclass AND i.indisprimary`, + [tableName] + ); + + return result.map((row) => row.attname); + } catch (error) { + console.error(`PK 컬럼 쑰회 였λ₯˜ (${tableName}):`, error); + return []; + } + } + /** * ν…Œμ΄λΈ” 쑴재 μ—¬λΆ€ 확인 */ @@ -286,7 +310,7 @@ class DataService { /** * νŠΉμ • 컬럼 쑴재 μ—¬λΆ€ 확인 */ - private async checkColumnExists( + async checkColumnExists( tableName: string, columnName: string ): Promise { diff --git a/backend-node/src/types/auth.ts b/backend-node/src/types/auth.ts index 35a2c0f5..6abd1e39 100644 --- a/backend-node/src/types/auth.ts +++ b/backend-node/src/types/auth.ts @@ -61,6 +61,7 @@ export interface PersonBean { partnerObjid?: string; authName?: string; companyCode?: string; + companyName?: string; // νšŒμ‚¬λͺ… μΆ”κ°€ photo?: string; locale?: string; // κΆŒν•œ 레벨 정보 (3단계 체계) @@ -94,6 +95,7 @@ export interface JwtPayload { userName: string; deptName?: string; companyCode?: string; + companyName?: string; // νšŒμ‚¬λͺ… μΆ”κ°€ userType?: string; userTypeName?: string; iat?: number; diff --git a/backend-node/src/utils/jwtUtils.ts b/backend-node/src/utils/jwtUtils.ts index f65781fc..44f75cbc 100644 --- a/backend-node/src/utils/jwtUtils.ts +++ b/backend-node/src/utils/jwtUtils.ts @@ -17,6 +17,7 @@ export class JwtUtils { userName: userInfo.userName, deptName: userInfo.deptName, companyCode: userInfo.companyCode, + companyName: userInfo.companyName, // νšŒμ‚¬λͺ… μΆ”κ°€ userType: userInfo.userType, userTypeName: userInfo.userTypeName, }; @@ -45,6 +46,7 @@ export class JwtUtils { userName: decoded.userName, deptName: decoded.deptName, companyCode: decoded.companyCode, + companyName: decoded.companyName, // νšŒμ‚¬λͺ… μΆ”κ°€ userType: decoded.userType, userTypeName: decoded.userTypeName, }; diff --git a/frontend/lib/api/data.ts b/frontend/lib/api/data.ts index 3f53db1f..3f621a3c 100644 --- a/frontend/lib/api/data.ts +++ b/frontend/lib/api/data.ts @@ -83,7 +83,7 @@ export const dataApi = { */ createRecord: async (tableName: string, data: Record): Promise => { const response = await apiClient.post(`/data/${tableName}`, data); - return response.data?.data || response.data; + return response.data; // success, data, message ν¬ν•¨λœ 전체 응닡 λ°˜ν™˜ }, /** diff --git a/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutComponent.tsx b/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutComponent.tsx index 670900d5..351dc218 100644 --- a/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutComponent.tsx +++ b/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutComponent.tsx @@ -6,10 +6,12 @@ import { SplitPanelLayoutConfig } from "./types"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; -import { Plus, Search, GripVertical, Loader2, ChevronDown, ChevronUp } from "lucide-react"; +import { Plus, Search, GripVertical, Loader2, ChevronDown, ChevronUp, Save } from "lucide-react"; import { dataApi } from "@/lib/api/data"; import { useToast } from "@/hooks/use-toast"; import { tableTypeApi } from "@/lib/api/screen"; +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription } from "@/components/ui/dialog"; +import { Label } from "@/components/ui/label"; export interface SplitPanelLayoutComponentProps extends ComponentRendererProps { // μΆ”κ°€ props @@ -47,6 +49,11 @@ export const SplitPanelLayoutComponent: React.FC const [rightTableColumns, setRightTableColumns] = useState([]); // 우츑 ν…Œμ΄λΈ” 컬럼 정보 const { toast } = useToast(); + // μΆ”κ°€ λͺ¨λ‹¬ μƒνƒœ + const [showAddModal, setShowAddModal] = useState(false); + const [addModalPanel, setAddModalPanel] = useState<"left" | "right" | null>(null); + const [addModalFormData, setAddModalFormData] = useState>({}); + // 리사이저 λ“œλž˜κ·Έ μƒνƒœ const [isDragging, setIsDragging] = useState(false); const [leftWidth, setLeftWidth] = useState(splitRatio); @@ -208,6 +215,115 @@ export const SplitPanelLayoutComponent: React.FC loadRightTableColumns(); }, [componentConfig.rightPanel?.tableName, isDesignMode]); + // μΆ”κ°€ λ²„νŠΌ ν•Έλ“€λŸ¬ + const handleAddClick = useCallback((panel: "left" | "right") => { + setAddModalPanel(panel); + setAddModalFormData({}); + setShowAddModal(true); + }, []); + + // μΆ”κ°€ λͺ¨λ‹¬ μ €μž₯ + const handleAddModalSave = useCallback(async () => { + const tableName = addModalPanel === "left" + ? componentConfig.leftPanel?.tableName + : componentConfig.rightPanel?.tableName; + + const modalColumns = addModalPanel === "left" + ? componentConfig.leftPanel?.addModalColumns + : componentConfig.rightPanel?.addModalColumns; + + if (!tableName) { + toast({ + title: "ν…Œμ΄λΈ” 였λ₯˜", + description: "ν…Œμ΄λΈ”λͺ…이 μ„€μ •λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€.", + variant: "destructive", + }); + return; + } + + // ν•„μˆ˜ ν•„λ“œ 검증 + const requiredFields = (modalColumns || []).filter(col => col.required); + for (const field of requiredFields) { + if (!addModalFormData[field.name]) { + toast({ + title: "μž…λ ₯ 였λ₯˜", + description: `${field.label}은(λŠ”) ν•„μˆ˜ μž…λ ₯ ν•­λͺ©μž…λ‹ˆλ‹€.`, + variant: "destructive", + }); + return; + } + } + + try { + console.log("πŸ“ 데이터 μΆ”κ°€:", { tableName, data: addModalFormData }); + + const result = await dataApi.createRecord(tableName, addModalFormData); + + if (result.success) { + toast({ + title: "성곡", + description: "데이터가 μ„±κ³΅μ μœΌλ‘œ μΆ”κ°€λ˜μ—ˆμŠ΅λ‹ˆλ‹€.", + }); + + // λͺ¨λ‹¬ λ‹«κΈ° + setShowAddModal(false); + setAddModalFormData({}); + + // 데이터 μƒˆλ‘œκ³ μΉ¨ + if (addModalPanel === "left") { + loadLeftData(); + } else if (selectedLeftItem) { + loadRightData(selectedLeftItem); + } + } else { + toast({ + title: "μ €μž₯ μ‹€νŒ¨", + description: result.message || "데이터 좔가에 μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€.", + variant: "destructive", + }); + } + } catch (error: any) { + console.error("데이터 μΆ”κ°€ 였λ₯˜:", error); + + // μ—λŸ¬ λ©”μ‹œμ§€ μΆ”μΆœ + let errorMessage = "데이터 μΆ”κ°€ 쀑 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€."; + + if (error?.response?.data) { + const responseData = error.response.data; + + // λ°±μ—”λ“œμ—μ„œ λ°˜ν™˜ν•œ μ—λŸ¬ λ©”μ‹œμ§€ 확인 + if (responseData.error) { + // 쀑볡 ν‚€ μ—λŸ¬ 처리 + if (responseData.error.includes("duplicate key")) { + errorMessage = "이미 μ‘΄μž¬ν•˜λŠ” κ°’μž…λ‹ˆλ‹€. λ‹€λ₯Έ 값을 μž…λ ₯ν•΄μ£Όμ„Έμš”."; + } + // NOT NULL μ œμ•½μ‘°κ±΄ μ—λŸ¬ + else if (responseData.error.includes("null value")) { + const match = responseData.error.match(/column "(\w+)"/); + const columnName = match ? match[1] : "ν•„μˆ˜"; + errorMessage = `${columnName} ν•„λ“œλŠ” ν•„μˆ˜ μž…λ ₯ ν•­λͺ©μž…λ‹ˆλ‹€.`; + } + // μ™Έλž˜ν‚€ μ œμ•½μ‘°κ±΄ μ—λŸ¬ + else if (responseData.error.includes("foreign key")) { + errorMessage = "μ°Έμ‘°ν•˜λŠ” 데이터가 μ‘΄μž¬ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€."; + } + // 기타 μ—λŸ¬ + else { + errorMessage = responseData.message || responseData.error; + } + } else if (responseData.message) { + errorMessage = responseData.message; + } + } + + toast({ + title: "였λ₯˜", + description: errorMessage, + variant: "destructive", + }); + } + }, [addModalPanel, componentConfig, addModalFormData, toast, selectedLeftItem, loadLeftData, loadRightData]); + // 초기 데이터 λ‘œλ“œ useEffect(() => { if (!isDesignMode && componentConfig.autoLoad !== false) { @@ -295,8 +411,12 @@ export const SplitPanelLayoutComponent: React.FC {componentConfig.leftPanel?.title || "쒌츑 νŒ¨λ„"} - {componentConfig.leftPanel?.showAdd && ( - @@ -478,8 +598,12 @@ export const SplitPanelLayoutComponent: React.FC {componentConfig.rightPanel?.title || "우츑 νŒ¨λ„"} - {componentConfig.rightPanel?.showAdd && ( - @@ -712,6 +836,63 @@ export const SplitPanelLayoutComponent: React.FC + + {/* μΆ”κ°€ λͺ¨λ‹¬ */} + + + + + {addModalPanel === "left" ? componentConfig.leftPanel?.title : componentConfig.rightPanel?.title} μΆ”κ°€ + + + μƒˆλ‘œμš΄ 데이터λ₯Ό μΆ”κ°€ν•©λ‹ˆλ‹€. ν•„μˆ˜ ν•­λͺ©μ„ μž…λ ₯ν•΄μ£Όμ„Έμš”. + + + +
+ {(addModalPanel === "left" + ? componentConfig.leftPanel?.addModalColumns + : componentConfig.rightPanel?.addModalColumns + )?.map((col, index) => ( +
+ + { + setAddModalFormData(prev => ({ + ...prev, + [col.name]: e.target.value + })); + }} + placeholder={`${col.label} μž…λ ₯`} + className="h-8 text-xs sm:h-10 sm:text-sm" + required={col.required} + /> +
+ ))} +
+ + + + + +
+
); }; diff --git a/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutConfigPanel.tsx b/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutConfigPanel.tsx index 320d3063..2c7a0f00 100644 --- a/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutConfigPanel.tsx +++ b/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutConfigPanel.tsx @@ -74,6 +74,38 @@ export const SplitPanelLayoutConfigPanel: React.FC { + const leftTableName = config.leftPanel?.tableName || screenTableName; + if (leftTableName && loadedTableColumns[leftTableName] && config.leftPanel?.showAdd) { + const currentAddModalColumns = config.leftPanel?.addModalColumns || []; + const updatedColumns = ensurePrimaryKeysInAddModal(leftTableName, currentAddModalColumns); + + // PKκ°€ μΆ”κ°€λ˜μ—ˆμœΌλ©΄ μ—…λ°μ΄νŠΈ + if (updatedColumns.length !== currentAddModalColumns.length) { + console.log(`πŸ”„ 쒌츑 νŒ¨λ„: PK 컬럼 μžλ™ μΆ”κ°€ (${leftTableName})`); + updateLeftPanel({ addModalColumns: updatedColumns }); + } + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [config.leftPanel?.tableName, screenTableName, loadedTableColumns, config.leftPanel?.showAdd]); + + // 우츑 νŒ¨λ„ ν…Œμ΄λΈ” 컬럼 λ‘œλ“œ μ™„λ£Œ μ‹œ PK μžλ™ μΆ”κ°€ + useEffect(() => { + const rightTableName = config.rightPanel?.tableName; + if (rightTableName && loadedTableColumns[rightTableName] && config.rightPanel?.showAdd) { + const currentAddModalColumns = config.rightPanel?.addModalColumns || []; + const updatedColumns = ensurePrimaryKeysInAddModal(rightTableName, currentAddModalColumns); + + // PKκ°€ μΆ”κ°€λ˜μ—ˆμœΌλ©΄ μ—…λ°μ΄νŠΈ + if (updatedColumns.length !== currentAddModalColumns.length) { + console.log(`πŸ”„ 우츑 νŒ¨λ„: PK 컬럼 μžλ™ μΆ”κ°€ (${rightTableName})`); + updateRightPanel({ addModalColumns: updatedColumns }); + } + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [config.rightPanel?.tableName, loadedTableColumns, config.rightPanel?.showAdd]); + // ν…Œμ΄λΈ” 컬럼 λ‘œλ“œ ν•¨μˆ˜ const loadTableColumns = async (tableName: string) => { if (loadedTableColumns[tableName] || loadingColumns[tableName]) { @@ -98,6 +130,7 @@ export const SplitPanelLayoutConfigPanel: React.FC = [] + ) => { + const tableColumns = loadedTableColumns[tableName]; + if (!tableColumns) { + console.warn(`⚠️ ν…Œμ΄λΈ” ${tableName}의 컬럼 정보가 λ‘œλ“œλ˜μ§€ μ•ŠμŒ`); + return existingColumns; + } + + // PK 컬럼 μ°ΎκΈ° + const pkColumns = tableColumns.filter((col) => col.isPrimaryKey); + console.log(`πŸ”‘ ν…Œμ΄λΈ” ${tableName}의 PK 컬럼:`, pkColumns.map(c => c.columnName)); + + // μžλ™μœΌλ‘œ μ²˜λ¦¬λ˜λŠ” 컬럼 (λ°±μ—”λ“œμ—μ„œ μžλ™ μΆ”κ°€) + const autoHandledColumns = ['company_code', 'company_name']; + + // κΈ°μ‘΄ 컬럼 이름 λͺ©λ‘ + const existingColumnNames = existingColumns.map((col) => col.name); + + // PK μ»¬λŸΌμ„ 맨 μ•žμ— μΆ”κ°€ (이미 μžˆκ±°λ‚˜ μžλ™ μ²˜λ¦¬λ˜λŠ” μ»¬λŸΌμ€ μ œμ™Έ) + const pkColumnsToAdd = pkColumns + .filter((col) => !existingColumnNames.includes(col.columnName)) + .filter((col) => !autoHandledColumns.includes(col.columnName)) // μžλ™ 처리 컬럼 μ œμ™Έ + .map((col) => ({ + name: col.columnName, + label: col.columnLabel || col.columnName, + required: true, // PKλŠ” 항상 ν•„μˆ˜ + })); + + if (pkColumnsToAdd.length > 0) { + console.log(`βœ… PK 컬럼 ${pkColumnsToAdd.length}개 μžλ™ μΆ”κ°€:`, pkColumnsToAdd.map(c => c.name)); + } + + return [...pkColumnsToAdd, ...existingColumns]; + }; + const updateLeftPanel = (updates: Partial) => { const newConfig = { ...config, @@ -269,6 +340,149 @@ export const SplitPanelLayoutConfigPanel: React.FC + {/* 쒌츑 νŒ¨λ„ μΆ”κ°€ λͺ¨λ‹¬ 컬럼 μ„€μ • */} + {config.leftPanel?.showAdd && ( +
+
+ + +
+

+ μΆ”κ°€ λ²„νŠΌ 클릭 μ‹œ λͺ¨λ‹¬μ— ν‘œμ‹œλ  μž…λ ₯ ν•„λ“œλ₯Ό μ„ νƒν•˜μ„Έμš” +

+ +
+ {(config.leftPanel?.addModalColumns || []).length === 0 ? ( +
+

μ„€μ •λœ 컬럼이 μ—†μŠ΅λ‹ˆλ‹€

+
+ ) : ( + (config.leftPanel?.addModalColumns || []).map((col, index) => { + // ν˜„μž¬ 컬럼이 PK인지 확인 + const column = leftTableColumns.find(c => c.columnName === col.name); + const isPK = column?.isPrimaryKey || false; + + return ( +
+ {isPK && ( + + PK + + )} +
+ + + + + + + + μ»¬λŸΌμ„ 찾을 수 μ—†μŠ΅λ‹ˆλ‹€. + + {leftTableColumns + .filter((column) => !['company_code', 'company_name'].includes(column.columnName)) + .map((column) => ( + { + const newColumns = [...(config.leftPanel?.addModalColumns || [])]; + newColumns[index] = { + ...newColumns[index], + name: value, + label: column.columnLabel || value, + }; + updateLeftPanel({ addModalColumns: newColumns }); + }} + className="text-xs" + > + + {column.columnLabel || column.columnName} + + ({column.columnName}) + + + ))} + + + + +
+
+ +
+ +
+ ); + }) + )} +
+
+ )} {/* 우츑 νŒ¨λ„ μ„€μ • */} @@ -577,6 +791,151 @@ export const SplitPanelLayoutConfigPanel: React.FC + + {/* 우츑 νŒ¨λ„ μΆ”κ°€ λͺ¨λ‹¬ 컬럼 μ„€μ • */} + {config.rightPanel?.showAdd && ( +
+
+ + +
+

+ μΆ”κ°€ λ²„νŠΌ 클릭 μ‹œ λͺ¨λ‹¬μ— ν‘œμ‹œλ  μž…λ ₯ ν•„λ“œλ₯Ό μ„ νƒν•˜μ„Έμš” +

+ +
+ {(config.rightPanel?.addModalColumns || []).length === 0 ? ( +
+

μ„€μ •λœ 컬럼이 μ—†μŠ΅λ‹ˆλ‹€

+
+ ) : ( + (config.rightPanel?.addModalColumns || []).map((col, index) => { + // ν˜„μž¬ 컬럼이 PK인지 확인 + const column = rightTableColumns.find(c => c.columnName === col.name); + const isPK = column?.isPrimaryKey || false; + + return ( +
+ {isPK && ( + + PK + + )} +
+ + + + + + + + μ»¬λŸΌμ„ 찾을 수 μ—†μŠ΅λ‹ˆλ‹€. + + {rightTableColumns + .filter((column) => !['company_code', 'company_name'].includes(column.columnName)) + .map((column) => ( + { + const newColumns = [...(config.rightPanel?.addModalColumns || [])]; + newColumns[index] = { + ...newColumns[index], + name: value, + label: column.columnLabel || value, + }; + updateRightPanel({ addModalColumns: newColumns }); + }} + className="text-xs" + > + + {column.columnLabel || column.columnName} + + ({column.columnName}) + + + ))} + + + + +
+
+ +
+ +
+ ); + }) + )} +
+
+ )} {/* λ ˆμ΄μ•„μ›ƒ μ„€μ • */} diff --git a/frontend/lib/registry/components/split-panel-layout/types.ts b/frontend/lib/registry/components/split-panel-layout/types.ts index e2c6b052..a192587f 100644 --- a/frontend/lib/registry/components/split-panel-layout/types.ts +++ b/frontend/lib/registry/components/split-panel-layout/types.ts @@ -15,6 +15,12 @@ export interface SplitPanelLayoutConfig { label: string; width?: number; }>; + // μΆ”κ°€ λͺ¨λ‹¬μ—μ„œ μž…λ ₯받을 컬럼 μ„€μ • + addModalColumns?: Array<{ + name: string; + label: string; + required?: boolean; + }>; }; // 우츑 νŒ¨λ„ μ„€μ • @@ -29,6 +35,12 @@ export interface SplitPanelLayoutConfig { label: string; width?: number; }>; + // μΆ”κ°€ λͺ¨λ‹¬μ—μ„œ μž…λ ₯받을 컬럼 μ„€μ • + addModalColumns?: Array<{ + name: string; + label: string; + required?: boolean; + }>; // 쒌츑 선택 ν•­λͺ©κ³Όμ˜ 관계 μ„€μ • relation?: {