다국어 지원 및 테이블 설정 현황 문서를 업데이트하고, 컴포넌트 패널에서 UI 개선을 통해 사용자 경험을 향상시켰습니다. 또한, 통합 패널의 너비를 조정하고, 패널 토글 기능을 추가하여 인터페이스의 유연성을 높였습니다.
This commit is contained in:
parent
c71641c32c
commit
f160ba2a1b
|
|
@ -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` 지원 | 대기 |
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in New Issue