diff --git a/frontend/components/pop/designer/panels/ComponentPalette.tsx b/frontend/components/pop/designer/panels/ComponentPalette.tsx index a09eb44d..471db3fd 100644 --- a/frontend/components/pop/designer/panels/ComponentPalette.tsx +++ b/frontend/components/pop/designer/panels/ComponentPalette.tsx @@ -3,7 +3,7 @@ import { useDrag } from "react-dnd"; import { cn } from "@/lib/utils"; import { PopComponentType } from "../types/pop-layout"; -import { Square, FileText, MousePointer, BarChart3, LayoutGrid, MousePointerClick, List, Search, TextCursorInput, ScanLine } from "lucide-react"; +import { Square, FileText, MousePointer, BarChart3, LayoutGrid, MousePointerClick, List, Search, TextCursorInput, ScanLine, UserCircle } from "lucide-react"; import { DND_ITEM_TYPES } from "../constants"; // 컴포넌트 정의 @@ -75,6 +75,12 @@ const PALETTE_ITEMS: PaletteItem[] = [ icon: ScanLine, description: "바코드/QR 카메라 스캔", }, + { + type: "pop-profile", + label: "프로필", + icon: UserCircle, + description: "사용자 프로필 / PC 전환 / 로그아웃", + }, ]; // 드래그 가능한 컴포넌트 아이템 diff --git a/frontend/components/pop/designer/renderers/PopRenderer.tsx b/frontend/components/pop/designer/renderers/PopRenderer.tsx index 32ac610b..0fb99fc5 100644 --- a/frontend/components/pop/designer/renderers/PopRenderer.tsx +++ b/frontend/components/pop/designer/renderers/PopRenderer.tsx @@ -76,6 +76,7 @@ const COMPONENT_TYPE_LABELS: Record = { "pop-string-list": "리스트 목록", "pop-search": "검색", "pop-field": "입력", + "pop-profile": "프로필", }; // ======================================== diff --git a/frontend/components/pop/designer/types/pop-layout.ts b/frontend/components/pop/designer/types/pop-layout.ts index 2e400504..f88b6d59 100644 --- a/frontend/components/pop/designer/types/pop-layout.ts +++ b/frontend/components/pop/designer/types/pop-layout.ts @@ -9,7 +9,7 @@ /** * POP 컴포넌트 타입 */ -export type PopComponentType = "pop-sample" | "pop-text" | "pop-icon" | "pop-dashboard" | "pop-card-list" | "pop-button" | "pop-string-list" | "pop-search" | "pop-field" | "pop-scanner"; +export type PopComponentType = "pop-sample" | "pop-text" | "pop-icon" | "pop-dashboard" | "pop-card-list" | "pop-button" | "pop-string-list" | "pop-search" | "pop-field" | "pop-scanner" | "pop-profile"; /** * 데이터 흐름 정의 @@ -363,6 +363,7 @@ export const DEFAULT_COMPONENT_GRID_SIZE: Record = { + sm: { container: "h-8 w-8", text: "text-sm", px: 32 }, + md: { container: "h-10 w-10", text: "text-base", px: 40 }, + lg: { container: "h-12 w-12", text: "text-lg", px: 48 }, +}; + +const AVATAR_SIZE_LABELS: Record = { + sm: "작은 (32px)", + md: "보통 (40px)", + lg: "큰 (48px)", +}; + +// ======================================== +// 뷰어 컴포넌트 +// ======================================== + +interface PopProfileComponentProps { + config?: PopProfileConfig; + componentId?: string; + screenId?: string; +} + +function PopProfileComponent({ config: rawConfig }: PopProfileComponentProps) { + const router = useRouter(); + const { user, isLoggedIn, logout } = useAuth(); + const [open, setOpen] = useState(false); + + const config = useMemo(() => ({ + ...DEFAULT_CONFIG, + ...rawConfig, + }), [rawConfig]); + + const sizeInfo = AVATAR_SIZE_MAP[config.avatarSize || "md"]; + const initial = user?.userName?.substring(0, 1)?.toUpperCase() || "?"; + + const handlePcMode = () => { + setOpen(false); + router.push("/"); + }; + + const handleDashboard = () => { + setOpen(false); + router.push("/pop"); + }; + + const handleLogout = async () => { + setOpen(false); + await logout(); + }; + + const handleLogin = () => { + setOpen(false); + router.push("/login"); + }; + + return ( +
+ + + + + + {isLoggedIn && user ? ( + <> + {/* 사용자 정보 */} +
+
+ {user.photo && user.photo.trim() !== "" && user.photo !== "null" ? ( + {user.userName + ) : ( + initial + )} +
+
+ + {user.userName || "사용자"} ({user.userId || ""}) + + + {user.deptName || "부서 정보 없음"} + +
+
+ + {/* 메뉴 항목 */} +
+ {config.showDashboardLink && ( + + )} + {config.showPcMode && ( + + )} + {config.showLogout && ( + <> +
+ + + )} +
+ + ) : ( +
+

+ 로그인이 필요합니다 +

+ +
+ )} + + +
+ ); +} + +// ======================================== +// 설정 패널 +// ======================================== + +interface PopProfileConfigPanelProps { + config: PopProfileConfig; + onUpdate: (config: PopProfileConfig) => void; +} + +function PopProfileConfigPanel({ config: rawConfig, onUpdate }: PopProfileConfigPanelProps) { + const config = useMemo(() => ({ + ...DEFAULT_CONFIG, + ...rawConfig, + }), [rawConfig]); + + const updateConfig = (partial: Partial) => { + onUpdate({ ...config, ...partial }); + }; + + return ( +
+ {/* 아바타 크기 */} +
+ + +
+ + {/* 메뉴 항목 토글 */} +
+ + +
+ + updateConfig({ showDashboardLink: v })} + /> +
+ +
+ + updateConfig({ showPcMode: v })} + /> +
+ +
+ + updateConfig({ showLogout: v })} + /> +
+
+
+ ); +} + +// ======================================== +// 디자이너 미리보기 +// ======================================== + +function PopProfilePreview({ config }: { config?: PopProfileConfig }) { + const size = AVATAR_SIZE_MAP[config?.avatarSize || "md"]; + return ( +
+
+ +
+ 프로필 +
+ ); +} + +// ======================================== +// 레지스트리 등록 +// ======================================== + +PopComponentRegistry.registerComponent({ + id: "pop-profile", + name: "프로필", + description: "사용자 프로필 / PC 전환 / 로그아웃", + category: "action", + icon: "UserCircle", + component: PopProfileComponent, + configPanel: PopProfileConfigPanel, + preview: PopProfilePreview, + defaultProps: { + avatarSize: "md", + showDashboardLink: true, + showPcMode: true, + showLogout: true, + }, + connectionMeta: { + sendable: [], + receivable: [], + }, + touchOptimized: true, + supportedDevices: ["mobile", "tablet"], +});