다국어 지원 및 테이블 설정 현황 문서를 업데이트하고, 컴포넌트 패널에서 UI 개선을 통해 사용자 경험을 향상시켰습니다. 또한, 통합 패널의 너비를 조정하고, 패널 토글 기능을 추가하여 인터페이스의 유연성을 높였습니다.

This commit is contained in:
kjs 2026-01-16 09:59:16 +09:00
parent c71641c32c
commit f160ba2a1b
5 changed files with 97 additions and 165 deletions

View File

@ -9,7 +9,7 @@
| 기능 | 적용 완료 | 미적용 | 해당없음 |
| -------------------------- | --------- | ------ | -------- |
| **다국어 지원** | 3개 | 10개 | 3개 |
| **다국어 지원** | 3개 | 9개 | 4개 |
| **컴포넌트별 테이블 설정** | 6개 | 4개 | 6개 |
---
@ -18,12 +18,12 @@
### 데이터 표시 (Display) - 4개
| 컴포넌트 | 다국어 지원 | 테이블 설정 | 비고 |
| ------------------- | :---------: | :---------: | ------------------------------------------------------------------- |
| **테이블 리스트** | ✅ 적용 | ✅ 적용 | `customTableName`, `useCustomTable` 지원 |
| **카드 디스플레이** | ❌ 미적용 | ✅ 적용 | Combobox UI로 테이블 선택, `customTableName`, `useCustomTable` 지원 |
| **텍스트 표시** | ❌ 미적용 | 해당없음 | 정적 텍스트 표시용 |
| **피벗 그리드** | ❌ 미적용 | ⚠️ 부분 | `tableName` 설정 가능하나 Combobox UI 없음 |
| 컴포넌트 | 다국어 지원 | 테이블 설정 | 비고 |
| ------------------- | :---------: | :---------: | --------------------------------------------- |
| **테이블 리스트** | ✅ 적용 | ✅ 적용 | `customTableName`, `useCustomTable` 지원 |
| **카드 디스플레이** | 해당없음 | ✅ 적용 | DB 데이터 표시용, 버튼은 시스템 다국어로 처리 |
| **텍스트 표시** | ❌ 미적용 | 해당없음 | 정적 텍스트 표시용 |
| **피벗 그리드** | ❌ 미적용 | ⚠️ 부분 | `tableName` 설정 가능하나 Combobox UI 없음 |
---
@ -79,11 +79,18 @@
- `button-primary`: 버튼 텍스트 다국어 지원
- `split-panel-layout`: 패널 제목 다국어 지원
**미적용 (10개)**
**해당없음 (4개)**
- `card-display`, `text-display`, `pivot-grid`
- `card-display`: DB 데이터 표시용, 버튼 라벨은 시스템 다국어로 처리
- `text-display`: 정적 텍스트 표시용
- `divider-line`: 시각적 구분용
- `구분선`: 시각적 구분용
**미적용 (9개)**
- `pivot-grid`
- `unified-repeater`, `repeat-screen-modal`
- `tabs`, `section-card`, `section-paper`, `divider-line`
- `tabs`, `section-card`, `section-paper`
- `numbering-rule`, `rack-structure`, `location-swap-selector`, `table-search-widget`
---
@ -131,12 +138,12 @@
### 2순위: 다국어 지원 확대
| 컴포넌트 | 작업 내용 |
| ------------------ | -------------------------- |
| `unified-repeater` | 컬럼 라벨 `langKeyId` 지원 |
| `card-display` | 필드 라벨 `langKeyId` 지원 |
| `tabs` | 탭 이름 `langKeyId` 지원 |
| `section-card` | 제목 `langKeyId` 지원 |
| 컴포넌트 | 작업 내용 | 상태 |
| ------------------ | -------------------------- | ----------- |
| `card-display` | 버튼 라벨 시스템 다국어 | 해당없음 |
| `unified-repeater` | 컬럼 라벨 `langKeyId` 지원 | 대기 |
| `tabs` | 탭 이름 `langKeyId` 지원 | 대기 |
| `section-card` | 제목 `langKeyId` 지원 | 대기 |
---

View File

@ -101,7 +101,6 @@ import { ScreenPreviewProvider } from "@/contexts/ScreenPreviewContext";
import { TableOptionsProvider } from "@/contexts/TableOptionsContext";
// 새로운 통합 UI 컴포넌트
import { LeftUnifiedToolbar, defaultToolbarButtons } from "./toolbar/LeftUnifiedToolbar";
import { SlimToolbar } from "./toolbar/SlimToolbar";
import { UnifiedPropertiesPanel } from "./panels/UnifiedPropertiesPanel";
@ -4464,15 +4463,14 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
onGenerateMultilang={handleGenerateMultilang}
isGeneratingMultilang={isGeneratingMultilang}
onOpenMultilangSettings={() => setShowMultilangSettingsModal(true)}
isPanelOpen={panelStates.unified?.isOpen || false}
onTogglePanel={() => togglePanel("unified")}
/>
{/* 메인 컨테이너 (좌측 툴바 + 패널들 + 캔버스) */}
{/* 메인 컨테이너 (패널들 + 캔버스) */}
<div className="flex flex-1 overflow-hidden">
{/* 좌측 통합 툴바 */}
<LeftUnifiedToolbar buttons={defaultToolbarButtons} panelStates={panelStates} onTogglePanel={togglePanel} />
{/* 통합 패널 */}
{/* 통합 패널 - 좌측 사이드바 제거 후 너비 300px로 확장 */}
{panelStates.unified?.isOpen && (
<div className="border-border bg-card flex h-full w-[240px] flex-col border-r shadow-sm overflow-hidden">
<div className="border-border bg-card flex h-full w-[300px] flex-col border-r shadow-sm overflow-hidden">
<div className="border-border flex items-center justify-between border-b px-4 py-3 shrink-0">
<h3 className="text-foreground text-sm font-semibold"></h3>
<button

View File

@ -5,7 +5,7 @@ import { Input } from "@/components/ui/input";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { ComponentRegistry } from "@/lib/registry/ComponentRegistry";
import { ComponentDefinition, ComponentCategory } from "@/types/component";
import { Search, Package, Grid, Layers, Palette, Zap, MousePointer, Database } from "lucide-react";
import { Search, Package, Grid, Layers, Palette, Zap, MousePointer, Database, GripVertical } from "lucide-react";
import { TableInfo, ColumnInfo } from "@/types/screen";
import TablesPanel from "./TablesPanel";
@ -72,8 +72,8 @@ export function ComponentsPanel({
// unified-hierarchy 제거 - 현재 미사용
{
id: "unified-repeater",
name: "통합 반복 데이터",
description: "반복 데이터 관리 (인라인/모달/버튼 모드)",
name: "리피터 그리드",
description: "행 단위로 데이터를 추가/수정/삭제",
category: "data" as ComponentCategory,
tags: ["repeater", "table", "modal", "button", "unified"],
defaultSize: { width: 600, height: 300 },
@ -188,7 +188,25 @@ export function ComponentsPanel({
e.dataTransfer.effectAllowed = "copy";
};
// 컴포넌트 카드 렌더링 함수
// 카테고리별 배경색 매핑
const getCategoryColor = (category: string) => {
switch (category) {
case "data":
return "from-blue-500/10 to-blue-600/10 text-blue-600 group-hover:from-blue-500/20 group-hover:to-blue-600/20";
case "display":
return "from-emerald-500/10 to-emerald-600/10 text-emerald-600 group-hover:from-emerald-500/20 group-hover:to-emerald-600/20";
case "input":
return "from-violet-500/10 to-violet-600/10 text-violet-600 group-hover:from-violet-500/20 group-hover:to-violet-600/20";
case "layout":
return "from-amber-500/10 to-amber-600/10 text-amber-600 group-hover:from-amber-500/20 group-hover:to-amber-600/20";
case "action":
return "from-rose-500/10 to-rose-600/10 text-rose-600 group-hover:from-rose-500/20 group-hover:to-rose-600/20";
default:
return "from-slate-500/10 to-slate-600/10 text-slate-600 group-hover:from-slate-500/20 group-hover:to-slate-600/20";
}
};
// 컴포넌트 카드 렌더링 함수 (컴팩트 버전)
const renderComponentCard = (component: ComponentDefinition) => (
<div
key={component.id}
@ -202,21 +220,27 @@ export function ComponentsPanel({
e.currentTarget.style.opacity = "1";
e.currentTarget.style.transform = "none";
}}
className="group bg-card hover:border-primary/50 cursor-grab rounded-lg border p-3 shadow-sm transition-all duration-200 hover:-translate-y-0.5 hover:shadow-md active:translate-y-0 active:scale-[0.98] active:cursor-grabbing"
className="group bg-card hover:border-primary/40 cursor-grab rounded-lg border px-3 py-2.5 transition-all duration-200 hover:shadow-sm active:scale-[0.98] active:cursor-grabbing"
>
<div className="flex items-start gap-3">
<div className="bg-primary/10 text-primary group-hover:bg-primary/20 flex h-10 w-10 items-center justify-center rounded-md transition-all duration-200">
<div className="flex items-center gap-2.5">
<div
className={`flex h-8 w-8 shrink-0 items-center justify-center rounded-md bg-gradient-to-br transition-all duration-200 ${getCategoryColor(component.category)}`}
>
{getCategoryIcon(component.category)}
</div>
<div className="min-w-0 flex-1">
<h4 className="mb-1 text-xs leading-tight font-semibold">{component.name}</h4>
<p className="text-muted-foreground mb-1.5 line-clamp-2 text-xs leading-relaxed">{component.description}</p>
<div className="flex items-center">
<span className="bg-muted text-muted-foreground rounded-full px-2 py-0.5 text-xs font-medium">
<span className="text-foreground block truncate text-xs font-medium">{component.name}</span>
<div className="flex items-center gap-2">
<span className="text-muted-foreground text-[10px] capitalize">{component.category}</span>
<span className="text-muted-foreground/60 text-[10px]">|</span>
<span className="text-muted-foreground text-[10px]">
{component.defaultSize.width}×{component.defaultSize.height}
</span>
</div>
</div>
<div className="text-muted-foreground/40 group-hover:text-muted-foreground/60 transition-colors">
<GripVertical className="h-3.5 w-3.5" />
</div>
</div>
</div>
);
@ -275,13 +299,13 @@ export function ComponentsPanel({
{/* 테이블 컬럼 탭 */}
<TabsContent value="tables" className="mt-0 flex-1 overflow-y-auto">
<TablesPanel
tables={tables}
searchTerm={searchTerm}
onSearchChange={onSearchChange || (() => {})}
<TablesPanel
tables={tables}
searchTerm={searchTerm}
onSearchChange={onSearchChange || (() => {})}
onDragStart={onTableDragStart || (() => {})}
selectedTableName={selectedTableName}
placedColumns={placedColumns}
selectedTableName={selectedTableName}
placedColumns={placedColumns}
onTableSelect={onTableSelect}
showTableSelector={showTableSelector}
/>

View File

@ -20,6 +20,8 @@ import {
Zap,
Languages,
Settings2,
PanelLeft,
PanelLeftClose,
} from "lucide-react";
import { ScreenResolution, SCREEN_RESOLUTIONS } from "@/types/screen";
import {
@ -60,6 +62,9 @@ interface SlimToolbarProps {
onGenerateMultilang?: () => void;
isGeneratingMultilang?: boolean;
onOpenMultilangSettings?: () => void;
// 패널 토글 기능
isPanelOpen?: boolean;
onTogglePanel?: () => void;
}
export const SlimToolbar: React.FC<SlimToolbarProps> = ({
@ -76,6 +81,8 @@ export const SlimToolbar: React.FC<SlimToolbarProps> = ({
onGenerateMultilang,
isGeneratingMultilang = false,
onOpenMultilangSettings,
isPanelOpen = false,
onTogglePanel,
}) => {
// 사용자 정의 해상도 상태
const [customWidth, setCustomWidth] = useState("");
@ -121,13 +128,33 @@ export const SlimToolbar: React.FC<SlimToolbarProps> = ({
return (
<div className="flex h-14 items-center justify-between border-b border-gray-200 bg-gradient-to-r from-gray-50 to-white px-4 shadow-sm">
{/* 좌측: 네비게이션 화면 정보 */}
{/* 좌측: 네비게이션 + 패널 토글 + 화면 정보 */}
<div className="flex items-center space-x-4">
<Button variant="ghost" size="sm" onClick={onBack} className="flex items-center space-x-2">
<ArrowLeft className="h-4 w-4" />
<span></span>
</Button>
{onTogglePanel && <div className="h-6 w-px bg-gray-300" />}
{/* 패널 토글 버튼 */}
{onTogglePanel && (
<Button
variant={isPanelOpen ? "default" : "outline"}
size="sm"
onClick={onTogglePanel}
className="flex items-center space-x-2"
title="패널 열기/닫기 (P)"
>
{isPanelOpen ? (
<PanelLeftClose className="h-4 w-4" />
) : (
<PanelLeft className="h-4 w-4" />
)}
<span></span>
</Button>
)}
<div className="h-6 w-px bg-gray-300" />
<div className="flex items-center space-x-3">

View File

@ -760,130 +760,6 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
<div className="text-sm font-medium"> </div>
<div className="space-y-6">
{/* 커스텀 테이블 설정 */}
<div className="space-y-3">
<div>
<h3 className="text-sm font-semibold"> </h3>
<p className="text-muted-foreground text-[10px]"> </p>
</div>
<hr className="border-border" />
{/* 커스텀 테이블 사용 여부 */}
{/* 현재 선택된 테이블 표시 (카드 형태) */}
<div className="flex items-center gap-2 rounded-md border bg-slate-50 p-2">
<Database className="h-4 w-4 text-blue-500" />
<div className="flex-1">
<div className="text-xs font-medium">
{config.customTableName || config.selectedTable || screenTableName || "테이블 미선택"}
</div>
<div className="text-[10px] text-muted-foreground">
{config.useCustomTable ? "커스텀 테이블" : "화면 기본 테이블"}
{config.isReadOnly && " (읽기전용)"}
</div>
</div>
</div>
{/* 테이블 선택 Combobox (기본/전체 그룹) */}
<Popover open={tableComboboxOpen} onOpenChange={setTableComboboxOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={tableComboboxOpen}
className="h-8 w-full justify-between text-xs"
disabled={loadingTables}
>
...
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-full p-0" align="start">
<Command>
<CommandInput placeholder="테이블 검색..." className="h-8 text-xs" />
<CommandList>
<CommandEmpty className="py-2 text-xs"> </CommandEmpty>
{/* 그룹 1: 화면 기본 테이블 */}
{screenTableName && (
<CommandGroup heading="기본 (화면 테이블)">
<CommandItem
key={`default-${screenTableName}`}
value={screenTableName}
onSelect={(currentValue) => {
console.log("🔍 기본 테이블 선택:", currentValue, screenTableName);
onChange({
...config,
useCustomTable: false,
customTableName: undefined,
selectedTable: screenTableName,
columns: [],
});
setTableComboboxOpen(false);
}}
className="text-xs cursor-pointer"
>
<Check
className={cn(
"mr-2 h-3 w-3",
!config.useCustomTable && (config.selectedTable === screenTableName || !config.selectedTable)
? "opacity-100"
: "opacity-0",
)}
/>
<Database className="mr-2 h-3 w-3 text-blue-500" />
{screenTableName}
</CommandItem>
</CommandGroup>
)}
{/* 그룹 2: 전체 테이블 */}
<CommandGroup heading="전체 테이블">
{availableTables
.filter((table) => table.tableName !== screenTableName)
.map((table) => (
<CommandItem
key={table.tableName}
value={table.tableName}
onSelect={(currentValue) => {
console.log("🔍 전체 테이블 선택:", currentValue, table.tableName);
onChange({
...config,
useCustomTable: true,
customTableName: table.tableName,
selectedTable: table.tableName,
columns: [],
});
setTableComboboxOpen(false);
}}
className="text-xs cursor-pointer"
>
<Check
className={cn(
"mr-2 h-3 w-3",
config.customTableName === table.tableName ? "opacity-100" : "opacity-0",
)}
/>
<Table2 className="mr-2 h-3 w-3 text-slate-400" />
{table.displayName || table.tableName}
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
{/* 읽기전용 설정 */}
<div className="flex items-center space-x-2">
<Checkbox
id="isReadOnly"
checked={config.isReadOnly || false}
onCheckedChange={(checked) => handleChange("isReadOnly", checked)}
/>
<Label htmlFor="isReadOnly" className="text-xs"> ( )</Label>
</div>
</div>
{/* 툴바 버튼 설정 */}
<div className="space-y-3">
<div>