From a819ea6bfa30ec48f7eb2d67580cd9ada1fa14b7 Mon Sep 17 00:00:00 2001 From: kjs Date: Thu, 30 Oct 2025 18:30:39 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=ED=94=8C=EB=A1=9C=EC=9A=B0=20=EC=9C=84?= =?UTF-8?q?=EC=A0=AF=20=EB=94=94=EC=9E=90=EC=9D=B8=20=EA=B0=9C=EC=84=A0=20?= =?UTF-8?q?=EB=B0=8F=20=EA=B2=80=EC=83=89=20=ED=95=84=ED=84=B0=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B0=95=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 플로우 위젯 단계 박스 미니멀 디자인 적용 - 테두리와 배경 제거, 하단 선만 표시 - STEP 배지 제거, 단계명과 건수 상하 배치 - 선택 인디케이터(ChevronUp) 제거 - 건수 폰트 굵기 조정 (font-medium) - 검색 필터 기능 개선 - 그리드 컬럼 수 확장 (최대 6개까지) - 상단 타이틀과 검색 필터 사이 여백 조정 - 검색 필터 설정 시 표시되는 컬럼만 선택 가능하도록 변경 - 필터 설정을 사용자별로 저장하도록 변경 - 이전 사용자의 필터 설정 자동 정리 로직 추가 - 기본 버튼 컴포넌트 스타일 변경 - 배경 흰색, 검정 테두리로 변경 --- .../components/screen/widgets/FlowWidget.tsx | 165 ++++++++++++------ frontend/components/ui/button.tsx | 2 +- 2 files changed, 110 insertions(+), 57 deletions(-) diff --git a/frontend/components/screen/widgets/FlowWidget.tsx b/frontend/components/screen/widgets/FlowWidget.tsx index 9508cb0d..bffae228 100644 --- a/frontend/components/screen/widgets/FlowWidget.tsx +++ b/frontend/components/screen/widgets/FlowWidget.tsx @@ -38,6 +38,7 @@ import { import { Label } from "@/components/ui/label"; import { Input } from "@/components/ui/input"; import { useScreenPreview } from "@/contexts/ScreenPreviewContext"; +import { useAuth } from "@/hooks/useAuth"; interface FlowWidgetProps { component: FlowComponent; @@ -55,6 +56,7 @@ export function FlowWidget({ onFlowRefresh, }: FlowWidgetProps) { const { isPreviewMode } = useScreenPreview(); // 프리뷰 모드 확인 + const { user } = useAuth(); // 사용자 정보 가져오기 // 🆕 전역 상태 관리 const setSelectedStep = useFlowStepStore((state) => state.setSelectedStep); @@ -117,30 +119,64 @@ export function FlowWidget({ // 🆕 플로우 컴포넌트 ID (버튼이 이 플로우를 참조할 때 사용) const flowComponentId = component.id; - // 🆕 localStorage 키 생성 + // 🆕 localStorage 키 생성 (사용자별로 저장) const filterSettingKey = useMemo(() => { - if (!flowId || selectedStepId === null) return null; - return `flowWidget_searchFilters_${flowId}_${selectedStepId}`; - }, [flowId, selectedStepId]); + if (!flowId || selectedStepId === null || !user?.userId) return null; + return `flowWidget_searchFilters_${user.userId}_${flowId}_${selectedStepId}`; + }, [flowId, selectedStepId, user?.userId]); // 🆕 저장된 필터 설정 불러오기 useEffect(() => { - if (!filterSettingKey || allAvailableColumns.length === 0) return; + if (!filterSettingKey || stepDataColumns.length === 0 || !user?.userId) return; try { + // 현재 사용자의 필터 설정만 불러오기 const saved = localStorage.getItem(filterSettingKey); if (saved) { const savedFilters = JSON.parse(saved); - setSearchFilterColumns(new Set(savedFilters)); + // 현재 단계에 표시되는 컬럼만 필터링 + const validFilters = savedFilters.filter((col: string) => stepDataColumns.includes(col)); + setSearchFilterColumns(new Set(validFilters)); } else { // 초기값: 빈 필터 (사용자가 선택해야 함) setSearchFilterColumns(new Set()); } + + // 이전 사용자의 필터 설정 정리 (사용자 ID가 다른 키들 제거) + if (typeof window !== "undefined") { + const currentUserId = user.userId; + const keysToRemove: string[] = []; + + // localStorage의 모든 키를 확인 + for (let i = 0; i < localStorage.length; i++) { + const key = localStorage.key(i); + if (key && key.startsWith("flowWidget_searchFilters_")) { + // 키 형식: flowWidget_searchFilters_${userId}_${flowId}_${stepId} + // split("_")를 하면 ["flowWidget", "searchFilters", "사용자ID", "플로우ID", "스텝ID"] + // 따라서 userId는 parts[2]입니다 + const parts = key.split("_"); + if (parts.length >= 3) { + const userIdFromKey = parts[2]; // flowWidget_searchFilters_ 다음이 userId + // 현재 사용자 ID와 다른 사용자의 설정은 제거 + if (userIdFromKey !== currentUserId) { + keysToRemove.push(key); + } + } + } + } + + // 이전 사용자의 설정 제거 + if (keysToRemove.length > 0) { + keysToRemove.forEach(key => { + localStorage.removeItem(key); + }); + } + } } catch (error) { console.error("필터 설정 불러오기 실패:", error); setSearchFilterColumns(new Set()); } - }, [filterSettingKey, allAvailableColumns]); + }, [filterSettingKey, stepDataColumns, user?.userId]); // 🆕 필터 설정 저장 const saveFilterSettings = useCallback(() => { @@ -174,14 +210,14 @@ export function FlowWidget({ // 🆕 전체 선택/해제 const toggleAllFilters = useCallback(() => { - if (searchFilterColumns.size === allAvailableColumns.length) { + if (searchFilterColumns.size === stepDataColumns.length) { // 전체 해제 setSearchFilterColumns(new Set()); } else { // 전체 선택 - setSearchFilterColumns(new Set(allAvailableColumns)); + setSearchFilterColumns(new Set(stepDataColumns)); } - }, [searchFilterColumns, allAvailableColumns]); + }, [searchFilterColumns, stepDataColumns]); // 🆕 검색 초기화 const handleClearSearch = useCallback(() => { @@ -638,59 +674,76 @@ export function FlowWidget({ {/* 스텝 카드 */}
handleStepClick(step.id, step.stepName)} > - {/* 단계 번호 배지 */} -
- Step {step.stepOrder} -
+ {/* 콘텐츠 */} +
+ {/* 스텝 이름 */} +

+ {step.stepName} +

- {/* 스텝 이름 */} -

- {step.stepName} -

- - {/* 데이터 건수 */} - {showStepCount && ( -
-
- + {/* 데이터 건수 */} + {showStepCount && ( +
+ {stepCounts[step.id] || 0} - +
-
- )} + )} +
- {/* 선택 인디케이터 */} - {selectedStepId === step.id && ( -
- -
- )} + {/* 하단 선 */} +
{/* 화살표 (마지막 스텝 제외) */} {index < steps.length - 1 && ( -
+
{displayMode === "horizontal" ? ( - - - +
+
+ + + +
+
) : ( - - - +
+
+ + + +
+
)}
)} @@ -720,7 +773,7 @@ export function FlowWidget({
{/* 🆕 필터 설정 버튼 */} - {allAvailableColumns.length > 0 && ( + {stepDataColumns.length > 0 && (
-
+
{Array.from(searchFilterColumns).map((col) => (