From 3ab32820e991bcebba12ba815893c5dc002d9d0e Mon Sep 17 00:00:00 2001 From: kjs Date: Thu, 4 Dec 2025 10:39:07 +0900 Subject: [PATCH 1/5] =?UTF-8?q?next.js=20=EB=B2=84=EC=A0=84=2015.4.8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lib/registry/DynamicComponentRenderer.tsx | 6 +- .../select-basic/SelectBasicComponent.tsx | 71 +++++++++------- frontend/package-lock.json | 80 +++++++++---------- frontend/package.json | 2 +- 4 files changed, 88 insertions(+), 71 deletions(-) diff --git a/frontend/lib/registry/DynamicComponentRenderer.tsx b/frontend/lib/registry/DynamicComponentRenderer.tsx index c0e0c87e..8609623b 100644 --- a/frontend/lib/registry/DynamicComponentRenderer.tsx +++ b/frontend/lib/registry/DynamicComponentRenderer.tsx @@ -170,8 +170,9 @@ export const DynamicComponentRenderer: React.FC = } }; - // ๐Ÿ†• disabledFields ์ฒดํฌ - const isFieldDisabled = props.disabledFields?.includes(columnName) || (component as any).readonly; + // ๐Ÿ†• disabledFields ์ฒดํฌ + readonly ์ฒดํฌ + const isFieldDisabled = props.disabledFields?.includes(columnName) || (component as any).disabled; + const isFieldReadonly = (component as any).readonly || (component as any).componentConfig?.readonly; return ( = placeholder={component.componentConfig?.placeholder || "์„ ํƒํ•˜์„ธ์š”"} required={(component as any).required} disabled={isFieldDisabled} + readonly={isFieldReadonly} className="w-full" /> ); diff --git a/frontend/lib/registry/components/select-basic/SelectBasicComponent.tsx b/frontend/lib/registry/components/select-basic/SelectBasicComponent.tsx index f19fafdc..30eef51b 100644 --- a/frontend/lib/registry/components/select-basic/SelectBasicComponent.tsx +++ b/frontend/lib/registry/components/select-basic/SelectBasicComponent.tsx @@ -52,6 +52,10 @@ const SelectBasicComponent: React.FC = ({ menuObjid, // ๐Ÿ†• ๋ฉ”๋‰ด OBJID ...props }) => { + // ๐Ÿ†• ์ฝ๊ธฐ์ „์šฉ/๋น„ํ™œ์„ฑํ™” ์ƒํƒœ ํ™•์ธ + const isReadonly = (component as any).readonly || (props as any).readonly || componentConfig?.readonly || false; + const isDisabled = (component as any).disabled || (props as any).disabled || componentConfig?.disabled || false; + const isFieldDisabled = isDesignMode || isReadonly || isDisabled; // ํ™”๋ฉด ์ปจํ…์ŠคํŠธ (๋ฐ์ดํ„ฐ ์ œ๊ณต์ž๋กœ ๋“ฑ๋ก) const screenContext = useScreenContextOptional(); @@ -327,7 +331,7 @@ const SelectBasicComponent: React.FC = ({ // ํด๋ฆญ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ (React Query๋กœ ๊ฐ„์†Œํ™”) const handleToggle = () => { - if (isDesignMode) return; + if (isFieldDisabled) return; // ๐Ÿ†• ์ฝ๊ธฐ์ „์šฉ/๋น„ํ™œ์„ฑํ™” ์ƒํƒœ์—์„œ๋Š” ํ† ๊ธ€ ๋ถˆ๊ฐ€ // React Query๊ฐ€ ์ž๋™์œผ๋กœ ์บ์‹œ ๊ด€๋ฆฌํ•˜๋ฏ€๋กœ ์ˆ˜๋™ ์ƒˆ๋กœ๊ณ ์นจ ๋ถˆํ•„์š” setIsOpen(!isOpen); @@ -425,7 +429,7 @@ const SelectBasicComponent: React.FC = ({ value={option.value} checked={selectedValue === option.value} onChange={() => handleOptionSelect(option.value, option.label)} - disabled={isDesignMode} + disabled={isFieldDisabled} className="border-input text-primary focus:ring-ring h-4 w-4" /> {option.label} @@ -456,12 +460,14 @@ const SelectBasicComponent: React.FC = ({ placeholder="์ฝ”๋“œ ๋˜๋Š” ์ฝ”๋“œ๋ช… ์ž…๋ ฅ..." className={cn( "h-10 w-full rounded-lg border border-gray-300 bg-white px-3 py-2 transition-all outline-none", - !isDesignMode && "hover:border-orange-400 focus:border-orange-500 focus:ring-2 focus:ring-orange-200", + !isFieldDisabled && "hover:border-orange-400 focus:border-orange-500 focus:ring-2 focus:ring-orange-200", isSelected && "ring-2 ring-orange-500", + isFieldDisabled && "bg-gray-100 cursor-not-allowed", )} - readOnly={isDesignMode} + readOnly={isFieldDisabled} + disabled={isFieldDisabled} /> - {isOpen && !isDesignMode && filteredOptions.length > 0 && ( + {isOpen && !isFieldDisabled && filteredOptions.length > 0 && (
{filteredOptions.map((option, index) => (
= ({
{selectedLabel || placeholder} = ({
- {isOpen && !isDesignMode && ( + {isOpen && !isFieldDisabled && (
{isLoadingCodes ? (
๋กœ๋”ฉ ์ค‘...
@@ -538,8 +545,9 @@ const SelectBasicComponent: React.FC = ({
{selectedValues.map((val, idx) => { @@ -567,8 +575,9 @@ const SelectBasicComponent: React.FC = ({ type="text" placeholder={selectedValues.length > 0 ? "" : placeholder} className="min-w-[100px] flex-1 border-none bg-transparent outline-none" - onClick={() => setIsOpen(true)} - readOnly={isDesignMode} + onClick={() => !isFieldDisabled && setIsOpen(true)} + readOnly={isFieldDisabled} + disabled={isFieldDisabled} />
@@ -589,19 +598,22 @@ const SelectBasicComponent: React.FC = ({ type="text" value={searchQuery} onChange={(e) => { + if (isFieldDisabled) return; setSearchQuery(e.target.value); setIsOpen(true); }} - onFocus={() => setIsOpen(true)} + onFocus={() => !isFieldDisabled && setIsOpen(true)} placeholder={placeholder} className={cn( "h-10 w-full rounded-lg border border-gray-300 bg-white px-3 py-2 transition-all outline-none", - !isDesignMode && "hover:border-orange-400 focus:border-orange-500 focus:ring-2 focus:ring-orange-200", + !isFieldDisabled && "hover:border-orange-400 focus:border-orange-500 focus:ring-2 focus:ring-orange-200", isSelected && "ring-2 ring-orange-500", + isFieldDisabled && "bg-gray-100 cursor-not-allowed", )} - readOnly={isDesignMode} + readOnly={isFieldDisabled} + disabled={isFieldDisabled} /> - {isOpen && !isDesignMode && filteredOptions.length > 0 && ( + {isOpen && !isFieldDisabled && filteredOptions.length > 0 && (
{filteredOptions.map((option, index) => (
= ({
{selectedLabel || placeholder} = ({
- {isOpen && !isDesignMode && ( + {isOpen && !isFieldDisabled && (
= ({
!isDesignMode && setIsOpen(true)} + onClick={() => !isFieldDisabled && setIsOpen(true)} style={{ - pointerEvents: isDesignMode ? "none" : "auto", + pointerEvents: isFieldDisabled ? "none" : "auto", height: "100%" }} > @@ -726,7 +740,7 @@ const SelectBasicComponent: React.FC = ({ {placeholder} )}
- {isOpen && !isDesignMode && ( + {isOpen && !isFieldDisabled && (
{(isLoadingCodes || isLoadingCategories) ? (
๋กœ๋”ฉ ์ค‘...
@@ -789,13 +803,14 @@ const SelectBasicComponent: React.FC = ({
{selectedLabel || placeholder} = ({
- {isOpen && !isDesignMode && ( + {isOpen && !isFieldDisabled && (
{isLoadingCodes ? (
๋กœ๋”ฉ ์ค‘...
diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 08f030e2..78d65fbb 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -60,7 +60,7 @@ "leaflet": "^1.9.4", "lucide-react": "^0.525.0", "mammoth": "^1.11.0", - "next": "15.4.4", + "next": "^15.4.8", "react": "19.1.0", "react-day-picker": "^9.11.1", "react-dnd": "^16.0.1", @@ -1145,9 +1145,9 @@ } }, "node_modules/@next/env": { - "version": "15.4.4", - "resolved": "https://registry.npmjs.org/@next/env/-/env-15.4.4.tgz", - "integrity": "sha512-SJKOOkULKENyHSYXE5+KiFU6itcIb6wSBjgM92meK0HVKpo94dNOLZVdLLuS7/BxImROkGoPsjR4EnuDucqiiA==", + "version": "15.4.8", + "resolved": "https://registry.npmjs.org/@next/env/-/env-15.4.8.tgz", + "integrity": "sha512-LydLa2MDI1NMrOFSkO54mTc8iIHSttj6R6dthITky9ylXV2gCGi0bHQjVCtLGRshdRPjyh2kXbxJukDtBWQZtQ==", "license": "MIT" }, "node_modules/@next/eslint-plugin-next": { @@ -1161,9 +1161,9 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "15.4.4", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.4.4.tgz", - "integrity": "sha512-eVG55dnGwfUuG+TtnUCt+mEJ+8TGgul6nHEvdb8HEH7dmJIFYOCApAaFrIrxwtEq2Cdf+0m5sG1Np8cNpw9EAw==", + "version": "15.4.8", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.4.8.tgz", + "integrity": "sha512-Pf6zXp7yyQEn7sqMxur6+kYcywx5up1J849psyET7/8pG2gQTVMjU3NzgIt8SeEP5to3If/SaWmaA6H6ysBr1A==", "cpu": [ "arm64" ], @@ -1177,9 +1177,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "15.4.4", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.4.4.tgz", - "integrity": "sha512-zqG+/8apsu49CltEj4NAmCGZvHcZbOOOsNoTVeIXphYWIbE4l6A/vuQHyqll0flU2o3dmYCXsBW5FmbrGDgljQ==", + "version": "15.4.8", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.4.8.tgz", + "integrity": "sha512-xla6AOfz68a6kq3gRQccWEvFC/VRGJmA/QuSLENSO7CZX5WIEkSz7r1FdXUjtGCQ1c2M+ndUAH7opdfLK1PQbw==", "cpu": [ "x64" ], @@ -1193,9 +1193,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "15.4.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.4.4.tgz", - "integrity": "sha512-LRD4l2lq4R+2QCHBQVC0wjxxkLlALGJCwigaJ5FSRSqnje+MRKHljQNZgDCaKUZQzO/TXxlmUdkZP/X3KNGZaw==", + "version": "15.4.8", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.4.8.tgz", + "integrity": "sha512-y3fmp+1Px/SJD+5ntve5QLZnGLycsxsVPkTzAc3zUiXYSOlTPqT8ynfmt6tt4fSo1tAhDPmryXpYKEAcoAPDJw==", "cpu": [ "arm64" ], @@ -1209,9 +1209,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "15.4.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.4.4.tgz", - "integrity": "sha512-LsGUCTvuZ0690fFWerA4lnQvjkYg9gHo12A3wiPUR4kCxbx/d+SlwmonuTH2SWZI+RVGA9VL3N0S03WTYv6bYg==", + "version": "15.4.8", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.4.8.tgz", + "integrity": "sha512-DX/L8VHzrr1CfwaVjBQr3GWCqNNFgyWJbeQ10Lx/phzbQo3JNAxUok1DZ8JHRGcL6PgMRgj6HylnLNndxn4Z6A==", "cpu": [ "arm64" ], @@ -1225,9 +1225,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "15.4.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.4.4.tgz", - "integrity": "sha512-aOy5yNRpLL3wNiJVkFYl6w22hdREERNjvegE6vvtix8LHRdsTHhWTpgvcYdCK7AIDCQW5ATmzr9XkPHvSoAnvg==", + "version": "15.4.8", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.4.8.tgz", + "integrity": "sha512-9fLAAXKAL3xEIFdKdzG5rUSvSiZTLLTCc6JKq1z04DR4zY7DbAPcRvNm3K1inVhTiQCs19ZRAgUerHiVKMZZIA==", "cpu": [ "x64" ], @@ -1241,9 +1241,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "15.4.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.4.4.tgz", - "integrity": "sha512-FL7OAn4UkR8hKQRGBmlHiHinzOb07tsfARdGh7v0Z0jEJ3sz8/7L5bR23ble9E6DZMabSStqlATHlSxv1fuzAg==", + "version": "15.4.8", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.4.8.tgz", + "integrity": "sha512-s45V7nfb5g7dbS7JK6XZDcapicVrMMvX2uYgOHP16QuKH/JA285oy6HcxlKqwUNaFY/UC6EvQ8QZUOo19cBKSA==", "cpu": [ "x64" ], @@ -1257,9 +1257,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "15.4.4", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.4.4.tgz", - "integrity": "sha512-eEdNW/TXwjYhOulQh0pffTMMItWVwKCQpbziSBmgBNFZIIRn2GTXrhrewevs8wP8KXWYMx8Z+mNU0X+AfvtrRg==", + "version": "15.4.8", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.4.8.tgz", + "integrity": "sha512-KjgeQyOAq7t/HzAJcWPGA8X+4WY03uSCZ2Ekk98S9OgCFsb6lfBE3dbUzUuEQAN2THbwYgFfxX2yFTCMm8Kehw==", "cpu": [ "arm64" ], @@ -1273,9 +1273,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "15.4.4", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.4.4.tgz", - "integrity": "sha512-SE5pYNbn/xZKMy1RE3pAs+4xD32OI4rY6mzJa4XUkp/ItZY+OMjIgilskmErt8ls/fVJ+Ihopi2QIeW6O3TrMw==", + "version": "15.4.8", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.4.8.tgz", + "integrity": "sha512-Exsmf/+42fWVnLMaZHzshukTBxZrSwuuLKFvqhGHJ+mC1AokqieLY/XzAl3jc/CqhXLqLY3RRjkKJ9YnLPcRWg==", "cpu": [ "x64" ], @@ -10876,12 +10876,12 @@ "license": "MIT" }, "node_modules/next": { - "version": "15.4.4", - "resolved": "https://registry.npmjs.org/next/-/next-15.4.4.tgz", - "integrity": "sha512-kNcubvJjOL9yUOfwtZF3HfDhuhp+kVD+FM2A6Tyua1eI/xfmY4r/8ZS913MMz+oWKDlbps/dQOWdDricuIkXLw==", + "version": "15.4.8", + "resolved": "https://registry.npmjs.org/next/-/next-15.4.8.tgz", + "integrity": "sha512-jwOXTz/bo0Pvlf20FSb6VXVeWRssA2vbvq9SdrOPEg9x8E1B27C2rQtvriAn600o9hH61kjrVRexEffv3JybuA==", "license": "MIT", "dependencies": { - "@next/env": "15.4.4", + "@next/env": "15.4.8", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", @@ -10894,14 +10894,14 @@ "node": "^18.18.0 || ^19.8.0 || >= 20.0.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "15.4.4", - "@next/swc-darwin-x64": "15.4.4", - "@next/swc-linux-arm64-gnu": "15.4.4", - "@next/swc-linux-arm64-musl": "15.4.4", - "@next/swc-linux-x64-gnu": "15.4.4", - "@next/swc-linux-x64-musl": "15.4.4", - "@next/swc-win32-arm64-msvc": "15.4.4", - "@next/swc-win32-x64-msvc": "15.4.4", + "@next/swc-darwin-arm64": "15.4.8", + "@next/swc-darwin-x64": "15.4.8", + "@next/swc-linux-arm64-gnu": "15.4.8", + "@next/swc-linux-arm64-musl": "15.4.8", + "@next/swc-linux-x64-gnu": "15.4.8", + "@next/swc-linux-x64-musl": "15.4.8", + "@next/swc-win32-arm64-msvc": "15.4.8", + "@next/swc-win32-x64-msvc": "15.4.8", "sharp": "^0.34.3" }, "peerDependencies": { diff --git a/frontend/package.json b/frontend/package.json index 6d4f3369..8241df53 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -68,7 +68,7 @@ "leaflet": "^1.9.4", "lucide-react": "^0.525.0", "mammoth": "^1.11.0", - "next": "15.4.4", + "next": "^15.4.8", "react": "19.1.0", "react-day-picker": "^9.11.1", "react-dnd": "^16.0.1", -- 2.43.0 From 127f4dc783d035dc94285c80289235fb54325e72 Mon Sep 17 00:00:00 2001 From: kjs Date: Thu, 4 Dec 2025 13:37:17 +0900 Subject: [PATCH 2/5] =?UTF-8?q?=EC=88=AB=EC=9E=90=EC=BB=AC=EB=9F=BC=20?= =?UTF-8?q?=EC=B2=9C=EB=8B=A8=EC=9C=84=20=EA=B5=AC=EB=B6=84=EC=9E=90=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../table-list/TableListComponent.tsx | 25 ++- .../table-list/TableListConfigPanel.tsx | 179 ++++++++++-------- .../registry/components/table-list/types.ts | 3 + 3 files changed, 124 insertions(+), 83 deletions(-) diff --git a/frontend/lib/registry/components/table-list/TableListComponent.tsx b/frontend/lib/registry/components/table-list/TableListComponent.tsx index 261fa108..7f6fded5 100644 --- a/frontend/lib/registry/components/table-list/TableListComponent.tsx +++ b/frontend/lib/registry/components/table-list/TableListComponent.tsx @@ -1906,12 +1906,16 @@ export const TableListComponent: React.FC = ({ return "-"; } - // ์ˆซ์ž ํƒ€์ž… ํฌ๋งทํŒ… + // ์ˆซ์ž ํƒ€์ž… ํฌ๋งทํŒ… (์ฒœ๋‹จ์œ„ ๊ตฌ๋ถ„์ž ์„ค์ • ํ™•์ธ) if (inputType === "number" || inputType === "decimal") { if (value !== null && value !== undefined && value !== "") { const numValue = typeof value === "string" ? parseFloat(value) : value; if (!isNaN(numValue)) { - return numValue.toLocaleString("ko-KR"); + // thousandSeparator๊ฐ€ false๊ฐ€ ์•„๋‹Œ ๊ฒฝ์šฐ(๊ธฐ๋ณธ๊ฐ’ true) ์ฒœ๋‹จ์œ„ ๊ตฌ๋ถ„์ž ์ ์šฉ + if (column.thousandSeparator !== false) { + return numValue.toLocaleString("ko-KR"); + } + return String(numValue); } } return String(value); @@ -1922,7 +1926,11 @@ export const TableListComponent: React.FC = ({ if (value !== null && value !== undefined && value !== "") { const numValue = typeof value === "string" ? parseFloat(value) : value; if (!isNaN(numValue)) { - return numValue.toLocaleString("ko-KR"); + // thousandSeparator๊ฐ€ false๊ฐ€ ์•„๋‹Œ ๊ฒฝ์šฐ(๊ธฐ๋ณธ๊ฐ’ true) ์ฒœ๋‹จ์œ„ ๊ตฌ๋ถ„์ž ์ ์šฉ + if (column.thousandSeparator !== false) { + return numValue.toLocaleString("ko-KR"); + } + return String(numValue); } } return String(value); @@ -1939,10 +1947,15 @@ export const TableListComponent: React.FC = ({ } } return "-"; - case "number": - return typeof value === "number" ? value.toLocaleString() : value; case "currency": - return typeof value === "number" ? `โ‚ฉ${value.toLocaleString()}` : value; + if (typeof value === "number") { + // thousandSeparator๊ฐ€ false๊ฐ€ ์•„๋‹Œ ๊ฒฝ์šฐ(๊ธฐ๋ณธ๊ฐ’ true) ์ฒœ๋‹จ์œ„ ๊ตฌ๋ถ„์ž ์ ์šฉ + if (column.thousandSeparator !== false) { + return `โ‚ฉ${value.toLocaleString()}`; + } + return `โ‚ฉ${value}`; + } + return value; case "boolean": return value ? "์˜ˆ" : "์•„๋‹ˆ์˜ค"; default: diff --git a/frontend/lib/registry/components/table-list/TableListConfigPanel.tsx b/frontend/lib/registry/components/table-list/TableListConfigPanel.tsx index 9de2f6d8..6e6c414f 100644 --- a/frontend/lib/registry/components/table-list/TableListConfigPanel.tsx +++ b/frontend/lib/registry/components/table-list/TableListConfigPanel.tsx @@ -1074,86 +1074,111 @@ export const TableListConfigPanel: React.FC = ({ {/* ๊ฐ„๊ฒฐํ•œ ๋ฆฌ์ŠคํŠธ ํ˜•์‹ ์ปฌ๋Ÿผ ์„ค์ • */}
- {config.columns?.map((column, index) => ( -
- {/* ์ปฌ๋Ÿผ๋ช… */} - - {availableColumns.find((col) => col.columnName === column.columnName)?.label || - column.displayName || - column.columnName} - + {config.columns?.map((column, index) => { + // ํ•ด๋‹น ์ปฌ๋Ÿผ์˜ input_type ํ™•์ธ + const columnInfo = availableColumns.find((col) => col.columnName === column.columnName); + const isNumberType = columnInfo?.input_type === "number" || columnInfo?.input_type === "decimal"; + + return ( +
+
+ {/* ์ปฌ๋Ÿผ๋ช… */} + + {columnInfo?.label || column.displayName || column.columnName} + + + {/* ์ˆซ์ž ํƒ€์ž…์ธ ๊ฒฝ์šฐ ์ฒœ๋‹จ์œ„ ๊ตฌ๋ถ„์ž ์„ค์ • */} + {isNumberType && ( +
+ { + updateColumn(column.columnName, { thousandSeparator: checked as boolean }); + }} + className="h-3 w-3" + /> + +
+ )} +
- {/* ํ•„ํ„ฐ ์ฒดํฌ๋ฐ•์Šค + ์ˆœ์„œ ๋ณ€๊ฒฝ + ์‚ญ์ œ ๋ฒ„ํŠผ */} -
- f.columnName === column.columnName) || false} - onCheckedChange={(checked) => { - const currentFilters = config.filter?.filters || []; - const columnLabel = - availableColumns.find((col) => col.columnName === column.columnName)?.label || - column.displayName || - column.columnName; + {/* ํ•„ํ„ฐ ์ฒดํฌ๋ฐ•์Šค + ์ˆœ์„œ ๋ณ€๊ฒฝ + ์‚ญ์ œ ๋ฒ„ํŠผ */} +
+ f.columnName === column.columnName) || false} + onCheckedChange={(checked) => { + const currentFilters = config.filter?.filters || []; + const columnLabel = + columnInfo?.label || column.displayName || column.columnName; - if (checked) { - // ํ•„ํ„ฐ ์ถ”๊ฐ€ - handleChange("filter", { - ...config.filter, - enabled: true, - filters: [ - ...currentFilters, - { - columnName: column.columnName, - label: columnLabel, - type: "text", - }, - ], - }); - } else { - // ํ•„ํ„ฐ ์ œ๊ฑฐ - handleChange("filter", { - ...config.filter, - filters: currentFilters.filter((f) => f.columnName !== column.columnName), - }); - } - }} - className="h-3 w-3" - /> + if (checked) { + // ํ•„ํ„ฐ ์ถ”๊ฐ€ + handleChange("filter", { + ...config.filter, + enabled: true, + filters: [ + ...currentFilters, + { + columnName: column.columnName, + label: columnLabel, + type: "text", + }, + ], + }); + } else { + // ํ•„ํ„ฐ ์ œ๊ฑฐ + handleChange("filter", { + ...config.filter, + filters: currentFilters.filter((f) => f.columnName !== column.columnName), + }); + } + }} + className="h-3 w-3" + /> +
+ + {/* ์ˆœ์„œ ๋ณ€๊ฒฝ + ์‚ญ์ œ ๋ฒ„ํŠผ */} +
+ + + +
- - {/* ์ˆœ์„œ ๋ณ€๊ฒฝ + ์‚ญ์ œ ๋ฒ„ํŠผ */} -
- - - -
-
- ))} + ); + })}
)} diff --git a/frontend/lib/registry/components/table-list/types.ts b/frontend/lib/registry/components/table-list/types.ts index 0322926b..b69b9238 100644 --- a/frontend/lib/registry/components/table-list/types.ts +++ b/frontend/lib/registry/components/table-list/types.ts @@ -59,6 +59,9 @@ export interface ColumnConfig { isEntityJoin?: boolean; // Entity ์กฐ์ธ๋œ ์ปฌ๋Ÿผ์ธ์ง€ ์—ฌ๋ถ€ entityJoinInfo?: EntityJoinInfo; // Entity ์กฐ์ธ ์ƒ์„ธ ์ •๋ณด + // ์ˆซ์ž ํฌ๋งทํŒ… ์„ค์ • + thousandSeparator?: boolean; // ์ฒœ๋‹จ์œ„ ๊ตฌ๋ถ„์ž ์‚ฌ์šฉ ์—ฌ๋ถ€ (๊ธฐ๋ณธ: true) + // ๐ŸŽฏ ์—”ํ‹ฐํ‹ฐ ์ปฌ๋Ÿผ ํ‘œ์‹œ ์„ค์ • (ํ™”๋ฉด๋ณ„ ๋™์  ์„ค์ •) entityDisplayConfig?: { displayColumns: string[]; // ํ‘œ์‹œํ•  ์ปฌ๋Ÿผ๋“ค (๊ธฐ๋ณธ ํ…Œ์ด๋ธ” + ์กฐ์ธ ํ…Œ์ด๋ธ”) -- 2.43.0 From 2cddb4225520e99f999eb0ca7950f00d2b667408 Mon Sep 17 00:00:00 2001 From: kjs Date: Thu, 4 Dec 2025 14:30:52 +0900 Subject: [PATCH 3/5] =?UTF-8?q?=EC=97=94=ED=8B=B0=ED=8B=B0=20=ED=91=9C?= =?UTF-8?q?=EC=8B=9C=EA=B8=B0=EB=8A=A5=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../table-list/TableListComponent.tsx | 63 ++-- .../table-list/TableListConfigPanel.tsx | 326 +++++++++--------- 2 files changed, 198 insertions(+), 191 deletions(-) diff --git a/frontend/lib/registry/components/table-list/TableListComponent.tsx b/frontend/lib/registry/components/table-list/TableListComponent.tsx index 7f6fded5..6a1f01fe 100644 --- a/frontend/lib/registry/components/table-list/TableListComponent.tsx +++ b/frontend/lib/registry/components/table-list/TableListComponent.tsx @@ -1183,13 +1183,20 @@ export const TableListComponent: React.FC = ({ referenceTable: col.additionalJoinInfo!.referenceTable, })); - // console.log("๐Ÿ” [TableList] API ํ˜ธ์ถœ ์‹œ์ž‘", { - // tableName: tableConfig.selectedTable, - // page, - // pageSize, - // sortBy, - // sortOrder, - // }); + // ๐ŸŽฏ ํ™”๋ฉด๋ณ„ ์—”ํ‹ฐํ‹ฐ ํ‘œ์‹œ ์„ค์ • ์ˆ˜์ง‘ + const screenEntityConfigs: Record = {}; + (tableConfig.columns || []) + .filter((col) => col.entityDisplayConfig && col.entityDisplayConfig.displayColumns?.length > 0) + .forEach((col) => { + screenEntityConfigs[col.columnName] = { + displayColumns: col.entityDisplayConfig!.displayColumns, + separator: col.entityDisplayConfig!.separator || " - ", + sourceTable: col.entityDisplayConfig!.sourceTable || tableConfig.selectedTable, + joinTable: col.entityDisplayConfig!.joinTable, + }; + }); + + console.log("๐ŸŽฏ [TableList] ํ™”๋ฉด๋ณ„ ์—”ํ‹ฐํ‹ฐ ์„ค์ •:", screenEntityConfigs); // ๐ŸŽฏ ํ•ญ์ƒ entityJoinApi ์‚ฌ์šฉ (writer ์ปฌ๋Ÿผ ์ž๋™ ์กฐ์ธ ์ง€์›) response = await entityJoinApi.getTableDataWithJoins(tableConfig.selectedTable, { @@ -1200,6 +1207,7 @@ export const TableListComponent: React.FC = ({ search: hasFilters ? filters : undefined, enableEntityJoin: true, additionalJoinColumns: entityJoinColumns.length > 0 ? entityJoinColumns : undefined, + screenEntityConfigs: Object.keys(screenEntityConfigs).length > 0 ? screenEntityConfigs : undefined, // ๐ŸŽฏ ํ™”๋ฉด๋ณ„ ์—”ํ‹ฐํ‹ฐ ์„ค์ • ์ „๋‹ฌ dataFilter: tableConfig.dataFilter, // ๐Ÿ†• ๋ฐ์ดํ„ฐ ํ•„ํ„ฐ ์ „๋‹ฌ }); @@ -1756,33 +1764,46 @@ export const TableListComponent: React.FC = ({ const formatCellValue = useCallback( (value: any, column: ColumnConfig, rowData?: Record) => { - if (value === null || value === undefined) return "-"; - - // ๐ŸŽฏ writer ์ปฌ๋Ÿผ ์ž๋™ ๋ณ€ํ™˜: user_id -> user_name - if (column.columnName === "writer" && rowData && rowData.writer_name) { - return rowData.writer_name; - } - - // ๐ŸŽฏ ์—”ํ‹ฐํ‹ฐ ์ปฌ๋Ÿผ ํ‘œ์‹œ ์„ค์ •์ด ์žˆ๋Š” ๊ฒฝ์šฐ + // ๐ŸŽฏ ์—”ํ‹ฐํ‹ฐ ์ปฌ๋Ÿผ ํ‘œ์‹œ ์„ค์ •์ด ์žˆ๋Š” ๊ฒฝ์šฐ - value๊ฐ€ null์ด์–ด๋„ rowData์—์„œ ์กฐํ•ฉ ๊ฐ€๋Šฅ + // ์ด ์ฒดํฌ๋ฅผ ๊ฐ€์žฅ ๋จผ์ € ์ˆ˜ํ–‰ (null ์ฒดํฌ๋ณด๋‹ค ์•ž์—) if (column.entityDisplayConfig && rowData) { - // displayColumns ๋˜๋Š” selectedColumns ๋‘˜ ๋‹ค ์ฒดํฌ - const displayColumns = column.entityDisplayConfig.displayColumns || column.entityDisplayConfig.selectedColumns; + const displayColumns = column.entityDisplayConfig.displayColumns || (column.entityDisplayConfig as any).selectedColumns; const separator = column.entityDisplayConfig.separator; if (displayColumns && displayColumns.length > 0) { // ์„ ํƒ๋œ ์ปฌ๋Ÿผ๋“ค์˜ ๊ฐ’์„ ๊ตฌ๋ถ„์ž๋กœ ์กฐํ•ฉ const values = displayColumns - .map((colName) => { - const cellValue = rowData[colName]; + .map((colName: string) => { + // 1. ๋จผ์ € ์ง์ ‘ ์ปฌ๋Ÿผ๋ช…์œผ๋กœ ์‹œ๋„ (๊ธฐ๋ณธ ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ์ธ ๊ฒฝ์šฐ) + let cellValue = rowData[colName]; + + // 2. ์—†์œผ๋ฉด ${sourceColumn}_${colName} ํ˜•์‹์œผ๋กœ ์‹œ๋„ (์กฐ์ธ ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ์ธ ๊ฒฝ์šฐ) + if (cellValue === null || cellValue === undefined) { + const joinedKey = `${column.columnName}_${colName}`; + cellValue = rowData[joinedKey]; + } + if (cellValue === null || cellValue === undefined) return ""; return String(cellValue); }) - .filter((v) => v !== ""); // ๋นˆ ๊ฐ’ ์ œ์™ธ + .filter((v: string) => v !== ""); // ๋นˆ ๊ฐ’ ์ œ์™ธ - return values.join(separator || " - "); + const result = values.join(separator || " - "); + if (result) { + return result; // ๊ฒฐ๊ณผ๊ฐ€ ์žˆ์œผ๋ฉด ๋ฐ˜ํ™˜ + } + // ๊ฒฐ๊ณผ๊ฐ€ ๋น„์–ด์žˆ์œผ๋ฉด ์•„๋ž˜๋กœ ๊ณ„์† ์ง„ํ–‰ (์›๋ž˜ ๊ฐ’ ์‚ฌ์šฉ) } } + // value๊ฐ€ null/undefined๋ฉด "-" ๋ฐ˜ํ™˜ + if (value === null || value === undefined) return "-"; + + // ๐ŸŽฏ writer ์ปฌ๋Ÿผ ์ž๋™ ๋ณ€ํ™˜: user_id -> user_name + if (column.columnName === "writer" && rowData && rowData.writer_name) { + return rowData.writer_name; + } + const meta = columnMeta[column.columnName]; // inputType ๊ธฐ๋ฐ˜ ํฌ๋งทํŒ… (columnMeta์—์„œ ๊ฐ€์ ธ์˜จ inputType ์šฐ์„ ) diff --git a/frontend/lib/registry/components/table-list/TableListConfigPanel.tsx b/frontend/lib/registry/components/table-list/TableListConfigPanel.tsx index 6e6c414f..823424cc 100644 --- a/frontend/lib/registry/components/table-list/TableListConfigPanel.tsx +++ b/frontend/lib/registry/components/table-list/TableListConfigPanel.tsx @@ -467,42 +467,22 @@ export const TableListConfigPanel: React.FC = ({ // ๐ŸŽฏ ์—”ํ‹ฐํ‹ฐ ์ปฌ๋Ÿผ์˜ ํ‘œ์‹œ ์ปฌ๋Ÿผ ์ •๋ณด ๋กœ๋“œ const loadEntityDisplayConfig = async (column: ColumnConfig) => { - if (!column.isEntityJoin || !column.entityJoinInfo) { - return; - } + const configKey = `${column.columnName}`; + + // ์ด๋ฏธ ๋กœ๋“œ๋œ ๊ฒฝ์šฐ ์Šคํ‚ต + if (entityDisplayConfigs[configKey]) return; - // entityDisplayConfig๊ฐ€ ์—†์œผ๋ฉด ์ดˆ๊ธฐํ™” - if (!column.entityDisplayConfig) { - // sourceTable์„ ๊ฒฐ์ •: entityJoinInfo -> config.selectedTable -> screenTableName ์ˆœ์„œ - const initialSourceTable = column.entityJoinInfo?.sourceTable || config.selectedTable || screenTableName; - - if (!initialSourceTable) { - return; - } - - const updatedColumns = config.columns?.map((col) => { - if (col.columnName === column.columnName) { - return { - ...col, - entityDisplayConfig: { - displayColumns: [], - separator: " - ", - sourceTable: initialSourceTable, - joinTable: "", - }, - }; - } - return col; - }); - - if (updatedColumns) { - handleChange("columns", updatedColumns); - // ์—…๋ฐ์ดํŠธ๋œ ์ปฌ๋Ÿผ์œผ๋กœ ๋‹ค์‹œ ์‹œ๋„ - const updatedColumn = updatedColumns.find((col) => col.columnName === column.columnName); - if (updatedColumn) { - return loadEntityDisplayConfig(updatedColumn); - } - } + if (!column.isEntityJoin) { + // ์—”ํ‹ฐํ‹ฐ ์ปฌ๋Ÿผ์ด ์•„๋‹ˆ๋ฉด ๋นˆ ์ƒํƒœ๋กœ ์„ค์ •ํ•˜์—ฌ ๋กœ๋”ฉ ์ƒํƒœ ํ•ด์ œ + setEntityDisplayConfigs((prev) => ({ + ...prev, + [configKey]: { + sourceColumns: [], + joinColumns: [], + selectedColumns: [], + separator: " - ", + }, + })); return; } @@ -512,32 +492,56 @@ export const TableListConfigPanel: React.FC = ({ // 3. config.selectedTable // 4. screenTableName const sourceTable = - column.entityDisplayConfig.sourceTable || + column.entityDisplayConfig?.sourceTable || column.entityJoinInfo?.sourceTable || config.selectedTable || screenTableName; - let joinTable = column.entityDisplayConfig.joinTable; - - // sourceTable์ด ์—ฌ์ „ํžˆ ๋น„์–ด์žˆ์œผ๋ฉด ์—๋Ÿฌ + // sourceTable์ด ๋น„์–ด์žˆ์œผ๋ฉด ๋นˆ ์ƒํƒœ๋กœ ์„ค์ • if (!sourceTable) { + console.warn("โš ๏ธ sourceTable์„ ์ฐพ์„ ์ˆ˜ ์—†์Œ:", column.columnName); + setEntityDisplayConfigs((prev) => ({ + ...prev, + [configKey]: { + sourceColumns: [], + joinColumns: [], + selectedColumns: column.entityDisplayConfig?.displayColumns || [], + separator: column.entityDisplayConfig?.separator || " - ", + }, + })); return; } - if (!joinTable && sourceTable) { - // joinTable์ด ์—†์œผ๋ฉด tableTypeApi๋กœ ์กฐํšŒํ•ด์„œ ์„ค์ • + let joinTable = column.entityDisplayConfig?.joinTable; + + // joinTable์ด ์—†์œผ๋ฉด tableTypeApi๋กœ ์กฐํšŒํ•ด์„œ ์„ค์ • + if (!joinTable) { try { + console.log("๐Ÿ” tableTypeApi๋กœ ์ปฌ๋Ÿผ ์ •๋ณด ์กฐํšŒ:", { + tableName: sourceTable, + columnName: column.columnName, + }); + const columnList = await tableTypeApi.getColumns(sourceTable); const columnInfo = columnList.find((col: any) => (col.column_name || col.columnName) === column.columnName); + console.log("๐Ÿ” ์ปฌ๋Ÿผ ์ •๋ณด ์กฐํšŒ ๊ฒฐ๊ณผ:", { + columnInfo: columnInfo, + referenceTable: columnInfo?.reference_table || columnInfo?.referenceTable, + referenceColumn: columnInfo?.reference_column || columnInfo?.referenceColumn, + }); + if (columnInfo?.reference_table || columnInfo?.referenceTable) { joinTable = columnInfo.reference_table || columnInfo.referenceTable; + console.log("โœ… tableTypeApi์—์„œ ์กฐ์ธ ํ…Œ์ด๋ธ” ์ •๋ณด ์ฐพ์Œ:", joinTable); // entityDisplayConfig ์—…๋ฐ์ดํŠธ const updatedConfig = { ...column.entityDisplayConfig, sourceTable: sourceTable, joinTable: joinTable, + displayColumns: column.entityDisplayConfig?.displayColumns || [], + separator: column.entityDisplayConfig?.separator || " - ", }; // ์ปฌ๋Ÿผ ์„ค์ • ์—…๋ฐ์ดํŠธ @@ -553,74 +557,27 @@ export const TableListConfigPanel: React.FC = ({ } } catch (error) { console.error("tableTypeApi ์ปฌ๋Ÿผ ์ •๋ณด ์กฐํšŒ ์‹คํŒจ:", error); - console.log("โŒ ์กฐํšŒ ์‹คํŒจ ์ƒ์„ธ:", { sourceTable, columnName: column.columnName }); } - } else if (!joinTable) { - console.warn("โš ๏ธ sourceTable์ด ์—†์–ด์„œ joinTable ์กฐํšŒ ๋ถˆ๊ฐ€:", column.columnName); } console.log("๐Ÿ” ์ตœ์ข… ์ถ”์ถœํ•œ ๊ฐ’:", { sourceTable, joinTable }); - const configKey = `${column.columnName}`; - - // ์ด๋ฏธ ๋กœ๋“œ๋œ ๊ฒฝ์šฐ ์Šคํ‚ต - if (entityDisplayConfigs[configKey]) return; - - // joinTable์ด ๋น„์–ด์žˆ์œผ๋ฉด tableTypeApi๋กœ ์ปฌ๋Ÿผ ์ •๋ณด๋ฅผ ๋‹ค์‹œ ๊ฐ€์ ธ์™€์„œ referenceTable ์ •๋ณด๋ฅผ ์ฐพ๊ธฐ - let actualJoinTable = joinTable; - if (!actualJoinTable && sourceTable) { - try { - console.log("๐Ÿ” tableTypeApi๋กœ ์ปฌ๋Ÿผ ์ •๋ณด ๋‹ค์‹œ ์กฐํšŒ:", { - tableName: sourceTable, - columnName: column.columnName, - }); - - const columnList = await tableTypeApi.getColumns(sourceTable); - const columnInfo = columnList.find((col: any) => (col.column_name || col.columnName) === column.columnName); - - console.log("๐Ÿ” ์ปฌ๋Ÿผ ์ •๋ณด ์กฐํšŒ ๊ฒฐ๊ณผ:", { - columnInfo: columnInfo, - referenceTable: columnInfo?.reference_table || columnInfo?.referenceTable, - referenceColumn: columnInfo?.reference_column || columnInfo?.referenceColumn, - }); - - if (columnInfo?.reference_table || columnInfo?.referenceTable) { - actualJoinTable = columnInfo.reference_table || columnInfo.referenceTable; - console.log("โœ… tableTypeApi์—์„œ ์กฐ์ธ ํ…Œ์ด๋ธ” ์ •๋ณด ์ฐพ์Œ:", actualJoinTable); - - // entityDisplayConfig ์—…๋ฐ์ดํŠธ - const updatedConfig = { - ...column.entityDisplayConfig, - joinTable: actualJoinTable, - }; - - // ์ปฌ๋Ÿผ ์„ค์ • ์—…๋ฐ์ดํŠธ - const updatedColumns = config.columns?.map((col) => - col.columnName === column.columnName ? { ...col, entityDisplayConfig: updatedConfig } : col, - ); - - if (updatedColumns) { - handleChange("columns", updatedColumns); - } - } - } catch (error) { - console.error("tableTypeApi ์ปฌ๋Ÿผ ์ •๋ณด ์กฐํšŒ ์‹คํŒจ:", error); - } - } - - // sourceTable๊ณผ joinTable์ด ๋ชจ๋‘ ์žˆ์–ด์•ผ ๋กœ๋“œ - if (!sourceTable || !actualJoinTable) { - return; - } try { - // ๊ธฐ๋ณธ ํ…Œ์ด๋ธ”๊ณผ ์กฐ์ธ ํ…Œ์ด๋ธ”์˜ ์ปฌ๋Ÿผ ์ •๋ณด๋ฅผ ๋ณ‘๋ ฌ๋กœ ๋กœ๋“œ - const [sourceResult, joinResult] = await Promise.all([ - entityJoinApi.getReferenceTableColumns(sourceTable), - entityJoinApi.getReferenceTableColumns(actualJoinTable), - ]); - + // ๊ธฐ๋ณธ ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ ์ •๋ณด๋Š” ํ•ญ์ƒ ๋กœ๋“œ + const sourceResult = await entityJoinApi.getReferenceTableColumns(sourceTable); const sourceColumns = sourceResult.columns || []; - const joinColumns = joinResult.columns || []; + + // joinTable์ด ์žˆ์œผ๋ฉด ์กฐ์ธ ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ๋„ ๋กœ๋“œ + let joinColumns: Array<{ columnName: string; displayName: string; dataType: string }> = []; + if (joinTable) { + try { + const joinResult = await entityJoinApi.getReferenceTableColumns(joinTable); + joinColumns = joinResult.columns || []; + } catch (joinError) { + console.warn("โš ๏ธ ์กฐ์ธ ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ ๋กœ๋“œ ์‹คํŒจ:", joinTable, joinError); + // ์กฐ์ธ ํ…Œ์ด๋ธ” ๋กœ๋“œ ์‹คํŒจํ•ด๋„ ์†Œ์Šค ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ์€ ํ‘œ์‹œ + } + } setEntityDisplayConfigs((prev) => ({ ...prev, @@ -633,6 +590,16 @@ export const TableListConfigPanel: React.FC = ({ })); } catch (error) { console.error("์—”ํ‹ฐํ‹ฐ ํ‘œ์‹œ ์ปฌ๋Ÿผ ์ •๋ณด ๋กœ๋“œ ์‹คํŒจ:", error); + // ์—๋Ÿฌ ๋ฐœ์ƒ ์‹œ์—๋„ ๋นˆ ์ƒํƒœ๋กœ ์„ค์ •ํ•˜์—ฌ ๋กœ๋”ฉ ์ƒํƒœ ํ•ด์ œ + setEntityDisplayConfigs((prev) => ({ + ...prev, + [configKey]: { + sourceColumns: [], + joinColumns: [], + selectedColumns: column.entityDisplayConfig?.displayColumns || [], + separator: column.entityDisplayConfig?.separator || " - ", + }, + })); } }; @@ -873,76 +840,95 @@ export const TableListConfigPanel: React.FC = ({ {/* ํ‘œ์‹œ ์ปฌ๋Ÿผ ์„ ํƒ (๋‹ค์ค‘ ์„ ํƒ) */}
- - - - - - - - - ์ปฌ๋Ÿผ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. - {entityDisplayConfigs[column.columnName].sourceColumns.length > 0 && ( - - {entityDisplayConfigs[column.columnName].sourceColumns.map((col) => ( - toggleEntityDisplayColumn(column.columnName, col.columnName)} - className="text-xs" - > - - {col.displayName} - - ))} - - )} - {entityDisplayConfigs[column.columnName].joinColumns.length > 0 && ( - - {entityDisplayConfigs[column.columnName].joinColumns.map((col) => ( - toggleEntityDisplayColumn(column.columnName, col.columnName)} - className="text-xs" - > - - {col.displayName} - - ))} - - )} - - - - + {entityDisplayConfigs[column.columnName].sourceColumns.length === 0 && + entityDisplayConfigs[column.columnName].joinColumns.length === 0 ? ( +
+ ํ‘œ์‹œ ๊ฐ€๋Šฅํ•œ ์ปฌ๋Ÿผ์ด ์—†์Šต๋‹ˆ๋‹ค. + {!column.entityDisplayConfig?.joinTable && ( +

+ ํ…Œ์ด๋ธ” ํƒ€์ž… ๊ด€๋ฆฌ์—์„œ ์ฐธ์กฐ ํ…Œ์ด๋ธ”์„ ์„ค์ •ํ•˜๋ฉด ๋” ๋งŽ์€ ์ปฌ๋Ÿผ์„ ์„ ํƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +

+ )} +
+ ) : ( + + + + + + + + + ์ปฌ๋Ÿผ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. + {entityDisplayConfigs[column.columnName].sourceColumns.length > 0 && ( + + {entityDisplayConfigs[column.columnName].sourceColumns.map((col) => ( + toggleEntityDisplayColumn(column.columnName, col.columnName)} + className="text-xs" + > + + {col.displayName} + + ))} + + )} + {entityDisplayConfigs[column.columnName].joinColumns.length > 0 && ( + + {entityDisplayConfigs[column.columnName].joinColumns.map((col) => ( + toggleEntityDisplayColumn(column.columnName, col.columnName)} + className="text-xs" + > + + {col.displayName} + + ))} + + )} + + + + + )}
+ {/* ์ฐธ์กฐ ํ…Œ์ด๋ธ” ๋ฏธ์„ค์ • ์•ˆ๋‚ด */} + {!column.entityDisplayConfig?.joinTable && entityDisplayConfigs[column.columnName].sourceColumns.length > 0 && ( +
+ ํ˜„์žฌ ๊ธฐ๋ณธ ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ๋งŒ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค. ํ…Œ์ด๋ธ” ํƒ€์ž… ๊ด€๋ฆฌ์—์„œ ์ฐธ์กฐ ํ…Œ์ด๋ธ”์„ ์„ค์ •ํ•˜๋ฉด ์กฐ์ธ๋œ ํ…Œ์ด๋ธ”์˜ ์ปฌ๋Ÿผ๋„ ์„ ํƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +
+ )} + {/* ์„ ํƒ๋œ ์ปฌ๋Ÿผ ๋ฏธ๋ฆฌ๋ณด๊ธฐ */} {entityDisplayConfigs[column.columnName].selectedColumns.length > 0 && (
-- 2.43.0 From 93d99373434f3244214c3f2b214e5184f12f2749 Mon Sep 17 00:00:00 2001 From: kjs Date: Thu, 4 Dec 2025 16:02:00 +0900 Subject: [PATCH 4/5] =?UTF-8?q?=EC=9E=90=EB=8F=99=EC=99=84=EC=84=B1=20?= =?UTF-8?q?=EA=B2=80=EC=83=89=20=EC=9E=85=EB=A0=A5=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EB=8B=A4=EC=A4=91=20=EC=BB=AC=EB=9F=BC=20?= =?UTF-8?q?=ED=91=9C=EC=8B=9C=20=EA=B8=B0=EB=8A=A5=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AutocompleteSearchInputComponent.tsx | 22 ++- .../AutocompleteSearchInputConfigPanel.tsx | 156 +++++++++++++----- .../autocomplete-search-input/types.ts | 3 + 3 files changed, 134 insertions(+), 47 deletions(-) diff --git a/frontend/lib/registry/components/autocomplete-search-input/AutocompleteSearchInputComponent.tsx b/frontend/lib/registry/components/autocomplete-search-input/AutocompleteSearchInputComponent.tsx index 1c5920f0..7a115ea3 100644 --- a/frontend/lib/registry/components/autocomplete-search-input/AutocompleteSearchInputComponent.tsx +++ b/frontend/lib/registry/components/autocomplete-search-input/AutocompleteSearchInputComponent.tsx @@ -42,10 +42,26 @@ export function AutocompleteSearchInputComponent({ // config prop ์šฐ์„ , ์—†์œผ๋ฉด ๊ฐœ๋ณ„ prop ์‚ฌ์šฉ const tableName = config?.tableName || propTableName || ""; const displayField = config?.displayField || propDisplayField || ""; + const displayFields = config?.displayFields || (displayField ? [displayField] : []); // ๋‹ค์ค‘ ํ‘œ์‹œ ํ•„๋“œ + const displaySeparator = config?.displaySeparator || " โ†’ "; // ๊ตฌ๋ถ„์ž const valueField = config?.valueField || propValueField || ""; - const searchFields = config?.searchFields || propSearchFields || [displayField]; + const searchFields = config?.searchFields || propSearchFields || displayFields; // ๊ฒ€์ƒ‰ ํ•„๋“œ๋„ ๋‹ค์ค‘ ํ‘œ์‹œ ํ•„๋“œ ์‚ฌ์šฉ const placeholder = config?.placeholder || propPlaceholder || "๊ฒ€์ƒ‰..."; + // ๋‹ค์ค‘ ํ•„๋“œ ๊ฐ’์„ ์กฐํ•ฉํ•˜์—ฌ ํ‘œ์‹œ ๋ฌธ์ž์—ด ์ƒ์„ฑ + const getDisplayValue = (item: EntitySearchResult): string => { + if (displayFields.length > 1) { + // ์—ฌ๋Ÿฌ ํ•„๋“œ๋ฅผ ๊ตฌ๋ถ„์ž๋กœ ์กฐํ•ฉ + const values = displayFields + .map((field) => item[field]) + .filter((v) => v !== null && v !== undefined && v !== "") + .map((v) => String(v)); + return values.join(displaySeparator); + } + // ๋‹จ์ผ ํ•„๋“œ + return item[displayField] || ""; + }; + const [inputValue, setInputValue] = useState(""); const [isOpen, setIsOpen] = useState(false); const [selectedData, setSelectedData] = useState(null); @@ -115,7 +131,7 @@ export function AutocompleteSearchInputComponent({ const handleSelect = (item: EntitySearchResult) => { setSelectedData(item); - setInputValue(item[displayField] || ""); + setInputValue(getDisplayValue(item)); console.log("๐Ÿ” AutocompleteSearchInput handleSelect:", { item, @@ -239,7 +255,7 @@ export function AutocompleteSearchInputComponent({ onClick={() => handleSelect(item)} className="w-full px-3 py-2 text-left text-xs transition-colors hover:bg-accent sm:text-sm" > -
{item[displayField]}
+
{getDisplayValue(item)}
))}
diff --git a/frontend/lib/registry/components/autocomplete-search-input/AutocompleteSearchInputConfigPanel.tsx b/frontend/lib/registry/components/autocomplete-search-input/AutocompleteSearchInputConfigPanel.tsx index d2290c2f..bb0b8175 100644 --- a/frontend/lib/registry/components/autocomplete-search-input/AutocompleteSearchInputConfigPanel.tsx +++ b/frontend/lib/registry/components/autocomplete-search-input/AutocompleteSearchInputConfigPanel.tsx @@ -184,52 +184,118 @@ export function AutocompleteSearchInputConfigPanel({
- {/* 2. ํ‘œ์‹œ ํ•„๋“œ ์„ ํƒ */} + {/* 2. ํ‘œ์‹œ ํ•„๋“œ ์„ ํƒ (๋‹ค์ค‘ ์„ ํƒ ๊ฐ€๋Šฅ) */}
- - - - - - - - - - ํ•„๋“œ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. - - {sourceTableColumns.map((column) => ( - { - updateConfig({ displayField: column.columnName }); - setOpenDisplayFieldCombo(false); + +
+ {/* ์„ ํƒ๋œ ํ•„๋“œ ํ‘œ์‹œ */} + {(localConfig.displayFields && localConfig.displayFields.length > 0) ? ( +
+ {localConfig.displayFields.map((fieldName) => { + const col = sourceTableColumns.find((c) => c.columnName === fieldName); + return ( + + {col?.displayName || fieldName} + + + ); + })} +
+ ) : ( +
+ ์•„๋ž˜์—์„œ ํ‘œ์‹œํ•  ํ•„๋“œ๋ฅผ ์„ ํƒํ•˜์„ธ์š” +
+ )} + + {/* ํ•„๋“œ ์„ ํƒ ๋“œ๋กญ๋‹ค์šด */} + + + + + + + + + ํ•„๋“œ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. + + {sourceTableColumns.map((column) => { + const isSelected = localConfig.displayFields?.includes(column.columnName); + return ( + { + const currentFields = localConfig.displayFields || []; + let newFields: string[]; + if (isSelected) { + newFields = currentFields.filter((f) => f !== column.columnName); + } else { + newFields = [...currentFields, column.columnName]; + } + updateConfig({ + displayFields: newFields, + displayField: newFields[0] || "", // ์ฒซ ๋ฒˆ์งธ ํ•„๋“œ๋ฅผ ๊ธฐ๋ณธ displayField๋กœ + }); + }} + className="text-xs sm:text-sm" + > + +
+ {column.displayName || column.columnName} + {column.displayName && {column.columnName}} +
+
+ ); + })} +
+
+
+
+
+ + {/* ๊ตฌ๋ถ„์ž ์„ค์ • */} + {localConfig.displayFields && localConfig.displayFields.length > 1 && ( +
+ + updateConfig({ displaySeparator: e.target.value })} + placeholder=" โ†’ " + className="h-7 w-20 text-xs text-center" + /> + + ๋ฏธ๋ฆฌ๋ณด๊ธฐ: {localConfig.displayFields.map((f) => { + const col = sourceTableColumns.find((c) => c.columnName === f); + return col?.displayName || f; + }).join(localConfig.displaySeparator || " โ†’ ")} + +
+ )} +
{/* 3. ์ €์žฅ ๋Œ€์ƒ ํ…Œ์ด๋ธ” ์„ ํƒ */} @@ -419,7 +485,9 @@ export function AutocompleteSearchInputConfigPanel({ ์™ธ๋ถ€ ํ…Œ์ด๋ธ”: {localConfig.tableName}

- ํ‘œ์‹œ ํ•„๋“œ: {localConfig.displayField} + ํ‘œ์‹œ ํ•„๋“œ: {localConfig.displayFields?.length + ? localConfig.displayFields.join(localConfig.displaySeparator || " โ†’ ") + : localConfig.displayField}

์ €์žฅ ํ…Œ์ด๋ธ”: {localConfig.targetTable} diff --git a/frontend/lib/registry/components/autocomplete-search-input/types.ts b/frontend/lib/registry/components/autocomplete-search-input/types.ts index 85101e89..ea1c3734 100644 --- a/frontend/lib/registry/components/autocomplete-search-input/types.ts +++ b/frontend/lib/registry/components/autocomplete-search-input/types.ts @@ -29,5 +29,8 @@ export interface AutocompleteSearchInputConfig { fieldMappings?: FieldMapping[]; // ๋งคํ•‘ํ•  ํ•„๋“œ ๋ชฉ๋ก // ์ €์žฅ ๋Œ€์ƒ ํ…Œ์ด๋ธ” (๊ฐ„์†Œํ™” ๋ฒ„์ „) targetTable?: string; + // ๐Ÿ†• ๋‹ค์ค‘ ํ‘œ์‹œ ํ•„๋“œ ์„ค์ • (์—ฌ๋Ÿฌ ์ปฌ๋Ÿผ ์กฐํ•ฉ) + displayFields?: string[]; // ์—ฌ๋Ÿฌ ์ปฌ๋Ÿผ์„ ์กฐํ•ฉํ•˜์—ฌ ํ‘œ์‹œ + displaySeparator?: string; // ๊ตฌ๋ถ„์ž (๊ธฐ๋ณธ๊ฐ’: " - ") } -- 2.43.0 From bc66f3bba1b92c62e5de44f46cee48547ca58dd7 Mon Sep 17 00:00:00 2001 From: kjs Date: Thu, 4 Dec 2025 18:26:35 +0900 Subject: [PATCH 5/5] =?UTF-8?q?=EA=B1=B0=EB=9E=98=EC=B2=98=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/controllers/entityJoinController.ts | 15 + .../src/services/tableManagementService.ts | 46 +++ frontend/lib/api/entityJoin.ts | 9 + .../button-primary/ButtonPrimaryComponent.tsx | 26 +- .../SplitPanelLayoutComponent.tsx | 112 ++++-- .../table-list/TableListComponent.tsx | 56 +++ .../table-list/TableListConfigPanel.tsx | 335 ++++++++++++++++++ .../registry/components/table-list/types.ts | 18 + frontend/lib/utils/buttonActions.ts | 19 + ..._์ž„๋ฒ ๋”ฉ_๋ฐ_๋ฐ์ดํ„ฐ_์ „๋‹ฌ_์‹œ์Šคํ…œ_๊ตฌํ˜„_๊ณ„ํš์„œ.md | 1 + ํ™”๋ฉด_์ž„๋ฒ ๋”ฉ_์‹œ์Šคํ…œ_Phase1-4_๊ตฌํ˜„_์™„๋ฃŒ.md | 1 + ํ™”๋ฉด_์ž„๋ฒ ๋”ฉ_์‹œ์Šคํ…œ_์ถฉ๋Œ_๋ถ„์„_๋ณด๊ณ ์„œ.md | 1 + 12 files changed, 600 insertions(+), 39 deletions(-) diff --git a/backend-node/src/controllers/entityJoinController.ts b/backend-node/src/controllers/entityJoinController.ts index 66e20ccd..00727f1d 100644 --- a/backend-node/src/controllers/entityJoinController.ts +++ b/backend-node/src/controllers/entityJoinController.ts @@ -29,6 +29,7 @@ export class EntityJoinController { screenEntityConfigs, // ํ™”๋ฉด๋ณ„ ์—”ํ‹ฐํ‹ฐ ์„ค์ • (JSON ๋ฌธ์ž์—ด) autoFilter, // ๐Ÿ”’ ๋ฉ€ํ‹ฐํ…Œ๋„Œ์‹œ ์ž๋™ ํ•„ํ„ฐ dataFilter, // ๐Ÿ†• ๋ฐ์ดํ„ฐ ํ•„ํ„ฐ (JSON ๋ฌธ์ž์—ด) + excludeFilter, // ๐Ÿ†• ์ œ์™ธ ํ•„ํ„ฐ (JSON ๋ฌธ์ž์—ด) - ๋‹ค๋ฅธ ํ…Œ์ด๋ธ”์— ์ด๋ฏธ ์กด์žฌํ•˜๋Š” ๋ฐ์ดํ„ฐ ์ œ์™ธ userLang, // userLang์€ ๋ณ„๋„๋กœ ๋ถ„๋ฆฌํ•˜์—ฌ search์— ํฌํ•จ๋˜์ง€ ์•Š๋„๋ก ํ•จ ...otherParams } = req.query; @@ -125,6 +126,19 @@ export class EntityJoinController { } } + // ๐Ÿ†• ์ œ์™ธ ํ•„ํ„ฐ ์ฒ˜๋ฆฌ (๋‹ค๋ฅธ ํ…Œ์ด๋ธ”์— ์ด๋ฏธ ์กด์žฌํ•˜๋Š” ๋ฐ์ดํ„ฐ ์ œ์™ธ) + let parsedExcludeFilter: any = undefined; + if (excludeFilter) { + try { + parsedExcludeFilter = + typeof excludeFilter === "string" ? JSON.parse(excludeFilter) : excludeFilter; + logger.info("์ œ์™ธ ํ•„ํ„ฐ ํŒŒ์‹ฑ ์™„๋ฃŒ:", parsedExcludeFilter); + } catch (error) { + logger.warn("์ œ์™ธ ํ•„ํ„ฐ ํŒŒ์‹ฑ ์˜ค๋ฅ˜:", error); + parsedExcludeFilter = undefined; + } + } + const result = await tableManagementService.getTableDataWithEntityJoins( tableName, { @@ -141,6 +155,7 @@ export class EntityJoinController { additionalJoinColumns: parsedAdditionalJoinColumns, screenEntityConfigs: parsedScreenEntityConfigs, dataFilter: parsedDataFilter, // ๐Ÿ†• ๋ฐ์ดํ„ฐ ํ•„ํ„ฐ ์ „๋‹ฌ + excludeFilter: parsedExcludeFilter, // ๐Ÿ†• ์ œ์™ธ ํ•„ํ„ฐ ์ „๋‹ฌ } ); diff --git a/backend-node/src/services/tableManagementService.ts b/backend-node/src/services/tableManagementService.ts index 8e01903b..781a9498 100644 --- a/backend-node/src/services/tableManagementService.ts +++ b/backend-node/src/services/tableManagementService.ts @@ -2462,6 +2462,14 @@ export class TableManagementService { }>; screenEntityConfigs?: Record; // ํ™”๋ฉด๋ณ„ ์—”ํ‹ฐํ‹ฐ ์„ค์ • dataFilter?: any; // ๐Ÿ†• ๋ฐ์ดํ„ฐ ํ•„ํ„ฐ + excludeFilter?: { + enabled: boolean; + referenceTable: string; + referenceColumn: string; + sourceColumn: string; + filterColumn?: string; + filterValue?: any; + }; // ๐Ÿ†• ์ œ์™ธ ํ•„ํ„ฐ (๋‹ค๋ฅธ ํ…Œ์ด๋ธ”์— ์ด๋ฏธ ์กด์žฌํ•˜๋Š” ๋ฐ์ดํ„ฐ ์ œ์™ธ) } ): Promise { const startTime = Date.now(); @@ -2716,6 +2724,44 @@ export class TableManagementService { } } + // ๐Ÿ†• ์ œ์™ธ ํ•„ํ„ฐ ์ ์šฉ (๋‹ค๋ฅธ ํ…Œ์ด๋ธ”์— ์ด๋ฏธ ์กด์žฌํ•˜๋Š” ๋ฐ์ดํ„ฐ ์ œ์™ธ) + if (options.excludeFilter && options.excludeFilter.enabled) { + const { + referenceTable, + referenceColumn, + sourceColumn, + filterColumn, + filterValue, + } = options.excludeFilter; + + if (referenceTable && referenceColumn && sourceColumn) { + // ์„œ๋ธŒ์ฟผ๋ฆฌ๋กœ ์ด๋ฏธ ์กด์žฌํ•˜๋Š” ๋ฐ์ดํ„ฐ ์ œ์™ธ + let excludeSubquery = `main."${sourceColumn}" NOT IN ( + SELECT "${referenceColumn}" FROM "${referenceTable}" + WHERE "${referenceColumn}" IS NOT NULL`; + + // ์ถ”๊ฐ€ ํ•„ํ„ฐ ์กฐ๊ฑด์ด ์žˆ์œผ๋ฉด ์ ์šฉ (์˜ˆ: ํŠน์ • ๊ฑฐ๋ž˜์ฒ˜์˜ ํ’ˆ๋ชฉ๋งŒ ์ œ์™ธ) + if (filterColumn && filterValue !== undefined && filterValue !== null) { + excludeSubquery += ` AND "${filterColumn}" = '${String(filterValue).replace(/'/g, "''")}'`; + } + + excludeSubquery += ")"; + + whereClause = whereClause + ? `${whereClause} AND ${excludeSubquery}` + : excludeSubquery; + + logger.info(`๐Ÿšซ ์ œ์™ธ ํ•„ํ„ฐ ์ ์šฉ (Entity ์กฐ์ธ):`, { + referenceTable, + referenceColumn, + sourceColumn, + filterColumn, + filterValue, + excludeSubquery, + }); + } + } + // ORDER BY ์ ˆ ๊ตฌ์„ฑ const orderBy = options.sortBy ? `main.${options.sortBy} ${options.sortOrder === "desc" ? "DESC" : "ASC"}` diff --git a/frontend/lib/api/entityJoin.ts b/frontend/lib/api/entityJoin.ts index a84f3355..a3206df9 100644 --- a/frontend/lib/api/entityJoin.ts +++ b/frontend/lib/api/entityJoin.ts @@ -69,6 +69,14 @@ export const entityJoinApi = { }>; screenEntityConfigs?: Record; // ๐ŸŽฏ ํ™”๋ฉด๋ณ„ ์—”ํ‹ฐํ‹ฐ ์„ค์ • dataFilter?: any; // ๐Ÿ†• ๋ฐ์ดํ„ฐ ํ•„ํ„ฐ + excludeFilter?: { + enabled: boolean; + referenceTable: string; + referenceColumn: string; + sourceColumn: string; + filterColumn?: string; + filterValue?: any; + }; // ๐Ÿ†• ์ œ์™ธ ํ•„ํ„ฐ (๋‹ค๋ฅธ ํ…Œ์ด๋ธ”์— ์ด๋ฏธ ์กด์žฌํ•˜๋Š” ๋ฐ์ดํ„ฐ ์ œ์™ธ) } = {}, ): Promise => { // ๐Ÿ”’ ๋ฉ€ํ‹ฐํ…Œ๋„Œ์‹œ: company_code ์ž๋™ ํ•„ํ„ฐ๋ง ํ™œ์„ฑํ™” @@ -90,6 +98,7 @@ export const entityJoinApi = { screenEntityConfigs: params.screenEntityConfigs ? JSON.stringify(params.screenEntityConfigs) : undefined, // ๐ŸŽฏ ํ™”๋ฉด๋ณ„ ์—”ํ‹ฐํ‹ฐ ์„ค์ • autoFilter: JSON.stringify(autoFilter), // ๐Ÿ”’ ๋ฉ€ํ‹ฐํ…Œ๋„Œ์‹œ ํ•„ํ„ฐ๋ง dataFilter: params.dataFilter ? JSON.stringify(params.dataFilter) : undefined, // ๐Ÿ†• ๋ฐ์ดํ„ฐ ํ•„ํ„ฐ + excludeFilter: params.excludeFilter ? JSON.stringify(params.excludeFilter) : undefined, // ๐Ÿ†• ์ œ์™ธ ํ•„ํ„ฐ }, }); return response.data.data; diff --git a/frontend/lib/registry/components/button-primary/ButtonPrimaryComponent.tsx b/frontend/lib/registry/components/button-primary/ButtonPrimaryComponent.tsx index 0bf8bea2..5816940a 100644 --- a/frontend/lib/registry/components/button-primary/ButtonPrimaryComponent.tsx +++ b/frontend/lib/registry/components/button-primary/ButtonPrimaryComponent.tsx @@ -663,9 +663,29 @@ export const ButtonPrimaryComponent: React.FC = ({ return; } + // ๐Ÿ†• modalDataStore์—์„œ ์„ ํƒ๋œ ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ (๋ถ„ํ•  ํŒจ๋„ ๋“ฑ์—์„œ ์„ ํƒํ•œ ๋ฐ์ดํ„ฐ) + let effectiveSelectedRowsData = selectedRowsData; + if ((!selectedRowsData || selectedRowsData.length === 0) && effectiveTableName) { + try { + const { useModalDataStore } = await import("@/stores/modalDataStore"); + const dataRegistry = useModalDataStore.getState().dataRegistry; + const modalData = dataRegistry[effectiveTableName]; + if (modalData && modalData.length > 0) { + effectiveSelectedRowsData = modalData; + console.log("๐Ÿ”— [ButtonPrimaryComponent] modalDataStore์—์„œ ์„ ํƒ๋œ ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ด:", { + tableName: effectiveTableName, + count: modalData.length, + data: modalData, + }); + } + } catch (error) { + console.warn("modalDataStore ์ ‘๊ทผ ์‹คํŒจ:", error); + } + } + // ์‚ญ์ œ ์•ก์…˜์ธ๋ฐ ์„ ํƒ๋œ ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์œผ๋ฉด ๊ฒฝ๊ณ  ๋ฉ”์‹œ์ง€ ํ‘œ์‹œํ•˜๊ณ  ์ค‘๋‹จ const hasDataToDelete = - (selectedRowsData && selectedRowsData.length > 0) || (flowSelectedData && flowSelectedData.length > 0); + (effectiveSelectedRowsData && effectiveSelectedRowsData.length > 0) || (flowSelectedData && flowSelectedData.length > 0); if (processedConfig.action.type === "delete" && !hasDataToDelete) { toast.warning("์‚ญ์ œํ•  ํ•ญ๋ชฉ์„ ๋จผ์ € ์„ ํƒํ•ด์ฃผ์„ธ์š”."); @@ -724,9 +744,9 @@ export const ButtonPrimaryComponent: React.FC = ({ onClose, onFlowRefresh, // ํ”Œ๋กœ์šฐ ์ƒˆ๋กœ๊ณ ์นจ ์ฝœ๋ฐฑ ์ถ”๊ฐ€ onSave: finalOnSave, // ๐Ÿ†• EditModal์˜ handleSave ์ฝœ๋ฐฑ (props์—์„œ๋„ ์ถ”์ถœ) - // ํ…Œ์ด๋ธ” ์„ ํƒ๋œ ํ–‰ ์ •๋ณด ์ถ”๊ฐ€ + // ํ…Œ์ด๋ธ” ์„ ํƒ๋œ ํ–‰ ์ •๋ณด ์ถ”๊ฐ€ (modalDataStore์—์„œ ๊ฐ€์ ธ์˜จ ๋ฐ์ดํ„ฐ ์šฐ์„ ) selectedRows, - selectedRowsData, + selectedRowsData: effectiveSelectedRowsData, // ํ…Œ์ด๋ธ” ์ •๋ ฌ ์ •๋ณด ์ถ”๊ฐ€ sortBy, // ๐Ÿ†• ์ •๋ ฌ ์ปฌ๋Ÿผ sortOrder, // ๐Ÿ†• ์ •๋ ฌ ๋ฐฉํ–ฅ diff --git a/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutComponent.tsx b/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutComponent.tsx index fdaddfc3..ac44eded 100644 --- a/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutComponent.tsx +++ b/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutComponent.tsx @@ -293,8 +293,17 @@ export const SplitPanelLayoutComponent: React.FC ) => { if (value === null || value === undefined) return "-"; - // ์นดํ…Œ๊ณ ๋ฆฌ ๋งคํ•‘์ด ์žˆ๋Š”์ง€ ํ™•์ธ - const mapping = categoryMappings[columnName]; + // ๐Ÿ†• ์นดํ…Œ๊ณ ๋ฆฌ ๋งคํ•‘ ์ฐพ๊ธฐ (์—ฌ๋Ÿฌ ํ‚ค ํ˜•ํƒœ ์‹œ๋„) + // 1. ์ „์ฒด ์ปฌ๋Ÿผ๋ช… (์˜ˆ: "item_info.material") + // 2. ์ปฌ๋Ÿผ๋ช…๋งŒ (์˜ˆ: "material") + let mapping = categoryMappings[columnName]; + + if (!mapping && columnName.includes(".")) { + // ์กฐ์ธ๋œ ์ปฌ๋Ÿผ์˜ ๊ฒฝ์šฐ ์ปฌ๋Ÿผ๋ช…๋งŒ์œผ๋กœ ๋‹ค์‹œ ์‹œ๋„ + const simpleColumnName = columnName.split(".").pop() || columnName; + mapping = categoryMappings[simpleColumnName]; + } + if (mapping && mapping[String(value)]) { const categoryData = mapping[String(value)]; const displayLabel = categoryData.label || String(value); @@ -690,43 +699,69 @@ export const SplitPanelLayoutComponent: React.FC loadLeftCategoryMappings(); }, [componentConfig.leftPanel?.tableName, isDesignMode]); - // ์šฐ์ธก ํ…Œ์ด๋ธ” ์นดํ…Œ๊ณ ๋ฆฌ ๋งคํ•‘ ๋กœ๋“œ + // ์šฐ์ธก ํ…Œ์ด๋ธ” ์นดํ…Œ๊ณ ๋ฆฌ ๋งคํ•‘ ๋กœ๋“œ (์กฐ์ธ๋œ ํ…Œ์ด๋ธ” ํฌํ•จ) useEffect(() => { const loadRightCategoryMappings = async () => { const rightTableName = componentConfig.rightPanel?.tableName; if (!rightTableName || isDesignMode) return; try { - // 1. ์ปฌ๋Ÿผ ๋ฉ”ํƒ€ ์ •๋ณด ์กฐํšŒ - const columnsResponse = await tableTypeApi.getColumns(rightTableName); - const categoryColumns = columnsResponse.filter((col: any) => col.inputType === "category"); - - if (categoryColumns.length === 0) { - setRightCategoryMappings({}); - return; - } - - // 2. ๊ฐ ์นดํ…Œ๊ณ ๋ฆฌ ์ปฌ๋Ÿผ์— ๋Œ€ํ•œ ๊ฐ’ ์กฐํšŒ const mappings: Record> = {}; - for (const col of categoryColumns) { - const columnName = col.columnName || col.column_name; - try { - const response = await apiClient.get(`/table-categories/${rightTableName}/${columnName}/values`); + // ๐Ÿ†• ์šฐ์ธก ํŒจ๋„ ์ปฌ๋Ÿผ ์„ค์ •์—์„œ ์กฐ์ธ๋œ ํ…Œ์ด๋ธ” ์ถ”์ถœ + const rightColumns = componentConfig.rightPanel?.columns || []; + const tablesToLoad = new Set([rightTableName]); + + // ์ปฌ๋Ÿผ๋ช…์—์„œ ํ…Œ์ด๋ธ”๋ช… ์ถ”์ถœ (์˜ˆ: "item_info.material" -> "item_info") + rightColumns.forEach((col: any) => { + const colName = col.name || col.columnName; + if (colName && colName.includes(".")) { + const joinTableName = colName.split(".")[0]; + tablesToLoad.add(joinTableName); + } + }); - if (response.data.success && response.data.data) { - const valueMap: Record = {}; - response.data.data.forEach((item: any) => { - valueMap[item.value_code || item.valueCode] = { - label: item.value_label || item.valueLabel, - color: item.color, - }; - }); - mappings[columnName] = valueMap; - console.log(`โœ… ์šฐ์ธก ์นดํ…Œ๊ณ ๋ฆฌ ๋งคํ•‘ ๋กœ๋“œ [${columnName}]:`, valueMap); + console.log("๐Ÿ” ์šฐ์ธก ํŒจ๋„ ์นดํ…Œ๊ณ ๋ฆฌ ๋กœ๋“œ ๋Œ€์ƒ ํ…Œ์ด๋ธ”:", Array.from(tablesToLoad)); + + // ๊ฐ ํ…Œ์ด๋ธ”์— ๋Œ€ํ•ด ์นดํ…Œ๊ณ ๋ฆฌ ๋งคํ•‘ ๋กœ๋“œ + for (const tableName of tablesToLoad) { + try { + // 1. ์ปฌ๋Ÿผ ๋ฉ”ํƒ€ ์ •๋ณด ์กฐํšŒ + const columnsResponse = await tableTypeApi.getColumns(tableName); + const categoryColumns = columnsResponse.filter((col: any) => col.inputType === "category"); + + // 2. ๊ฐ ์นดํ…Œ๊ณ ๋ฆฌ ์ปฌ๋Ÿผ์— ๋Œ€ํ•œ ๊ฐ’ ์กฐํšŒ + for (const col of categoryColumns) { + const columnName = col.columnName || col.column_name; + try { + const response = await apiClient.get(`/table-categories/${tableName}/${columnName}/values`); + + if (response.data.success && response.data.data) { + const valueMap: Record = {}; + response.data.data.forEach((item: any) => { + valueMap[item.value_code || item.valueCode] = { + label: item.value_label || item.valueLabel, + color: item.color, + }; + }); + + // ์กฐ์ธ๋œ ํ…Œ์ด๋ธ”์˜ ๊ฒฝ์šฐ "ํ…Œ์ด๋ธ”๋ช….์ปฌ๋Ÿผ๋ช…" ํ˜•ํƒœ๋กœ ์ €์žฅ + const mappingKey = tableName === rightTableName ? columnName : `${tableName}.${columnName}`; + mappings[mappingKey] = valueMap; + + // ๐Ÿ†• ์ปฌ๋Ÿผ๋ช…๋งŒ์œผ๋กœ๋„ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋„๋ก ์ถ”๊ฐ€ ์ €์žฅ (๋ชจ๋“  ํ…Œ์ด๋ธ”) + // ๊ธฐ์กด ๋งคํ•‘์ด ์žˆ์œผ๋ฉด ๋ณ‘ํ•ฉ, ์—†์œผ๋ฉด ์ƒˆ๋กœ ์ƒ์„ฑ + mappings[columnName] = { ...(mappings[columnName] || {}), ...valueMap }; + + console.log(`โœ… ์šฐ์ธก ์นดํ…Œ๊ณ ๋ฆฌ ๋งคํ•‘ ๋กœ๋“œ [${mappingKey}]:`, valueMap); + console.log(`โœ… ์šฐ์ธก ์นดํ…Œ๊ณ ๋ฆฌ ๋งคํ•‘ (์ปฌ๋Ÿผ๋ช…๋งŒ) [${columnName}]:`, mappings[columnName]); + } + } catch (error) { + console.error(`์šฐ์ธก ์นดํ…Œ๊ณ ๋ฆฌ ๊ฐ’ ์กฐํšŒ ์‹คํŒจ [${tableName}.${columnName}]:`, error); + } } } catch (error) { - console.error(`์šฐ์ธก ์นดํ…Œ๊ณ ๋ฆฌ ๊ฐ’ ์กฐํšŒ ์‹คํŒจ [${columnName}]:`, error); + console.error(`ํ…Œ์ด๋ธ” ${tableName} ์ปฌ๋Ÿผ ์ •๋ณด ์กฐํšŒ ์‹คํŒจ:`, error); } } @@ -737,7 +772,7 @@ export const SplitPanelLayoutComponent: React.FC }; loadRightCategoryMappings(); - }, [componentConfig.rightPanel?.tableName, isDesignMode]); + }, [componentConfig.rightPanel?.tableName, componentConfig.rightPanel?.columns, isDesignMode]); // ํ•ญ๋ชฉ ํŽผ์น˜๊ธฐ/์ ‘๊ธฐ ํ† ๊ธ€ const toggleExpand = useCallback((itemId: any) => { @@ -2149,9 +2184,12 @@ export const SplitPanelLayoutComponent: React.FC const format = colConfig?.format; const boldValue = colConfig?.bold ?? false; - // ์ˆซ์ž ํฌ๋งท ์ ์šฉ - let displayValue = String(value || "-"); - if (value !== null && value !== undefined && value !== "" && format) { + // ๐Ÿ†• ์นดํ…Œ๊ณ ๋ฆฌ ๋งคํ•‘ ์ ์šฉ + const formattedValue = formatCellValue(key, value, rightCategoryMappings); + + // ์ˆซ์ž ํฌ๋งท ์ ์šฉ (์นดํ…Œ๊ณ ๋ฆฌ๊ฐ€ ์•„๋‹Œ ๊ฒฝ์šฐ๋งŒ) + let displayValue: React.ReactNode = formattedValue; + if (typeof formattedValue === 'string' && value !== null && value !== undefined && value !== "" && format) { const numValue = typeof value === 'number' ? value : parseFloat(String(value)); if (!isNaN(numValue)) { displayValue = numValue.toLocaleString('ko-KR', { @@ -2175,7 +2213,6 @@ export const SplitPanelLayoutComponent: React.FC )} {displayValue} @@ -2240,9 +2277,12 @@ export const SplitPanelLayoutComponent: React.FC const colConfig = rightColumns?.find(c => c.name === key); const format = colConfig?.format; - // ์ˆซ์ž ํฌ๋งท ์ ์šฉ - let displayValue = String(value); - if (value !== null && value !== undefined && value !== "" && format) { + // ๐Ÿ†• ์นดํ…Œ๊ณ ๋ฆฌ ๋งคํ•‘ ์ ์šฉ + const formattedValue = formatCellValue(key, value, rightCategoryMappings); + + // ์ˆซ์ž ํฌ๋งท ์ ์šฉ (์นดํ…Œ๊ณ ๋ฆฌ๊ฐ€ ์•„๋‹Œ ๊ฒฝ์šฐ๋งŒ) + let displayValue: React.ReactNode = formattedValue; + if (typeof formattedValue === 'string' && value !== null && value !== undefined && value !== "" && format) { const numValue = typeof value === 'number' ? value : parseFloat(String(value)); if (!isNaN(numValue)) { displayValue = numValue.toLocaleString('ko-KR', { diff --git a/frontend/lib/registry/components/table-list/TableListComponent.tsx b/frontend/lib/registry/components/table-list/TableListComponent.tsx index 6a1f01fe..4f78ed23 100644 --- a/frontend/lib/registry/components/table-list/TableListComponent.tsx +++ b/frontend/lib/registry/components/table-list/TableListComponent.tsx @@ -179,6 +179,7 @@ export const TableListComponent: React.FC = ({ config, className, style, + formData: propFormData, // ๐Ÿ†• ๋ถ€๋ชจ์—์„œ ์ „๋‹ฌ๋ฐ›์€ formData onFormDataChange, componentConfig, onSelectedRowsChange, @@ -1198,6 +1199,60 @@ export const TableListComponent: React.FC = ({ console.log("๐ŸŽฏ [TableList] ํ™”๋ฉด๋ณ„ ์—”ํ‹ฐํ‹ฐ ์„ค์ •:", screenEntityConfigs); + // ๐Ÿ†• ์ œ์™ธ ํ•„ํ„ฐ ์ฒ˜๋ฆฌ (๋‹ค๋ฅธ ํ…Œ์ด๋ธ”์— ์ด๋ฏธ ์กด์žฌํ•˜๋Š” ๋ฐ์ดํ„ฐ ์ œ์™ธ) + let excludeFilterParam: any = undefined; + if (tableConfig.excludeFilter?.enabled) { + const excludeConfig = tableConfig.excludeFilter; + let filterValue: any = undefined; + + // ํ•„ํ„ฐ ๊ฐ’ ์†Œ์Šค์— ๋”ฐ๋ผ ๊ฐ’ ๊ฐ€์ ธ์˜ค๊ธฐ (์šฐ์„ ์ˆœ์œ„: formData > URL > ๋ถ„ํ• ํŒจ๋„) + if (excludeConfig.filterColumn && excludeConfig.filterValueField) { + const fieldName = excludeConfig.filterValueField; + + // 1์ˆœ์œ„: props๋กœ ์ „๋‹ฌ๋ฐ›์€ formData์—์„œ ๊ฐ’ ๊ฐ€์ ธ์˜ค๊ธฐ (๋ชจ๋‹ฌ์—์„œ ์‚ฌ์šฉ) + if (propFormData && propFormData[fieldName]) { + filterValue = propFormData[fieldName]; + console.log("๐Ÿ”— [TableList] formData์—์„œ excludeFilter ๊ฐ’ ๊ฐ€์ ธ์˜ค๊ธฐ:", { + field: fieldName, + value: filterValue, + }); + } + // 2์ˆœ์œ„: URL ํŒŒ๋ผ๋ฏธํ„ฐ์—์„œ ๊ฐ’ ๊ฐ€์ ธ์˜ค๊ธฐ + else if (typeof window !== "undefined") { + const urlParams = new URLSearchParams(window.location.search); + filterValue = urlParams.get(fieldName); + if (filterValue) { + console.log("๐Ÿ”— [TableList] URL์—์„œ excludeFilter ๊ฐ’ ๊ฐ€์ ธ์˜ค๊ธฐ:", { + field: fieldName, + value: filterValue, + }); + } + } + // 3์ˆœ์œ„: ๋ถ„ํ•  ํŒจ๋„ ๋ถ€๋ชจ ๋ฐ์ดํ„ฐ์—์„œ ๊ฐ’ ๊ฐ€์ ธ์˜ค๊ธฐ + if (!filterValue && splitPanelContext?.selectedLeftData) { + filterValue = splitPanelContext.selectedLeftData[fieldName]; + if (filterValue) { + console.log("๐Ÿ”— [TableList] ๋ถ„ํ• ํŒจ๋„์—์„œ excludeFilter ๊ฐ’ ๊ฐ€์ ธ์˜ค๊ธฐ:", { + field: fieldName, + value: filterValue, + }); + } + } + } + + if (filterValue || !excludeConfig.filterColumn) { + excludeFilterParam = { + enabled: true, + referenceTable: excludeConfig.referenceTable, + referenceColumn: excludeConfig.referenceColumn, + sourceColumn: excludeConfig.sourceColumn, + filterColumn: excludeConfig.filterColumn, + filterValue: filterValue, + }; + console.log("๐Ÿšซ [TableList] ์ œ์™ธ ํ•„ํ„ฐ ์ ์šฉ:", excludeFilterParam); + } + } + // ๐ŸŽฏ ํ•ญ์ƒ entityJoinApi ์‚ฌ์šฉ (writer ์ปฌ๋Ÿผ ์ž๋™ ์กฐ์ธ ์ง€์›) response = await entityJoinApi.getTableDataWithJoins(tableConfig.selectedTable, { page, @@ -1209,6 +1264,7 @@ export const TableListComponent: React.FC = ({ additionalJoinColumns: entityJoinColumns.length > 0 ? entityJoinColumns : undefined, screenEntityConfigs: Object.keys(screenEntityConfigs).length > 0 ? screenEntityConfigs : undefined, // ๐ŸŽฏ ํ™”๋ฉด๋ณ„ ์—”ํ‹ฐํ‹ฐ ์„ค์ • ์ „๋‹ฌ dataFilter: tableConfig.dataFilter, // ๐Ÿ†• ๋ฐ์ดํ„ฐ ํ•„ํ„ฐ ์ „๋‹ฌ + excludeFilter: excludeFilterParam, // ๐Ÿ†• ์ œ์™ธ ํ•„ํ„ฐ ์ „๋‹ฌ }); // ์‹ค์ œ ๋ฐ์ดํ„ฐ์˜ item_number๋งŒ ์ถ”์ถœํ•˜์—ฌ ์ค‘๋ณต ํ™•์ธ diff --git a/frontend/lib/registry/components/table-list/TableListConfigPanel.tsx b/frontend/lib/registry/components/table-list/TableListConfigPanel.tsx index 823424cc..209b3d2d 100644 --- a/frontend/lib/registry/components/table-list/TableListConfigPanel.tsx +++ b/frontend/lib/registry/components/table-list/TableListConfigPanel.tsx @@ -9,6 +9,7 @@ import { Badge } from "@/components/ui/badge"; import { TableListConfig, ColumnConfig } from "./types"; import { entityJoinApi } from "@/lib/api/entityJoin"; import { tableTypeApi } from "@/lib/api/screen"; +import { tableManagementApi } from "@/lib/api/tableManagement"; import { Plus, Trash2, ArrowUp, ArrowDown, ChevronsUpDown, Check } from "lucide-react"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command"; @@ -73,6 +74,12 @@ export const TableListConfigPanel: React.FC = ({ const [loadingEntityJoins, setLoadingEntityJoins] = useState(false); + // ๐Ÿ†• ์ œ์™ธ ํ•„ํ„ฐ์šฉ ์ฐธ์กฐ ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ ๋ชฉ๋ก + const [referenceTableColumns, setReferenceTableColumns] = useState< + Array<{ columnName: string; dataType: string; label?: string }> + >([]); + const [loadingReferenceColumns, setLoadingReferenceColumns] = useState(false); + // ๐Ÿ”„ ์™ธ๋ถ€์—์„œ config๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ ๋‚ด๋ถ€ ์ƒํƒœ ๋™๊ธฐํ™” (ํ‘œ์˜ ํŽ˜์ด์ง€๋„ค์ด์…˜ ๋ณ€๊ฒฝ ๊ฐ์ง€) useEffect(() => { // console.log("๐Ÿ”„ TableListConfigPanel - ์™ธ๋ถ€ config ๋ณ€๊ฒฝ ๊ฐ์ง€:", { @@ -237,6 +244,42 @@ export const TableListConfigPanel: React.FC = ({ fetchEntityJoinColumns(); }, [config.selectedTable, screenTableName]); + // ๐Ÿ†• ์ œ์™ธ ํ•„ํ„ฐ์šฉ ์ฐธ์กฐ ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ ๊ฐ€์ ธ์˜ค๊ธฐ + useEffect(() => { + const fetchReferenceColumns = async () => { + const refTable = config.excludeFilter?.referenceTable; + if (!refTable) { + setReferenceTableColumns([]); + return; + } + + setLoadingReferenceColumns(true); + try { + console.log("๐Ÿ”— ์ฐธ์กฐ ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ:", refTable); + const result = await tableManagementApi.getColumnList(refTable); + if (result.success && result.data) { + // result.data๋Š” { columns: [], total, page, size, totalPages } ํ˜•ํƒœ + const columns = result.data.columns || []; + setReferenceTableColumns( + columns.map((col: any) => ({ + columnName: col.columnName || col.column_name, + dataType: col.dataType || col.data_type || "text", + label: col.displayName || col.columnLabel || col.column_label || col.columnName || col.column_name, + })) + ); + console.log("โœ… ์ฐธ์กฐ ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ ๋กœ๋“œ ์™„๋ฃŒ:", columns.length, "๊ฐœ"); + } + } catch (error) { + console.error("โŒ ์ฐธ์กฐ ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ ์กฐํšŒ ์˜ค๋ฅ˜:", error); + setReferenceTableColumns([]); + } finally { + setLoadingReferenceColumns(false); + } + }; + + fetchReferenceColumns(); + }, [config.excludeFilter?.referenceTable]); + // ๐ŸŽฏ ์—”ํ‹ฐํ‹ฐ ์ปฌ๋Ÿผ ์ž๋™ ๋กœ๋“œ useEffect(() => { const entityColumns = config.columns?.filter((col) => col.isEntityJoin && col.entityDisplayConfig); @@ -1333,6 +1376,298 @@ export const TableListConfigPanel: React.FC = ({

+ + {/* ๐Ÿ†• ์ œ์™ธ ํ•„ํ„ฐ ์„ค์ • (๋‹ค๋ฅธ ํ…Œ์ด๋ธ”์— ์ด๋ฏธ ์กด์žฌํ•˜๋Š” ๋ฐ์ดํ„ฐ ์ œ์™ธ) */} +
+
+

์ œ์™ธ ํ•„ํ„ฐ

+

+ ๋‹ค๋ฅธ ํ…Œ์ด๋ธ”์— ์ด๋ฏธ ์กด์žฌํ•˜๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ๋ชฉ๋ก์—์„œ ์ œ์™ธํ•ฉ๋‹ˆ๋‹ค +

+
+
+ + {/* ์ œ์™ธ ํ•„ํ„ฐ ํ™œ์„ฑํ™” */} +
+ { + handleChange("excludeFilter", { + ...config.excludeFilter, + enabled: checked as boolean, + }); + }} + /> + +
+ + {config.excludeFilter?.enabled && ( +
+ {/* ์ฐธ์กฐ ํ…Œ์ด๋ธ” ์„ ํƒ */} +
+ + + + + + + + + + ํ…Œ์ด๋ธ”์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค + + {availableTables.map((table) => ( + { + handleChange("excludeFilter", { + ...config.excludeFilter, + referenceTable: table.tableName, + referenceColumn: undefined, + sourceColumn: undefined, + filterColumn: undefined, + filterValueField: undefined, + }); + }} + className="text-xs" + > + + {table.displayName || table.tableName} + + ))} + + + + + +
+ + {config.excludeFilter?.referenceTable && ( + <> + {/* ๋น„๊ต ์ปฌ๋Ÿผ ์„ค์ • - ํ•œ ์ค„์— ๋‘ ๊ฐœ */} +
+ {/* ์ฐธ์กฐ ์ปฌ๋Ÿผ (๋งคํ•‘ ํ…Œ์ด๋ธ”) */} +
+ + + + + + + + + + ์—†์Œ + + {referenceTableColumns.map((col) => ( + { + handleChange("excludeFilter", { + ...config.excludeFilter, + referenceColumn: col.columnName, + }); + }} + className="text-xs" + > + + {col.label || col.columnName} + + ))} + + + + + +
+ + {/* ์†Œ์Šค ์ปฌ๋Ÿผ (ํ˜„์žฌ ํ…Œ์ด๋ธ”) */} +
+ + + + + + + + + + ์—†์Œ + + {availableColumns.map((col) => ( + { + handleChange("excludeFilter", { + ...config.excludeFilter, + sourceColumn: col.columnName, + }); + }} + className="text-xs" + > + + {col.label || col.columnName} + + ))} + + + + + +
+
+ + {/* ์กฐ๊ฑด ํ•„ํ„ฐ - ํŠน์ • ์กฐ๊ฑด์˜ ๋ฐ์ดํ„ฐ๋งŒ ์ œ์™ธ */} +
+ +

+ ํŠน์ • ์กฐ๊ฑด์˜ ๋ฐ์ดํ„ฐ๋งŒ ์ œ์™ธํ•˜๋ ค๋ฉด ์„ค์ •ํ•˜์„ธ์š” (์˜ˆ: ํŠน์ • ๊ฑฐ๋ž˜์ฒ˜์˜ ํ’ˆ๋ชฉ๋งŒ) +

+
+ {/* ํ•„ํ„ฐ ์ปฌ๋Ÿผ (๋งคํ•‘ ํ…Œ์ด๋ธ”) */} + + + + + + + + + ์—†์Œ + + { + handleChange("excludeFilter", { + ...config.excludeFilter, + filterColumn: undefined, + filterValueField: undefined, + }); + }} + className="text-xs text-muted-foreground" + > + + ์‚ฌ์šฉ ์•ˆํ•จ + + {referenceTableColumns.map((col) => ( + { + // ํ•„ํ„ฐ ์ปฌ๋Ÿผ ์„ ํƒ ์‹œ ๊ฐ™์€ ์ด๋ฆ„์˜ ํ•„๋“œ๋ฅผ ์ž๋™์œผ๋กœ ์„ค์ • + handleChange("excludeFilter", { + ...config.excludeFilter, + filterColumn: col.columnName, + filterValueField: col.columnName, // ๊ฐ™์€ ์ด๋ฆ„์œผ๋กœ ์ž๋™ ์„ค์ • + filterValueSource: "url", + }); + }} + className="text-xs" + > + + {col.label || col.columnName} + + ))} + + + + + + + {/* ํ•„ํ„ฐ ๊ฐ’ ํ•„๋“œ๋ช… (๋ถ€๋ชจ ํ™”๋ฉด์—์„œ ์ „๋‹ฌ๋ฐ›๋Š” ํ•„๋“œ) */} + { + handleChange("excludeFilter", { + ...config.excludeFilter, + filterValueField: e.target.value, + }); + }} + disabled={!config.excludeFilter?.filterColumn} + className="h-8 text-xs" + /> +
+
+ + )} + + {/* ์„ค์ • ์š”์•ฝ */} + {config.excludeFilter?.referenceTable && config.excludeFilter?.referenceColumn && config.excludeFilter?.sourceColumn && ( +
+ ์„ค์ • ์š”์•ฝ: {config.selectedTable || screenTableName}.{config.excludeFilter.sourceColumn} ๊ฐ€ + {" "}{config.excludeFilter.referenceTable}.{config.excludeFilter.referenceColumn} ์— + {config.excludeFilter.filterColumn && config.excludeFilter.filterValueField && ( + <> ({config.excludeFilter.filterColumn}=URL์˜ {config.excludeFilter.filterValueField}์ผ ๋•Œ) + )} + {" "}์ด๋ฏธ ์žˆ์œผ๋ฉด ์ œ์™ธ +
+ )} +
+ )} +
); diff --git a/frontend/lib/registry/components/table-list/types.ts b/frontend/lib/registry/components/table-list/types.ts index b69b9238..2475f58f 100644 --- a/frontend/lib/registry/components/table-list/types.ts +++ b/frontend/lib/registry/components/table-list/types.ts @@ -185,6 +185,21 @@ export interface LinkedFilterConfig { enabled?: boolean; // ํ™œ์„ฑํ™” ์—ฌ๋ถ€ (๊ธฐ๋ณธ: true) } +/** + * ์ œ์™ธ ํ•„ํ„ฐ ์„ค์ • + * ๋‹ค๋ฅธ ํ…Œ์ด๋ธ”์— ์ด๋ฏธ ์กด์žฌํ•˜๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ์ œ์™ธํ•˜๊ณ  ํ‘œ์‹œ + * ์˜ˆ: ๊ฑฐ๋ž˜์ฒ˜์— ์ด๋ฏธ ๋“ฑ๋ก๋œ ํ’ˆ๋ชฉ์„ ํ’ˆ๋ชฉ ์„ ํƒ ๋ชจ๋‹ฌ์—์„œ ์ œ์™ธ + */ +export interface ExcludeFilterConfig { + enabled: boolean; // ์ œ์™ธ ํ•„ํ„ฐ ํ™œ์„ฑํ™” ์—ฌ๋ถ€ + referenceTable: string; // ์ฐธ์กฐ ํ…Œ์ด๋ธ” (์˜ˆ: customer_item_mapping) + referenceColumn: string; // ์ฐธ์กฐ ํ…Œ์ด๋ธ”์˜ ๋น„๊ต ์ปฌ๋Ÿผ (์˜ˆ: item_id) + sourceColumn: string; // ํ˜„์žฌ ํ…Œ์ด๋ธ”์˜ ๋น„๊ต ์ปฌ๋Ÿผ (์˜ˆ: item_number) + filterColumn?: string; // ์ฐธ์กฐ ํ…Œ์ด๋ธ”์˜ ํ•„ํ„ฐ ์ปฌ๋Ÿผ (์˜ˆ: customer_id) + filterValueSource?: "url" | "formData" | "parentData"; // ํ•„ํ„ฐ ๊ฐ’ ์†Œ์Šค (๊ธฐ๋ณธ: url) + filterValueField?: string; // ํ•„ํ„ฐ ๊ฐ’ ํ•„๋“œ๋ช… (์˜ˆ: customer_code) +} + /** * TableList ์ปดํฌ๋„ŒํŠธ ์„ค์ • ํƒ€์ž… */ @@ -249,6 +264,9 @@ export interface TableListConfig extends ComponentConfig { // ๐Ÿ†• ์—ฐ๊ฒฐ๋œ ํ•„ํ„ฐ (๋‹ค๋ฅธ ์ปดํฌ๋„ŒํŠธ ๊ฐ’์œผ๋กœ ํ•„ํ„ฐ๋ง) linkedFilters?: LinkedFilterConfig[]; + // ๐Ÿ†• ์ œ์™ธ ํ•„ํ„ฐ (๋‹ค๋ฅธ ํ…Œ์ด๋ธ”์— ์ด๋ฏธ ์กด์žฌํ•˜๋Š” ๋ฐ์ดํ„ฐ ์ œ์™ธ) + excludeFilter?: ExcludeFilterConfig; + // ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ onRowClick?: (row: any) => void; onRowDoubleClick?: (row: any) => void; diff --git a/frontend/lib/utils/buttonActions.ts b/frontend/lib/utils/buttonActions.ts index 522f8651..22e491f6 100644 --- a/frontend/lib/utils/buttonActions.ts +++ b/frontend/lib/utils/buttonActions.ts @@ -1236,8 +1236,13 @@ export class ButtonActionExecutor { } else { console.log("๐Ÿ”„ ํ…Œ์ด๋ธ” ๋ฐ์ดํ„ฐ ์‚ญ์ œ ์™„๋ฃŒ, ํ…Œ์ด๋ธ” ์ƒˆ๋กœ๊ณ ์นจ ํ˜ธ์ถœ"); context.onRefresh?.(); // ํ…Œ์ด๋ธ” ์ƒˆ๋กœ๊ณ ์นจ + + // ๐Ÿ†• ๋ถ„ํ•  ํŒจ๋„ ๋“ฑ ์ „์—ญ ํ…Œ์ด๋ธ” ์ƒˆ๋กœ๊ณ ์นจ ์ด๋ฒคํŠธ ๋ฐœ์ƒ + window.dispatchEvent(new CustomEvent("refreshTable")); + console.log("๐Ÿ”„ refreshTable ์ „์—ญ ์ด๋ฒคํŠธ ๋ฐœ์ƒ"); } + toast.success(config.successMessage || `${dataToDelete.length}๊ฐœ ํ•ญ๋ชฉ์ด ์‚ญ์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.`); return true; } @@ -1258,6 +1263,12 @@ export class ButtonActionExecutor { } context.onRefresh?.(); + + // ๐Ÿ†• ๋ถ„ํ•  ํŒจ๋„ ๋“ฑ ์ „์—ญ ํ…Œ์ด๋ธ” ์ƒˆ๋กœ๊ณ ์นจ ์ด๋ฒคํŠธ ๋ฐœ์ƒ + window.dispatchEvent(new CustomEvent("refreshTable")); + console.log("๐Ÿ”„ refreshTable ์ „์—ญ ์ด๋ฒคํŠธ ๋ฐœ์ƒ (๋‹จ์ผ ์‚ญ์ œ)"); + + toast.success(config.successMessage || "์‚ญ์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค."); return true; } catch (error) { console.error("์‚ญ์ œ ์˜ค๋ฅ˜:", error); @@ -1536,6 +1547,13 @@ export class ButtonActionExecutor { } } + // ๐Ÿ†• ๋ถ€๋ชจ ํ™”๋ฉด์˜ ์„ ํƒ๋œ ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ (excludeFilter์—์„œ ์‚ฌ์šฉ) + const parentData = dataRegistry[dataSourceId]?.[0]?.originalData || dataRegistry[dataSourceId]?.[0] || {}; + console.log("๐Ÿ“ฆ [openModalWithData] ๋ถ€๋ชจ ๋ฐ์ดํ„ฐ ์ „๋‹ฌ:", { + dataSourceId, + parentData, + }); + // ๐Ÿ†• ์ „์—ญ ๋ชจ๋‹ฌ ์ƒํƒœ ์—…๋ฐ์ดํŠธ๋ฅผ ์œ„ํ•œ ์ด๋ฒคํŠธ ๋ฐœ์ƒ (URL ํŒŒ๋ผ๋ฏธํ„ฐ ํฌํ•จ) const modalEvent = new CustomEvent("openScreenModal", { detail: { @@ -1544,6 +1562,7 @@ export class ButtonActionExecutor { description: description, size: config.modalSize || "lg", // ๋ฐ์ดํ„ฐ ์ž…๋ ฅ ํ™”๋ฉด์€ ๊ธฐ๋ณธ large urlParams: { dataSourceId }, // ๐Ÿ†• ์ฃผ ๋ฐ์ดํ„ฐ ์†Œ์Šค๋งŒ ์ „๋‹ฌ (๋‚˜๋จธ์ง€๋Š” modalDataStore์—์„œ ์ž๋™์œผ๋กœ ์ฐพ์Œ) + splitPanelParentData: parentData, // ๐Ÿ†• ๋ถ€๋ชจ ๋ฐ์ดํ„ฐ ์ „๋‹ฌ (excludeFilter์—์„œ ์‚ฌ์šฉ) }, }); diff --git a/ํ™”๋ฉด_์ž„๋ฒ ๋”ฉ_๋ฐ_๋ฐ์ดํ„ฐ_์ „๋‹ฌ_์‹œ์Šคํ…œ_๊ตฌํ˜„_๊ณ„ํš์„œ.md b/ํ™”๋ฉด_์ž„๋ฒ ๋”ฉ_๋ฐ_๋ฐ์ดํ„ฐ_์ „๋‹ฌ_์‹œ์Šคํ…œ_๊ตฌํ˜„_๊ณ„ํš์„œ.md index f8f84f1f..74d9d0ed 100644 --- a/ํ™”๋ฉด_์ž„๋ฒ ๋”ฉ_๋ฐ_๋ฐ์ดํ„ฐ_์ „๋‹ฌ_์‹œ์Šคํ…œ_๊ตฌํ˜„_๊ณ„ํš์„œ.md +++ b/ํ™”๋ฉด_์ž„๋ฒ ๋”ฉ_๋ฐ_๋ฐ์ดํ„ฐ_์ „๋‹ฌ_์‹œ์Šคํ…œ_๊ตฌํ˜„_๊ณ„ํš์„œ.md @@ -1679,3 +1679,4 @@ const ์ถœ๊ณ ๋“ฑ๋ก_์„ค์ •: ScreenSplitPanel = { ํ™”๋ฉด ์ž„๋ฒ ๋”ฉ ๋ฐ ๋ฐ์ดํ„ฐ ์ „๋‹ฌ ์‹œ์Šคํ…œ์€ ๋ณต์žกํ•œ ์—…๋ฌด ์›Œํฌํ”Œ๋กœ์šฐ๋ฅผ ํšจ์œจ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐ•๋ ฅํ•œ ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค. ๋‹จ๊ณ„๋ณ„๋กœ ์ฒด๊ณ„์ ์œผ๋กœ ๊ตฌํ˜„ํ•˜๋ฉด ์•ฝ 3.5๊ฐœ์›” ๋‚ด์— ์™„์„ฑํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ด๋ฅผ ํ†ตํ•ด ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ํฌ๊ฒŒ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + diff --git a/ํ™”๋ฉด_์ž„๋ฒ ๋”ฉ_์‹œ์Šคํ…œ_Phase1-4_๊ตฌํ˜„_์™„๋ฃŒ.md b/ํ™”๋ฉด_์ž„๋ฒ ๋”ฉ_์‹œ์Šคํ…œ_Phase1-4_๊ตฌํ˜„_์™„๋ฃŒ.md index 2a3e16de..47526bb1 100644 --- a/ํ™”๋ฉด_์ž„๋ฒ ๋”ฉ_์‹œ์Šคํ…œ_Phase1-4_๊ตฌํ˜„_์™„๋ฃŒ.md +++ b/ํ™”๋ฉด_์ž„๋ฒ ๋”ฉ_์‹œ์Šคํ…œ_Phase1-4_๊ตฌํ˜„_์™„๋ฃŒ.md @@ -526,3 +526,4 @@ const { data: config } = await getScreenSplitPanel(screenId); ์ด์ œ ์ž…๊ณ  ๋“ฑ๋ก๊ณผ ๊ฐ™์€ ๋ณต์žกํ•œ ์›Œํฌํ”Œ๋กœ์šฐ๋ฅผ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ ๋‹จ๊ณ„๋Š” ๊ฐ ์ปดํฌ๋„ŒํŠธ ํƒ€์ž…๋ณ„ DataReceivable ์ธํ„ฐํŽ˜์ด์Šค ๊ตฌํ˜„๊ณผ ์„ค์ • UI ๊ฐœ๋ฐœ์ž…๋‹ˆ๋‹ค. + diff --git a/ํ™”๋ฉด_์ž„๋ฒ ๋”ฉ_์‹œ์Šคํ…œ_์ถฉ๋Œ_๋ถ„์„_๋ณด๊ณ ์„œ.md b/ํ™”๋ฉด_์ž„๋ฒ ๋”ฉ_์‹œ์Šคํ…œ_์ถฉ๋Œ_๋ถ„์„_๋ณด๊ณ ์„œ.md index 7334e8b3..135d36d8 100644 --- a/ํ™”๋ฉด_์ž„๋ฒ ๋”ฉ_์‹œ์Šคํ…œ_์ถฉ๋Œ_๋ถ„์„_๋ณด๊ณ ์„œ.md +++ b/ํ™”๋ฉด_์ž„๋ฒ ๋”ฉ_์‹œ์Šคํ…œ_์ถฉ๋Œ_๋ถ„์„_๋ณด๊ณ ์„œ.md @@ -513,3 +513,4 @@ function ScreenViewPage() { ์ƒˆ๋กœ์šด ์‹œ์Šคํ…œ์€ ๊ธฐ์กด ์‹œ์Šคํ…œ๊ณผ **๋…๋ฆฝ์ ์œผ๋กœ ๋™์ž‘**ํ•˜๋ฉฐ, ์ตœ์†Œํ•œ์˜ ์ˆ˜์ •๋งŒ์œผ๋กœ ํ†ตํ•ฉ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. ํ™”๋ฉด ํŽ˜์ด์ง€์— ์กฐ๊ฑด ๋ถ„๊ธฐ๋งŒ ์ถ”๊ฐ€ํ•˜๋ฉด ๋ฐ”๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + -- 2.43.0