diff --git a/frontend/components/admin/dashboard/CanvasElement.tsx b/frontend/components/admin/dashboard/CanvasElement.tsx index aced2eb9..393f3141 100644 --- a/frontend/components/admin/dashboard/CanvasElement.tsx +++ b/frontend/components/admin/dashboard/CanvasElement.tsx @@ -17,6 +17,11 @@ const ExchangeWidget = dynamic(() => import("@/components/dashboard/widgets/Exch loading: () =>
로딩 중...
, }); +const CalculatorWidget = dynamic(() => import("@/components/dashboard/widgets/CalculatorWidget"), { + ssr: false, + loading: () =>
로딩 중...
, +}); + // 시계 위젯 임포트 import { ClockWidget } from "./widgets/ClockWidget"; // 달력 위젯 임포트 @@ -385,6 +390,11 @@ export function CanvasElement({ }} /> + ) : element.type === "widget" && element.subtype === "calculator" ? ( + // 계산기 위젯 렌더링 +
+ +
) : element.type === "widget" && element.subtype === "calendar" ? ( // 달력 위젯 렌더링
diff --git a/frontend/components/admin/dashboard/DashboardCanvas.tsx b/frontend/components/admin/dashboard/DashboardCanvas.tsx index a1e4d65e..d8b7007e 100644 --- a/frontend/components/admin/dashboard/DashboardCanvas.tsx +++ b/frontend/components/admin/dashboard/DashboardCanvas.tsx @@ -13,6 +13,7 @@ interface DashboardCanvasProps { onRemoveElement: (id: string) => void; onSelectElement: (id: string | null) => void; onConfigureElement?: (element: DashboardElement) => void; + backgroundColor?: string; } /** @@ -32,6 +33,7 @@ export const DashboardCanvas = forwardRef( onRemoveElement, onSelectElement, onConfigureElement, + backgroundColor = '#f9fafb', }, ref, ) => { @@ -104,8 +106,9 @@ export const DashboardCanvas = forwardRef( return (
+ void; onSaveLayout: () => void; + canvasBackgroundColor: string; + onCanvasBackgroundColorChange: (color: string) => void; } /** * 대시보드 툴바 컴포넌트 * - 전체 삭제, 레이아웃 저장 등 주요 액션 버튼 */ -export function DashboardToolbar({ onClearCanvas, onSaveLayout }: DashboardToolbarProps) { +export function DashboardToolbar({ onClearCanvas, onSaveLayout, canvasBackgroundColor, onCanvasBackgroundColorChange }: DashboardToolbarProps) { + const [showColorPicker, setShowColorPicker] = useState(false); return (
+ + {/* 캔버스 배경색 변경 버튼 */} +
+ + + {/* 색상 선택 패널 */} + {showColorPicker && ( +
+
+ onCanvasBackgroundColorChange(e.target.value)} + className="h-10 w-16 border border-gray-300 rounded cursor-pointer" + /> + onCanvasBackgroundColorChange(e.target.value)} + placeholder="#ffffff" + className="flex-1 px-2 py-1 text-sm border border-gray-300 rounded" + /> +
+ + {/* 프리셋 색상 */} +
+ {[ + '#ffffff', '#f9fafb', '#f3f4f6', '#e5e7eb', + '#3b82f6', '#8b5cf6', '#ec4899', '#f59e0b', + '#10b981', '#06b6d4', '#6366f1', '#84cc16', + ].map((color) => ( +
+ + +
+ )} +
); } diff --git a/frontend/components/admin/dashboard/types.ts b/frontend/components/admin/dashboard/types.ts index 2ac0bb6d..2e753d1b 100644 --- a/frontend/components/admin/dashboard/types.ts +++ b/frontend/components/admin/dashboard/types.ts @@ -15,7 +15,8 @@ export type ElementSubtype = | "exchange" | "weather" | "clock" - | "calendar"; // 위젯 타입 + | "calendar" + | "calculator"; // 위젯 타입 export interface Position { x: number; diff --git a/frontend/components/dashboard/widgets/CalculatorWidget.tsx b/frontend/components/dashboard/widgets/CalculatorWidget.tsx new file mode 100644 index 00000000..6e7aad4d --- /dev/null +++ b/frontend/components/dashboard/widgets/CalculatorWidget.tsx @@ -0,0 +1,286 @@ +'use client'; + +/** + * 계산기 위젯 컴포넌트 + * - 기본 사칙연산 지원 + * - 실시간 계산 + * - 대시보드 위젯으로 사용 가능 + */ + +import React, { useState } from 'react'; +import { Button } from '@/components/ui/button'; + +interface CalculatorWidgetProps { + className?: string; +} + +export default function CalculatorWidget({ className = '' }: CalculatorWidgetProps) { + const [display, setDisplay] = useState('0'); + const [previousValue, setPreviousValue] = useState(null); + const [operation, setOperation] = useState(null); + const [waitingForOperand, setWaitingForOperand] = useState(false); + + // 숫자 입력 처리 + const handleNumber = (num: string) => { + if (waitingForOperand) { + setDisplay(num); + setWaitingForOperand(false); + } else { + setDisplay(display === '0' ? num : display + num); + } + }; + + // 소수점 입력 + const handleDecimal = () => { + if (waitingForOperand) { + setDisplay('0.'); + setWaitingForOperand(false); + } else if (display.indexOf('.') === -1) { + setDisplay(display + '.'); + } + }; + + // 연산자 입력 + const handleOperation = (nextOperation: string) => { + const inputValue = parseFloat(display); + + if (previousValue === null) { + setPreviousValue(inputValue); + } else if (operation) { + const currentValue = previousValue || 0; + const newValue = calculate(currentValue, inputValue, operation); + + setDisplay(String(newValue)); + setPreviousValue(newValue); + } + + setWaitingForOperand(true); + setOperation(nextOperation); + }; + + // 계산 수행 + const calculate = (firstValue: number, secondValue: number, operation: string): number => { + switch (operation) { + case '+': + return firstValue + secondValue; + case '-': + return firstValue - secondValue; + case '×': + return firstValue * secondValue; + case '÷': + return secondValue !== 0 ? firstValue / secondValue : 0; + default: + return secondValue; + } + }; + + // 등호 처리 + const handleEquals = () => { + const inputValue = parseFloat(display); + + if (previousValue !== null && operation) { + const newValue = calculate(previousValue, inputValue, operation); + setDisplay(String(newValue)); + setPreviousValue(null); + setOperation(null); + setWaitingForOperand(true); + } + }; + + // 초기화 + const handleClear = () => { + setDisplay('0'); + setPreviousValue(null); + setOperation(null); + setWaitingForOperand(false); + }; + + // 백스페이스 + const handleBackspace = () => { + if (!waitingForOperand) { + const newDisplay = display.slice(0, -1); + setDisplay(newDisplay || '0'); + } + }; + + // 부호 변경 + const handleSign = () => { + const value = parseFloat(display); + setDisplay(String(value * -1)); + }; + + // 퍼센트 + const handlePercent = () => { + const value = parseFloat(display); + setDisplay(String(value / 100)); + }; + + return ( +
+
+ {/* 디스플레이 */} +
+
+
+ {operation && previousValue !== null && ( +
+ {previousValue} {operation} +
+ )} +
+
+ {display} +
+
+
+ + {/* 버튼 그리드 */} +
+ {/* 첫 번째 줄 */} + + + + + + {/* 두 번째 줄 */} + + + + + + {/* 세 번째 줄 */} + + + + + + {/* 네 번째 줄 */} + + + + + + {/* 다섯 번째 줄 */} + + + +
+
+
+ ); +} +