From fc0bc3e5c8f6052b18073e78d6ab99e768f4ee85 Mon Sep 17 00:00:00 2001 From: leeheejin Date: Wed, 22 Oct 2025 11:23:38 +0900 Subject: [PATCH 1/3] =?UTF-8?q?=EC=9E=90=EB=8F=99=EC=8A=A4=ED=81=AC?= =?UTF-8?q?=EB=A1=A4,=20=EB=8B=A4=EC=A4=91=EC=84=A0=ED=83=9D=20=ED=95=98?= =?UTF-8?q?=EA=B3=A0=20=ED=9C=A0=EB=A1=9C=20=EC=9C=84=EC=95=84=EB=9E=98=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99=20=EA=B0=80=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/dashboard/CanvasElement.tsx | 101 +++++++++++++++-- .../admin/dashboard/DashboardCanvas.tsx | 103 ++++++++++++++---- .../admin/dashboard/DashboardDesigner.tsx | 24 ++-- 3 files changed, 189 insertions(+), 39 deletions(-) diff --git a/frontend/components/admin/dashboard/CanvasElement.tsx b/frontend/components/admin/dashboard/CanvasElement.tsx index 7bd4165e..55d45480 100644 --- a/frontend/components/admin/dashboard/CanvasElement.tsx +++ b/frontend/components/admin/dashboard/CanvasElement.tsx @@ -172,6 +172,10 @@ export function CanvasElement({ const [isDragging, setIsDragging] = useState(false); const [isResizing, setIsResizing] = useState(false); const [dragStart, setDragStart] = useState({ x: 0, y: 0, elementX: 0, elementY: 0 }); + const dragStartRef = useRef({ x: 0, y: 0, elementX: 0, elementY: 0, initialScrollY: 0 }); // ๐Ÿ”ฅ ์Šคํฌ๋กค ์กฐ์ •์šฉ ref + const autoScrollDirectionRef = useRef<"up" | "down" | null>(null); // ๐Ÿ”ฅ ์ž๋™ ์Šคํฌ๋กค ๋ฐฉํ–ฅ + const autoScrollFrameRef = useRef(null); // ๐Ÿ”ฅ requestAnimationFrame ID + const lastMouseYRef = useRef(window.innerHeight / 2); // ๐Ÿ”ฅ ๋งˆ์ง€๋ง‰ ๋งˆ์šฐ์Šค Y ์œ„์น˜ (์ดˆ๊ธฐ๊ฐ’: ํ™”๋ฉด ์ค‘๊ฐ„) const [resizeStart, setResizeStart] = useState({ x: 0, y: 0, @@ -213,12 +217,18 @@ export function CanvasElement({ } setIsDragging(true); - setDragStart({ + const startPos = { x: e.clientX, y: e.clientY, elementX: element.position.x, elementY: element.position.y, - }); + initialScrollY: window.pageYOffset, // ๐Ÿ”ฅ ๋“œ๋ž˜๊ทธ ์‹œ์ž‘ ์‹œ์ ์˜ ์Šคํฌ๋กค ์œ„์น˜ + }; + setDragStart(startPos); + dragStartRef.current = startPos; // ๐Ÿ”ฅ ref์—๋„ ์ €์žฅ + + // ๐Ÿ”ฅ ๋“œ๋ž˜๊ทธ ์‹œ์ž‘ ์‹œ ๋งˆ์šฐ์Šค ์œ„์น˜ ์ดˆ๊ธฐํ™” (ํ™”๋ฉด ์ค‘๊ฐ„) + lastMouseYRef.current = window.innerHeight / 2; // ๐Ÿ”ฅ ๋‹ค์ค‘ ์„ ํƒ๋œ ๊ฒฝ์šฐ, ๋‹ค๋ฅธ ์œ„์ ฏ๋“ค์˜ ์˜คํ”„์…‹ ๊ณ„์‚ฐ if (selectedElements.length > 1 && selectedElements.includes(element.id) && onMultiDragStart) { @@ -269,8 +279,25 @@ export function CanvasElement({ const handleMouseMove = useCallback( (e: MouseEvent) => { if (isDragging) { - const deltaX = e.clientX - dragStart.x; - const deltaY = e.clientY - dragStart.y; + // ๐Ÿ”ฅ ์ž๋™ ์Šคํฌ๋กค: ๋‹ค์ค‘ ์„ ํƒ ์‹œ ์ฒซ ๋ฒˆ์งธ ์œ„์ ฏ์—์„œ๋งŒ ์ฒ˜๋ฆฌ + const isFirstSelectedElement = !selectedElements || selectedElements.length === 0 || selectedElements[0] === element.id; + + if (isFirstSelectedElement) { + const scrollThreshold = 100; + const viewportHeight = window.innerHeight; + const mouseY = e.clientY; + + // ๐Ÿ”ฅ ํ•ญ์ƒ ๋งˆ์šฐ์Šค ์œ„์น˜ ์—…๋ฐ์ดํŠธ + lastMouseYRef.current = mouseY; + // console.log("๐Ÿ–ฑ๏ธ ๋งˆ์šฐ์Šค ์œ„์น˜ ์—…๋ฐ์ดํŠธ:", { mouseY, viewportHeight, top: scrollThreshold, bottom: viewportHeight - scrollThreshold }); + } + + // ๐Ÿ”ฅ ํ˜„์žฌ ์Šคํฌ๋กค ์œ„์น˜๋ฅผ ๊ณ ๋ คํ•œ deltaY ๊ณ„์‚ฐ + const currentScrollY = window.pageYOffset; + const scrollDelta = currentScrollY - dragStartRef.current.initialScrollY; + + const deltaX = e.clientX - dragStartRef.current.x; + const deltaY = e.clientY - dragStartRef.current.y + scrollDelta; // ๐Ÿ”ฅ ์Šคํฌ๋กค ๋ณ€ํ™”๋Ÿ‰ ๋ฐ˜์˜ // ์ž„์‹œ ์œ„์น˜ ๊ณ„์‚ฐ let rawX = Math.max(0, dragStart.elementX + deltaX); @@ -356,6 +383,7 @@ export function CanvasElement({ allElements, onUpdateMultiple, onMultiDragMove, + // dragStartRef, autoScrollDirectionRef, autoScrollFrameRef๋Š” ref๋ผ์„œ dependency ๋ถˆํ•„์š” ], ); @@ -393,7 +421,6 @@ export function CanvasElement({ position: { x: Math.max(0, Math.min(canvasWidth - targetElement.size.width, finalX + relativeX)), y: Math.max(0, finalY + relativeY), - z: targetElement.position.z, }, }, }; @@ -401,7 +428,7 @@ export function CanvasElement({ .filter((update): update is { id: string; updates: Partial } => update !== null); if (updates.length > 0) { - console.log("๐Ÿ”ฅ ๋‹ค์ค‘ ์„ ํƒ ์š”์†Œ ํ•จ๊ป˜ ์ด๋™:", updates); + // console.log("๐Ÿ”ฅ ๋‹ค์ค‘ ์„ ํƒ ์š”์†Œ ํ•จ๊ป˜ ์ด๋™:", updates); onUpdateMultiple(updates); } } @@ -437,6 +464,13 @@ export function CanvasElement({ setIsDragging(false); setIsResizing(false); + + // ๐Ÿ”ฅ ์ž๋™ ์Šคํฌ๋กค ์ •๋ฆฌ + autoScrollDirectionRef.current = null; + if (autoScrollFrameRef.current) { + cancelAnimationFrame(autoScrollFrameRef.current); + autoScrollFrameRef.current = null; + } }, [ isDragging, isResizing, @@ -455,6 +489,60 @@ export function CanvasElement({ dragStart.elementY, ]); + // ๐Ÿ”ฅ ์ž๋™ ์Šคํฌ๋กค ๋ฃจํ”„ (requestAnimationFrame ์‚ฌ์šฉ) + useEffect(() => { + if (!isDragging) return; + + const scrollSpeed = 3; // ๐Ÿ”ฅ ์†๋„๋ฅผ ์ข€ ๋” ๋ถ€๋“œ๋Ÿฝ๊ฒŒ (5 โ†’ 3) + const scrollThreshold = 100; + let animationFrameId: number; + let lastTime = performance.now(); + + const autoScrollLoop = (currentTime: number) => { + const viewportHeight = window.innerHeight; + const lastMouseY = lastMouseYRef.current; + + // ๐Ÿ”ฅ ์Šคํฌ๋กค ๋ฐฉํ–ฅ ๊ฒฐ์ • + let shouldScroll = false; + let scrollDirection = 0; + + if (lastMouseY < scrollThreshold) { + // ์œ„์ชฝ ์˜์—ญ + shouldScroll = true; + scrollDirection = -scrollSpeed; + // console.log("โฌ†๏ธ ์œ„๋กœ ์Šคํฌ๋กค ์กฐ๊ฑด ๋งŒ์กฑ:", { lastMouseY, scrollThreshold }); + } else if (lastMouseY > viewportHeight - scrollThreshold) { + // ์•„๋ž˜์ชฝ ์˜์—ญ + shouldScroll = true; + scrollDirection = scrollSpeed; + // console.log("โฌ‡๏ธ ์•„๋ž˜๋กœ ์Šคํฌ๋กค ์กฐ๊ฑด ๋งŒ์กฑ:", { lastMouseY, boundary: viewportHeight - scrollThreshold }); + } + + // ๐Ÿ”ฅ ํ”„๋ ˆ์ž„ ๊ฐ„๊ฒฉ ๊ณ„์‚ฐ + const deltaTime = currentTime - lastTime; + + // ๐Ÿ”ฅ 10ms ๊ฐ„๊ฒฉ์œผ๋กœ ์Šคํฌ๋กค + if (shouldScroll && deltaTime >= 10) { + window.scrollBy(0, scrollDirection); + // console.log("โœ… ์Šคํฌ๋กค ์‹คํ–‰:", { scrollDirection, deltaTime }); + lastTime = currentTime; + } + + // ๊ณ„์† ๋ฐ˜๋ณต + animationFrameId = requestAnimationFrame(autoScrollLoop); + }; + + // ๋ฃจํ”„ ์‹œ์ž‘ + animationFrameId = requestAnimationFrame(autoScrollLoop); + autoScrollFrameRef.current = animationFrameId; + + return () => { + if (animationFrameId) { + cancelAnimationFrame(animationFrameId); + } + }; + }, [isDragging]); + // ์ „์—ญ ๋งˆ์šฐ์Šค ์ด๋ฒคํŠธ ๋“ฑ๋ก React.useEffect(() => { if (isDragging || isResizing) { @@ -586,7 +674,6 @@ export function CanvasElement({ const displayPosition = tempPosition || (multiDragOffset && !isDragging ? { x: element.position.x + multiDragOffset.x, y: element.position.y + multiDragOffset.y, - z: element.position.z, } : element.position); const displaySize = tempSize || element.size; diff --git a/frontend/components/admin/dashboard/DashboardCanvas.tsx b/frontend/components/admin/dashboard/DashboardCanvas.tsx index f586df57..1c2414c1 100644 --- a/frontend/components/admin/dashboard/DashboardCanvas.tsx +++ b/frontend/components/admin/dashboard/DashboardCanvas.tsx @@ -57,9 +57,14 @@ export const DashboardCanvas = forwardRef( } | null>(null); const [isSelecting, setIsSelecting] = useState(false); const [justSelected, setJustSelected] = useState(false); // ๐Ÿ”ฅ ๋ฐฉ๊ธˆ ์„ ํƒํ–ˆ๋Š”์ง€ ํ”Œ๋ž˜๊ทธ + const [isDraggingAny, setIsDraggingAny] = useState(false); // ๐Ÿ”ฅ ํ˜„์žฌ ๋“œ๋ž˜๊ทธ ์ค‘์ธ์ง€ ํ”Œ๋ž˜๊ทธ // ๐Ÿ”ฅ ๋‹ค์ค‘ ์„ ํƒ๋œ ์œ„์ ฏ๋“ค์˜ ์ž„์‹œ ์œ„์น˜ (๋“œ๋ž˜๊ทธ ์ค‘ ์‹œ๊ฐ์  ํ”ผ๋“œ๋ฐฑ) const [multiDragOffsets, setMultiDragOffsets] = useState>({}); + + // ๐Ÿ”ฅ ์„ ํƒ ๋ฐ•์Šค ๋“œ๋ž˜๊ทธ ์ค‘ ์ž๋™ ์Šคํฌ๋กค + const lastMouseYForSelectionRef = React.useRef(window.innerHeight / 2); + const selectionAutoScrollFrameRef = React.useRef(null); // ํ˜„์žฌ ์บ”๋ฒ„์Šค ํฌ๊ธฐ์— ๋งž๋Š” ๊ทธ๋ฆฌ๋“œ ์„ค์ • ๊ณ„์‚ฐ const gridConfig = useMemo(() => calculateGridConfig(canvasWidth), [canvasWidth]); @@ -207,11 +212,11 @@ export const DashboardCanvas = forwardRef( const isWidget = target.closest("[data-element-id]"); if (isWidget) { - console.log("๐Ÿšซ ์œ„์ ฏ ๋‚ด๋ถ€ ํด๋ฆญ - ์„ ํƒ ๋ฐ•์Šค ์‹œ์ž‘ ์•ˆํ•จ"); + // console.log("๐Ÿšซ ์œ„์ ฏ ๋‚ด๋ถ€ ํด๋ฆญ - ์„ ํƒ ๋ฐ•์Šค ์‹œ์ž‘ ์•ˆํ•จ"); return; } - console.log("โœ… ๋นˆ ๊ณต๊ฐ„ ํด๋ฆญ - ์„ ํƒ ๋ฐ•์Šค ์‹œ์ž‘"); + // console.log("โœ… ๋นˆ ๊ณต๊ฐ„ ํด๋ฆญ - ์„ ํƒ ๋ฐ•์Šค ์‹œ์ž‘"); if (!ref || typeof ref === "function") return; const rect = ref.current?.getBoundingClientRect(); @@ -246,7 +251,7 @@ export const DashboardCanvas = forwardRef( const minY = Math.min(selectionBox.startY, selectionBox.endY); const maxY = Math.max(selectionBox.startY, selectionBox.endY); - console.log("๐Ÿ” ์„ ํƒ ๋ฐ•์Šค:", { minX, maxX, minY, maxY }); + // console.log("๐Ÿ” ์„ ํƒ ๋ฐ•์Šค:", { minX, maxX, minY, maxY }); // ์„ ํƒ ๋ฐ•์Šค ์•ˆ์— ์žˆ๋Š” ์š”์†Œ๋“ค ์ฐพ๊ธฐ (70% ์ด์ƒ ๊ฒน์น˜๋ฉด ์„ ํƒ) const selectedIds = elements @@ -276,18 +281,18 @@ export const DashboardCanvas = forwardRef( // 70% ์ด์ƒ ๊ฒน์น˜๋ฉด ์„ ํƒ const overlapPercentage = overlapArea / elementArea; - console.log(`๐Ÿ“ฆ ์š”์†Œ ${el.id}:`, { - position: el.position, - size: el.size, - overlapPercentage: (overlapPercentage * 100).toFixed(1) + "%", - selected: overlapPercentage >= 0.7, - }); + // console.log(`๐Ÿ“ฆ ์š”์†Œ ${el.id}:`, { + // position: el.position, + // size: el.size, + // overlapPercentage: (overlapPercentage * 100).toFixed(1) + "%", + // selected: overlapPercentage >= 0.7, + // }); return overlapPercentage >= 0.7; }) .map((el) => el.id); - console.log("โœ… ์„ ํƒ๋œ ์š”์†Œ:", selectedIds); + // console.log("โœ… ์„ ํƒ๋œ ์š”์†Œ:", selectedIds); if (selectedIds.length > 0) { onSelectMultiple(selectedIds); @@ -313,30 +318,33 @@ export const DashboardCanvas = forwardRef( const x = e.clientX - rect.left + (ref.current?.scrollLeft || 0); const y = e.clientY - rect.top + (ref.current?.scrollTop || 0); - console.log("๐Ÿ–ฑ๏ธ ๋งˆ์šฐ์Šค ์ด๋™:", { x, y, startX: selectionBox.startX, startY: selectionBox.startY, isSelecting }); + // ๐Ÿ”ฅ ์ž๋™ ์Šคํฌ๋กค์„ ์œ„ํ•œ ๋งˆ์šฐ์Šค Y ์œ„์น˜ ์ €์žฅ + lastMouseYForSelectionRef.current = e.clientY; + + // console.log("๐Ÿ–ฑ๏ธ ๋งˆ์šฐ์Šค ์ด๋™:", { x, y, startX: selectionBox.startX, startY: selectionBox.startY, isSelecting }); // ๐Ÿ”ฅ selectionBox๊ฐ€ ์žˆ์ง€๋งŒ ์•„์ง isSelecting์ด false์ธ ๊ฒฝ์šฐ (๋“œ๋ž˜๊ทธ ์‹œ์ž‘ ๋Œ€๊ธฐ) if (!isSelecting) { const deltaX = Math.abs(x - selectionBox.startX); const deltaY = Math.abs(y - selectionBox.startY); - console.log("๐Ÿ“ ์ด๋™ ๊ฑฐ๋ฆฌ:", { deltaX, deltaY }); + // console.log("๐Ÿ“ ์ด๋™ ๊ฑฐ๋ฆฌ:", { deltaX, deltaY }); // ๐Ÿ”ฅ 5px ์ด์ƒ ์›€์ง์ด๋ฉด ์„ ํƒ ๋ฐ•์Šค ํ™œ์„ฑํ™” (์œ„์ ฏ ๋“œ๋ž˜๊ทธ์™€ ๊ตฌ๋ถ„) if (deltaX > 5 || deltaY > 5) { - console.log("๐ŸŽฏ ์„ ํƒ ๋ฐ•์Šค ํ™œ์„ฑํ™” (5px ์ด์ƒ ์ด๋™)"); + // console.log("๐ŸŽฏ ์„ ํƒ ๋ฐ•์Šค ํ™œ์„ฑํ™” (5px ์ด์ƒ ์ด๋™)"); setIsSelecting(true); } return; } // ๐Ÿ”ฅ ์„ ํƒ ๋ฐ•์Šค ์—…๋ฐ์ดํŠธ - console.log("๐Ÿ“ฆ ์„ ํƒ ๋ฐ•์Šค ์—…๋ฐ์ดํŠธ:", { startX: selectionBox.startX, startY: selectionBox.startY, endX: x, endY: y }); + // console.log("๐Ÿ“ฆ ์„ ํƒ ๋ฐ•์Šค ์—…๋ฐ์ดํŠธ:", { startX: selectionBox.startX, startY: selectionBox.startY, endX: x, endY: y }); setSelectionBox((prev) => (prev ? { ...prev, endX: x, endY: y } : null)); }; const handleDocumentMouseUp = () => { - console.log("๐Ÿ–ฑ๏ธ ๋งˆ์šฐ์Šค ์—… - handleMouseUp ํ˜ธ์ถœ"); + // console.log("๐Ÿ–ฑ๏ธ ๋งˆ์šฐ์Šค ์—… - handleMouseUp ํ˜ธ์ถœ"); handleMouseUp(); }; @@ -349,24 +357,77 @@ export const DashboardCanvas = forwardRef( }; }, [selectionBox, isSelecting, ref, handleMouseUp]); + // ๐Ÿ”ฅ ์„ ํƒ ๋ฐ•์Šค ๋“œ๋ž˜๊ทธ ์ค‘ ์ž๋™ ์Šคํฌ๋กค + useEffect(() => { + if (!isSelecting) { + // console.log("โŒ ์ž๋™ ์Šคํฌ๋กค ๋น„ํ™œ์„ฑํ™”: isSelecting =", isSelecting); + return; + } + + // console.log("โœ… ์ž๋™ ์Šคํฌ๋กค ํ™œ์„ฑํ™”: isSelecting =", isSelecting); + + const scrollSpeed = 3; + const scrollThreshold = 100; + let animationFrameId: number; + let lastTime = performance.now(); + + const autoScrollLoop = (currentTime: number) => { + const viewportHeight = window.innerHeight; + const lastMouseY = lastMouseYForSelectionRef.current; + + let shouldScroll = false; + let scrollDirection = 0; + + if (lastMouseY < scrollThreshold) { + shouldScroll = true; + scrollDirection = -scrollSpeed; + // console.log("โฌ†๏ธ ์œ„๋กœ ์Šคํฌ๋กค (์„ ํƒ ๋ฐ•์Šค):", { lastMouseY, scrollThreshold }); + } else if (lastMouseY > viewportHeight - scrollThreshold) { + shouldScroll = true; + scrollDirection = scrollSpeed; + // console.log("โฌ‡๏ธ ์•„๋ž˜๋กœ ์Šคํฌ๋กค (์„ ํƒ ๋ฐ•์Šค):", { lastMouseY, boundary: viewportHeight - scrollThreshold }); + } + + const deltaTime = currentTime - lastTime; + + if (shouldScroll && deltaTime >= 10) { + window.scrollBy(0, scrollDirection); + // console.log("โœ… ์Šคํฌ๋กค ์‹คํ–‰ (์„ ํƒ ๋ฐ•์Šค):", { scrollDirection, deltaTime }); + lastTime = currentTime; + } + + animationFrameId = requestAnimationFrame(autoScrollLoop); + }; + + animationFrameId = requestAnimationFrame(autoScrollLoop); + selectionAutoScrollFrameRef.current = animationFrameId; + + return () => { + if (animationFrameId) { + cancelAnimationFrame(animationFrameId); + } + // console.log("๐Ÿ›‘ ์ž๋™ ์Šคํฌ๋กค ์ •๋ฆฌ"); + }; + }, [isSelecting]); + // ์บ”๋ฒ„์Šค ํด๋ฆญ ์‹œ ์„ ํƒ ํ•ด์ œ const handleCanvasClick = useCallback( (e: React.MouseEvent) => { - // ๐Ÿ”ฅ ๋ฐฉ๊ธˆ ์„ ํƒํ–ˆ์œผ๋ฉด ํด๋ฆญ ์ด๋ฒคํŠธ ๋ฌด์‹œ (์„ ํƒ ํ•ด์ œ ๋ฐฉ์ง€) - if (justSelected) { - console.log("๐Ÿšซ ๋ฐฉ๊ธˆ ์„ ํƒํ–ˆ์œผ๋ฏ€๋กœ ํด๋ฆญ ์ด๋ฒคํŠธ ๋ฌด์‹œ"); + // ๐Ÿ”ฅ ๋ฐฉ๊ธˆ ์„ ํƒํ–ˆ๊ฑฐ๋‚˜ ๋“œ๋ž˜๊ทธ ์ค‘์ด๋ฉด ํด๋ฆญ ์ด๋ฒคํŠธ ๋ฌด์‹œ (์„ ํƒ ํ•ด์ œ ๋ฐฉ์ง€) + if (justSelected || isDraggingAny) { + // console.log("๐Ÿšซ ๋ฐฉ๊ธˆ ์„ ํƒํ–ˆ๊ฑฐ๋‚˜ ๋“œ๋ž˜๊ทธ ์ค‘์ด๋ฏ€๋กœ ํด๋ฆญ ์ด๋ฒคํŠธ ๋ฌด์‹œ"); return; } if (e.target === e.currentTarget) { - console.log("โœ… ๋นˆ ๊ณต๊ฐ„ ํด๋ฆญ - ์„ ํƒ ํ•ด์ œ"); + // console.log("โœ… ๋นˆ ๊ณต๊ฐ„ ํด๋ฆญ - ์„ ํƒ ํ•ด์ œ"); onSelectElement(null); if (onSelectMultiple) { onSelectMultiple([]); } } }, - [onSelectElement, onSelectMultiple, justSelected], + [onSelectElement, onSelectMultiple, justSelected, isDraggingAny], ); // ๋™์  ๊ทธ๋ฆฌ๋“œ ํฌ๊ธฐ ๊ณ„์‚ฐ @@ -462,6 +523,7 @@ export const DashboardCanvas = forwardRef( onMultiDragStart={(draggedId, initialOffsets) => { // ๐Ÿ”ฅ ๋‹ค์ค‘ ๋“œ๋ž˜๊ทธ ์‹œ์ž‘ - ์ดˆ๊ธฐ ์˜คํ”„์…‹ ์ €์žฅ setMultiDragOffsets(initialOffsets); + setIsDraggingAny(true); }} onMultiDragMove={(draggedElement, tempPosition) => { // ๐Ÿ”ฅ ๋‹ค์ค‘ ๋“œ๋ž˜๊ทธ ์ค‘ - ๋‹ค๋ฅธ ์œ„์ ฏ๋“ค์˜ ์œ„์น˜ ์‹ค์‹œ๊ฐ„ ์—…๋ฐ์ดํŠธ @@ -486,6 +548,7 @@ export const DashboardCanvas = forwardRef( onMultiDragEnd={() => { // ๐Ÿ”ฅ ๋‹ค์ค‘ ๋“œ๋ž˜๊ทธ ์ข…๋ฃŒ - ์˜คํ”„์…‹ ์ดˆ๊ธฐํ™” setMultiDragOffsets({}); + setIsDraggingAny(false); }} onRemove={onRemoveElement} onSelect={onSelectElement} diff --git a/frontend/components/admin/dashboard/DashboardDesigner.tsx b/frontend/components/admin/dashboard/DashboardDesigner.tsx index a73e6a47..2d482ab3 100644 --- a/frontend/components/admin/dashboard/DashboardDesigner.tsx +++ b/frontend/components/admin/dashboard/DashboardDesigner.tsx @@ -9,7 +9,7 @@ import { ListWidgetConfigModal } from "./widgets/ListWidgetConfigModal"; import { YardWidgetConfigModal } from "./widgets/YardWidgetConfigModal"; import { DashboardSaveModal } from "./DashboardSaveModal"; import { DashboardElement, ElementType, ElementSubtype } from "./types"; -import { GRID_CONFIG, snapToGrid, snapSizeToGrid, calculateCellSize } from "./gridUtils"; +import { GRID_CONFIG, snapToGrid, snapSizeToGrid, calculateCellSize, calculateGridConfig } from "./gridUtils"; import { Resolution, RESOLUTIONS, detectScreenResolution } from "./ResolutionSelector"; import { DashboardProvider } from "@/contexts/DashboardContext"; import { useMenu } from "@/contexts/MenuContext"; @@ -214,22 +214,22 @@ export default function DashboardDesigner({ dashboardId: initialDashboardId }: D return; } - // ๊ธฐ๋ณธ ํฌ๊ธฐ ์„ค์ • - let defaultCells = { width: 2, height: 2 }; // ๊ธฐ๋ณธ ์œ„์ ฏ ํฌ๊ธฐ + // ๊ธฐ๋ณธ ํฌ๊ธฐ ์„ค์ • (์„œ๋ธŒ๊ทธ๋ฆฌ๋“œ ๊ธฐ์ค€) + const gridConfig = calculateGridConfig(canvasConfig.width); + const subGridSize = gridConfig.SUB_GRID_SIZE; + + // ์„œ๋ธŒ๊ทธ๋ฆฌ๋“œ ๊ธฐ์ค€ ๊ธฐ๋ณธ ํฌ๊ธฐ (ํ”ฝ์…€) + let defaultWidth = subGridSize * 10; // ๊ธฐ๋ณธ ์œ„์ ฏ: ์„œ๋ธŒ๊ทธ๋ฆฌ๋“œ 10์นธ + let defaultHeight = subGridSize * 10; // ๊ธฐ๋ณธ ์œ„์ ฏ: ์„œ๋ธŒ๊ทธ๋ฆฌ๋“œ 10์นธ if (type === "chart") { - defaultCells = { width: 4, height: 3 }; // ์ฐจํŠธ + defaultWidth = subGridSize * 20; // ์ฐจํŠธ: ์„œ๋ธŒ๊ทธ๋ฆฌ๋“œ 20์นธ + defaultHeight = subGridSize * 15; // ์ฐจํŠธ: ์„œ๋ธŒ๊ทธ๋ฆฌ๋“œ 15์นธ } else if (type === "widget" && subtype === "calendar") { - defaultCells = { width: 2, height: 3 }; // ๋‹ฌ๋ ฅ ์ตœ์†Œ ํฌ๊ธฐ + defaultWidth = subGridSize * 10; // ๋‹ฌ๋ ฅ: ์„œ๋ธŒ๊ทธ๋ฆฌ๋“œ 10์นธ + defaultHeight = subGridSize * 15; // ๋‹ฌ๋ ฅ: ์„œ๋ธŒ๊ทธ๋ฆฌ๋“œ 15์นธ } - // ํ˜„์žฌ ํ•ด์ƒ๋„์— ๋งž๋Š” ์…€ ํฌ๊ธฐ ๊ณ„์‚ฐ - const cellSize = Math.floor((canvasConfig.width + GRID_CONFIG.GAP) / GRID_CONFIG.COLUMNS) - GRID_CONFIG.GAP; - const cellWithGap = cellSize + GRID_CONFIG.GAP; - - const defaultWidth = defaultCells.width * cellWithGap - GRID_CONFIG.GAP; - const defaultHeight = defaultCells.height * cellWithGap - GRID_CONFIG.GAP; - // ํฌ๊ธฐ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ if (isNaN(defaultWidth) || isNaN(defaultHeight) || defaultWidth <= 0 || defaultHeight <= 0) { // console.error("Invalid size calculated:", { From 0198426c46d23ad56665dc60ae5e1142df262365 Mon Sep 17 00:00:00 2001 From: kjs Date: Wed, 22 Oct 2025 14:52:13 +0900 Subject: [PATCH 2/3] =?UTF-8?q?=EC=A0=84=EC=B2=B4=EC=A0=81=EC=9D=B8=20?= =?UTF-8?q?=EC=8A=A4=ED=83=80=EC=9D=BC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .cursor/rules/admin-page-style-guide.mdc | 749 ++++++++++++++++++ docs/ADMIN_STYLE_GUIDE_EXAMPLE.md | 435 ++++++++++ frontend/app/(main)/admin/batchmng/page.tsx | 329 ++++---- frontend/app/(main)/admin/commonCode/page.tsx | 76 +- frontend/app/(main)/admin/company/page.tsx | 20 +- frontend/app/(main)/admin/dashboard/page.tsx | 130 +-- frontend/app/(main)/admin/dataflow/page.tsx | 26 +- .../admin/external-call-configs/page.tsx | 308 +++---- .../admin/external-connections/page.tsx | 224 +++--- .../app/(main)/admin/flow-management/page.tsx | 611 +++++++------- frontend/app/(main)/admin/menu/page.tsx | 20 +- frontend/app/(main)/admin/screenMng/page.tsx | 74 +- frontend/app/(main)/admin/tableMng/page.tsx | 747 +++++++++-------- frontend/app/(main)/admin/userMng/page.tsx | 22 +- frontend/components/admin/BatchCard.tsx | 162 ++-- frontend/components/admin/BatchJobModal.tsx | 128 ++- frontend/components/admin/CategoryItem.tsx | 23 +- .../admin/CodeCategoryFormModal.tsx | 29 +- .../components/admin/CodeCategoryPanel.tsx | 87 +- frontend/components/admin/CodeDetailPanel.tsx | 185 ++--- frontend/components/admin/CodeFormModal.tsx | 54 +- frontend/components/admin/CompanyTable.tsx | 268 ++++--- frontend/components/admin/CompanyToolbar.tsx | 25 +- .../components/admin/DiskUsageSummary.tsx | 187 ++--- .../admin/ExternalCallConfigModal.tsx | 152 ++-- .../admin/ExternalDbConnectionModal.tsx | 74 +- frontend/components/admin/MenuManagement.tsx | 516 ++++++------ .../admin/RestApiConnectionList.tsx | 208 +++-- .../components/admin/SortableCodeItem.tsx | 20 +- frontend/components/admin/SqlQueryModal.tsx | 25 +- frontend/components/admin/UserManagement.tsx | 12 +- frontend/components/admin/UserTable.tsx | 327 +++++--- frontend/components/admin/UserToolbar.tsx | 182 ++--- frontend/components/common/ScrollToTop.tsx | 60 ++ frontend/components/dataflow/DataFlowList.tsx | 373 ++++++--- frontend/components/screen/ScreenDesigner.tsx | 14 +- frontend/components/screen/ScreenList.tsx | 676 +++++++++++----- 37 files changed, 4695 insertions(+), 2863 deletions(-) create mode 100644 .cursor/rules/admin-page-style-guide.mdc create mode 100644 docs/ADMIN_STYLE_GUIDE_EXAMPLE.md create mode 100644 frontend/components/common/ScrollToTop.tsx diff --git a/.cursor/rules/admin-page-style-guide.mdc b/.cursor/rules/admin-page-style-guide.mdc new file mode 100644 index 00000000..d0cdaf23 --- /dev/null +++ b/.cursor/rules/admin-page-style-guide.mdc @@ -0,0 +1,749 @@ +--- +description: ๊ด€๋ฆฌ์ž ํŽ˜์ด์ง€ ํ‘œ์ค€ ์Šคํƒ€์ผ ๊ฐ€์ด๋“œ - shadcn/ui ๊ธฐ๋ฐ˜ ์ผ๊ด€๋œ ๋””์ž์ธ ์‹œ์Šคํ…œ +globs: **/app/(main)/admin/**/*.tsx,**/components/admin/**/*.tsx +--- + +# ๊ด€๋ฆฌ์ž ํŽ˜์ด์ง€ ํ‘œ์ค€ ์Šคํƒ€์ผ ๊ฐ€์ด๋“œ + +์ด ๊ฐ€์ด๋“œ๋Š” ๊ด€๋ฆฌ์ž ํŽ˜์ด์ง€์˜ ์ผ๊ด€๋œ UI/UX๋ฅผ ์œ„ํ•œ ํ‘œ์ค€ ์Šคํƒ€์ผ ๊ทœ์น™์ž…๋‹ˆ๋‹ค. +๋ชจ๋“  ๊ด€๋ฆฌ์ž ํŽ˜์ด์ง€๋Š” ์ด ๊ฐ€์ด๋“œ๋ฅผ ๋”ฐ๋ผ์•ผ ํ•ฉ๋‹ˆ๋‹ค. + +## 1. ํŽ˜์ด์ง€ ๋ ˆ์ด์•„์›ƒ ๊ตฌ์กฐ + +### ๊ธฐ๋ณธ ํŽ˜์ด์ง€ ํ…œํ”Œ๋ฆฟ + +```tsx +export default function AdminPage() { + return ( +
+
+ {/* ํŽ˜์ด์ง€ ํ—ค๋” */} +
+

ํŽ˜์ด์ง€ ์ œ๋ชฉ

+

ํŽ˜์ด์ง€ ์„ค๋ช…

+
+ + {/* ๋ฉ”์ธ ์ปจํ…์ธ  */} + +
+ + {/* Scroll to Top ๋ฒ„ํŠผ (๋ชจ๋ฐ”์ผ/ํƒœ๋ธ”๋ฆฟ ์ „์šฉ) */} + +
+ ); +} +``` + +**ํ•„์ˆ˜ ์ ์šฉ ์‚ฌํ•ญ:** + +- ์ตœ์ƒ์œ„: `flex min-h-screen flex-col bg-background` +- ์ปจํ…์ธ  ์˜์—ญ: `space-y-6 p-6` (24px ์ขŒ์šฐ ์—ฌ๋ฐฑ, 24px ๊ฐ„๊ฒฉ) +- ํ—ค๋” ๊ตฌ๋ถ„์„ : `border-b pb-4` (ํ…Œ๋‘๋ฆฌ ๋ฐ•์Šค ์‚ฌ์šฉ ๊ธˆ์ง€) +- Scroll to Top: ๋ชจ๋“  ๊ด€๋ฆฌ์ž ํŽ˜์ด์ง€์— ํฌํ•จ + +## 2. Color System (์ƒ‰์ƒ ์‹œ์Šคํ…œ) + +### CSS Variables ์‚ฌ์šฉ (ํ•˜๋“œ์ฝ”๋”ฉ ๊ธˆ์ง€) + +```tsx +// โŒ ์ž˜๋ชป๋œ ์˜ˆ์‹œ +
+ +// โœ… ์˜ฌ๋ฐ”๋ฅธ ์˜ˆ์‹œ +
+
+
+``` + +**ํ‘œ์ค€ ์ƒ‰์ƒ ํ† ํฐ:** + +- `bg-background` / `text-foreground`: ๊ธฐ๋ณธ ๋ฐฐ๊ฒฝ/ํ…์ŠคํŠธ +- `bg-card` / `text-card-foreground`: ์นด๋“œ ๋ฐฐ๊ฒฝ/ํ…์ŠคํŠธ +- `bg-muted` / `text-muted-foreground`: ๋ณด์กฐ ๋ฐฐ๊ฒฝ/ํ…์ŠคํŠธ +- `bg-primary` / `text-primary`: ๋ฉ”์ธ ์•ก์…˜ +- `bg-destructive` / `text-destructive`: ์‚ญ์ œ/์—๋Ÿฌ +- `border-border`: ํ…Œ๋‘๋ฆฌ +- `ring-ring`: ํฌ์ปค์Šค ๋ง + +## 3. Typography (ํƒ€์ดํฌ๊ทธ๋ž˜ํ”ผ) + +### ํ‘œ์ค€ ํ…์ŠคํŠธ ํฌ๊ธฐ์™€ ๊ฐ€์ค‘์น˜ + +```tsx +// ํŽ˜์ด์ง€ ์ œ๋ชฉ +

+ +// ์„น์…˜ ์ œ๋ชฉ +

+

+

+ +// ๋ณธ๋ฌธ ํ…์ŠคํŠธ +

+ +// ๋ณด์กฐ ํ…์ŠคํŠธ +

+

+ +// ๋ผ๋ฒจ +

// 24px + +// ์„น์…˜ ๋ ˆ๋ฒจ ๊ฐ„๊ฒฉ +
// 16px + +// ํ•„๋“œ ๋ ˆ๋ฒจ ๊ฐ„๊ฒฉ +
// 8px + +// ํŒจ๋”ฉ +
// 24px (์นด๋“œ) +
// 16px (๋‚ด๋ถ€ ์„น์…˜) + +// ๊ฐญ +
// 16px (flex/grid) +
// 8px (๋ฒ„ํŠผ ๊ทธ๋ฃน) +``` + +## 5. ๊ฒ€์ƒ‰ ํˆด๋ฐ” (Toolbar) + +### ํŒจํ„ด A: ํ†ตํ•ฉ ๊ฒ€์ƒ‰ ์˜์—ญ (๊ถŒ์žฅ) + +```tsx +
+ {/* ๊ฒ€์ƒ‰ ๋ฐ ์•ก์…˜ ์˜์—ญ */} +
+ {/* ๊ฒ€์ƒ‰ ์˜์—ญ */} +
+ {/* ํ†ตํ•ฉ ๊ฒ€์ƒ‰ */} +
+
+ + +
+
+ + {/* ๊ณ ๊ธ‰ ๊ฒ€์ƒ‰ ํ† ๊ธ€ */} + +
+ + {/* ์•ก์…˜ ๋ฒ„ํŠผ ์˜์—ญ */} +
+
+ ์ด{" "} + + {count.toLocaleString()} + {" "} + ๊ฑด +
+ +
+
+ + {/* ๊ณ ๊ธ‰ ๊ฒ€์ƒ‰ ์˜ต์…˜ */} + {showAdvanced && ( +
+
+

๊ณ ๊ธ‰ ๊ฒ€์ƒ‰ ์˜ต์…˜

+

์„ค๋ช…

+
+
+ +
+
+ )} +
+``` + +### ํŒจํ„ด B: ์ œ๋ชฉ + ๊ฒ€์ƒ‰ + ๋ฒ„ํŠผ ํ•œ ์ค„ (๊ณต๊ฐ„ ํšจ์œจ์ ) + +```tsx +{ + /* ์ƒ๋‹จ ํ—ค๋”: ์ œ๋ชฉ + ๊ฒ€์ƒ‰ + ๋ฒ„ํŠผ */ +} +
+ {/* ์™ผ์ชฝ: ์ œ๋ชฉ */} +

ํŽ˜์ด์ง€ ์ œ๋ชฉ

+ + {/* ์˜ค๋ฅธ์ชฝ: ๊ฒ€์ƒ‰ + ๋ฒ„ํŠผ */} +
+ {/* ํ•„ํ„ฐ ์„ ํƒ */} +
+ +
+ + {/* ๊ฒ€์ƒ‰ ์ž…๋ ฅ */} +
+ +
+ + {/* ์ดˆ๊ธฐํ™” ๋ฒ„ํŠผ */} + + + {/* ์ฃผ์š” ์•ก์…˜ ๋ฒ„ํŠผ */} + + + {/* ์กฐ๊ฑด๋ถ€ ๋ฒ„ํŠผ (์„ ํƒ ์‹œ) */} + {selectedCount > 0 && ( + + )} +
+
; +``` + +**ํ•„์ˆ˜ ์ ์šฉ ์‚ฌํ•ญ:** + +- โŒ ๊ฒ€์ƒ‰ ์˜์—ญ์— ๋ฐ•์Šค/ํ…Œ๋‘๋ฆฌ ์‚ฌ์šฉ ๊ธˆ์ง€ +- โœ… ๊ฒ€์ƒ‰์ฐฝ ๊ถŒ์žฅ ๋„ˆ๋น„: `w-full sm:w-[240px]` ~ `sm:w-[400px]` +- โœ… ํ•„ํ„ฐ/Select ๊ถŒ์žฅ ๋„ˆ๋น„: `w-full sm:w-[160px]` ~ `sm:w-[200px]` +- โœ… ๊ณ ๊ธ‰ ๊ฒ€์ƒ‰ ํ•„๋“œ: placeholder๋งŒ ์‚ฌ์šฉ (๋ผ๋ฒจ ์ œ๊ฑฐ) +- โœ… ๊ฒ€์ƒ‰ ์•„์ด์ฝ˜: `Search` (lucide-react) +- โœ… Input/Select ๋†’์ด: `h-10` (40px) +- โœ… ์ƒ๋‹จ ํ—ค๋”์— `relative` ์ถ”๊ฐ€ (๋“œ๋กญ๋‹ค์šด ํ‘œ์‹œ์šฉ) + +## 6. Button (๋ฒ„ํŠผ) + +### ํ‘œ์ค€ ๋ฒ„ํŠผ variants์™€ ํฌ๊ธฐ + +```tsx +// Primary ์•ก์…˜ + + +// Secondary ์•ก์…˜ + + +// Ghost ๋ฒ„ํŠผ (์•„์ด์ฝ˜ ์ „์šฉ) + + +// Destructive + +``` + +**ํ‘œ์ค€ ํฌ๊ธฐ:** + +- `h-10`: ๊ธฐ๋ณธ ๋ฒ„ํŠผ (40px) +- `h-9`: ์ž‘์€ ๋ฒ„ํŠผ (36px) +- `h-8`: ์•„์ด์ฝ˜ ๋ฒ„ํŠผ (32px) + +**์•„์ด์ฝ˜ ํฌ๊ธฐ:** + +- `h-4 w-4`: ๋ฒ„ํŠผ ๋‚ด ์•„์ด์ฝ˜ (16px) + +## 7. Input (์ž…๋ ฅ ํ•„๋“œ) + +### ํ‘œ์ค€ Input ์Šคํƒ€์ผ + +```tsx +// ๊ธฐ๋ณธ + + +// ๊ฒ€์ƒ‰ (์•„์ด์ฝ˜ ํฌํ•จ) +
+ + +
+ +// ๋กœ๋”ฉ/์•กํ‹ฐ๋ธŒ + + +// ๋น„ํ™œ์„ฑํ™” + +``` + +**ํ•„์ˆ˜ ์ ์šฉ ์‚ฌํ•ญ:** + +- ๋†’์ด: `h-10` (40px) +- ํ…์ŠคํŠธ: `text-sm` +- ํฌ์ปค์Šค: ์ž๋™ ์ ์šฉ (`ring-2 ring-ring`) + +## 8. Table & Card (ํ…Œ์ด๋ธ”๊ณผ ์นด๋“œ) + +### ๋ฐ˜์‘ํ˜• ํ…Œ์ด๋ธ”/์นด๋“œ ๊ตฌ์กฐ + +```tsx +// ์‹ค์ œ ๋ฐ์ดํ„ฐ ๋ Œ๋”๋ง +return ( + <> + {/* ๋ฐ์Šคํฌํ†ฑ ํ…Œ์ด๋ธ” ๋ทฐ (lg ์ด์ƒ) */} +
+ + + + ์ปฌ๋Ÿผ + + + + + ๋ฐ์ดํ„ฐ + + +
+
+ + {/* ๋ชจ๋ฐ”์ผ/ํƒœ๋ธ”๋ฆฟ ์นด๋“œ ๋ทฐ (lg ๋ฏธ๋งŒ) */} +
+ {items.map((item) => ( +
+ {/* ํ—ค๋” */} +
+
+

{item.name}

+

{item.id}

+
+ +
+ + {/* ์ •๋ณด */} +
+
+ ํ•„๋“œ + {item.value} +
+
+ + {/* ์•ก์…˜ */} +
+ +
+
+ ))} +
+ +); +``` + +**ํ…Œ์ด๋ธ” ํ‘œ์ค€:** + +- ํ—ค๋”: `h-12` (48px), `bg-muted/50`, `font-semibold` +- ๋ฐ์ดํ„ฐ ํ–‰: `h-16` (64px), `hover:bg-muted/50` +- ํ…์ŠคํŠธ: `text-sm` + +**์นด๋“œ ํ‘œ์ค€:** + +- ์ปจํ…Œ์ด๋„ˆ: `rounded-lg border bg-card p-4 shadow-sm` +- ํ—ค๋” ์ œ๋ชฉ: `text-base font-semibold` +- ๋ถ€์ œ๋ชฉ: `text-sm text-muted-foreground` +- ์ •๋ณด ๋ผ๋ฒจ: `text-sm text-muted-foreground` +- ์ •๋ณด ๊ฐ’: `text-sm font-medium` +- ๋ฒ„ํŠผ: `h-9 flex-1 gap-2 text-sm` + +## 9. Loading States (๋กœ๋”ฉ ์ƒํƒœ) + +### Skeleton UI ํŒจํ„ด + +```tsx +// ํ…Œ์ด๋ธ” ์Šค์ผˆ๋ ˆํ†ค (๋ฐ์Šคํฌํ†ฑ) +
+ + ... + + {Array.from({ length: 10 }).map((_, index) => ( + + +
+
+
+ ))} +
+
+
+ +// ์นด๋“œ ์Šค์ผˆ๋ ˆํ†ค (๋ชจ๋ฐ”์ผ/ํƒœ๋ธ”๋ฆฟ) +
+ {Array.from({ length: 6 }).map((_, index) => ( +
+
+
+
+
+
+
+
+
+ {Array.from({ length: 5 }).map((_, i) => ( +
+
+
+
+ ))} +
+
+ ))} +
+``` + +## 10. Empty States (๋นˆ ์ƒํƒœ) + +### ํ‘œ์ค€ Empty State + +```tsx +
+
+

๋ฐ์ดํ„ฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.

+
+
+``` + +## 11. Error States (์—๋Ÿฌ ์ƒํƒœ) + +### ํ‘œ์ค€ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ + +```tsx +
+
+

+ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค +

+ +
+

{errorMessage}

+
+``` + +## 12. Responsive Design (๋ฐ˜์‘ํ˜•) + +### Breakpoints + +- `sm`: 640px (๋ชจ๋ฐ”์ผ ๊ฐ€๋กœ/ํƒœ๋ธ”๋ฆฟ) +- `md`: 768px (ํƒœ๋ธ”๋ฆฟ) +- `lg`: 1024px (๋…ธํŠธ๋ถ) +- `xl`: 1280px (๋ฐ์Šคํฌํ†ฑ) + +### ๋ชจ๋ฐ”์ผ ์šฐ์„  ํŒจํ„ด + +```tsx +// ๋ ˆ์ด์•„์›ƒ +
+ +// ๊ทธ๋ฆฌ๋“œ +
+ +// ๊ฒ€์ƒ‰์ฐฝ +
+ +// ํ…Œ์ด๋ธ”/์นด๋“œ ์ „ํ™˜ +
{/* ๋ฐ์Šคํฌํ†ฑ ํ…Œ์ด๋ธ” */} +
{/* ๋ชจ๋ฐ”์ผ ์นด๋“œ */} + +// ๊ฐ„๊ฒฉ +
+
+``` + +## 13. ์ขŒ์šฐ ๋ ˆ์ด์•„์›ƒ (Side-by-Side Layout) + +### ์‚ฌ์ด๋“œ๋ฐ” + ๋ฉ”์ธ ์˜์—ญ ๊ตฌ์กฐ + +```tsx +
+ {/* ์ขŒ์ธก ์‚ฌ์ด๋“œ๋ฐ” (20-30%) */} +
+
+

์‚ฌ์ด๋“œ๋ฐ” ์ œ๋ชฉ

+ + {/* ์‚ฌ์ด๋“œ๋ฐ” ์ปจํ…์ธ  */} +
+
+

ํ•ญ๋ชฉ

+

์„ค๋ช…

+
+
+
+
+ + {/* ์šฐ์ธก ๋ฉ”์ธ ์˜์—ญ (70-80%) */} +
+
+

๋ฉ”์ธ ์ œ๋ชฉ

+ + {/* ๋ฉ”์ธ ์ปจํ…์ธ  */} +
{/* ์ปจํ…์ธ  */}
+
+
+
+``` + +**ํ•„์ˆ˜ ์ ์šฉ ์‚ฌํ•ญ:** + +- โœ… ์ขŒ์šฐ ๊ตฌ๋ถ„: `border-r` ์‚ฌ์šฉ (์„ธ๋กœ ๊ตฌ๋ถ„์„ ) +- โœ… ๊ฐ„๊ฒฉ: `gap-6` (24px) +- โœ… ์‚ฌ์ด๋“œ๋ฐ” ํŒจ๋”ฉ: `pr-6` (์˜ค๋ฅธ์ชฝ 24px) +- โœ… ๋ฉ”์ธ ์˜์—ญ ํŒจ๋”ฉ: `pl-0` (gap์œผ๋กœ ๊ฐ„๊ฒฉ ํ™•๋ณด) +- โœ… ๋น„์œจ: 20:80 ๋˜๋Š” 30:70 +- โŒ ๊ณผ๋„ํ•œ ๊ตฌ๋ถ„์„  ์‚ฌ์šฉ ๊ธˆ์ง€ (์„ธ๋กœ ๊ตฌ๋ถ„์„  1๊ฐœ๋งŒ) +- โŒ ์‚ฌ์ด๋“œ๋ฐ”์™€ ๋ฉ”์ธ ์˜์—ญ ๊ฐ๊ฐ์— ์ถ”๊ฐ€ border ๊ธˆ์ง€ + +## 14. Custom Dropdown (์ปค์Šคํ…€ ๋“œ๋กญ๋‹ค์šด) + +### ์ปค์Šคํ…€ Select/Dropdown ๊ตฌ์กฐ + +```tsx +{ + /* ๋“œ๋กญ๋‹ค์šด ์ปจํ…Œ์ด๋„ˆ */ +} +
+
+ {/* ํŠธ๋ฆฌ๊ฑฐ ๋ฒ„ํŠผ */} + + + {/* ๋“œ๋กญ๋‹ค์šด ๋ฉ”๋‰ด */} + {isOpen && ( +
+ {/* ๊ฒ€์ƒ‰ (์„ ํƒ์‚ฌํ•ญ) */} +
+ setSearchText(e.target.value)} + className="h-8 text-sm" + onClick={(e) => e.stopPropagation()} + /> +
+ + {/* ์˜ต์…˜ ๋ชฉ๋ก */} +
+ {options.map((option) => ( +
{ + setValue(option.value); + setIsOpen(false); + }} + > + {option.label} +
+ ))} +
+
+ )} +
+
; +``` + +**ํ•„์ˆ˜ ์ ์šฉ ์‚ฌํ•ญ:** + +- โœ… z-index: `z-[100]` (๋‹ค๋ฅธ ์š”์†Œ ์œ„์— ํ‘œ์‹œ) +- โœ… ๊ทธ๋ฆผ์ž: `shadow-lg` (๋ช…ํ™•ํ•œ ๋ ˆ์ด์–ด ๊ตฌ๋ถ„) +- โœ… ์ตœ์†Œ ๋„ˆ๋น„: `min-w-[200px]` (๋‚ด์šฉ์ด ์ž˜๋ฆฌ์ง€ ์•Š๋„๋ก) +- โœ… ์ตœ๋Œ€ ๋†’์ด: `max-h-48` (์Šคํฌ๋กค ๊ฐ€๋Šฅ) +- โœ… ์• ๋‹ˆ๋ฉ”์ด์…˜: ํ™”์‚ดํ‘œ ์•„์ด์ฝ˜ ํšŒ์ „ (`rotate-180`) +- โœ… ๋ถ€๋ชจ ์š”์†Œ: `relative` ํด๋ž˜์Šค ํ•„์š” +- โš ๏ธ ๋ถ€๋ชจ์— `overflow-hidden` ์‚ฌ์šฉ ์‹œ ๋“œ๋กญ๋‹ค์šด ์ž˜๋ฆผ ์ฃผ์˜ + +**๋“œ๋กญ๋‹ค์šด์ด ์ž˜๋ฆด ๋•Œ ํ•ด๊ฒฐ๋ฐฉ๋ฒ•:** + +```tsx +// ๋ถ€๋ชจ ์š”์†Œ์˜ overflow ์ œ๊ฑฐ +
// overflow-hidden ์ œ๊ฑฐ + +// ๋˜๋Š” ์ƒ๋‹จ ํ—ค๋”์— relative ์ถ”๊ฐ€ +
// ๋“œ๋กญ๋‹ค์šด ํฌ์ง€์…”๋‹ ๊ธฐ์ค€์  +``` + +## 15. Scroll to Top Button + +### ๋ชจ๋ฐ”์ผ/ํƒœ๋ธ”๋ฆฟ ์ „์šฉ ๋ฒ„ํŠผ + +```tsx +import { ScrollToTop } from "@/components/common/ScrollToTop"; + +// ํŽ˜์ด์ง€์— ์ถ”๊ฐ€ +; +``` + +**ํŠน์ง•:** + +- ๋ฐ์Šคํฌํ†ฑ์—์„œ ์ˆจ๊น€ (`lg:hidden`) +- ์Šคํฌ๋กค 200px ์ด์ƒ ์‹œ ๋‚˜ํƒ€๋‚จ +- ๋ถ€๋“œ๋Ÿฌ์šด ํŽ˜์ด๋“œ ์ธ/์•„์›ƒ ์• ๋‹ˆ๋ฉ”์ด์…˜ +- ์˜ค๋ฅธ์ชฝ ํ•˜๋‹จ ๊ณ ์ • ์œ„์น˜ +- ์›ํ˜• ๋””์ž์ธ (`rounded-full`) + +## 14. Accessibility (์ ‘๊ทผ์„ฑ) + +### ํ•„์ˆ˜ ์ ์šฉ ์‚ฌํ•ญ + +```tsx +// Label๊ณผ Input ์—ฐ๊ฒฐ + + + +// ๋ฒ„ํŠผ์— aria-label + + +// Switch์— aria-label + + +// ํฌ์ปค์Šค ํ‘œ์‹œ (์ž๋™ ์ ์šฉ) +focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring +``` + +## 15. Class ์ˆœ์„œ (์ผ๊ด€์„ฑ) + +### ํ‘œ์ค€ ํด๋ž˜์Šค ์ž‘์„ฑ ์ˆœ์„œ + +1. Layout: `flex`, `grid`, `block` +2. Position: `fixed`, `absolute`, `relative` +3. Sizing: `w-full`, `h-10` +4. Spacing: `p-4`, `m-2`, `gap-4` +5. Typography: `text-sm`, `font-medium` +6. Colors: `bg-primary`, `text-white` +7. Border: `border`, `rounded-md` +8. Effects: `shadow-sm`, `opacity-50` +9. States: `hover:`, `focus:`, `disabled:` +10. Responsive: `sm:`, `md:`, `lg:` + +## 16. ๊ธˆ์ง€ ์‚ฌํ•ญ + +### โŒ ์ ˆ๋Œ€ ์‚ฌ์šฉํ•˜์ง€ ๋ง ๊ฒƒ + +1. ํ•˜๋“œ์ฝ”๋”ฉ๋œ ์ƒ‰์ƒ (`bg-gray-50`, `text-blue-500` ๋“ฑ) +2. ์ธ๋ผ์ธ ์Šคํƒ€์ผ๋กœ ์ƒ‰์ƒ ์ง€์ • (`style={{ color: '#3b82f6' }}`) +3. ํฌ์ปค์Šค ์Šคํƒ€์ผ ์ œ๊ฑฐ (`outline-none`๋งŒ ๋‹จ๋… ์‚ฌ์šฉ) +4. ์ค‘์ฒฉ๋œ ๋ฐ•์Šค (Card ์•ˆ์— Card, Border ์•ˆ์— Border) +5. ๊ฒ€์ƒ‰ ์˜์—ญ์— ๋ถˆํ•„์š”ํ•œ ๋ฐ•์Šค/ํ…Œ๋‘๋ฆฌ +6. ๊ฒ€์ƒ‰ ํ•„๋“œ์— ๋ผ๋ฒจ (placeholder๋งŒ ์‚ฌ์šฉ) +7. ๋ฐ˜์‘ํ˜• ๋ฌด์‹œ (๋ฐ์Šคํฌํ†ฑ ์ „์šฉ ์Šคํƒ€์ผ) +8. **์ด๋ชจ์ง€ ์‚ฌ์šฉ** (์‚ฌ์šฉ์ž๊ฐ€ ๋ช…์‹œ์ ์œผ๋กœ ์š”์ฒญํ•˜์ง€ ์•Š๋Š” ํ•œ ์ ˆ๋Œ€ ์‚ฌ์šฉ ๊ธˆ์ง€) +9. ๊ณผ๋„ํ•œ ๊ตฌ๋ถ„์„  ์‚ฌ์šฉ (์ตœ์†Œํ•œ์œผ๋กœ ์œ ์ง€) +10. ๋“œ๋กญ๋‹ค์šด ๋ถ€๋ชจ์— `overflow-hidden` (์ž˜๋ฆผ ๋ฐœ์ƒ) + +## 17. ์ฒดํฌ๋ฆฌ์ŠคํŠธ + +์ƒˆ๋กœ์šด ๊ด€๋ฆฌ์ž ํŽ˜์ด์ง€ ์ž‘์„ฑ ์‹œ ๋‹ค์Œ์„ ํ™•์ธํ•˜์„ธ์š”: + +### ํŽ˜์ด์ง€ ๋ ˆ๋ฒจ + +- [ ] `bg-background` ์‚ฌ์šฉ (ํ•˜๋“œ์ฝ”๋”ฉ ๊ธˆ์ง€) +- [ ] `space-y-6 p-6` ๊ตฌ์กฐ +- [ ] ํŽ˜์ด์ง€ ํ—ค๋”์— `border-b pb-4` +- [ ] `ScrollToTop` ์ปดํฌ๋„ŒํŠธ ํฌํ•จ + +### ๊ฒ€์ƒ‰ ํˆด๋ฐ” + +- [ ] ๋ฐ•์Šค/ํ…Œ๋‘๋ฆฌ ์—†์Œ +- [ ] ๊ฒ€์ƒ‰์ฐฝ ์ตœ๋Œ€ ๋„ˆ๋น„ `sm:w-[400px]` +- [ ] ๊ณ ๊ธ‰ ๊ฒ€์ƒ‰ ํ•„๋“œ์— ๋ผ๋ฒจ ์—†์Œ (placeholder๋งŒ) +- [ ] ๋ฐ˜์‘ํ˜• ๋ ˆ์ด์•„์›ƒ ์ ์šฉ + +### ํ…Œ์ด๋ธ”/์นด๋“œ + +- [ ] ๋ฐ์Šคํฌํ†ฑ: ํ…Œ์ด๋ธ” (`hidden lg:block`) +- [ ] ๋ชจ๋ฐ”์ผ: ์นด๋“œ (`lg:hidden`) +- [ ] ํ‘œ์ค€ ๋†’์ด์™€ ๊ฐ„๊ฒฉ ์ ์šฉ +- [ ] ๋กœ๋”ฉ/Empty ์ƒํƒœ ๊ตฌํ˜„ + +### ๋ฒ„ํŠผ + +- [ ] ํ‘œ์ค€ variants ์‚ฌ์šฉ +- [ ] ํ‘œ์ค€ ๋†’์ด: `h-10`, `h-9`, `h-8` +- [ ] ์•„์ด์ฝ˜ ํฌ๊ธฐ: `h-4 w-4` +- [ ] `gap-2`๋กœ ์•„์ด์ฝ˜๊ณผ ํ…์ŠคํŠธ ๊ฐ„๊ฒฉ + +### ๋ฐ˜์‘ํ˜• + +- [ ] ๋ชจ๋ฐ”์ผ ์šฐ์„  ๋””์ž์ธ +- [ ] Breakpoints ์ ์šฉ (`sm:`, `lg:`) +- [ ] ํ…Œ์ด๋ธ”/์นด๋“œ ์ „ํ™˜ +- [ ] Scroll to Top ๋ฒ„ํŠผ + +### ์ ‘๊ทผ์„ฑ + +- [ ] Label `htmlFor` / Input `id` ์—ฐ๊ฒฐ +- [ ] ๋ฒ„ํŠผ `aria-label` +- [ ] Switch `aria-label` +- [ ] ํฌ์ปค์Šค ํ‘œ์‹œ ์œ ์ง€ + +## ์ฐธ๊ณ  ํŒŒ์ผ + +์™„์„ฑ๋œ ์˜ˆ์‹œ: + +### ๊ธฐ๋ณธ ํŒจํ„ด + +- [์‚ฌ์šฉ์ž ๊ด€๋ฆฌ ํŽ˜์ด์ง€]() - ๊ธฐ๋ณธ ํŽ˜์ด์ง€ ๊ตฌ์กฐ +- [๊ฒ€์ƒ‰ ํˆด๋ฐ”](mdc:frontend/components/admin/UserToolbar.tsx) - ํŒจํ„ด A (ํ†ตํ•ฉ ๊ฒ€์ƒ‰) +- [ํ…Œ์ด๋ธ”/์นด๋“œ](mdc:frontend/components/admin/UserTable.tsx) - ๋ฐ˜์‘ํ˜• ํ…Œ์ด๋ธ”/์นด๋“œ +- [Scroll to Top](mdc:frontend/components/common/ScrollToTop.tsx) - ์Šคํฌ๋กค ๋ฒ„ํŠผ + +### ๊ณ ๊ธ‰ ํŒจํ„ด + +- [๋ฉ”๋‰ด ๊ด€๋ฆฌ ํŽ˜์ด์ง€]() - ์ขŒ์šฐ ๋ ˆ์ด์•„์›ƒ + ํŒจํ„ด B (์ œ๋ชฉ+๊ฒ€์ƒ‰+๋ฒ„ํŠผ) +- [๋ฉ”๋‰ด ๊ด€๋ฆฌ ์ปดํฌ๋„ŒํŠธ](mdc:frontend/components/admin/MenuManagement.tsx) - ์ปค์Šคํ…€ ๋“œ๋กญ๋‹ค์šด + ์ขŒ์šฐ ๋ ˆ์ด์•„์›ƒ diff --git a/docs/ADMIN_STYLE_GUIDE_EXAMPLE.md b/docs/ADMIN_STYLE_GUIDE_EXAMPLE.md new file mode 100644 index 00000000..2ff7f4e1 --- /dev/null +++ b/docs/ADMIN_STYLE_GUIDE_EXAMPLE.md @@ -0,0 +1,435 @@ +# ๊ด€๋ฆฌ์ž ํŽ˜์ด์ง€ ์Šคํƒ€์ผ ๊ฐ€์ด๋“œ ์ ์šฉ ์˜ˆ์‹œ + +## ๊ฐœ์š” + +์‚ฌ์šฉ์ž ๊ด€๋ฆฌ ํŽ˜์ด์ง€๋ฅผ ์˜ˆ์‹œ๋กœ shadcn/ui ์Šคํƒ€์ผ ๊ฐ€์ด๋“œ์— ๋งž์ถฐ ์žฌ์ž‘์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค. +์ด ์˜ˆ์‹œ๋ฅผ ๊ธฐ์ค€์œผ๋กœ ๋‹ค๋ฅธ ๊ด€๋ฆฌ์ž ํŽ˜์ด์ง€๋“ค๋„ ์ผ๊ด€๋œ ์Šคํƒ€์ผ๋กœ ํ†ต์ผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +## ์ ์šฉ๋œ ์ฃผ์š” ์›์น™ + +### 1. Color System (์ƒ‰์ƒ ์‹œ์Šคํ…œ) + +**CSS Variables ์‚ฌ์šฉ (ํ•˜๋“œ์ฝ”๋”ฉ๋œ ์ƒ‰์ƒ ๊ธˆ์ง€)** +```tsx +// โŒ ์ž˜๋ชป๋œ ์˜ˆ์‹œ +
+ +// โœ… ์˜ฌ๋ฐ”๋ฅธ ์˜ˆ์‹œ +
+
+
+
+
+``` + +**์ ์šฉ ์‚ฌ๋ก€:** +- ํŽ˜์ด์ง€ ๋ฐฐ๊ฒฝ: `bg-background` +- ์นด๋“œ ๋ฐฐ๊ฒฝ: `bg-card` +- ๋ณด์กฐ ํ…์ŠคํŠธ: `text-muted-foreground` +- ์ฃผ์š” ์•ก์…˜: `text-primary`, `border-primary` +- ์—๋Ÿฌ ๋ฉ”์‹œ์ง€: `text-destructive`, `bg-destructive/10` + +### 2. Typography (ํƒ€์ดํฌ๊ทธ๋ž˜ํ”ผ) + +**์ผ๊ด€๋œ ํฐํŠธ ํฌ๊ธฐ์™€ ๊ฐ€์ค‘์น˜** +```tsx +// ํŽ˜์ด์ง€ ์ œ๋ชฉ +

์‚ฌ์šฉ์ž ๊ด€๋ฆฌ

+ +// ์„น์…˜ ์ œ๋ชฉ +

๊ณ ๊ธ‰ ๊ฒ€์ƒ‰ ์˜ต์…˜

+ +// ๋ณธ๋ฌธ ํ…์ŠคํŠธ +

์„ค๋ช… ํ…์ŠคํŠธ

+ +// ๋ผ๋ฒจ + + +// ๋ณด์กฐ ํ…์ŠคํŠธ +

๋„์›€๋ง

+``` + +### 3. Spacing System (๊ฐ„๊ฒฉ) + +**์ผ๊ด€๋œ ๊ฐ„๊ฒฉ ์‚ฌ์šฉ (4px ๊ธฐ์ค€)** +```tsx +// ์ปดํฌ๋„ŒํŠธ ๊ฐ„ ๊ฐ„๊ฒฉ +
// 24px (ํŽ˜์ด์ง€ ๋ ˆ๋ฒจ) +
// 16px (์„น์…˜ ๋ ˆ๋ฒจ) +
// 8px (ํ•„๋“œ ๋ ˆ๋ฒจ) + +// ํŒจ๋”ฉ +
// 24px (์นด๋“œ) +
// 16px (๋‚ด๋ถ€ ์„น์…˜) + +// ๊ฐญ +
// 16px (flex/grid) +
// 8px (๋ฒ„ํŠผ ๊ทธ๋ฃน) +``` + +### 4. Border & Radius (ํ…Œ๋‘๋ฆฌ ๋ฐ ๋‘ฅ๊ทผ ๋ชจ์„œ๋ฆฌ) + +**ํ‘œ์ค€ radius ์‚ฌ์šฉ** +```tsx +// ์นด๋“œ/ํŒจ๋„ +
+ +// ์ž…๋ ฅ ํ•„๋“œ + + +// ๋ฒ„ํŠผ + + +// Secondary ์•ก์…˜ + + +// Ghost ๋ฒ„ํŠผ (์•„์ด์ฝ˜ ์ „์šฉ) + +``` + +**ํฌ๊ธฐ ํ‘œ์ค€:** +- `h-10`: ๊ธฐ๋ณธ ๋ฒ„ํŠผ (40px) +- `h-9`: ์ž‘์€ ๋ฒ„ํŠผ (36px) +- `h-8`: ์•„์ด์ฝ˜ ๋ฒ„ํŠผ (32px) + +### 6. Input States (์ž…๋ ฅ ํ•„๋“œ ์ƒํƒœ) + +**ํ‘œ์ค€ Input ์Šคํƒ€์ผ** +```tsx +// ๊ธฐ๋ณธ + + +// ํฌ์ปค์Šค (์ž๋™ ์ ์šฉ) +// focus:ring-2 focus:ring-ring + +// ๋กœ๋”ฉ/์•กํ‹ฐ๋ธŒ + + +// ๋น„ํ™œ์„ฑํ™” + +``` + +### 7. Form Structure (ํผ ๊ตฌ์กฐ) + +**ํ‘œ์ค€ ํ•„๋“œ ๊ตฌ์กฐ** +```tsx +
+ + +

+ ๋„์›€๋ง ํ…์ŠคํŠธ +

+
+``` + +### 8. Table Structure (ํ…Œ์ด๋ธ” ๊ตฌ์กฐ) + +**ํ‘œ์ค€ ํ…Œ์ด๋ธ” ์Šคํƒ€์ผ** +```tsx +
+ + + + + ์ปฌ๋Ÿผ๋ช… + + + + + + + ๋ฐ์ดํ„ฐ + + + +
+
+``` + +**๋†’์ด ํ‘œ์ค€:** +- ํ—ค๋”: `h-12` (48px) +- ๋ฐ์ดํ„ฐ ํ–‰: `h-16` (64px) + +### 9. Loading States (๋กœ๋”ฉ ์ƒํƒœ) + +**Skeleton UI** +```tsx +
+``` + +### 10. Empty States (๋นˆ ์ƒํƒœ) + +**ํ‘œ์ค€ Empty State** +```tsx + +
+

๋“ฑ๋ก๋œ ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.

+
+
+``` + +### 11. Error States (์—๋Ÿฌ ์ƒํƒœ) + +**ํ‘œ์ค€ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€** +```tsx +
+
+

์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค

+ +
+

{errorMessage}

+
+``` + +### 12. Responsive Design (๋ฐ˜์‘ํ˜•) + +**๋ชจ๋ฐ”์ผ ์šฐ์„  ์ ‘๊ทผ** +```tsx +// ๋ ˆ์ด์•„์›ƒ +
+ +// ๊ทธ๋ฆฌ๋“œ +
+ +// ํ…์ŠคํŠธ +

+ +// ๊ฐ„๊ฒฉ +
+``` + +### 13. Accessibility (์ ‘๊ทผ์„ฑ) + +**ํ•„์ˆ˜ ์ ์šฉ ์‚ฌํ•ญ** +```tsx +// Label๊ณผ Input ์—ฐ๊ฒฐ + + + +// ๋ฒ„ํŠผ์— aria-label + + +// Switch์— aria-label + +``` + +## ํŽ˜์ด์ง€ ๊ตฌ์กฐ ํ…œํ”Œ๋ฆฟ + +### Page Component +```tsx +export default function AdminPage() { + return ( +
+
+ {/* ํŽ˜์ด์ง€ ํ—ค๋” */} +
+

ํŽ˜์ด์ง€ ์ œ๋ชฉ

+

ํŽ˜์ด์ง€ ์„ค๋ช…

+
+ + {/* ๋ฉ”์ธ ์ปจํ…์ธ  */} + +
+
+ ); +} +``` + +### Toolbar Component +```tsx +export function Toolbar() { + return ( +
+ {/* ๊ฒ€์ƒ‰ ์˜์—ญ */} +
+
+ {/* ๊ฒ€์ƒ‰ ์ž…๋ ฅ */} +
+
+ + +
+
+ + {/* ๋ฒ„ํŠผ */} + +
+
+ + {/* ์•ก์…˜ ๋ฒ„ํŠผ ์˜์—ญ */} +
+
+ ์ด {count.toLocaleString()} ๊ฑด +
+ + +
+
+ ); +} +``` + +## ์ ์šฉํ•ด์•ผ ํ•  ๋‹ค๋ฅธ ๊ด€๋ฆฌ์ž ํŽ˜์ด์ง€ + +### ์šฐ์„ ์ˆœ์œ„ 1 (ํ•ต์‹ฌ ํŽ˜์ด์ง€) +- [ ] ๋ฉ”๋‰ด ๊ด€๋ฆฌ (`/admin/menu`) +- [ ] ๊ณตํ†ต์ฝ”๋“œ ๊ด€๋ฆฌ (`/admin/commonCode`) +- [ ] ํšŒ์‚ฌ ๊ด€๋ฆฌ (`/admin/company`) +- [ ] ํ…Œ์ด๋ธ” ๊ด€๋ฆฌ (`/admin/tableMng`) + +### ์šฐ์„ ์ˆœ์œ„ 2 (์ž์ฃผ ์‚ฌ์šฉํ•˜๋Š” ํŽ˜์ด์ง€) +- [ ] ์™ธ๋ถ€ ์—ฐ๊ฒฐ ๊ด€๋ฆฌ (`/admin/external-connections`) +- [ ] ์™ธ๋ถ€ ํ˜ธ์ถœ ์„ค์ • (`/admin/external-call-configs`) +- [ ] ๋ฐฐ์น˜ ๊ด€๋ฆฌ (`/admin/batch-management`) +- [ ] ๋ ˆ์ด์•„์›ƒ ๊ด€๋ฆฌ (`/admin/layouts`) + +### ์šฐ์„ ์ˆœ์œ„ 3 (๊ธฐํƒ€ ๊ด€๋ฆฌ ํŽ˜์ด์ง€) +- [ ] ํ…œํ”Œ๋ฆฟ ๊ด€๋ฆฌ (`/admin/templates`) +- [ ] ํ‘œ์ค€ ๊ด€๋ฆฌ (`/admin/standards`) +- [ ] ๋‹ค๊ตญ์–ด ๊ด€๋ฆฌ (`/admin/i18n`) +- [ ] ์ˆ˜์ง‘ ๊ด€๋ฆฌ (`/admin/collection-management`) + +## ์ฒดํฌ๋ฆฌ์ŠคํŠธ + +๊ฐ ํŽ˜์ด์ง€ ์ž‘์—… ์‹œ ๋‹ค์Œ์„ ํ™•์ธํ•˜์„ธ์š”: + +### ๋ ˆ์ด์•„์›ƒ +- [ ] `bg-background` ์‚ฌ์šฉ (ํ•˜๋“œ์ฝ”๋”ฉ๋œ ์ƒ‰์ƒ ์—†์Œ) +- [ ] `container mx-auto space-y-6 p-6` ๊ตฌ์กฐ +- [ ] ํŽ˜์ด์ง€ ํ—ค๋”์— `border-b pb-4` + +### ์ƒ‰์ƒ +- [ ] CSS Variables๋งŒ ์‚ฌ์šฉ (`bg-card`, `text-muted-foreground` ๋“ฑ) +- [ ] `bg-gray-*`, `text-gray-*` ๋“ฑ ํ•˜๋“œ์ฝ”๋”ฉ ์ œ๊ฑฐ + +### ํƒ€์ดํฌ๊ทธ๋ž˜ํ”ผ +- [ ] ํŽ˜์ด์ง€ ์ œ๋ชฉ: `text-3xl font-bold tracking-tight` +- [ ] ์„น์…˜ ์ œ๋ชฉ: `text-sm font-semibold` +- [ ] ๋ณธ๋ฌธ: `text-sm` +- [ ] ๋ณด์กฐ ํ…์ŠคํŠธ: `text-xs text-muted-foreground` + +### ๊ฐ„๊ฒฉ +- [ ] ํŽ˜์ด์ง€ ๋ ˆ๋ฒจ: `space-y-6` +- [ ] ์„น์…˜ ๋ ˆ๋ฒจ: `space-y-4` +- [ ] ํ•„๋“œ ๋ ˆ๋ฒจ: `space-y-2` +- [ ] ์นด๋“œ ํŒจ๋”ฉ: `p-4` ๋˜๋Š” `p-6` + +### ๋ฒ„ํŠผ +- [ ] ํ‘œ์ค€ variants ์‚ฌ์šฉ (`default`, `outline`, `ghost`) +- [ ] ํ‘œ์ค€ ํฌ๊ธฐ: `h-10` (๊ธฐ๋ณธ), `h-9` (์ž‘์Œ), `h-8` (์•„์ด์ฝ˜) +- [ ] ํ…์ŠคํŠธ: `text-sm font-medium` +- [ ] ์•„์ด์ฝ˜ + ํ…์ŠคํŠธ: `gap-2` + +### ์ž…๋ ฅ ํ•„๋“œ +- [ ] ๋†’์ด: `h-10` +- [ ] ํ…์ŠคํŠธ: `text-sm` +- [ ] Label๊ณผ Input `htmlFor`/`id` ์—ฐ๊ฒฐ +- [ ] `space-y-2` ๊ตฌ์กฐ + +### ํ…Œ์ด๋ธ” +- [ ] `rounded-lg border bg-card shadow-sm` +- [ ] ํ—ค๋”: `h-12 text-sm font-semibold bg-muted/50` +- [ ] ๋ฐ์ดํ„ฐ ํ–‰: `h-16 text-sm` +- [ ] Hover: `hover:bg-muted/50` + +### ๋ฐ˜์‘ํ˜• +- [ ] ๋ชจ๋ฐ”์ผ ์šฐ์„  ๋””์ž์ธ +- [ ] `sm:`, `md:`, `lg:` ๋ธŒ๋ ˆ์ดํฌํฌ์ธํŠธ ์‚ฌ์šฉ +- [ ] `flex-col sm:flex-row` ํŒจํ„ด + +### ์ ‘๊ทผ์„ฑ +- [ ] Label `htmlFor` ์†์„ฑ +- [ ] Input `id` ์†์„ฑ +- [ ] ๋ฒ„ํŠผ `aria-label` +- [ ] Switch `aria-label` + +## ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ ˆ์ฐจ + +1. **ํŽ˜์ด์ง€ ์ปดํฌ๋„ŒํŠธ ์ˆ˜์ •** (`page.tsx`) + - ๋ ˆ์ด์•„์›ƒ ๊ตฌ์กฐ ๋ณ€๊ฒฝ + - ์ƒ‰์ƒ CSS Variables๋กœ ๋ณ€๊ฒฝ + - ํŽ˜์ด์ง€ ํ—ค๋” ํ‘œ์ค€ํ™” + +2. **Toolbar ์ปดํฌ๋„ŒํŠธ ์ˆ˜์ •** + - ๊ฒ€์ƒ‰ ์˜์—ญ ์Šคํƒ€์ผ ํ†ต์ผ + - ๋ฒ„ํŠผ ์Šคํƒ€์ผ ํ‘œ์ค€ํ™” + - ๋ฐ˜์‘ํ˜• ๋ ˆ์ด์•„์›ƒ ์ ์šฉ + +3. **Table ์ปดํฌ๋„ŒํŠธ ์ˆ˜์ •** + - ํ…Œ์ด๋ธ” ์ปจํ…Œ์ด๋„ˆ ์Šคํƒ€์ผ ํ†ต์ผ + - ํ—ค๋”/๋ฐ์ดํ„ฐ ํ–‰ ๋†’์ด ํ‘œ์ค€ํ™” + - ๋กœ๋”ฉ/Empty State ํ‘œ์ค€ํ™” + +4. **Form ์ปดํฌ๋„ŒํŠธ ์ˆ˜์ •** (์žˆ๋Š” ๊ฒฝ์šฐ) + - ํ•„๋“œ ๊ตฌ์กฐ ํ‘œ์ค€ํ™” + - ๋ผ๋ฒจ๊ณผ ์ž…๋ ฅ ํ•„๋“œ ์—ฐ๊ฒฐ + - ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ์Šคํƒ€์ผ ํ†ต์ผ + +5. **Modal ์ปดํฌ๋„ŒํŠธ ์ˆ˜์ •** (์žˆ๋Š” ๊ฒฝ์šฐ) + - Dialog ํ‘œ์ค€ ํŒจํ„ด ์ ์šฉ + - ๋ฐ˜์‘ํ˜• ํฌ๊ธฐ (`max-w-[95vw] sm:max-w-[500px]`) + - ๋ฒ„ํŠผ ์Šคํƒ€์ผ ํ‘œ์ค€ํ™” + +6. **๋ฆฐํŠธ ์—๋Ÿฌ ํ™•์ธ** + ```bash + # ์ˆ˜์ •ํ•œ ํŒŒ์ผ๋“ค ํ™•์ธ + npm run lint + ``` + +7. **ํ…Œ์ŠคํŠธ** + - ๊ธฐ๋Šฅ ๋™์ž‘ ํ™•์ธ + - ๋ฐ˜์‘ํ˜• ํ™•์ธ (๋ชจ๋ฐ”์ผ/ํƒœ๋ธ”๋ฆฟ/๋ฐ์Šคํฌํ†ฑ) + - ๋‹คํฌ๋ชจ๋“œ ํ™•์ธ (์žˆ๋Š” ๊ฒฝ์šฐ) + +## ์ฐธ๊ณ  ํŒŒ์ผ + +### ์™„์„ฑ๋œ ์˜ˆ์‹œ +- `frontend/app/(main)/admin/userMng/page.tsx` +- `frontend/components/admin/UserToolbar.tsx` +- `frontend/components/admin/UserTable.tsx` +- `frontend/components/admin/UserManagement.tsx` + +### ์Šคํƒ€์ผ ๊ฐ€์ด๋“œ +- `.cursorrules` - ์ „์ฒด ์Šคํƒ€์ผ ๊ทœ์น™ +- Section 1-21: ๊ฐ ์Šคํƒ€์ผ ์š”์†Œ๋ณ„ ์ƒ์„ธ ๊ฐ€์ด๋“œ + diff --git a/frontend/app/(main)/admin/batchmng/page.tsx b/frontend/app/(main)/admin/batchmng/page.tsx index 184ae578..46aedf1f 100644 --- a/frontend/app/(main)/admin/batchmng/page.tsx +++ b/frontend/app/(main)/admin/batchmng/page.tsx @@ -1,17 +1,8 @@ "use client"; import React, { useState, useEffect } from "react"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; -import { - Table, - TableBody, - TableCell, - TableHead, - TableHeader, - TableRow -} from "@/components/ui/table"; import { Plus, Search, @@ -26,6 +17,7 @@ import { BatchMapping, } from "@/lib/api/batch"; import BatchCard from "@/components/admin/BatchCard"; +import { ScrollToTop } from "@/components/common/ScrollToTop"; export default function BatchManagementPage() { const router = useRouter(); @@ -178,187 +170,198 @@ export default function BatchManagementPage() { }; return ( -
- {/* ํ—ค๋” */} -
-
-

๋ฐฐ์น˜ ๊ด€๋ฆฌ

-

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ฐ„ ๋ฐฐ์น˜ ์ž‘์—…์„ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

+
+
+ {/* ํŽ˜์ด์ง€ ํ—ค๋” */} +
+

๋ฐฐ์น˜ ๊ด€๋ฆฌ

+

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ฐ„ ๋ฐฐ์น˜ ์ž‘์—…์„ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

- -
- {/* ๊ฒ€์ƒ‰ ๋ฐ ํ•„ํ„ฐ */} - - -
-
- - handleSearch(e.target.value)} - className="pl-10" - /> + {/* ๊ฒ€์ƒ‰ ๋ฐ ์•ก์…˜ ์˜์—ญ */} +
+ {/* ๊ฒ€์ƒ‰ ์˜์—ญ */} +
+
+
+ + handleSearch(e.target.value)} + className="h-10 pl-10 text-sm" + /> +
+
- - - {/* ๋ฐฐ์น˜ ๋ชฉ๋ก */} - - - - ๋ฐฐ์น˜ ๋ชฉ๋ก ({batchConfigs.length}๊ฐœ) - {loading && } - - - - {batchConfigs.length === 0 ? ( -
- -

๋ฐฐ์น˜๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค

-

- {searchTerm ? "๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค." : "์ƒˆ๋กœ์šด ๋ฐฐ์น˜๋ฅผ ์ถ”๊ฐ€ํ•ด๋ณด์„ธ์š”."} -

+ {/* ์•ก์…˜ ๋ฒ„ํŠผ ์˜์—ญ */} +
+
+ ์ด{" "} + + {batchConfigs.length.toLocaleString()} + {" "} + ๊ฑด +
+ +
+
+ + {/* ๋ฐฐ์น˜ ๋ชฉ๋ก */} + {batchConfigs.length === 0 ? ( +
+
+ +
+

๋ฐฐ์น˜๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค

+

+ {searchTerm ? "๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค." : "์ƒˆ๋กœ์šด ๋ฐฐ์น˜๋ฅผ ์ถ”๊ฐ€ํ•ด๋ณด์„ธ์š”."} +

+
{!searchTerm && ( )}
- ) : ( -
- {batchConfigs.map((batch) => ( - { - console.log("๐Ÿ–ฑ๏ธ ๋น„ํ™œ์„ฑํ™”/ํ™œ์„ฑํ™” ๋ฒ„ํŠผ ํด๋ฆญ:", { batchId, currentStatus }); - toggleBatchStatus(batchId, currentStatus); - }} - onEdit={(batchId) => router.push(`/admin/batchmng/edit/${batchId}`)} - onDelete={deleteBatch} - getMappingSummary={getMappingSummary} - /> - ))} -
- )} - - - - {/* ํŽ˜์ด์ง€๋„ค์ด์…˜ */} - {totalPages > 1 && ( -
- - -
- {Array.from({ length: Math.min(5, totalPages) }, (_, i) => { - const pageNum = i + 1; - return ( - - ); - })}
- - -
- )} + ) : ( +
+ {batchConfigs.map((batch) => ( + { + toggleBatchStatus(batchId, currentStatus); + }} + onEdit={(batchId) => router.push(`/admin/batchmng/edit/${batchId}`)} + onDelete={deleteBatch} + getMappingSummary={getMappingSummary} + /> + ))} +
+ )} - {/* ๋ฐฐ์น˜ ํƒ€์ž… ์„ ํƒ ๋ชจ๋‹ฌ */} - {isBatchTypeModalOpen && ( -
- - - ๋ฐฐ์น˜ ํƒ€์ž… ์„ ํƒ - - -
- {/* DB โ†’ DB */} -
handleBatchTypeSelect('db-to-db')} - > -
- - - -
-
-
DB โ†’ DB
-
๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ฐ„ ๋ฐ์ดํ„ฐ ๋™๊ธฐํ™”
-
+ {/* ํŽ˜์ด์ง€๋„ค์ด์…˜ */} + {totalPages > 1 && ( +
+ + +
+ {Array.from({ length: Math.min(5, totalPages) }, (_, i) => { + const pageNum = i + 1; + return ( + + ); + })} +
+ + +
+ )} + + {/* ๋ฐฐ์น˜ ํƒ€์ž… ์„ ํƒ ๋ชจ๋‹ฌ */} + {isBatchTypeModalOpen && ( +
+
+
+

๋ฐฐ์น˜ ํƒ€์ž… ์„ ํƒ

+ +
+ {/* DB โ†’ DB */} + + + {/* REST API โ†’ DB */} +
- {/* REST API โ†’ DB */} -
handleBatchTypeSelect('restapi-to-db')} - > -
- - - -
-
-
REST API โ†’ DB
-
REST API์—์„œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋กœ ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘
-
+
+
+
+
+ )} +
-
- -
- - -
- )} + {/* Scroll to Top ๋ฒ„ํŠผ */} +
); } \ No newline at end of file diff --git a/frontend/app/(main)/admin/commonCode/page.tsx b/frontend/app/(main)/admin/commonCode/page.tsx index bdb435f7..9c1bf507 100644 --- a/frontend/app/(main)/admin/commonCode/page.tsx +++ b/frontend/app/(main)/admin/commonCode/page.tsx @@ -1,59 +1,49 @@ "use client"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { CodeCategoryPanel } from "@/components/admin/CodeCategoryPanel"; import { CodeDetailPanel } from "@/components/admin/CodeDetailPanel"; import { useSelectedCategory } from "@/hooks/useSelectedCategory"; -// import { useMultiLang } from "@/hooks/useMultiLang"; // ๋ฌดํ•œ ๋ฃจํ”„ ๋ฐฉ์ง€๋ฅผ ์œ„ํ•ด ์ž„์‹œ ์ œ๊ฑฐ +import { ScrollToTop } from "@/components/common/ScrollToTop"; export default function CommonCodeManagementPage() { - // const { getText } = useMultiLang(); // ๋ฌดํ•œ ๋ฃจํ”„ ๋ฐฉ์ง€๋ฅผ ์œ„ํ•ด ์ž„์‹œ ์ œ๊ฑฐ const { selectedCategoryCode, selectCategory } = useSelectedCategory(); return ( -
-
- {/* ํŽ˜์ด์ง€ ์ œ๋ชฉ */} -
-
-

๊ณตํ†ต์ฝ”๋“œ ๊ด€๋ฆฌ

-

์‹œ์Šคํ…œ์—์„œ ์‚ฌ์šฉํ•˜๋Š” ๊ณตํ†ต์ฝ”๋“œ๋ฅผ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค

+
+
+ {/* ํŽ˜์ด์ง€ ํ—ค๋” */} +
+

๊ณตํ†ต์ฝ”๋“œ ๊ด€๋ฆฌ

+

์‹œ์Šคํ…œ์—์„œ ์‚ฌ์šฉํ•˜๋Š” ๊ณตํ†ต์ฝ”๋“œ๋ฅผ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค

+
+ + {/* ๋ฉ”์ธ ์ฝ˜ํ…์ธ  - ์ขŒ์šฐ ๋ ˆ์ด์•„์›ƒ */} +
+ {/* ์ขŒ์ธก: ์นดํ…Œ๊ณ ๋ฆฌ ํŒจ๋„ */} +
+
+

์ฝ”๋“œ ์นดํ…Œ๊ณ ๋ฆฌ

+ +
+
+ + {/* ์šฐ์ธก: ์ฝ”๋“œ ์ƒ์„ธ ํŒจ๋„ */} +
+
+

+ ์ฝ”๋“œ ์ƒ์„ธ ์ •๋ณด + {selectedCategoryCode && ( + ({selectedCategoryCode}) + )} +

+ +
- - {/* ๋ฉ”์ธ ์ฝ˜ํ…์ธ  */} - {/* ๋ฐ˜์‘ํ˜• ๋ ˆ์ด์•„์›ƒ: PC๋Š” ๊ฐ€๋กœ, ๋ชจ๋ฐ”์ผ์€ ์„ธ๋กœ */} -
- {/* ์นดํ…Œ๊ณ ๋ฆฌ ํŒจ๋„ - PC์—์„œ ์ขŒ์ธก ๊ณ ์ • ๋„ˆ๋น„, ๋ชจ๋ฐ”์ผ์—์„œ ์ „์ฒด ๋„ˆ๋น„ */} -
- - - ๐Ÿ“‚ ์ฝ”๋“œ ์นดํ…Œ๊ณ ๋ฆฌ - - - - - -
- - {/* ์ฝ”๋“œ ์ƒ์„ธ ํŒจ๋„ - PC์—์„œ ๋‚˜๋จธ์ง€ ๊ณต๊ฐ„, ๋ชจ๋ฐ”์ผ์—์„œ ์ „์ฒด ๋„ˆ๋น„ */} -
- - - - ๐Ÿ“‹ ์ฝ”๋“œ ์ƒ์„ธ ์ •๋ณด - {selectedCategoryCode && ( - ({selectedCategoryCode}) - )} - - - - - - -
-
+ + {/* Scroll to Top ๋ฒ„ํŠผ */} +
); } diff --git a/frontend/app/(main)/admin/company/page.tsx b/frontend/app/(main)/admin/company/page.tsx index c24a3e10..c24afc7a 100644 --- a/frontend/app/(main)/admin/company/page.tsx +++ b/frontend/app/(main)/admin/company/page.tsx @@ -1,21 +1,25 @@ import { CompanyManagement } from "@/components/admin/CompanyManagement"; +import { ScrollToTop } from "@/components/common/ScrollToTop"; /** * ํšŒ์‚ฌ ๊ด€๋ฆฌ ํŽ˜์ด์ง€ */ export default function CompanyPage() { return ( -
-
- {/* ํŽ˜์ด์ง€ ์ œ๋ชฉ */} -
-
-

ํšŒ์‚ฌ ๊ด€๋ฆฌ

-

์‹œ์Šคํ…œ์—์„œ ์‚ฌ์šฉํ•˜๋Š” ํšŒ์‚ฌ ์ •๋ณด๋ฅผ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค

-
+
+
+ {/* ํŽ˜์ด์ง€ ํ—ค๋” */} +
+

ํšŒ์‚ฌ ๊ด€๋ฆฌ

+

์‹œ์Šคํ…œ์—์„œ ์‚ฌ์šฉํ•˜๋Š” ํšŒ์‚ฌ ์ •๋ณด๋ฅผ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค

+ + {/* ๋ฉ”์ธ ์ปจํ…์ธ  */}
+ + {/* Scroll to Top ๋ฒ„ํŠผ */} +
); } diff --git a/frontend/app/(main)/admin/dashboard/page.tsx b/frontend/app/(main)/admin/dashboard/page.tsx index d1ca6125..d5608e76 100644 --- a/frontend/app/(main)/admin/dashboard/page.tsx +++ b/frontend/app/(main)/admin/dashboard/page.tsx @@ -126,102 +126,108 @@ export default function DashboardListPage() { if (loading) { return ( -
+
-
๋กœ๋”ฉ ์ค‘...
-
๋Œ€์‹œ๋ณด๋“œ ๋ชฉ๋ก์„ ๋ถˆ๋Ÿฌ์˜ค๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค
+
๋กœ๋”ฉ ์ค‘...
+
๋Œ€์‹œ๋ณด๋“œ ๋ชฉ๋ก์„ ๋ถˆ๋Ÿฌ์˜ค๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค
); } return ( -
-
- {/* ํ—ค๋” */} -
-

๋Œ€์‹œ๋ณด๋“œ ๊ด€๋ฆฌ

-

๋Œ€์‹œ๋ณด๋“œ๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค

+
+
+ {/* ํŽ˜์ด์ง€ ํ—ค๋” */} +
+

๋Œ€์‹œ๋ณด๋“œ ๊ด€๋ฆฌ

+

๋Œ€์‹œ๋ณด๋“œ๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค

- {/* ์•ก์…˜ ๋ฐ” */} -
-
- + {/* ๊ฒ€์ƒ‰ ๋ฐ ์•ก์…˜ */} +
+
+ setSearchTerm(e.target.value)} - className="pl-9" + className="h-10 pl-10 text-sm" />
-
{/* ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ */} {error && ( - -

{error}

-
+
+
+

์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค

+ +
+

{error}

+
)} {/* ๋Œ€์‹œ๋ณด๋“œ ๋ชฉ๋ก */} {dashboards.length === 0 ? ( - -
- +
+
+

๋Œ€์‹œ๋ณด๋“œ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค

-

๋Œ€์‹œ๋ณด๋“œ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค

-

์ฒซ ๋ฒˆ์งธ ๋Œ€์‹œ๋ณด๋“œ๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ๋ฐ์ดํ„ฐ ์‹œ๊ฐํ™”๋ฅผ ์‹œ์ž‘ํ•˜์„ธ์š”

- - +
) : ( - +
- - ์ œ๋ชฉ - ์„ค๋ช… - ์ƒ์„ฑ์ผ - ์ˆ˜์ •์ผ - ์ž‘์—… + + ์ œ๋ชฉ + ์„ค๋ช… + ์ƒ์„ฑ์ผ + ์ˆ˜์ •์ผ + ์ž‘์—… {dashboards.map((dashboard) => ( - - {dashboard.title} - + + {dashboard.title} + {dashboard.description || "-"} - {formatDate(dashboard.createdAt)} - {formatDate(dashboard.updatedAt)} - + {formatDate(dashboard.createdAt)} + {formatDate(dashboard.updatedAt)} + - router.push(`/admin/dashboard/edit/${dashboard.id}`)} - className="gap-2" + className="gap-2 text-sm" > ํŽธ์ง‘ - handleCopy(dashboard)} className="gap-2"> + handleCopy(dashboard)} className="gap-2 text-sm"> ๋ณต์‚ฌ handleDeleteClick(dashboard.id, dashboard.title)} - className="gap-2 text-red-600 focus:text-red-600" + className="gap-2 text-sm text-destructive focus:text-destructive" > ์‚ญ์ œ @@ -233,23 +239,27 @@ export default function DashboardListPage() { ))}
- +
)}
{/* ์‚ญ์ œ ํ™•์ธ ๋ชจ๋‹ฌ */} - + - ๋Œ€์‹œ๋ณด๋“œ ์‚ญ์ œ - + ๋Œ€์‹œ๋ณด๋“œ ์‚ญ์ œ + "{deleteTarget?.title}" ๋Œ€์‹œ๋ณด๋“œ๋ฅผ ์‚ญ์ œํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ? -
์ด ์ž‘์—…์€ ๋˜๋Œ๋ฆด ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. +
+ ์ด ์ž‘์—…์€ ๋˜๋Œ๋ฆด ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.
- - ์ทจ์†Œ - + + ์ทจ์†Œ + ์‚ญ์ œ @@ -258,16 +268,18 @@ export default function DashboardListPage() { {/* ์„ฑ๊ณต ๋ชจ๋‹ฌ */} - + -
- +
+
- ์™„๋ฃŒ - {successMessage} + ์™„๋ฃŒ + {successMessage}
- +
diff --git a/frontend/app/(main)/admin/dataflow/page.tsx b/frontend/app/(main)/admin/dataflow/page.tsx index ff7e5aeb..f8a77e11 100644 --- a/frontend/app/(main)/admin/dataflow/page.tsx +++ b/frontend/app/(main)/admin/dataflow/page.tsx @@ -7,6 +7,7 @@ import { FlowEditor } from "@/components/dataflow/node-editor/FlowEditor"; import { useAuth } from "@/hooks/useAuth"; import { toast } from "sonner"; import { Button } from "@/components/ui/button"; +import { ScrollToTop } from "@/components/common/ScrollToTop"; import { ArrowLeft } from "lucide-react"; type Step = "list" | "editor"; @@ -50,17 +51,17 @@ export default function DataFlowPage() { // ์—๋””ํ„ฐ ๋ชจ๋“œ์ผ ๋•Œ๋Š” ๋ ˆ์ด์•„์›ƒ ์—†์ด ์ „์ฒด ํ™”๋ฉด ์‚ฌ์šฉ if (isEditorMode) { return ( -
+
{/* ์—๋””ํ„ฐ ํ—ค๋” */} -
+
-

๋…ธ๋“œ ํ”Œ๋กœ์šฐ ์—๋””ํ„ฐ

-

+

๋…ธ๋“œ ํ”Œ๋กœ์šฐ ์—๋””ํ„ฐ

+

๋“œ๋ž˜๊ทธ ์•ค ๋“œ๋กญ์œผ๋กœ ๋ฐ์ดํ„ฐ ์ œ์–ด ํ”Œ๋กœ์šฐ๋ฅผ ์‹œ๊ฐ์ ์œผ๋กœ ์„ค๊ณ„ํ•ฉ๋‹ˆ๋‹ค

@@ -76,19 +77,20 @@ export default function DataFlowPage() { } return ( -
-
- {/* ํŽ˜์ด์ง€ ์ œ๋ชฉ */} -
-
-

์ œ์–ด ๊ด€๋ฆฌ

-

๋…ธ๋“œ ๊ธฐ๋ฐ˜ ๋ฐ์ดํ„ฐ ํ”Œ๋กœ์šฐ๋ฅผ ์‹œ๊ฐ์ ์œผ๋กœ ์„ค๊ณ„ํ•˜๊ณ  ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค

-
+
+
+ {/* ํŽ˜์ด์ง€ ํ—ค๋” */} +
+

์ œ์–ด ๊ด€๋ฆฌ

+

๋…ธ๋“œ ๊ธฐ๋ฐ˜ ๋ฐ์ดํ„ฐ ํ”Œ๋กœ์šฐ๋ฅผ ์‹œ๊ฐ์ ์œผ๋กœ ์„ค๊ณ„ํ•˜๊ณ  ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค

{/* ํ”Œ๋กœ์šฐ ๋ชฉ๋ก */}
+ + {/* Scroll to Top ๋ฒ„ํŠผ */} +
); } diff --git a/frontend/app/(main)/admin/external-call-configs/page.tsx b/frontend/app/(main)/admin/external-call-configs/page.tsx index 805220ca..7e433ec7 100644 --- a/frontend/app/(main)/admin/external-call-configs/page.tsx +++ b/frontend/app/(main)/admin/external-call-configs/page.tsx @@ -161,205 +161,201 @@ export default function ExternalCallConfigsPage() { }; return ( -
-
- {/* ํŽ˜์ด์ง€ ํ—ค๋” */} -
-
-

์™ธ๋ถ€ ํ˜ธ์ถœ ๊ด€๋ฆฌ

-

Discord, Slack, ์นด์นด์˜คํ†ก ๋“ฑ ์™ธ๋ถ€ ํ˜ธ์ถœ ์„ค์ •์„ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

+
+
+ {/* ํŽ˜์ด์ง€ ํ—ค๋” */} +
+

์™ธ๋ถ€ ํ˜ธ์ถœ ๊ด€๋ฆฌ

+

Discord, Slack, ์นด์นด์˜คํ†ก ๋“ฑ ์™ธ๋ถ€ ํ˜ธ์ถœ ์„ค์ •์„ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

- -
- {/* ๊ฒ€์ƒ‰ ๋ฐ ํ•„ํ„ฐ */} - - - - - ๊ฒ€์ƒ‰ ๋ฐ ํ•„ํ„ฐ - - - - {/* ๊ฒ€์ƒ‰ */} -
-
- setSearchQuery(e.target.value)} - onKeyPress={handleSearchKeyPress} - /> + {/* ๊ฒ€์ƒ‰ ๋ฐ ํ•„ํ„ฐ ์˜์—ญ */} +
+ {/* ์ฒซ ๋ฒˆ์งธ ์ค„: ๊ฒ€์ƒ‰ + ์ถ”๊ฐ€ ๋ฒ„ํŠผ */} +
+
+
+
+ + setSearchQuery(e.target.value)} + onKeyPress={handleSearchKeyPress} + className="h-10 pl-10 text-sm" + /> +
+
+
-
- {/* ํ•„ํ„ฐ */} -
-
- - -
+ {/* ๋‘ ๋ฒˆ์งธ ์ค„: ํ•„ํ„ฐ */} +
+ -
- - -
+ -
- - -
+
- - +
- {/* ์„ค์ • ๋ชฉ๋ก */} - - - ์™ธ๋ถ€ ํ˜ธ์ถœ ์„ค์ • ๋ชฉ๋ก - - + {/* ์„ค์ • ๋ชฉ๋ก */} +
{loading ? ( // ๋กœ๋”ฉ ์ƒํƒœ -
-
๋กœ๋”ฉ ์ค‘...
+
+
๋กœ๋”ฉ ์ค‘...
) : configs.length === 0 ? ( // ๋นˆ ์ƒํƒœ -
-
- -

๋“ฑ๋ก๋œ ์™ธ๋ถ€ ํ˜ธ์ถœ ์„ค์ •์ด ์—†์Šต๋‹ˆ๋‹ค.

-

์ƒˆ ์™ธ๋ถ€ ํ˜ธ์ถœ์„ ์ถ”๊ฐ€ํ•ด๋ณด์„ธ์š”.

+
+
+

๋“ฑ๋ก๋œ ์™ธ๋ถ€ ํ˜ธ์ถœ ์„ค์ •์ด ์—†์Šต๋‹ˆ๋‹ค.

+

์ƒˆ ์™ธ๋ถ€ ํ˜ธ์ถœ์„ ์ถ”๊ฐ€ํ•ด๋ณด์„ธ์š”.

) : ( // ์„ค์ • ํ…Œ์ด๋ธ” ๋ชฉ๋ก - - ์„ค์ •๋ช… - ํ˜ธ์ถœ ํƒ€์ž… - API ํƒ€์ž… - ์„ค๋ช… - ์ƒํƒœ - ์ƒ์„ฑ์ผ - ์ž‘์—… + + ์„ค์ •๋ช… + ํ˜ธ์ถœ ํƒ€์ž… + API ํƒ€์ž… + ์„ค๋ช… + ์ƒํƒœ + ์ƒ์„ฑ์ผ + ์ž‘์—… {configs.map((config) => ( - - {config.config_name} - + + {config.config_name} + {getCallTypeLabel(config.call_type)} - + {config.api_type ? ( {getApiTypeLabel(config.api_type)} ) : ( - - + - )} - +
{config.description ? ( - + {config.description} ) : ( - - + - )}
- + {config.is_active === "Y" ? "ํ™œ์„ฑ" : "๋น„ํ™œ์„ฑ"} - + {config.created_date ? new Date(config.created_date).toLocaleDateString() : "-"} - +
- -
@@ -368,8 +364,7 @@ export default function ExternalCallConfigsPage() {
)} - - +
{/* ์™ธ๋ถ€ ํ˜ธ์ถœ ์„ค์ • ๋ชจ๋‹ฌ */} - + - ์™ธ๋ถ€ ํ˜ธ์ถœ ์„ค์ • ์‚ญ์ œ - + ์™ธ๋ถ€ ํ˜ธ์ถœ ์„ค์ • ์‚ญ์ œ + "{configToDelete?.config_name}" ์„ค์ •์„ ์‚ญ์ œํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?
์ด ์ž‘์—…์€ ๋˜๋Œ๋ฆด ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.
- - ์ทจ์†Œ - + + + ์ทจ์†Œ + + ์‚ญ์ œ diff --git a/frontend/app/(main)/admin/external-connections/page.tsx b/frontend/app/(main)/admin/external-connections/page.tsx index 42a20bdb..3c80ac58 100644 --- a/frontend/app/(main)/admin/external-connections/page.tsx +++ b/frontend/app/(main)/admin/external-connections/page.tsx @@ -227,14 +227,12 @@ export default function ExternalConnectionsPage() { }; return ( -
-
- {/* ํŽ˜์ด์ง€ ์ œ๋ชฉ */} -
-
-

์™ธ๋ถ€ ์ปค๋„ฅ์…˜ ๊ด€๋ฆฌ

-

์™ธ๋ถ€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ฐ REST API ์—ฐ๊ฒฐ ์ •๋ณด๋ฅผ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค

-
+
+
+ {/* ํŽ˜์ด์ง€ ํ—ค๋” */} +
+

์™ธ๋ถ€ ์ปค๋„ฅ์…˜ ๊ด€๋ฆฌ

+

์™ธ๋ถ€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ฐ REST API ์—ฐ๊ฒฐ ์ •๋ณด๋ฅผ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค

{/* ํƒญ */} @@ -253,166 +251,152 @@ export default function ExternalConnectionsPage() { {/* ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ ํƒญ */} {/* ๊ฒ€์ƒ‰ ๋ฐ ํ•„ํ„ฐ */} - - -
-
- {/* ๊ฒ€์ƒ‰ */} -
- - setSearchTerm(e.target.value)} - className="w-64 pl-10" - /> -
- - {/* DB ํƒ€์ž… ํ•„ํ„ฐ */} - - - {/* ํ™œ์„ฑ ์ƒํƒœ ํ•„ํ„ฐ */} - -
- - {/* ์ถ”๊ฐ€ ๋ฒ„ํŠผ */} - +
+
+ {/* ๊ฒ€์ƒ‰ */} +
+ + setSearchTerm(e.target.value)} + className="h-10 pl-10 text-sm" + />
- - + + {/* DB ํƒ€์ž… ํ•„ํ„ฐ */} + + + {/* ํ™œ์„ฑ ์ƒํƒœ ํ•„ํ„ฐ */} + +
+ + {/* ์ถ”๊ฐ€ ๋ฒ„ํŠผ */} + +
{/* ์—ฐ๊ฒฐ ๋ชฉ๋ก */} {loading ? ( -
-
๋กœ๋”ฉ ์ค‘...
+
+
๋กœ๋”ฉ ์ค‘...
) : connections.length === 0 ? ( - - -
- -

๋“ฑ๋ก๋œ ์—ฐ๊ฒฐ์ด ์—†์Šต๋‹ˆ๋‹ค

-

์ƒˆ ์™ธ๋ถ€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ์„ ์ถ”๊ฐ€ํ•ด๋ณด์„ธ์š”.

- -
-
-
+
+
+

๋“ฑ๋ก๋œ ์—ฐ๊ฒฐ์ด ์—†์Šต๋‹ˆ๋‹ค

+
+
) : ( - - - +
+
- - ์—ฐ๊ฒฐ๋ช… - DB ํƒ€์ž… - ํ˜ธ์ŠคํŠธ:ํฌํŠธ - ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค - ์‚ฌ์šฉ์ž - ์ƒํƒœ - ์ƒ์„ฑ์ผ - ์—ฐ๊ฒฐ ํ…Œ์ŠคํŠธ - ์ž‘์—… + + ์—ฐ๊ฒฐ๋ช… + DB ํƒ€์ž… + ํ˜ธ์ŠคํŠธ:ํฌํŠธ + ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค + ์‚ฌ์šฉ์ž + ์ƒํƒœ + ์ƒ์„ฑ์ผ + ์—ฐ๊ฒฐ ํ…Œ์ŠคํŠธ + ์ž‘์—… {connections.map((connection) => ( - - + +
{connection.connection_name}
- - + + {DB_TYPE_LABELS[connection.db_type] || connection.db_type} - + {connection.host}:{connection.port} - {connection.database_name} - {connection.username} - - + {connection.database_name} + {connection.username} + + {connection.is_active === "Y" ? "ํ™œ์„ฑ" : "๋น„ํ™œ์„ฑ"} - + {connection.created_date ? new Date(connection.created_date).toLocaleDateString() : "N/A"} - +
{testResults.has(connection.id!) && ( - + {testResults.get(connection.id!) ? "์„ฑ๊ณต" : "์‹คํŒจ"} )}
- -
+ +
@@ -422,8 +406,7 @@ export default function ExternalConnectionsPage() { ))}
-
-
+
)} {/* ์—ฐ๊ฒฐ ์„ค์ • ๋ชจ๋‹ฌ */} @@ -439,20 +422,25 @@ export default function ExternalConnectionsPage() { {/* ์‚ญ์ œ ํ™•์ธ ๋‹ค์ด์–ผ๋กœ๊ทธ */} - + - ์—ฐ๊ฒฐ ์‚ญ์ œ ํ™•์ธ - + ์—ฐ๊ฒฐ ์‚ญ์ œ ํ™•์ธ + "{connectionToDelete?.connection_name}" ์—ฐ๊ฒฐ์„ ์‚ญ์ œํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?
- ์ด ์ž‘์—…์€ ๋˜๋Œ๋ฆด ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. + ์ด ์ž‘์—…์€ ๋˜๋Œ๋ฆด ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.
- - ์ทจ์†Œ + + + ์ทจ์†Œ + ์‚ญ์ œ diff --git a/frontend/app/(main)/admin/flow-management/page.tsx b/frontend/app/(main)/admin/flow-management/page.tsx index f36bd5a2..e8166662 100644 --- a/frontend/app/(main)/admin/flow-management/page.tsx +++ b/frontend/app/(main)/admin/flow-management/page.tsx @@ -9,9 +9,8 @@ import { useState, useEffect } from "react"; import { useRouter } from "next/navigation"; -import { Plus, Edit2, Trash2, Play, Workflow, Table, Calendar, User, Check, ChevronsUpDown } from "lucide-react"; +import { Plus, Edit2, Trash2, Workflow, Table, Calendar, User, Check, ChevronsUpDown } from "lucide-react"; import { Button } from "@/components/ui/button"; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Dialog, @@ -32,6 +31,7 @@ import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { cn } from "@/lib/utils"; import { tableManagementApi } from "@/lib/api/tableManagement"; +import { ScrollToTop } from "@/components/common/ScrollToTop"; export default function FlowManagementPage() { const router = useRouter(); @@ -45,11 +45,15 @@ export default function FlowManagementPage() { const [selectedFlow, setSelectedFlow] = useState(null); // ํ…Œ์ด๋ธ” ๋ชฉ๋ก ๊ด€๋ จ ์ƒํƒœ - const [tableList, setTableList] = useState([]); // ๋‚ด๋ถ€ DB ํ…Œ์ด๋ธ” + const [tableList, setTableList] = useState>( + [], + ); const [loadingTables, setLoadingTables] = useState(false); const [openTableCombobox, setOpenTableCombobox] = useState(false); const [selectedDbSource, setSelectedDbSource] = useState<"internal" | number>("internal"); // "internal" ๋˜๋Š” ์™ธ๋ถ€ DB connection ID - const [externalConnections, setExternalConnections] = useState([]); + const [externalConnections, setExternalConnections] = useState< + Array<{ id: number; connection_name: string; db_type: string }> + >([]); const [externalTableList, setExternalTableList] = useState([]); const [loadingExternalTables, setLoadingExternalTables] = useState(false); @@ -74,10 +78,10 @@ export default function FlowManagementPage() { variant: "destructive", }); } - } catch (error: any) { + } catch (error) { toast({ title: "์˜ค๋ฅ˜ ๋ฐœ์ƒ", - description: error.message, + description: error instanceof Error ? error.message : "์•Œ ์ˆ˜ ์—†๋Š” ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.", variant: "destructive", }); } finally { @@ -87,6 +91,7 @@ export default function FlowManagementPage() { useEffect(() => { loadFlows(); + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); // ํ…Œ์ด๋ธ” ๋ชฉ๋ก ๋กœ๋“œ (๋‚ด๋ถ€ DB) @@ -128,7 +133,8 @@ export default function FlowManagementPage() { if (data.success && data.data) { // ๋ฉ”์ธ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค(ํ˜„์žฌ ์‹œ์Šคํ…œ) ์ œ์™ธ - connection_name์— "๋ฉ”์ธ" ๋˜๋Š” "ํ˜„์žฌ ์‹œ์Šคํ…œ"์ด ํฌํ•จ๋œ ๊ฒƒ ํ•„ํ„ฐ๋ง const filtered = data.data.filter( - (conn: any) => !conn.connection_name.includes("๋ฉ”์ธ") && !conn.connection_name.includes("ํ˜„์žฌ ์‹œ์Šคํ…œ"), + (conn: { connection_name: string }) => + !conn.connection_name.includes("๋ฉ”์ธ") && !conn.connection_name.includes("ํ˜„์žฌ ์‹œ์Šคํ…œ"), ); setExternalConnections(filtered); } @@ -164,7 +170,9 @@ export default function FlowManagementPage() { if (data.success && data.data) { const tables = Array.isArray(data.data) ? data.data : []; const tableNames = tables - .map((t: any) => (typeof t === "string" ? t : t.tableName || t.table_name || t.tablename || t.name)) + .map((t: string | { tableName?: string; table_name?: string; tablename?: string; name?: string }) => + typeof t === "string" ? t : t.tableName || t.table_name || t.tablename || t.name, + ) .filter(Boolean); setExternalTableList(tableNames); } else { @@ -224,10 +232,10 @@ export default function FlowManagementPage() { variant: "destructive", }); } - } catch (error: any) { + } catch (error) { toast({ title: "์˜ค๋ฅ˜ ๋ฐœ์ƒ", - description: error.message, + description: error instanceof Error ? error.message : "์•Œ ์ˆ˜ ์—†๋Š” ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.", variant: "destructive", }); } @@ -254,10 +262,10 @@ export default function FlowManagementPage() { variant: "destructive", }); } - } catch (error: any) { + } catch (error) { toast({ title: "์˜ค๋ฅ˜ ๋ฐœ์ƒ", - description: error.message, + description: error instanceof Error ? error.message : "์•Œ ์ˆ˜ ์—†๋Š” ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.", variant: "destructive", }); } @@ -269,317 +277,342 @@ export default function FlowManagementPage() { }; return ( -
- {/* ํ—ค๋” */} -
-
-

- - ํ”Œ๋กœ์šฐ ๊ด€๋ฆฌ -

-

์—…๋ฌด ํ”„๋กœ์„ธ์Šค ํ”Œ๋กœ์šฐ๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค

+
+
+ {/* ํŽ˜์ด์ง€ ํ—ค๋” */} +
+

ํ”Œ๋กœ์šฐ ๊ด€๋ฆฌ

+

์—…๋ฌด ํ”„๋กœ์„ธ์Šค ํ”Œ๋กœ์šฐ๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค

- -
- {/* ํ”Œ๋กœ์šฐ ์นด๋“œ ๋ชฉ๋ก */} - {loading ? ( -
-

๋กœ๋”ฉ ์ค‘...

+ {/* ์•ก์…˜ ๋ฒ„ํŠผ ์˜์—ญ */} +
+
- ) : flows.length === 0 ? ( - - - -

์ƒ์„ฑ๋œ ํ”Œ๋กœ์šฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค

- -
-
- ) : ( -
- {flows.map((flow) => ( - handleEdit(flow.id)} - > - -
+ + {/* ํ”Œ๋กœ์šฐ ์นด๋“œ ๋ชฉ๋ก */} + {loading ? ( +
+ {Array.from({ length: 6 }).map((_, index) => ( +
+
+
+
+
+
+
+ {Array.from({ length: 3 }).map((_, i) => ( +
+
+
+
+ ))} +
+
+
+
+
+
+ ))} +
+ ) : flows.length === 0 ? ( +
+
+
+ +
+

์ƒ์„ฑ๋œ ํ”Œ๋กœ์šฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค

+

+ ์ƒˆ ํ”Œ๋กœ์šฐ๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ์—…๋ฌด ํ”„๋กœ์„ธ์Šค๋ฅผ ๊ด€๋ฆฌํ•ด๋ณด์„ธ์š”. +

+ +
+
+ ) : ( +
+ {flows.map((flow) => ( +
handleEdit(flow.id)} + > + {/* ํ—ค๋” */} +
- - {flow.name} +
+

{flow.name}

{flow.isActive && ( - - ํ™œ์„ฑ - + ํ™œ์„ฑ )} - - - {flow.description || "์„ค๋ช… ์—†์Œ"} - -
-
- - -
-
- - {flow.tableName} - -
- - ์ƒ์„ฑ์ž: {flow.createdBy} -
-
- - {new Date(flow.updatedAt).toLocaleDateString("ko-KR")} +
+

{flow.description || "์„ค๋ช… ์—†์Œ"}

-
+ {/* ์ •๋ณด */} +
+
+
+ {flow.tableName} + +
+ + ์ƒ์„ฑ์ž: {flow.createdBy} +
+
+ + + {new Date(flow.updatedAt).toLocaleDateString("ko-KR")} + +
+ + + {/* ์•ก์…˜ */} +
- - - ))} - - )} - - {/* ์ƒ์„ฑ ๋‹ค์ด์–ผ๋กœ๊ทธ */} - - - - ์ƒˆ ํ”Œ๋กœ์šฐ ์ƒ์„ฑ - - ์ƒˆ๋กœ์šด ์—…๋ฌด ํ”„๋กœ์„ธ์Šค ํ”Œ๋กœ์šฐ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค - - - -
-
- - setFormData({ ...formData, name: e.target.value })} - placeholder="์˜ˆ: ์ œํ’ˆ ์ˆ˜๋ช…์ฃผ๊ธฐ ๊ด€๋ฆฌ" - className="h-8 text-xs sm:h-10 sm:text-sm" - /> -
- - {/* DB ์†Œ์Šค ์„ ํƒ */} -
- - -

- ํ”Œ๋กœ์šฐ์—์„œ ์‚ฌ์šฉํ•  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค -

-
- - {/* ํ…Œ์ด๋ธ” ์„ ํƒ */} -
- - - - - - - - - - ํ…Œ์ด๋ธ”์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. - - {selectedDbSource === "internal" - ? // ๋‚ด๋ถ€ DB ํ…Œ์ด๋ธ” ๋ชฉ๋ก - tableList.map((table) => ( - { - console.log("๐Ÿ“ Internal table selected:", { - tableName: table.tableName, - currentValue, - }); - setFormData({ ...formData, tableName: currentValue }); - setOpenTableCombobox(false); - }} - className="text-xs sm:text-sm" - > - -
- {table.displayName || table.tableName} - {table.description && ( - {table.description} - )} -
-
- )) - : // ์™ธ๋ถ€ DB ํ…Œ์ด๋ธ” ๋ชฉ๋ก - externalTableList.map((tableName, index) => ( - { - setFormData({ ...formData, tableName: currentValue }); - setOpenTableCombobox(false); - }} - className="text-xs sm:text-sm" - > - -
{tableName}
-
- ))} -
-
-
-
-
-

- ํ”Œ๋กœ์šฐ์˜ ๋ชจ๋“  ๋‹จ๊ณ„์—์„œ ์‚ฌ์šฉํ•  ๊ธฐ๋ณธ ํ…Œ์ด๋ธ”์ž…๋‹ˆ๋‹ค (๋‹จ๊ณ„๋งˆ๋‹ค ์ƒํƒœ ์ปฌ๋Ÿผ๋งŒ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค) -

-
- -
- -