ui수정
This commit is contained in:
parent
cc4d294906
commit
b68c0bd340
File diff suppressed because it is too large
Load Diff
|
|
@ -6,8 +6,6 @@
|
||||||
* 렌더링 모드별 설정:
|
* 렌더링 모드별 설정:
|
||||||
* - inline: 현재 화면 테이블 컬럼 직접 입력
|
* - inline: 현재 화면 테이블 컬럼 직접 입력
|
||||||
* - modal: 엔티티 선택 + 추가 입력 (FK 저장 + 추가 컬럼 입력)
|
* - modal: 엔티티 선택 + 추가 입력 (FK 저장 + 추가 컬럼 입력)
|
||||||
* - button: 버튼 클릭으로 관련 화면/모달 열기
|
|
||||||
* - mixed: inline + modal 기능 결합
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useState, useEffect, useMemo, useCallback } from "react";
|
import React, { useState, useEffect, useMemo, useCallback } from "react";
|
||||||
|
|
@ -17,7 +15,6 @@ import { Separator } from "@/components/ui/separator";
|
||||||
import { Checkbox } from "@/components/ui/checkbox";
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
import {
|
import {
|
||||||
Database,
|
Database,
|
||||||
|
|
@ -28,24 +25,15 @@ import {
|
||||||
ArrowRight,
|
ArrowRight,
|
||||||
Calculator,
|
Calculator,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { tableTypeApi, screenApi } from "@/lib/api/screen";
|
import { tableTypeApi } from "@/lib/api/screen";
|
||||||
import { commonCodeApi } from "@/lib/api/commonCode";
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import {
|
import {
|
||||||
UnifiedRepeaterConfig,
|
UnifiedRepeaterConfig,
|
||||||
RepeaterColumnConfig,
|
RepeaterColumnConfig,
|
||||||
RepeaterButtonConfig,
|
|
||||||
DEFAULT_REPEATER_CONFIG,
|
DEFAULT_REPEATER_CONFIG,
|
||||||
RENDER_MODE_OPTIONS,
|
RENDER_MODE_OPTIONS,
|
||||||
MODAL_SIZE_OPTIONS,
|
MODAL_SIZE_OPTIONS,
|
||||||
COLUMN_WIDTH_OPTIONS,
|
COLUMN_WIDTH_OPTIONS,
|
||||||
BUTTON_ACTION_OPTIONS,
|
|
||||||
BUTTON_VARIANT_OPTIONS,
|
|
||||||
BUTTON_LAYOUT_OPTIONS,
|
|
||||||
BUTTON_SOURCE_OPTIONS,
|
|
||||||
LABEL_FIELD_OPTIONS,
|
|
||||||
ButtonActionType,
|
|
||||||
ButtonVariant,
|
|
||||||
ColumnWidthOption,
|
ColumnWidthOption,
|
||||||
} from "@/types/unified-repeater";
|
} from "@/types/unified-repeater";
|
||||||
|
|
||||||
|
|
@ -85,17 +73,6 @@ interface CalculationRule {
|
||||||
label?: string;
|
label?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ScreenOption {
|
|
||||||
screenId: number;
|
|
||||||
screenName: string;
|
|
||||||
screenCode: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface CategoryOption {
|
|
||||||
categoryCode: string;
|
|
||||||
categoryName: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const UnifiedRepeaterConfigPanel: React.FC<UnifiedRepeaterConfigPanelProps> = ({
|
export const UnifiedRepeaterConfigPanel: React.FC<UnifiedRepeaterConfigPanelProps> = ({
|
||||||
config: propConfig,
|
config: propConfig,
|
||||||
onChange,
|
onChange,
|
||||||
|
|
@ -118,10 +95,6 @@ export const UnifiedRepeaterConfigPanel: React.FC<UnifiedRepeaterConfigPanelProp
|
||||||
...DEFAULT_REPEATER_CONFIG.modal,
|
...DEFAULT_REPEATER_CONFIG.modal,
|
||||||
...propConfig?.modal,
|
...propConfig?.modal,
|
||||||
},
|
},
|
||||||
button: {
|
|
||||||
...DEFAULT_REPEATER_CONFIG.button,
|
|
||||||
...propConfig?.button,
|
|
||||||
},
|
|
||||||
features: {
|
features: {
|
||||||
...DEFAULT_REPEATER_CONFIG.features,
|
...DEFAULT_REPEATER_CONFIG.features,
|
||||||
...propConfig?.features,
|
...propConfig?.features,
|
||||||
|
|
@ -132,14 +105,10 @@ export const UnifiedRepeaterConfigPanel: React.FC<UnifiedRepeaterConfigPanelProp
|
||||||
const [currentTableColumns, setCurrentTableColumns] = useState<ColumnOption[]>([]); // 현재 테이블 컬럼
|
const [currentTableColumns, setCurrentTableColumns] = useState<ColumnOption[]>([]); // 현재 테이블 컬럼
|
||||||
const [entityColumns, setEntityColumns] = useState<EntityColumnOption[]>([]); // 엔티티 타입 컬럼
|
const [entityColumns, setEntityColumns] = useState<EntityColumnOption[]>([]); // 엔티티 타입 컬럼
|
||||||
const [sourceTableColumns, setSourceTableColumns] = useState<ColumnOption[]>([]); // 소스(엔티티) 테이블 컬럼
|
const [sourceTableColumns, setSourceTableColumns] = useState<ColumnOption[]>([]); // 소스(엔티티) 테이블 컬럼
|
||||||
const [screens, setScreens] = useState<ScreenOption[]>([]);
|
|
||||||
const [categories, setCategories] = useState<CategoryOption[]>([]);
|
|
||||||
const [calculationRules, setCalculationRules] = useState<CalculationRule[]>([]);
|
const [calculationRules, setCalculationRules] = useState<CalculationRule[]>([]);
|
||||||
|
|
||||||
const [loadingColumns, setLoadingColumns] = useState(false);
|
const [loadingColumns, setLoadingColumns] = useState(false);
|
||||||
const [loadingSourceColumns, setLoadingSourceColumns] = useState(false);
|
const [loadingSourceColumns, setLoadingSourceColumns] = useState(false);
|
||||||
const [loadingScreens, setLoadingScreens] = useState(false);
|
|
||||||
const [loadingCategories, setLoadingCategories] = useState(false);
|
|
||||||
|
|
||||||
// 설정 업데이트 헬퍼
|
// 설정 업데이트 헬퍼
|
||||||
const updateConfig = useCallback(
|
const updateConfig = useCallback(
|
||||||
|
|
@ -167,15 +136,6 @@ export const UnifiedRepeaterConfigPanel: React.FC<UnifiedRepeaterConfigPanelProp
|
||||||
[config.modal, updateConfig],
|
[config.modal, updateConfig],
|
||||||
);
|
);
|
||||||
|
|
||||||
const updateButton = useCallback(
|
|
||||||
(field: string, value: any) => {
|
|
||||||
updateConfig({
|
|
||||||
button: { ...config.button, [field]: value },
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[config.button, updateConfig],
|
|
||||||
);
|
|
||||||
|
|
||||||
const updateFeatures = useCallback(
|
const updateFeatures = useCallback(
|
||||||
(field: string, value: boolean) => {
|
(field: string, value: boolean) => {
|
||||||
updateConfig({
|
updateConfig({
|
||||||
|
|
@ -284,64 +244,11 @@ export const UnifiedRepeaterConfigPanel: React.FC<UnifiedRepeaterConfigPanelProp
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (config.renderMode === "modal" || config.renderMode === "mixed") {
|
if (config.renderMode === "modal") {
|
||||||
loadSourceTableColumns();
|
loadSourceTableColumns();
|
||||||
}
|
}
|
||||||
}, [config.dataSource?.sourceTable, config.renderMode]);
|
}, [config.dataSource?.sourceTable, config.renderMode]);
|
||||||
|
|
||||||
// 화면 목록 로드
|
|
||||||
useEffect(() => {
|
|
||||||
const needScreens = config.renderMode === "button" || config.renderMode === "mixed";
|
|
||||||
if (!needScreens) return;
|
|
||||||
|
|
||||||
const loadScreens = async () => {
|
|
||||||
setLoadingScreens(true);
|
|
||||||
try {
|
|
||||||
const response = await screenApi.getScreens({ size: 1000 });
|
|
||||||
setScreens(
|
|
||||||
response.data.map((s) => ({
|
|
||||||
screenId: s.screenId!,
|
|
||||||
screenName: s.screenName,
|
|
||||||
screenCode: s.screenCode,
|
|
||||||
})),
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("화면 목록 로드 실패:", error);
|
|
||||||
} finally {
|
|
||||||
setLoadingScreens(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
loadScreens();
|
|
||||||
}, [config.renderMode]);
|
|
||||||
|
|
||||||
// 공통코드 카테고리 로드
|
|
||||||
useEffect(() => {
|
|
||||||
const needCategories =
|
|
||||||
(config.renderMode === "button" || config.renderMode === "mixed") &&
|
|
||||||
config.button?.sourceType === "commonCode";
|
|
||||||
if (!needCategories) return;
|
|
||||||
|
|
||||||
const loadCategories = async () => {
|
|
||||||
setLoadingCategories(true);
|
|
||||||
try {
|
|
||||||
const response = await commonCodeApi.categories.getList();
|
|
||||||
if (response.success && response.data) {
|
|
||||||
setCategories(
|
|
||||||
response.data.map((c) => ({
|
|
||||||
categoryCode: c.categoryCode,
|
|
||||||
categoryName: c.categoryName,
|
|
||||||
})),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("공통코드 카테고리 로드 실패:", error);
|
|
||||||
} finally {
|
|
||||||
setLoadingCategories(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
loadCategories();
|
|
||||||
}, [config.renderMode, config.button?.sourceType]);
|
|
||||||
|
|
||||||
// 컬럼 토글 (현재 테이블 컬럼 - 입력용)
|
// 컬럼 토글 (현재 테이블 컬럼 - 입력용)
|
||||||
const toggleInputColumn = (column: ColumnOption) => {
|
const toggleInputColumn = (column: ColumnOption) => {
|
||||||
const existingIndex = config.columns.findIndex((c) => c.key === column.columnName);
|
const existingIndex = config.columns.findIndex((c) => c.key === column.columnName);
|
||||||
|
|
@ -449,33 +356,9 @@ export const UnifiedRepeaterConfigPanel: React.FC<UnifiedRepeaterConfigPanelProp
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 수동 버튼 관리
|
|
||||||
const addManualButton = () => {
|
|
||||||
const newButton: RepeaterButtonConfig = {
|
|
||||||
id: `btn_${Date.now()}`,
|
|
||||||
label: "새 버튼",
|
|
||||||
action: "create",
|
|
||||||
variant: "outline",
|
|
||||||
};
|
|
||||||
const currentButtons = config.button?.manualButtons || [];
|
|
||||||
updateButton("manualButtons", [...currentButtons, newButton]);
|
|
||||||
};
|
|
||||||
|
|
||||||
const removeManualButton = (id: string) => {
|
|
||||||
const currentButtons = config.button?.manualButtons || [];
|
|
||||||
updateButton("manualButtons", currentButtons.filter((b) => b.id !== id));
|
|
||||||
};
|
|
||||||
|
|
||||||
const updateManualButton = (id: string, field: keyof RepeaterButtonConfig, value: any) => {
|
|
||||||
const currentButtons = config.button?.manualButtons || [];
|
|
||||||
const updated = currentButtons.map((b) => (b.id === id ? { ...b, [field]: value } : b));
|
|
||||||
updateButton("manualButtons", updated);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 모드 여부
|
// 모드 여부
|
||||||
const isInlineMode = config.renderMode === "inline";
|
const isInlineMode = config.renderMode === "inline";
|
||||||
const isModalMode = config.renderMode === "modal" || config.renderMode === "mixed";
|
const isModalMode = config.renderMode === "modal";
|
||||||
const isButtonMode = config.renderMode === "button" || config.renderMode === "mixed";
|
|
||||||
|
|
||||||
// 엔티티 컬럼 제외한 입력 가능 컬럼 (FK 컬럼 제외)
|
// 엔티티 컬럼 제외한 입력 가능 컬럼 (FK 컬럼 제외)
|
||||||
const inputableColumns = useMemo(() => {
|
const inputableColumns = useMemo(() => {
|
||||||
|
|
@ -489,11 +372,10 @@ export const UnifiedRepeaterConfigPanel: React.FC<UnifiedRepeaterConfigPanelProp
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<Tabs defaultValue="basic" className="w-full">
|
<Tabs defaultValue="basic" className="w-full">
|
||||||
<TabsList className="grid w-full grid-cols-4">
|
<TabsList className="grid w-full grid-cols-3">
|
||||||
<TabsTrigger value="basic" className="text-xs">기본</TabsTrigger>
|
<TabsTrigger value="basic" className="text-xs">기본</TabsTrigger>
|
||||||
<TabsTrigger value="columns" className="text-xs">컬럼</TabsTrigger>
|
<TabsTrigger value="columns" className="text-xs">컬럼</TabsTrigger>
|
||||||
<TabsTrigger value="modal" className="text-xs" disabled={!isModalMode}>모달</TabsTrigger>
|
<TabsTrigger value="modal" className="text-xs" disabled={!isModalMode}>모달</TabsTrigger>
|
||||||
<TabsTrigger value="button" className="text-xs" disabled={!isButtonMode}>버튼</TabsTrigger>
|
|
||||||
</TabsList>
|
</TabsList>
|
||||||
|
|
||||||
{/* 기본 설정 탭 */}
|
{/* 기본 설정 탭 */}
|
||||||
|
|
@ -514,7 +396,6 @@ export const UnifiedRepeaterConfigPanel: React.FC<UnifiedRepeaterConfigPanelProp
|
||||||
{opt.value === "inline" && "현재 테이블 컬럼 직접 입력"}
|
{opt.value === "inline" && "현재 테이블 컬럼 직접 입력"}
|
||||||
{opt.value === "modal" && "엔티티 선택 후 추가 정보 입력"}
|
{opt.value === "modal" && "엔티티 선택 후 추가 정보 입력"}
|
||||||
{opt.value === "button" && "버튼으로 관련 화면 열기"}
|
{opt.value === "button" && "버튼으로 관련 화면 열기"}
|
||||||
{opt.value === "mixed" && "직접 입력 + 엔티티 검색"}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
|
|
@ -952,252 +833,6 @@ export const UnifiedRepeaterConfigPanel: React.FC<UnifiedRepeaterConfigPanelProp
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
{/* 버튼 설정 탭 */}
|
|
||||||
<TabsContent value="button" className="mt-4 space-y-4">
|
|
||||||
{isButtonMode ? (
|
|
||||||
<>
|
|
||||||
{/* 버튼 소스 선택 */}
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label className="text-xs font-medium">버튼 소스</Label>
|
|
||||||
<RadioGroup
|
|
||||||
value={config.button?.sourceType || "manual"}
|
|
||||||
onValueChange={(value) => updateButton("sourceType", value)}
|
|
||||||
className="flex gap-4"
|
|
||||||
>
|
|
||||||
{BUTTON_SOURCE_OPTIONS.map((opt) => (
|
|
||||||
<div key={opt.value} className="flex items-center space-x-2">
|
|
||||||
<RadioGroupItem value={opt.value} id={opt.value} />
|
|
||||||
<label htmlFor={opt.value} className="text-xs">{opt.label}</label>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</RadioGroup>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Separator />
|
|
||||||
|
|
||||||
{/* 공통코드 모드 */}
|
|
||||||
{config.button?.sourceType === "commonCode" && (
|
|
||||||
<div className="space-y-3">
|
|
||||||
<Label className="text-xs font-medium">공통코드 설정</Label>
|
|
||||||
|
|
||||||
<div className="space-y-1">
|
|
||||||
<Label className="text-muted-foreground text-[10px]">코드 카테고리</Label>
|
|
||||||
<Select
|
|
||||||
value={config.button?.commonCode?.categoryCode || ""}
|
|
||||||
onValueChange={(value) =>
|
|
||||||
updateButton("commonCode", {
|
|
||||||
...config.button?.commonCode,
|
|
||||||
categoryCode: value,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<SelectTrigger className="h-8 text-xs">
|
|
||||||
<SelectValue placeholder={loadingCategories ? "로딩 중..." : "카테고리 선택"} />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
{categories.map((cat) => (
|
|
||||||
<SelectItem key={cat.categoryCode} value={cat.categoryCode}>
|
|
||||||
{cat.categoryName} ({cat.categoryCode})
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-1">
|
|
||||||
<Label className="text-muted-foreground text-[10px]">버튼 라벨 표시</Label>
|
|
||||||
<Select
|
|
||||||
value={config.button?.commonCode?.labelField || "codeName"}
|
|
||||||
onValueChange={(value) =>
|
|
||||||
updateButton("commonCode", {
|
|
||||||
...config.button?.commonCode,
|
|
||||||
labelField: value,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<SelectTrigger className="h-8 text-xs">
|
|
||||||
<SelectValue />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
{LABEL_FIELD_OPTIONS.map((opt) => (
|
|
||||||
<SelectItem key={opt.value} value={opt.value}>
|
|
||||||
{opt.label}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-1">
|
|
||||||
<Label className="text-muted-foreground text-[10px]">값 저장 컬럼</Label>
|
|
||||||
<Select
|
|
||||||
value={config.button?.commonCode?.valueField || ""}
|
|
||||||
onValueChange={(value) =>
|
|
||||||
updateButton("commonCode", {
|
|
||||||
...config.button?.commonCode,
|
|
||||||
valueField: value,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<SelectTrigger className="h-8 text-xs">
|
|
||||||
<SelectValue placeholder="컬럼 선택" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
{currentTableColumns.map((col) => (
|
|
||||||
<SelectItem key={col.columnName} value={col.columnName}>
|
|
||||||
{col.displayName}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* 수동 설정 모드 */}
|
|
||||||
{config.button?.sourceType === "manual" && (
|
|
||||||
<div className="space-y-3">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<Label className="text-xs font-medium">버튼 목록</Label>
|
|
||||||
<Button type="button" variant="outline" size="sm" onClick={addManualButton} className="h-7 text-xs">
|
|
||||||
<Plus className="mr-1 h-3 w-3" />
|
|
||||||
추가
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="max-h-48 space-y-2 overflow-y-auto">
|
|
||||||
{(config.button?.manualButtons || []).map((btn) => (
|
|
||||||
<div key={btn.id} className="space-y-2 rounded-md border p-2">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Input
|
|
||||||
value={btn.label}
|
|
||||||
onChange={(e) => updateManualButton(btn.id, "label", e.target.value)}
|
|
||||||
placeholder="버튼 라벨"
|
|
||||||
className="h-7 flex-1 text-xs"
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant="ghost"
|
|
||||||
size="sm"
|
|
||||||
onClick={() => removeManualButton(btn.id)}
|
|
||||||
className="text-destructive h-7 w-7 p-0"
|
|
||||||
>
|
|
||||||
<Trash2 className="h-3 w-3" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<div className="grid grid-cols-2 gap-2">
|
|
||||||
<Select
|
|
||||||
value={btn.action}
|
|
||||||
onValueChange={(value) => updateManualButton(btn.id, "action", value as ButtonActionType)}
|
|
||||||
>
|
|
||||||
<SelectTrigger className="h-7 text-xs">
|
|
||||||
<SelectValue placeholder="액션" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
{BUTTON_ACTION_OPTIONS.map((opt) => (
|
|
||||||
<SelectItem key={opt.value} value={opt.value}>
|
|
||||||
{opt.label}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
|
|
||||||
<Select
|
|
||||||
value={btn.variant}
|
|
||||||
onValueChange={(value) => updateManualButton(btn.id, "variant", value as ButtonVariant)}
|
|
||||||
>
|
|
||||||
<SelectTrigger className="h-7 text-xs">
|
|
||||||
<SelectValue placeholder="스타일" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
{BUTTON_VARIANT_OPTIONS.map((opt) => (
|
|
||||||
<SelectItem key={opt.value} value={opt.value}>
|
|
||||||
{opt.label}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{btn.action === "navigate" && (
|
|
||||||
<Select
|
|
||||||
value={String(btn.navigateScreen || "")}
|
|
||||||
onValueChange={(value) =>
|
|
||||||
updateManualButton(btn.id, "navigateScreen", value ? Number(value) : undefined)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<SelectTrigger className="h-7 text-xs">
|
|
||||||
<SelectValue placeholder="이동할 화면 선택" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
{screens.map((s) => (
|
|
||||||
<SelectItem key={s.screenId} value={String(s.screenId)}>
|
|
||||||
{s.screenName}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
|
|
||||||
{(config.button?.manualButtons || []).length === 0 && (
|
|
||||||
<p className="text-muted-foreground py-4 text-center text-xs">버튼을 추가해주세요</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Separator />
|
|
||||||
|
|
||||||
{/* 버튼 레이아웃 */}
|
|
||||||
<div className="grid grid-cols-2 gap-4">
|
|
||||||
<div className="space-y-1">
|
|
||||||
<Label className="text-muted-foreground text-[10px]">레이아웃</Label>
|
|
||||||
<Select
|
|
||||||
value={config.button?.layout || "horizontal"}
|
|
||||||
onValueChange={(value) => updateButton("layout", value)}
|
|
||||||
>
|
|
||||||
<SelectTrigger className="h-7 text-xs">
|
|
||||||
<SelectValue />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
{BUTTON_LAYOUT_OPTIONS.map((opt) => (
|
|
||||||
<SelectItem key={opt.value} value={opt.value}>
|
|
||||||
{opt.label}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-1">
|
|
||||||
<Label className="text-muted-foreground text-[10px]">기본 스타일</Label>
|
|
||||||
<Select
|
|
||||||
value={config.button?.style || "outline"}
|
|
||||||
onValueChange={(value) => updateButton("style", value)}
|
|
||||||
>
|
|
||||||
<SelectTrigger className="h-7 text-xs">
|
|
||||||
<SelectValue />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
{BUTTON_VARIANT_OPTIONS.map((opt) => (
|
|
||||||
<SelectItem key={opt.value} value={opt.value}>
|
|
||||||
{opt.label}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<p className="text-muted-foreground py-4 text-center text-xs">
|
|
||||||
버튼 또는 혼합 모드에서만 설정할 수 있습니다
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</TabsContent>
|
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -515,14 +515,14 @@ export function RepeaterTable({
|
||||||
width: `max(100%, ${Object.values(columnWidths).reduce((sum, w) => sum + w, 0) + 74}px)`,
|
width: `max(100%, ${Object.values(columnWidths).reduce((sum, w) => sum + w, 0) + 74}px)`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<thead className="sticky top-0 z-10 bg-gray-50">
|
<thead className="sticky top-0 z-20 bg-gray-50">
|
||||||
<tr>
|
<tr>
|
||||||
{/* 드래그 핸들 헤더 */}
|
{/* 드래그 핸들 헤더 - 좌측 고정 */}
|
||||||
<th className="w-8 border-r border-b border-gray-200 px-1 py-2 text-center font-medium text-gray-700">
|
<th className="sticky left-0 z-30 w-8 border-r border-b border-gray-200 bg-gray-50 px-1 py-2 text-center font-medium text-gray-700">
|
||||||
<span className="sr-only">순서</span>
|
<span className="sr-only">순서</span>
|
||||||
</th>
|
</th>
|
||||||
{/* 체크박스 헤더 */}
|
{/* 체크박스 헤더 - 좌측 고정 */}
|
||||||
<th className="w-10 border-r border-b border-gray-200 px-3 py-2 text-center font-medium text-gray-700">
|
<th className="sticky left-8 z-30 w-10 border-r border-b border-gray-200 bg-gray-50 px-3 py-2 text-center font-medium text-gray-700">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={isAllSelected}
|
checked={isAllSelected}
|
||||||
// @ts-expect-error - indeterminate는 HTML 속성
|
// @ts-expect-error - indeterminate는 HTML 속성
|
||||||
|
|
@ -649,8 +649,11 @@ export function RepeaterTable({
|
||||||
>
|
>
|
||||||
{({ attributes, listeners, isDragging }) => (
|
{({ attributes, listeners, isDragging }) => (
|
||||||
<>
|
<>
|
||||||
{/* 드래그 핸들 */}
|
{/* 드래그 핸들 - 좌측 고정 */}
|
||||||
<td className="border-r border-b border-gray-200 px-1 py-1 text-center">
|
<td className={cn(
|
||||||
|
"sticky left-0 z-10 border-r border-b border-gray-200 px-1 py-1 text-center",
|
||||||
|
selectedRows.has(rowIndex) ? "bg-blue-50" : "bg-white"
|
||||||
|
)}>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={cn(
|
className={cn(
|
||||||
|
|
@ -663,8 +666,11 @@ export function RepeaterTable({
|
||||||
<GripVertical className="h-4 w-4 text-gray-400" />
|
<GripVertical className="h-4 w-4 text-gray-400" />
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
{/* 체크박스 */}
|
{/* 체크박스 - 좌측 고정 */}
|
||||||
<td className="border-r border-b border-gray-200 px-3 py-1 text-center">
|
<td className={cn(
|
||||||
|
"sticky left-8 z-10 border-r border-b border-gray-200 px-3 py-1 text-center",
|
||||||
|
selectedRows.has(rowIndex) ? "bg-blue-50" : "bg-white"
|
||||||
|
)}>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={selectedRows.has(rowIndex)}
|
checked={selectedRows.has(rowIndex)}
|
||||||
onCheckedChange={(checked) => handleRowSelect(rowIndex, !!checked)}
|
onCheckedChange={(checked) => handleRowSelect(rowIndex, !!checked)}
|
||||||
|
|
|
||||||
|
|
@ -4,24 +4,11 @@
|
||||||
* 렌더링 모드:
|
* 렌더링 모드:
|
||||||
* - inline: 현재 테이블 컬럼 직접 입력 (simple-repeater-table)
|
* - inline: 현재 테이블 컬럼 직접 입력 (simple-repeater-table)
|
||||||
* - modal: 소스 테이블에서 검색/선택 후 복사 (modal-repeater-table)
|
* - modal: 소스 테이블에서 검색/선택 후 복사 (modal-repeater-table)
|
||||||
* - button: 버튼으로 관련 화면 열기 (related-data-buttons)
|
|
||||||
* - mixed: inline + modal 혼합
|
* - mixed: inline + modal 혼합
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// 렌더링 모드
|
// 렌더링 모드
|
||||||
export type RepeaterRenderMode = "inline" | "modal" | "button" | "mixed";
|
export type RepeaterRenderMode = "inline" | "modal";
|
||||||
|
|
||||||
// 버튼 소스 타입
|
|
||||||
export type ButtonSourceType = "commonCode" | "manual";
|
|
||||||
|
|
||||||
// 버튼 액션 타입
|
|
||||||
export type ButtonActionType = "create" | "update" | "delete" | "view" | "navigate" | "custom";
|
|
||||||
|
|
||||||
// 버튼 색상/스타일
|
|
||||||
export type ButtonVariant = "default" | "primary" | "secondary" | "destructive" | "outline" | "ghost";
|
|
||||||
|
|
||||||
// 버튼 레이아웃
|
|
||||||
export type ButtonLayout = "horizontal" | "vertical";
|
|
||||||
|
|
||||||
// 모달 크기
|
// 모달 크기
|
||||||
export type ModalSize = "sm" | "md" | "lg" | "xl" | "full";
|
export type ModalSize = "sm" | "md" | "lg" | "xl" | "full";
|
||||||
|
|
@ -50,29 +37,6 @@ export interface RepeaterColumnConfig {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// 버튼 설정 (수동 모드)
|
|
||||||
export interface RepeaterButtonConfig {
|
|
||||||
id: string;
|
|
||||||
label: string;
|
|
||||||
action: ButtonActionType;
|
|
||||||
variant: ButtonVariant;
|
|
||||||
icon?: string;
|
|
||||||
confirmMessage?: string;
|
|
||||||
// 네비게이트 액션용
|
|
||||||
navigateScreen?: number;
|
|
||||||
navigateParams?: Record<string, string>;
|
|
||||||
// 커스텀 액션용
|
|
||||||
customHandler?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 공통코드 버튼 설정
|
|
||||||
export interface CommonCodeButtonConfig {
|
|
||||||
categoryCode: string;
|
|
||||||
labelField: "codeValue" | "codeName";
|
|
||||||
valueField: string; // 버튼 클릭 시 전달할 값의 컬럼
|
|
||||||
variantMapping?: Record<string, ButtonVariant>; // 코드값별 색상 매핑
|
|
||||||
}
|
|
||||||
|
|
||||||
// 모달 표시 컬럼 (라벨 포함)
|
// 모달 표시 컬럼 (라벨 포함)
|
||||||
export interface ModalDisplayColumn {
|
export interface ModalDisplayColumn {
|
||||||
key: string;
|
key: string;
|
||||||
|
|
@ -162,15 +126,6 @@ export interface UnifiedRepeaterConfig {
|
||||||
// 모달 설정 (modal, mixed 모드)
|
// 모달 설정 (modal, mixed 모드)
|
||||||
modal?: RepeaterModalConfig;
|
modal?: RepeaterModalConfig;
|
||||||
|
|
||||||
// 버튼 설정 (button, mixed 모드)
|
|
||||||
button?: {
|
|
||||||
sourceType: ButtonSourceType;
|
|
||||||
commonCode?: CommonCodeButtonConfig;
|
|
||||||
manualButtons?: RepeaterButtonConfig[];
|
|
||||||
layout: ButtonLayout;
|
|
||||||
style: ButtonVariant;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 기능 옵션
|
// 기능 옵션
|
||||||
features: RepeaterFeatureOptions;
|
features: RepeaterFeatureOptions;
|
||||||
|
|
||||||
|
|
@ -190,7 +145,6 @@ export interface UnifiedRepeaterProps {
|
||||||
data?: any[]; // 초기 데이터 (없으면 API로 로드)
|
data?: any[]; // 초기 데이터 (없으면 API로 로드)
|
||||||
onDataChange?: (data: any[]) => void;
|
onDataChange?: (data: any[]) => void;
|
||||||
onRowClick?: (row: any) => void;
|
onRowClick?: (row: any) => void;
|
||||||
onButtonClick?: (action: ButtonActionType, row?: any, buttonConfig?: RepeaterButtonConfig) => void;
|
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -204,12 +158,6 @@ export const DEFAULT_REPEATER_CONFIG: UnifiedRepeaterConfig = {
|
||||||
sourceDisplayColumns: [],
|
sourceDisplayColumns: [],
|
||||||
searchFields: [],
|
searchFields: [],
|
||||||
},
|
},
|
||||||
button: {
|
|
||||||
sourceType: "manual",
|
|
||||||
manualButtons: [],
|
|
||||||
layout: "horizontal",
|
|
||||||
style: "outline",
|
|
||||||
},
|
|
||||||
features: {
|
features: {
|
||||||
showAddButton: true,
|
showAddButton: true,
|
||||||
showDeleteButton: true,
|
showDeleteButton: true,
|
||||||
|
|
@ -225,8 +173,6 @@ export const DEFAULT_REPEATER_CONFIG: UnifiedRepeaterConfig = {
|
||||||
export const RENDER_MODE_OPTIONS = [
|
export const RENDER_MODE_OPTIONS = [
|
||||||
{ value: "inline", label: "인라인 (직접 입력)" },
|
{ value: "inline", label: "인라인 (직접 입력)" },
|
||||||
{ value: "modal", label: "모달 (검색 선택)" },
|
{ value: "modal", label: "모달 (검색 선택)" },
|
||||||
{ value: "button", label: "버튼" },
|
|
||||||
{ value: "mixed", label: "혼합 (입력 + 검색)" },
|
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export const MODAL_SIZE_OPTIONS = [
|
export const MODAL_SIZE_OPTIONS = [
|
||||||
|
|
@ -249,35 +195,3 @@ export const COLUMN_WIDTH_OPTIONS = [
|
||||||
{ value: "300px", label: "300px" },
|
{ value: "300px", label: "300px" },
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export const BUTTON_ACTION_OPTIONS = [
|
|
||||||
{ value: "create", label: "생성" },
|
|
||||||
{ value: "update", label: "수정" },
|
|
||||||
{ value: "delete", label: "삭제" },
|
|
||||||
{ value: "view", label: "보기" },
|
|
||||||
{ value: "navigate", label: "화면 이동" },
|
|
||||||
{ value: "custom", label: "커스텀" },
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
export const BUTTON_VARIANT_OPTIONS = [
|
|
||||||
{ value: "default", label: "기본" },
|
|
||||||
{ value: "primary", label: "Primary" },
|
|
||||||
{ value: "secondary", label: "Secondary" },
|
|
||||||
{ value: "destructive", label: "삭제 (빨강)" },
|
|
||||||
{ value: "outline", label: "Outline" },
|
|
||||||
{ value: "ghost", label: "Ghost" },
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
export const BUTTON_LAYOUT_OPTIONS = [
|
|
||||||
{ value: "horizontal", label: "가로 배치" },
|
|
||||||
{ value: "vertical", label: "세로 배치" },
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
export const BUTTON_SOURCE_OPTIONS = [
|
|
||||||
{ value: "commonCode", label: "공통코드 사용" },
|
|
||||||
{ value: "manual", label: "수동 설정" },
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
export const LABEL_FIELD_OPTIONS = [
|
|
||||||
{ value: "codeName", label: "코드명" },
|
|
||||||
{ value: "codeValue", label: "코드값" },
|
|
||||||
] as const;
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue