ERP-node/PHASE_RESPONSIVE_LAYOUT.md

27 KiB

반응형 레이아웃 시스템 구현 계획서

📋 프로젝트 개요

목표

화면 디자이너는 절대 위치 기반으로 유지하되, 실제 화면 표시는 반응형으로 동작하도록 전환

핵심 원칙

  • 화면 디자이너의 절대 위치 기반 드래그앤드롭은 그대로 유지
  • 실제 화면 표시만 반응형으로 전환
  • 데이터 마이그레이션 불필요 (신규 화면부터 적용)
  • 기존 화면은 불러올 때 스마트 기본값 자동 생성

🎯 Phase 1: 기본 반응형 시스템 구축 (2-3일)

1.1 타입 정의 (2시간)

파일: frontend/types/responsive.ts

/**
 * 브레이크포인트 타입 정의
 */
export type Breakpoint = "desktop" | "tablet" | "mobile";

/**
 * 브레이크포인트별 설정
 */
export interface BreakpointConfig {
  minWidth: number; // 최소 너비 (px)
  maxWidth?: number; // 최대 너비 (px)
  columns: number; // 그리드 컬럼 수
}

/**
 * 기본 브레이크포인트 설정
 */
export const BREAKPOINTS: Record<Breakpoint, BreakpointConfig> = {
  desktop: {
    minWidth: 1200,
    columns: 12,
  },
  tablet: {
    minWidth: 768,
    maxWidth: 1199,
    columns: 8,
  },
  mobile: {
    minWidth: 0,
    maxWidth: 767,
    columns: 4,
  },
};

/**
 * 브레이크포인트별 반응형 설정
 */
export interface ResponsiveBreakpointConfig {
  gridColumns?: number; // 차지할 컬럼 수 (1-12)
  order?: number; // 정렬 순서
  hide?: boolean; // 숨김 여부
}

/**
 * 컴포넌트별 반응형 설정
 */
export interface ResponsiveComponentConfig {
  // 기본값 (디자이너에서 설정한 절대 위치)
  designerPosition: {
    x: number;
    y: number;
    width: number;
    height: number;
  };

  // 반응형 설정 (선택적)
  responsive?: {
    desktop?: ResponsiveBreakpointConfig;
    tablet?: ResponsiveBreakpointConfig;
    mobile?: ResponsiveBreakpointConfig;
  };

  // 스마트 기본값 사용 여부
  useSmartDefaults?: boolean;
}

1.2 스마트 기본값 생성기 (3시간)

파일: frontend/lib/utils/responsiveDefaults.ts

import { ComponentData } from "@/types/screen-management";
import { ResponsiveComponentConfig, BREAKPOINTS } from "@/types/responsive";

/**
 * 컴포넌트 크기에 따른 스마트 기본값 생성
 *
 * 로직:
 * - 작은 컴포넌트 (너비 25% 이하): 모바일에서도 같은 너비 유지
 * - 중간 컴포넌트 (너비 25-50%): 모바일에서 전체 너비로 확장
 * - 큰 컴포넌트 (너비 50% 이상): 모든 디바이스에서 전체 너비
 */
export function generateSmartDefaults(
  component: ComponentData,
  screenWidth: number = 1920
): ResponsiveComponentConfig["responsive"] {
  const componentWidthPercent = (component.size.width / screenWidth) * 100;

  // 작은 컴포넌트 (25% 이하)
  if (componentWidthPercent <= 25) {
    return {
      desktop: {
        gridColumns: 3, // 12컬럼 중 3개 (25%)
        order: 1,
        hide: false,
      },
      tablet: {
        gridColumns: 2, // 8컬럼 중 2개 (25%)
        order: 1,
        hide: false,
      },
      mobile: {
        gridColumns: 1, // 4컬럼 중 1개 (25%)
        order: 1,
        hide: false,
      },
    };
  }
  // 중간 컴포넌트 (25-50%)
  else if (componentWidthPercent <= 50) {
    return {
      desktop: {
        gridColumns: 6, // 12컬럼 중 6개 (50%)
        order: 1,
        hide: false,
      },
      tablet: {
        gridColumns: 4, // 8컬럼 중 4개 (50%)
        order: 1,
        hide: false,
      },
      mobile: {
        gridColumns: 4, // 4컬럼 전체 (100%)
        order: 1,
        hide: false,
      },
    };
  }
  // 큰 컴포넌트 (50% 이상)
  else {
    return {
      desktop: {
        gridColumns: 12, // 전체 너비
        order: 1,
        hide: false,
      },
      tablet: {
        gridColumns: 8, // 전체 너비
        order: 1,
        hide: false,
      },
      mobile: {
        gridColumns: 4, // 전체 너비
        order: 1,
        hide: false,
      },
    };
  }
}

/**
 * 컴포넌트에 반응형 설정이 없을 경우 자동 생성
 */
export function ensureResponsiveConfig(
  component: ComponentData,
  screenWidth?: number
): ComponentData {
  if (component.responsiveConfig) {
    return component;
  }

  return {
    ...component,
    responsiveConfig: {
      designerPosition: {
        x: component.position.x,
        y: component.position.y,
        width: component.size.width,
        height: component.size.height,
      },
      useSmartDefaults: true,
      responsive: generateSmartDefaults(component, screenWidth),
    },
  };
}

1.3 브레이크포인트 감지 훅 (1시간)

파일: frontend/hooks/useBreakpoint.ts

import { useState, useEffect } from "react";
import { Breakpoint, BREAKPOINTS } from "@/types/responsive";

/**
 * 현재 윈도우 크기에 따른 브레이크포인트 반환
 */
export function useBreakpoint(): Breakpoint {
  const [breakpoint, setBreakpoint] = useState<Breakpoint>("desktop");

  useEffect(() => {
    const updateBreakpoint = () => {
      const width = window.innerWidth;

      if (width >= BREAKPOINTS.desktop.minWidth) {
        setBreakpoint("desktop");
      } else if (width >= BREAKPOINTS.tablet.minWidth) {
        setBreakpoint("tablet");
      } else {
        setBreakpoint("mobile");
      }
    };

    // 초기 실행
    updateBreakpoint();

    // 리사이즈 이벤트 리스너 등록
    window.addEventListener("resize", updateBreakpoint);

    return () => window.removeEventListener("resize", updateBreakpoint);
  }, []);

  return breakpoint;
}

/**
 * 현재 브레이크포인트의 컬럼 수 반환
 */
export function useGridColumns(): number {
  const breakpoint = useBreakpoint();
  return BREAKPOINTS[breakpoint].columns;
}

1.4 반응형 레이아웃 엔진 (6시간)

파일: frontend/components/screen/ResponsiveLayoutEngine.tsx

import React, { useMemo } from "react";
import { ComponentData } from "@/types/screen-management";
import { Breakpoint, BREAKPOINTS } from "@/types/responsive";
import {
  generateSmartDefaults,
  ensureResponsiveConfig,
} from "@/lib/utils/responsiveDefaults";
import { DynamicComponentRenderer } from "@/lib/registry/DynamicComponentRenderer";

interface ResponsiveLayoutEngineProps {
  components: ComponentData[];
  breakpoint: Breakpoint;
  containerWidth: number;
  screenWidth?: number;
}

/**
 * 반응형 레이아웃 엔진
 *
 * 절대 위치로 배치된 컴포넌트들을 반응형 그리드로 변환
 *
 * 변환 로직:
 * 1. Y 위치 기준으로 행(row)으로 그룹화
 * 2. 각 행 내에서 X 위치 기준으로 정렬
 * 3. 반응형 설정 적용 (order, gridColumns, hide)
 * 4. CSS Grid로 렌더링
 */
export const ResponsiveLayoutEngine: React.FC<ResponsiveLayoutEngineProps> = ({
  components,
  breakpoint,
  containerWidth,
  screenWidth = 1920,
}) => {
  // 1단계: 컴포넌트들을 Y 위치 기준으로 행(row)으로 그룹화
  const rows = useMemo(() => {
    const sortedComponents = [...components].sort(
      (a, b) => a.position.y - b.position.y
    );

    const rows: ComponentData[][] = [];
    let currentRow: ComponentData[] = [];
    let currentRowY = 0;
    const ROW_THRESHOLD = 50; // 같은 행으로 간주할 Y 오차 범위 (px)

    sortedComponents.forEach((comp) => {
      if (currentRow.length === 0) {
        currentRow.push(comp);
        currentRowY = comp.position.y;
      } else if (Math.abs(comp.position.y - currentRowY) < ROW_THRESHOLD) {
        currentRow.push(comp);
      } else {
        rows.push(currentRow);
        currentRow = [comp];
        currentRowY = comp.position.y;
      }
    });

    if (currentRow.length > 0) {
      rows.push(currentRow);
    }

    return rows;
  }, [components]);

  // 2단계: 각 행 내에서 X 위치 기준으로 정렬
  const sortedRows = useMemo(() => {
    return rows.map((row) =>
      [...row].sort((a, b) => a.position.x - b.position.x)
    );
  }, [rows]);

  // 3단계: 반응형 설정 적용
  const responsiveComponents = useMemo(() => {
    return sortedRows.flatMap((row) =>
      row.map((comp) => {
        // 반응형 설정이 없으면 자동 생성
        const compWithConfig = ensureResponsiveConfig(comp, screenWidth);

        // 현재 브레이크포인트의 설정 가져오기
        const config = compWithConfig.responsiveConfig!.useSmartDefaults
          ? generateSmartDefaults(comp, screenWidth)[breakpoint]
          : compWithConfig.responsiveConfig!.responsive?.[breakpoint];

        return {
          ...compWithConfig,
          responsiveDisplay:
            config || generateSmartDefaults(comp, screenWidth)[breakpoint],
        };
      })
    );
  }, [sortedRows, breakpoint, screenWidth]);

  // 4단계: 필터링 및 정렬
  const visibleComponents = useMemo(() => {
    return responsiveComponents
      .filter((comp) => !comp.responsiveDisplay?.hide)
      .sort(
        (a, b) =>
          (a.responsiveDisplay?.order || 0) - (b.responsiveDisplay?.order || 0)
      );
  }, [responsiveComponents]);

  const gridColumns = BREAKPOINTS[breakpoint].columns;

  return (
    <div
      className="responsive-grid w-full"
      style={{
        display: "grid",
        gridTemplateColumns: `repeat(${gridColumns}, 1fr)`,
        gap: "16px",
        padding: "16px",
      }}
    >
      {visibleComponents.map((comp) => (
        <div
          key={comp.id}
          className="responsive-grid-item"
          style={{
            gridColumn: `span ${
              comp.responsiveDisplay?.gridColumns || gridColumns
            }`,
          }}
        >
          <DynamicComponentRenderer component={comp} isPreview={true} />
        </div>
      ))}
    </div>
  );
};

1.5 화면 표시 페이지 수정 (4시간)

파일: frontend/app/(main)/screens/[screenId]/page.tsx

// 기존 import 유지
import { ResponsiveLayoutEngine } from "@/components/screen/ResponsiveLayoutEngine";
import { useBreakpoint } from "@/hooks/useBreakpoint";

export default function ScreenViewPage({
  params,
}: {
  params: { screenId: string };
}) {
  const [layout, setLayout] = useState<LayoutData | null>(null);
  const breakpoint = useBreakpoint();

  // 반응형 모드 토글 (사용자 설정 또는 화면 설정에 따라)
  const [useResponsive, setUseResponsive] = useState(true);

  // 기존 로직 유지...

  if (!layout) {
    return <div>로딩 ...</div>;
  }

  const screenWidth = layout.screenResolution?.width || 1920;
  const screenHeight = layout.screenResolution?.height || 1080;

  return (
    <div className="h-full w-full bg-white">
      {useResponsive ? (
        // 반응형 모드
        <ResponsiveLayoutEngine
          components={layout.components || []}
          breakpoint={breakpoint}
          containerWidth={window.innerWidth}
          screenWidth={screenWidth}
        />
      ) : (
        // 기존 스케일 모드 (하위 호환성)
        <div className="overflow-auto" style={{ padding: "16px 0" }}>
          <div
            style={{
              width: `${screenWidth * scale}px`,
              minHeight: `${screenHeight * scale}px`,
              marginLeft: "16px",
              marginRight: "16px",
            }}
          >
            <div
              className="relative bg-white"
              style={{
                width: `${screenWidth}px`,
                minHeight: `${screenHeight}px`,
                transform: `scale(${scale})`,
                transformOrigin: "top left",
              }}
            >
              {layout.components?.map((component) => (
                <div
                  key={component.id}
                  style={{
                    position: "absolute",
                    left: `${component.position.x}px`,
                    top: `${component.position.y}px`,
                    width:
                      component.style?.width || `${component.size.width}px`,
                    minHeight:
                      component.style?.height || `${component.size.height}px`,
                    zIndex: component.position.z || 1,
                  }}
                >
                  <DynamicComponentRenderer
                    component={component}
                    isPreview={true}
                  />
                </div>
              ))}
            </div>
          </div>
        </div>
      )}
    </div>
  );
}

🎨 Phase 2: 디자이너 통합 (1-2일)

2.1 반응형 설정 패널 (5시간)

파일: frontend/components/screen/panels/ResponsiveConfigPanel.tsx

import React, { useState } from "react";
import { ComponentData } from "@/types/screen-management";
import {
  Breakpoint,
  BREAKPOINTS,
  ResponsiveComponentConfig,
} from "@/types/responsive";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Label } from "@/components/ui/label";
import { Input } from "@/components/ui/input";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Checkbox } from "@/components/ui/checkbox";

interface ResponsiveConfigPanelProps {
  component: ComponentData;
  onUpdate: (config: ResponsiveComponentConfig) => void;
}

export const ResponsiveConfigPanel: React.FC<ResponsiveConfigPanelProps> = ({
  component,
  onUpdate,
}) => {
  const [activeTab, setActiveTab] = useState<Breakpoint>("desktop");

  const config = component.responsiveConfig || {
    designerPosition: {
      x: component.position.x,
      y: component.position.y,
      width: component.size.width,
      height: component.size.height,
    },
    useSmartDefaults: true,
  };

  return (
    <Card>
      <CardHeader>
        <CardTitle>반응형 설정</CardTitle>
      </CardHeader>
      <CardContent className="space-y-4">
        {/* 스마트 기본값 토글 */}
        <div className="flex items-center space-x-2">
          <Checkbox
            id="smartDefaults"
            checked={config.useSmartDefaults}
            onCheckedChange={(checked) => {
              onUpdate({
                ...config,
                useSmartDefaults: checked as boolean,
              });
            }}
          />
          <Label htmlFor="smartDefaults">스마트 기본값 사용 (권장)</Label>
        </div>

        {/* 수동 설정 */}
        {!config.useSmartDefaults && (
          <Tabs
            value={activeTab}
            onValueChange={(v) => setActiveTab(v as Breakpoint)}
          >
            <TabsList className="grid w-full grid-cols-3">
              <TabsTrigger value="desktop">데스크톱</TabsTrigger>
              <TabsTrigger value="tablet">태블릿</TabsTrigger>
              <TabsTrigger value="mobile">모바일</TabsTrigger>
            </TabsList>

            <TabsContent value={activeTab} className="space-y-4">
              {/* 그리드 컬럼 수 */}
              <div className="space-y-2">
                <Label>너비 (그리드 컬럼)</Label>
                <Select
                  value={config.responsive?.[
                    activeTab
                  ]?.gridColumns?.toString()}
                  onValueChange={(v) => {
                    onUpdate({
                      ...config,
                      responsive: {
                        ...config.responsive,
                        [activeTab]: {
                          ...config.responsive?.[activeTab],
                          gridColumns: parseInt(v),
                        },
                      },
                    });
                  }}
                >
                  <SelectTrigger className="w-full">
                    <SelectValue placeholder="컬럼 수 선택" />
                  </SelectTrigger>
                  <SelectContent>
                    {[...Array(BREAKPOINTS[activeTab].columns)].map((_, i) => {
                      const cols = i + 1;
                      const percent = (
                        (cols / BREAKPOINTS[activeTab].columns) *
                        100
                      ).toFixed(0);
                      return (
                        <SelectItem key={cols} value={cols.toString()}>
                          {cols} / {BREAKPOINTS[activeTab].columns} ({percent}%)
                        </SelectItem>
                      );
                    })}
                  </SelectContent>
                </Select>
              </div>

              {/* 표시 순서 */}
              <div className="space-y-2">
                <Label>표시 순서</Label>
                <Input
                  type="number"
                  min="1"
                  value={config.responsive?.[activeTab]?.order || 1}
                  onChange={(e) => {
                    onUpdate({
                      ...config,
                      responsive: {
                        ...config.responsive,
                        [activeTab]: {
                          ...config.responsive?.[activeTab],
                          order: parseInt(e.target.value),
                        },
                      },
                    });
                  }}
                />
              </div>

              {/* 숨김 */}
              <div className="flex items-center space-x-2">
                <Checkbox
                  id={`hide-${activeTab}`}
                  checked={config.responsive?.[activeTab]?.hide || false}
                  onCheckedChange={(checked) => {
                    onUpdate({
                      ...config,
                      responsive: {
                        ...config.responsive,
                        [activeTab]: {
                          ...config.responsive?.[activeTab],
                          hide: checked as boolean,
                        },
                      },
                    });
                  }}
                />
                <Label htmlFor={`hide-${activeTab}`}>
                  {activeTab === "desktop"
                    ? "데스크톱"
                    : activeTab === "tablet"
                    ? "태블릿"
                    : "모바일"}
                  에서 숨김
                </Label>
              </div>
            </TabsContent>
          </Tabs>
        )}
      </CardContent>
    </Card>
  );
};

2.2 속성 패널 통합 (1시간)

파일: frontend/components/screen/panels/UnifiedPropertiesPanel.tsx 수정

// 기존 import에 추가
import { ResponsiveConfigPanel } from './ResponsiveConfigPanel';

// 컴포넌트 내부에 추가
return (
  <div className="space-y-4">
    {/* 기존 패널들 */}
    <PropertiesPanel ... />
    <StyleEditor ... />

    {/* 반응형 설정 패널 추가 */}
    <ResponsiveConfigPanel
      component={selectedComponent}
      onUpdate={(config) => {
        onUpdateComponent({
          ...selectedComponent,
          responsiveConfig: config
        });
      }}
    />

    {/* 기존 세부 설정 패널 */}
    <DetailSettingsPanel ... />
  </div>
);

2.3 미리보기 모드 (3시간)

파일: frontend/components/screen/ScreenDesigner.tsx 수정

// 추가 import
import { Breakpoint } from '@/types/responsive';
import { ResponsiveLayoutEngine } from './ResponsiveLayoutEngine';
import { useBreakpoint } from '@/hooks/useBreakpoint';
import { Button } from '@/components/ui/button';

export const ScreenDesigner: React.FC = () => {
  // 미리보기 모드: 'design' | 'desktop' | 'tablet' | 'mobile'
  const [previewMode, setPreviewMode] = useState<'design' | Breakpoint>('design');
  const currentBreakpoint = useBreakpoint();

  // ... 기존 로직 ...

  return (
    <div className="h-full flex flex-col">
      {/* 상단 툴바 */}
      <div className="flex gap-2 p-2 border-b bg-white">
        <Button
          variant={previewMode === 'design' ? 'default' : 'outline'}
          size="sm"
          onClick={() => setPreviewMode('design')}
        >
          디자인 모드
        </Button>
        <Button
          variant={previewMode === 'desktop' ? 'default' : 'outline'}
          size="sm"
          onClick={() => setPreviewMode('desktop')}
        >
          데스크톱 미리보기
        </Button>
        <Button
          variant={previewMode === 'tablet' ? 'default' : 'outline'}
          size="sm"
          onClick={() => setPreviewMode('tablet')}
        >
          태블릿 미리보기
        </Button>
        <Button
          variant={previewMode === 'mobile' ? 'default' : 'outline'}
          size="sm"
          onClick={() => setPreviewMode('mobile')}
        >
          모바일 미리보기
        </Button>
      </div>

      {/* 캔버스 영역 */}
      <div className="flex-1 overflow-auto">
        {previewMode === 'design' ? (
          // 기존 절대 위치 기반 디자이너
          <Canvas ... />
        ) : (
          // 반응형 미리보기
          <div
            className="mx-auto border border-gray-300"
            style={{
              width: previewMode === 'desktop' ? '100%' :
                     previewMode === 'tablet' ? '768px' :
                     '375px',
              minHeight: '100%'
            }}
          >
            <ResponsiveLayoutEngine
              components={components}
              breakpoint={previewMode}
              containerWidth={
                previewMode === 'desktop' ? window.innerWidth :
                previewMode === 'tablet' ? 768 :
                375
              }
              screenWidth={selectedScreen?.screenResolution?.width || 1920}
            />
          </div>
        )}
      </div>
    </div>
  );
};

💾 Phase 3: 저장/불러오기 (1일)

3.1 타입 업데이트 (2시간)

파일: frontend/types/screen-management.ts 수정

import { ResponsiveComponentConfig } from "./responsive";

export interface ComponentData {
  // ... 기존 필드들 ...

  // 반응형 설정 추가
  responsiveConfig?: ResponsiveComponentConfig;
}

3.2 저장 로직 (2시간)

파일: frontend/components/screen/ScreenDesigner.tsx 수정

// 저장 함수 수정
const handleSave = async () => {
  try {
    const layoutData: LayoutData = {
      screenResolution: {
        width: 1920,
        height: 1080,
      },
      components: components.map((comp) => ({
        ...comp,
        // 반응형 설정이 없으면 자동 생성
        responsiveConfig: comp.responsiveConfig || {
          designerPosition: {
            x: comp.position.x,
            y: comp.position.y,
            width: comp.size.width,
            height: comp.size.height,
          },
          useSmartDefaults: true,
        },
      })),
    };

    await screenApi.updateLayout(selectedScreen.id, layoutData);
    // ... 기존 로직 ...
  } catch (error) {
    console.error("저장 실패:", error);
  }
};

3.3 불러오기 로직 (2시간)

파일: frontend/components/screen/ScreenDesigner.tsx 수정

import { ensureResponsiveConfig } from "@/lib/utils/responsiveDefaults";

// 화면 불러오기
useEffect(() => {
  const loadScreen = async () => {
    if (!selectedScreenId) return;

    const screen = await screenApi.getScreenById(selectedScreenId);
    const layout = await screenApi.getLayout(selectedScreenId);

    // 반응형 설정이 없는 컴포넌트에 자동 생성
    const componentsWithResponsive = layout.components.map((comp) =>
      ensureResponsiveConfig(comp, layout.screenResolution?.width)
    );

    setSelectedScreen(screen);
    setComponents(componentsWithResponsive);
  };

  loadScreen();
}, [selectedScreenId]);

🧪 Phase 4: 테스트 및 최적화 (1일)

4.1 기능 테스트 체크리스트 (3시간)

  • 브레이크포인트 전환 테스트
    • 윈도우 크기 변경 시 자동 전환
    • desktop → tablet → mobile 순차 테스트
  • 스마트 기본값 생성 테스트
    • 작은 컴포넌트 (25% 이하)
    • 중간 컴포넌트 (25-50%)
    • 큰 컴포넌트 (50% 이상)
  • 수동 설정 적용 테스트
    • 그리드 컬럼 변경
    • 표시 순서 변경
    • 디바이스별 숨김
  • 미리보기 모드 테스트
    • 디자인 모드 ↔ 미리보기 모드 전환
    • 각 브레이크포인트 미리보기
  • 저장/불러오기 테스트
    • 반응형 설정 저장
    • 기존 화면 불러오기 시 자동 변환

4.2 성능 최적화 (3시간)

레이아웃 계산 메모이제이션

// ResponsiveLayoutEngine.tsx
const memoizedLayout = useMemo(() => {
  // 레이아웃 계산 로직
}, [components, breakpoint, screenWidth]);

ResizeObserver 최적화

// useBreakpoint.ts
// debounce 적용
const debouncedResize = debounce(updateBreakpoint, 150);
window.addEventListener("resize", debouncedResize);

불필요한 리렌더링 방지

// React.memo 적용
export const ResponsiveLayoutEngine = React.memo<ResponsiveLayoutEngineProps>(({...}) => {
  // ...
});

4.3 UI/UX 개선 (2시간)

  • 반응형 설정 패널 툴팁 추가
  • 미리보기 모드 전환 애니메이션
  • 로딩 상태 표시
  • 에러 처리 및 사용자 피드백

📅 최종 타임라인

Phase 작업 내용 소요 시간 누적 시간
Phase 1 타입 정의 및 유틸리티 6시간 6시간
Phase 1 반응형 레이아웃 엔진 6시간 12시간
Phase 1 화면 표시 페이지 수정 4시간 16시간 (2일)
Phase 2 반응형 설정 패널 5시간 21시간
Phase 2 디자이너 통합 4시간 25시간 (3일)
Phase 3 저장/불러오기 6시간 31시간 (4일)
Phase 4 테스트 및 최적화 8시간 39시간 (5일)

총 예상 시간: 39시간 (약 5일)


🎯 구현 우선순위

1단계: 핵심 기능 (필수)

  1. 타입 정의
  2. 스마트 기본값 생성기
  3. 브레이크포인트 훅
  4. 반응형 레이아웃 엔진
  5. 화면 표시 페이지 수정

2단계: 디자이너 UI (중요)

  1. 반응형 설정 패널
  2. 속성 패널 통합
  3. 미리보기 모드

3단계: 데이터 처리 (중요)

  1. 타입 업데이트
  2. 저장/불러오기 로직

4단계: 완성도 (선택)

  1. 테스트
  2. 최적화
  3. UI/UX 개선

완료 체크리스트

Phase 1: 기본 시스템

  • frontend/types/responsive.ts 생성
  • frontend/lib/utils/responsiveDefaults.ts 생성
  • frontend/hooks/useBreakpoint.ts 생성
  • frontend/components/screen/ResponsiveLayoutEngine.tsx 생성
  • frontend/app/(main)/screens/[screenId]/page.tsx 수정

Phase 2: 디자이너 통합

  • frontend/components/screen/panels/ResponsiveConfigPanel.tsx 생성
  • frontend/components/screen/panels/UnifiedPropertiesPanel.tsx 수정
  • frontend/components/screen/ScreenDesigner.tsx 수정

Phase 3: 데이터 처리

  • frontend/types/screen-management.ts 수정
  • 저장 로직 수정
  • 불러오기 로직 수정

Phase 4: 테스트

  • 기능 테스트 완료
  • 성능 최적화 완료
  • UI/UX 개선 완료

🚀 시작 준비 완료

이제 Phase 1부터 순차적으로 구현을 시작합니다.