ui개선
This commit is contained in:
parent
2a8081a253
commit
2e916678fa
|
|
@ -114,11 +114,11 @@ export const FloatingPanel: React.FC<FloatingPanelProps> = ({
|
|||
const newHeight = Math.min(Math.max(minHeight, contentHeight + headerHeight + padding), maxHeight);
|
||||
|
||||
// console.log(`🔧 패널 높이 자동 조정:`, {
|
||||
// panelId: id,
|
||||
// contentHeight,
|
||||
// calculatedHeight: newHeight,
|
||||
// currentHeight: panelSize.height,
|
||||
// willUpdate: Math.abs(panelSize.height - newHeight) > 10,
|
||||
// panelId: id,
|
||||
// contentHeight,
|
||||
// calculatedHeight: newHeight,
|
||||
// currentHeight: panelSize.height,
|
||||
// willUpdate: Math.abs(panelSize.height - newHeight) > 10,
|
||||
// });
|
||||
|
||||
// 현재 높이와 다르면 업데이트
|
||||
|
|
@ -227,7 +227,7 @@ export const FloatingPanel: React.FC<FloatingPanelProps> = ({
|
|||
<div
|
||||
ref={panelRef}
|
||||
className={cn(
|
||||
"fixed z-[100] rounded-xl border border-gray-200/60 bg-white/95 backdrop-blur-sm shadow-xl shadow-gray-900/10",
|
||||
"bg-card text-card-foreground fixed z-[100] rounded-lg border shadow-lg",
|
||||
isDragging ? "cursor-move shadow-2xl" : "transition-all duration-200 ease-in-out",
|
||||
isResizing && "cursor-se-resize",
|
||||
className,
|
||||
|
|
@ -239,28 +239,28 @@ export const FloatingPanel: React.FC<FloatingPanelProps> = ({
|
|||
height: `${panelSize.height}px`,
|
||||
transform: isDragging ? "scale(1.01)" : "scale(1)",
|
||||
transition: isDragging ? "none" : "transform 0.1s ease-out, box-shadow 0.1s ease-out",
|
||||
zIndex: isDragging ? 101 : 100, // 항상 컴포넌트보다 위에 표시
|
||||
zIndex: isDragging ? 101 : 100,
|
||||
}}
|
||||
>
|
||||
{/* 헤더 */}
|
||||
<div
|
||||
ref={dragHandleRef}
|
||||
data-header="true"
|
||||
className="flex cursor-move items-center justify-between rounded-t-xl border-b border-gray-200/60 bg-gradient-to-r from-gray-50 to-slate-50 p-4"
|
||||
className="bg-muted/40 flex cursor-move items-center justify-between border-b px-4 py-3"
|
||||
onMouseDown={handleDragStart}
|
||||
style={{
|
||||
userSelect: "none", // 텍스트 선택 방지
|
||||
userSelect: "none",
|
||||
WebkitUserSelect: "none",
|
||||
MozUserSelect: "none",
|
||||
msUserSelect: "none",
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
<GripVertical className="h-4 w-4 text-gray-400" />
|
||||
<h3 className="text-sm font-medium text-gray-900">{title}</h3>
|
||||
<div className="flex items-center gap-2">
|
||||
<GripVertical className="text-muted-foreground h-4 w-4" />
|
||||
<h3 className="text-sm font-semibold">{title}</h3>
|
||||
</div>
|
||||
<button onClick={onClose} className="rounded-lg p-2 transition-all duration-200 hover:bg-white/80 hover:shadow-sm">
|
||||
<X className="h-4 w-4 text-gray-500 hover:text-gray-700" />
|
||||
<button onClick={onClose} className="hover:bg-accent rounded-md p-1.5 transition-colors">
|
||||
<X className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
|
@ -282,7 +282,7 @@ export const FloatingPanel: React.FC<FloatingPanelProps> = ({
|
|||
{/* 리사이즈 핸들 */}
|
||||
{resizable && !autoHeight && (
|
||||
<div className="absolute right-0 bottom-0 h-4 w-4 cursor-se-resize" onMouseDown={handleResizeStart}>
|
||||
<div className="absolute right-1 bottom-1 h-2 w-2 rounded-sm bg-gradient-to-br from-gray-400 to-gray-500 shadow-sm" />
|
||||
<div className="bg-muted-foreground/40 absolute right-1 bottom-1 h-2 w-2 rounded-sm" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -83,9 +83,9 @@ export const RealtimePreviewDynamic: React.FC<RealtimePreviewProps> = ({
|
|||
// 선택 상태에 따른 스타일 (z-index 낮춤 - 패널과 모달보다 아래)
|
||||
const selectionStyle = isSelected
|
||||
? {
|
||||
outline: "2px solid #3b82f6",
|
||||
outline: "2px solid hsl(var(--primary))",
|
||||
outlineOffset: "2px",
|
||||
zIndex: 20, // 패널과 모달보다 낮게 설정
|
||||
zIndex: 20,
|
||||
}
|
||||
: {};
|
||||
|
||||
|
|
@ -183,16 +183,16 @@ export const RealtimePreviewDynamic: React.FC<RealtimePreviewProps> = ({
|
|||
|
||||
{/* 선택된 컴포넌트 정보 표시 */}
|
||||
{isSelected && (
|
||||
<div className="absolute -top-8 left-0 rounded-lg bg-gray-800/90 px-3 py-2 text-xs text-white shadow-lg backdrop-blur-sm">
|
||||
<div className="bg-primary text-primary-foreground absolute -top-7 left-0 rounded-md px-2.5 py-1 text-xs font-medium shadow-sm">
|
||||
{type === "widget" && (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex items-center gap-1.5">
|
||||
{getWidgetIcon((component as WidgetComponent).widgetType)}
|
||||
<span className="font-medium">{(component as WidgetComponent).widgetType || "widget"}</span>
|
||||
<span>{(component as WidgetComponent).widgetType || "widget"}</span>
|
||||
</div>
|
||||
)}
|
||||
{type !== "widget" && (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-medium">{component.componentConfig?.type || type}</span>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span>{component.componentConfig?.type || type}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -28,79 +28,91 @@ export default function StyleEditor({ style, onStyleChange, className }: StyleEd
|
|||
};
|
||||
|
||||
return (
|
||||
<div className={`space-y-6 p-4 ${className}`}>
|
||||
<div className={`space-y-6 p-6 ${className}`}>
|
||||
{/* 여백 섹션 */}
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<Box className="h-4 w-4 text-blue-600" />
|
||||
<h3 className="font-semibold text-gray-900">여백</h3>
|
||||
<Box className="text-primary h-4 w-4" />
|
||||
<h3 className="text-sm font-semibold">여백</h3>
|
||||
</div>
|
||||
<Separator />
|
||||
<div className="space-y-4">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="margin">외부 여백</Label>
|
||||
<Separator className="my-2" />
|
||||
<div className="space-y-3">
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div className="space-y-1.5">
|
||||
<Label htmlFor="margin" className="text-xs font-medium">
|
||||
외부 여백
|
||||
</Label>
|
||||
<Input
|
||||
id="margin"
|
||||
type="text"
|
||||
placeholder="10px, 1rem"
|
||||
placeholder="10px"
|
||||
value={localStyle.margin || ""}
|
||||
onChange={(e) => handleStyleChange("margin", e.target.value)}
|
||||
className="h-8"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="padding">내부 여백</Label>
|
||||
<div className="space-y-1.5">
|
||||
<Label htmlFor="padding" className="text-xs font-medium">
|
||||
내부 여백
|
||||
</Label>
|
||||
<Input
|
||||
id="padding"
|
||||
type="text"
|
||||
placeholder="10px, 1rem"
|
||||
placeholder="10px"
|
||||
value={localStyle.padding || ""}
|
||||
onChange={(e) => handleStyleChange("padding", e.target.value)}
|
||||
className="h-8"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="gap">간격</Label>
|
||||
<Input
|
||||
id="gap"
|
||||
type="text"
|
||||
placeholder="10px, 1rem"
|
||||
value={localStyle.gap || ""}
|
||||
onChange={(e) => handleStyleChange("gap", e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-1.5">
|
||||
<Label htmlFor="gap" className="text-xs font-medium">
|
||||
간격
|
||||
</Label>
|
||||
<Input
|
||||
id="gap"
|
||||
type="text"
|
||||
placeholder="10px"
|
||||
value={localStyle.gap || ""}
|
||||
onChange={(e) => handleStyleChange("gap", e.target.value)}
|
||||
className="h-8"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 테두리 섹션 */}
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<Square className="h-4 w-4 text-green-600" />
|
||||
<h3 className="font-semibold text-gray-900">테두리</h3>
|
||||
<Square className="text-primary h-4 w-4" />
|
||||
<h3 className="text-sm font-semibold">테두리</h3>
|
||||
</div>
|
||||
<Separator />
|
||||
<div className="space-y-4">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="borderWidth">테두리 두께</Label>
|
||||
<Separator className="my-2" />
|
||||
<div className="space-y-3">
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div className="space-y-1.5">
|
||||
<Label htmlFor="borderWidth" className="text-xs font-medium">
|
||||
두께
|
||||
</Label>
|
||||
<Input
|
||||
id="borderWidth"
|
||||
type="text"
|
||||
placeholder="1px, 2px"
|
||||
placeholder="1px"
|
||||
value={localStyle.borderWidth || ""}
|
||||
onChange={(e) => handleStyleChange("borderWidth", e.target.value)}
|
||||
className="h-8"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="borderStyle">테두리 스타일</Label>
|
||||
<div className="space-y-1.5">
|
||||
<Label htmlFor="borderStyle" className="text-xs font-medium">
|
||||
스타일
|
||||
</Label>
|
||||
<Select
|
||||
value={localStyle.borderStyle || "solid"}
|
||||
onValueChange={(value) => handleStyleChange("borderStyle", value)}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectTrigger className="h-8">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
|
|
@ -113,24 +125,39 @@ export default function StyleEditor({ style, onStyleChange, className }: StyleEd
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="borderColor">테두리 색상</Label>
|
||||
<Input
|
||||
id="borderColor"
|
||||
type="color"
|
||||
value={localStyle.borderColor || "#000000"}
|
||||
onChange={(e) => handleStyleChange("borderColor", e.target.value)}
|
||||
/>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div className="space-y-1.5">
|
||||
<Label htmlFor="borderColor" className="text-xs font-medium">
|
||||
색상
|
||||
</Label>
|
||||
<div className="flex gap-2">
|
||||
<Input
|
||||
id="borderColor"
|
||||
type="color"
|
||||
value={localStyle.borderColor || "#000000"}
|
||||
onChange={(e) => handleStyleChange("borderColor", e.target.value)}
|
||||
className="h-8 w-14 p-1"
|
||||
/>
|
||||
<Input
|
||||
type="text"
|
||||
value={localStyle.borderColor || "#000000"}
|
||||
onChange={(e) => handleStyleChange("borderColor", e.target.value)}
|
||||
placeholder="#000000"
|
||||
className="h-8 flex-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="borderRadius">모서리 둥글기</Label>
|
||||
<div className="space-y-1.5">
|
||||
<Label htmlFor="borderRadius" className="text-xs font-medium">
|
||||
모서리
|
||||
</Label>
|
||||
<Input
|
||||
id="borderRadius"
|
||||
type="text"
|
||||
placeholder="5px, 10px"
|
||||
placeholder="5px"
|
||||
value={localStyle.borderRadius || ""}
|
||||
onChange={(e) => handleStyleChange("borderRadius", e.target.value)}
|
||||
className="h-8"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -138,74 +165,106 @@ export default function StyleEditor({ style, onStyleChange, className }: StyleEd
|
|||
</div>
|
||||
|
||||
{/* 배경 섹션 */}
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<Palette className="h-4 w-4 text-purple-600" />
|
||||
<h3 className="font-semibold text-gray-900">배경</h3>
|
||||
<Palette className="text-primary h-4 w-4" />
|
||||
<h3 className="text-sm font-semibold">배경</h3>
|
||||
</div>
|
||||
<Separator />
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="backgroundColor">배경 색상</Label>
|
||||
<Input
|
||||
id="backgroundColor"
|
||||
type="color"
|
||||
value={localStyle.backgroundColor || "#ffffff"}
|
||||
onChange={(e) => handleStyleChange("backgroundColor", e.target.value)}
|
||||
/>
|
||||
<Separator className="my-2" />
|
||||
<div className="space-y-3">
|
||||
<div className="space-y-1.5">
|
||||
<Label htmlFor="backgroundColor" className="text-xs font-medium">
|
||||
배경 색상
|
||||
</Label>
|
||||
<div className="flex gap-2">
|
||||
<Input
|
||||
id="backgroundColor"
|
||||
type="color"
|
||||
value={localStyle.backgroundColor || "#ffffff"}
|
||||
onChange={(e) => handleStyleChange("backgroundColor", e.target.value)}
|
||||
className="h-8 w-14 p-1"
|
||||
/>
|
||||
<Input
|
||||
type="text"
|
||||
value={localStyle.backgroundColor || "#ffffff"}
|
||||
onChange={(e) => handleStyleChange("backgroundColor", e.target.value)}
|
||||
placeholder="#ffffff"
|
||||
className="h-8 flex-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="backgroundImage">배경 이미지</Label>
|
||||
<div className="space-y-1.5">
|
||||
<Label htmlFor="backgroundImage" className="text-xs font-medium">
|
||||
배경 이미지
|
||||
</Label>
|
||||
<Input
|
||||
id="backgroundImage"
|
||||
type="text"
|
||||
placeholder="url('image.jpg')"
|
||||
value={localStyle.backgroundImage || ""}
|
||||
onChange={(e) => handleStyleChange("backgroundImage", e.target.value)}
|
||||
className="h-8"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 텍스트 섹션 */}
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<Type className="h-4 w-4 text-orange-600" />
|
||||
<h3 className="font-semibold text-gray-900">텍스트</h3>
|
||||
<Type className="text-primary h-4 w-4" />
|
||||
<h3 className="text-sm font-semibold">텍스트</h3>
|
||||
</div>
|
||||
<Separator />
|
||||
<div className="space-y-4">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="color">텍스트 색상</Label>
|
||||
<Input
|
||||
id="color"
|
||||
type="color"
|
||||
value={localStyle.color || "#000000"}
|
||||
onChange={(e) => handleStyleChange("color", e.target.value)}
|
||||
/>
|
||||
<Separator className="my-2" />
|
||||
<div className="space-y-3">
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div className="space-y-1.5">
|
||||
<Label htmlFor="color" className="text-xs font-medium">
|
||||
색상
|
||||
</Label>
|
||||
<div className="flex gap-2">
|
||||
<Input
|
||||
id="color"
|
||||
type="color"
|
||||
value={localStyle.color || "#000000"}
|
||||
onChange={(e) => handleStyleChange("color", e.target.value)}
|
||||
className="h-8 w-14 p-1"
|
||||
/>
|
||||
<Input
|
||||
type="text"
|
||||
value={localStyle.color || "#000000"}
|
||||
onChange={(e) => handleStyleChange("color", e.target.value)}
|
||||
placeholder="#000000"
|
||||
className="h-8 flex-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="fontSize">글자 크기</Label>
|
||||
<div className="space-y-1.5">
|
||||
<Label htmlFor="fontSize" className="text-xs font-medium">
|
||||
크기
|
||||
</Label>
|
||||
<Input
|
||||
id="fontSize"
|
||||
type="text"
|
||||
placeholder="14px, 1rem"
|
||||
placeholder="14px"
|
||||
value={localStyle.fontSize || ""}
|
||||
onChange={(e) => handleStyleChange("fontSize", e.target.value)}
|
||||
className="h-8"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="fontWeight">글자 굵기</Label>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div className="space-y-1.5">
|
||||
<Label htmlFor="fontWeight" className="text-xs font-medium">
|
||||
굵기
|
||||
</Label>
|
||||
<Select
|
||||
value={localStyle.fontWeight || "normal"}
|
||||
onValueChange={(value) => handleStyleChange("fontWeight", value)}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectTrigger className="h-8">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
|
|
@ -219,13 +278,15 @@ export default function StyleEditor({ style, onStyleChange, className }: StyleEd
|
|||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="textAlign">텍스트 정렬</Label>
|
||||
<div className="space-y-1.5">
|
||||
<Label htmlFor="textAlign" className="text-xs font-medium">
|
||||
정렬
|
||||
</Label>
|
||||
<Select
|
||||
value={localStyle.textAlign || "left"}
|
||||
onValueChange={(value) => handleStyleChange("textAlign", value)}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectTrigger className="h-8">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
|
|
|
|||
|
|
@ -107,17 +107,17 @@ export function ComponentsPanel({ className }: ComponentsPanelProps) {
|
|||
e.currentTarget.style.opacity = "1";
|
||||
e.currentTarget.style.transform = "none";
|
||||
}}
|
||||
className="group cursor-grab rounded-lg border border-gray-200/40 bg-white/90 p-4 shadow-sm backdrop-blur-sm transition-all duration-300 hover:-translate-y-1 hover:scale-[1.02] hover:border-purple-300/60 hover:bg-white hover:shadow-lg hover:shadow-purple-500/15 active:translate-y-0 active:scale-[0.98] active:cursor-grabbing"
|
||||
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"
|
||||
>
|
||||
<div className="flex items-start space-x-3">
|
||||
<div className="flex h-12 w-12 items-center justify-center rounded-xl bg-purple-100 text-purple-700 shadow-md transition-all duration-300 group-hover:scale-110 group-hover:shadow-lg">
|
||||
<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">
|
||||
{getCategoryIcon(component.category)}
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<h4 className="mb-1 text-sm leading-tight font-semibold text-gray-900">{component.name}</h4>
|
||||
<p className="mb-2 line-clamp-2 text-xs leading-relaxed text-gray-500">{component.description}</p>
|
||||
<div className="flex items-center space-x-2 text-xs text-gray-400">
|
||||
<span className="rounded-full bg-purple-100 px-2 py-0.5 font-medium text-purple-700">
|
||||
<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">
|
||||
{component.defaultSize.width}×{component.defaultSize.height}
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -128,80 +128,80 @@ export function ComponentsPanel({ className }: ComponentsPanelProps) {
|
|||
|
||||
// 빈 상태 렌더링
|
||||
const renderEmptyState = () => (
|
||||
<div className="flex h-32 items-center justify-center text-center text-gray-500">
|
||||
<div className="p-8">
|
||||
<Package className="mx-auto mb-3 h-12 w-12 text-gray-300" />
|
||||
<p className="text-muted-foreground text-sm font-medium">컴포넌트를 찾을 수 없습니다</p>
|
||||
<p className="mt-1 text-xs text-gray-400">검색어를 조정해보세요</p>
|
||||
<div className="flex h-32 items-center justify-center text-center">
|
||||
<div className="p-6">
|
||||
<Package className="text-muted-foreground/40 mx-auto mb-2 h-10 w-10" />
|
||||
<p className="text-muted-foreground text-xs font-medium">컴포넌트를 찾을 수 없습니다</p>
|
||||
<p className="text-muted-foreground/60 mt-1 text-xs">검색어를 조정해보세요</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={`flex h-full flex-col border-r border-gray-200/60 bg-slate-50 p-6 shadow-sm ${className}`}>
|
||||
<div className={`bg-background flex h-full flex-col p-4 ${className}`}>
|
||||
{/* 헤더 */}
|
||||
<div className="mb-4">
|
||||
<h2 className="mb-1 text-lg font-semibold text-gray-900">컴포넌트</h2>
|
||||
<p className="text-sm text-gray-500">{allComponents.length}개의 사용 가능한 컴포넌트</p>
|
||||
<div className="mb-3">
|
||||
<h2 className="mb-0.5 text-sm font-semibold">컴포넌트</h2>
|
||||
<p className="text-muted-foreground text-xs">{allComponents.length}개 사용 가능</p>
|
||||
</div>
|
||||
|
||||
{/* 검색 */}
|
||||
<div className="mb-4">
|
||||
<div className="mb-3">
|
||||
<div className="relative">
|
||||
<Search className="absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 text-gray-400" />
|
||||
<Search className="text-muted-foreground absolute top-1/2 left-2.5 h-3.5 w-3.5 -translate-y-1/2" />
|
||||
<Input
|
||||
placeholder="컴포넌트 검색..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="border-0 bg-white/80 pl-10 shadow-sm backdrop-blur-sm transition-colors focus:bg-white"
|
||||
className="h-8 pl-8 text-xs"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 카테고리 탭 */}
|
||||
<Tabs defaultValue="input" className="flex flex-1 flex-col">
|
||||
<TabsList className="mb-4 grid w-full grid-cols-4 bg-white/80 p-1">
|
||||
<TabsTrigger value="input" className="flex items-center gap-1 text-xs">
|
||||
<TabsList className="mb-3 grid h-8 w-full grid-cols-4">
|
||||
<TabsTrigger value="input" className="flex items-center gap-1 px-1 text-xs">
|
||||
<Edit3 className="h-3 w-3" />
|
||||
입력
|
||||
<span className="hidden sm:inline">입력</span>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="action" className="flex items-center gap-1 text-xs">
|
||||
<TabsTrigger value="action" className="flex items-center gap-1 px-1 text-xs">
|
||||
<Zap className="h-3 w-3" />
|
||||
액션
|
||||
<span className="hidden sm:inline">액션</span>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="display" className="flex items-center gap-1 text-xs">
|
||||
<TabsTrigger value="display" className="flex items-center gap-1 px-1 text-xs">
|
||||
<BarChart3 className="h-3 w-3" />
|
||||
표시
|
||||
<span className="hidden sm:inline">표시</span>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="layout" className="flex items-center gap-1 text-xs">
|
||||
<TabsTrigger value="layout" className="flex items-center gap-1 px-1 text-xs">
|
||||
<Layers className="h-3 w-3" />
|
||||
레이아웃
|
||||
<span className="hidden sm:inline">레이아웃</span>
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
{/* 입력 컴포넌트 */}
|
||||
<TabsContent value="input" className="mt-0 flex-1 space-y-3 overflow-y-auto">
|
||||
<TabsContent value="input" className="mt-0 flex-1 space-y-2 overflow-y-auto">
|
||||
{getFilteredComponents("input").length > 0
|
||||
? getFilteredComponents("input").map(renderComponentCard)
|
||||
: renderEmptyState()}
|
||||
</TabsContent>
|
||||
|
||||
{/* 액션 컴포넌트 */}
|
||||
<TabsContent value="action" className="mt-0 flex-1 space-y-3 overflow-y-auto">
|
||||
<TabsContent value="action" className="mt-0 flex-1 space-y-2 overflow-y-auto">
|
||||
{getFilteredComponents("action").length > 0
|
||||
? getFilteredComponents("action").map(renderComponentCard)
|
||||
: renderEmptyState()}
|
||||
</TabsContent>
|
||||
|
||||
{/* 표시 컴포넌트 */}
|
||||
<TabsContent value="display" className="mt-0 flex-1 space-y-3 overflow-y-auto">
|
||||
<TabsContent value="display" className="mt-0 flex-1 space-y-2 overflow-y-auto">
|
||||
{getFilteredComponents("display").length > 0
|
||||
? getFilteredComponents("display").map(renderComponentCard)
|
||||
: renderEmptyState()}
|
||||
</TabsContent>
|
||||
|
||||
{/* 레이아웃 컴포넌트 */}
|
||||
<TabsContent value="layout" className="mt-0 flex-1 space-y-3 overflow-y-auto">
|
||||
<TabsContent value="layout" className="mt-0 flex-1 space-y-2 overflow-y-auto">
|
||||
{getFilteredComponents("layout").length > 0
|
||||
? getFilteredComponents("layout").map(renderComponentCard)
|
||||
: renderEmptyState()}
|
||||
|
|
@ -209,14 +209,12 @@ export function ComponentsPanel({ className }: ComponentsPanelProps) {
|
|||
</Tabs>
|
||||
|
||||
{/* 도움말 */}
|
||||
<div className="mt-4 rounded-xl border border-purple-100/60 bg-gradient-to-r from-purple-50 to-pink-50 p-4">
|
||||
<div className="flex items-start space-x-3">
|
||||
<MousePointer className="mt-0.5 h-4 w-4 flex-shrink-0 text-purple-600" />
|
||||
<div className="flex-1">
|
||||
<p className="text-xs leading-relaxed text-gray-700">
|
||||
컴포넌트를 <span className="font-semibold text-purple-700">드래그</span>하여 화면에 추가하세요
|
||||
</p>
|
||||
</div>
|
||||
<div className="border-primary/20 bg-primary/5 mt-3 rounded-lg border p-3">
|
||||
<div className="flex items-start gap-2">
|
||||
<MousePointer className="text-primary mt-0.5 h-3.5 w-3.5 flex-shrink-0" />
|
||||
<p className="text-muted-foreground text-xs leading-relaxed">
|
||||
컴포넌트를 <span className="text-foreground font-semibold">드래그</span>하여 화면에 추가하세요
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -81,30 +81,30 @@ export const DetailSettingsPanel: React.FC<DetailSettingsPanelProps> = ({
|
|||
return (
|
||||
<div className="flex h-full flex-col">
|
||||
{/* 헤더 */}
|
||||
<div className="border-b border-gray-200 p-4">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="border-b p-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Settings className="text-muted-foreground h-4 w-4" />
|
||||
<h3 className="font-medium text-gray-900">레이아웃 설정</h3>
|
||||
<h3 className="text-sm font-semibold">레이아웃 설정</h3>
|
||||
</div>
|
||||
<div className="mt-2 flex items-center space-x-2">
|
||||
<span className="text-muted-foreground text-sm">타입:</span>
|
||||
<span className="rounded bg-purple-100 px-2 py-1 text-xs font-medium text-purple-800">
|
||||
<div className="mt-2 flex items-center gap-2">
|
||||
<span className="text-muted-foreground text-xs">타입:</span>
|
||||
<span className="bg-primary/10 text-primary rounded-md px-2 py-0.5 text-xs font-medium">
|
||||
{layoutComponent.layoutType}
|
||||
</span>
|
||||
</div>
|
||||
<div className="mt-1 text-xs text-gray-500">ID: {layoutComponent.id}</div>
|
||||
<div className="text-muted-foreground mt-1 text-xs">ID: {layoutComponent.id}</div>
|
||||
</div>
|
||||
|
||||
{/* 레이아웃 설정 영역 */}
|
||||
<div className="flex-1 space-y-4 overflow-y-auto p-4">
|
||||
<div className="flex-1 space-y-3 overflow-y-auto p-4">
|
||||
{/* 기본 정보 */}
|
||||
<div>
|
||||
<label className="mb-2 block text-sm font-medium text-gray-700">레이아웃 이름</label>
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-xs font-medium">레이아웃 이름</label>
|
||||
<input
|
||||
type="text"
|
||||
value={layoutComponent.label || ""}
|
||||
onChange={(e) => onUpdateProperty(layoutComponent.id, "label", e.target.value)}
|
||||
className="focus:border-primary w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:ring-2 focus:ring-blue-500"
|
||||
className="border-input bg-background focus-visible:ring-ring h-8 w-full rounded-md border px-3 text-xs focus-visible:ring-1 focus-visible:outline-none"
|
||||
placeholder="레이아웃 이름을 입력하세요"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -55,44 +55,44 @@ export const GridPanel: React.FC<GridPanelProps> = ({
|
|||
return (
|
||||
<div className="flex h-full flex-col">
|
||||
{/* 헤더 */}
|
||||
<div className="border-b border-gray-200 p-4">
|
||||
<div className="border-b p-4">
|
||||
<div className="mb-3 flex items-center justify-between">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Grid3X3 className="h-4 w-4 text-muted-foreground" />
|
||||
<h3 className="font-medium text-gray-900">격자 설정</h3>
|
||||
<div className="flex items-center gap-2">
|
||||
<Grid3X3 className="text-muted-foreground h-4 w-4" />
|
||||
<h3 className="text-sm font-semibold">격자 설정</h3>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="flex items-center gap-1.5">
|
||||
{onForceGridUpdate && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={onForceGridUpdate}
|
||||
className="flex items-center space-x-1"
|
||||
className="h-7 px-2 text-xs"
|
||||
title="현재 해상도에 맞게 모든 컴포넌트를 격자에 재정렬합니다"
|
||||
>
|
||||
<RefreshCw className="h-3 w-3" />
|
||||
<span>재정렬</span>
|
||||
<RefreshCw className="mr-1 h-3 w-3" />
|
||||
재정렬
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Button size="sm" variant="outline" onClick={onResetGrid} className="flex items-center space-x-1">
|
||||
<RotateCcw className="h-3 w-3" />
|
||||
<span>초기화</span>
|
||||
<Button size="sm" variant="outline" onClick={onResetGrid} className="h-7 px-2 text-xs">
|
||||
<RotateCcw className="mr-1 h-3 w-3" />
|
||||
초기화
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 주요 토글들 */}
|
||||
<div className="space-y-3">
|
||||
<div className="space-y-2.5">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="flex items-center gap-2">
|
||||
{gridSettings.showGrid ? (
|
||||
<Eye className="h-4 w-4 text-primary" />
|
||||
<Eye className="text-primary h-3.5 w-3.5" />
|
||||
) : (
|
||||
<EyeOff className="h-4 w-4 text-gray-400" />
|
||||
<EyeOff className="text-muted-foreground h-3.5 w-3.5" />
|
||||
)}
|
||||
<Label htmlFor="showGrid" className="text-sm font-medium">
|
||||
<Label htmlFor="showGrid" className="text-xs font-medium">
|
||||
격자 표시
|
||||
</Label>
|
||||
</div>
|
||||
|
|
@ -104,9 +104,9 @@ export const GridPanel: React.FC<GridPanelProps> = ({
|
|||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Zap className="h-4 w-4 text-green-600" />
|
||||
<Label htmlFor="snapToGrid" className="text-sm font-medium">
|
||||
<div className="flex items-center gap-2">
|
||||
<Zap className="text-primary h-3.5 w-3.5" />
|
||||
<Label htmlFor="snapToGrid" className="text-xs font-medium">
|
||||
격자 스냅
|
||||
</Label>
|
||||
</div>
|
||||
|
|
@ -120,14 +120,14 @@ export const GridPanel: React.FC<GridPanelProps> = ({
|
|||
</div>
|
||||
|
||||
{/* 설정 영역 */}
|
||||
<div className="flex-1 space-y-6 overflow-y-auto p-4">
|
||||
<div className="flex-1 space-y-4 overflow-y-auto p-4">
|
||||
{/* 격자 구조 */}
|
||||
<div className="space-y-4">
|
||||
<h4 className="font-medium text-gray-900">격자 구조</h4>
|
||||
<div className="space-y-3">
|
||||
<h4 className="text-xs font-semibold">격자 구조</h4>
|
||||
|
||||
<div>
|
||||
<Label htmlFor="columns" className="mb-2 block text-sm font-medium">
|
||||
컬럼 수: {gridSettings.columns}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="columns" className="text-xs font-medium">
|
||||
컬럼 수: <span className="text-primary">{gridSettings.columns}</span>
|
||||
</Label>
|
||||
<Slider
|
||||
id="columns"
|
||||
|
|
@ -138,15 +138,15 @@ export const GridPanel: React.FC<GridPanelProps> = ({
|
|||
onValueChange={([value]) => updateSetting("columns", value)}
|
||||
className="w-full"
|
||||
/>
|
||||
<div className="mt-1 flex justify-between text-xs text-gray-500">
|
||||
<div className="text-muted-foreground flex justify-between text-xs">
|
||||
<span>1</span>
|
||||
<span>24</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label htmlFor="gap" className="mb-2 block text-sm font-medium">
|
||||
간격: {gridSettings.gap}px
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="gap" className="text-xs font-medium">
|
||||
간격: <span className="text-primary">{gridSettings.gap}px</span>
|
||||
</Label>
|
||||
<Slider
|
||||
id="gap"
|
||||
|
|
@ -157,15 +157,15 @@ export const GridPanel: React.FC<GridPanelProps> = ({
|
|||
onValueChange={([value]) => updateSetting("gap", value)}
|
||||
className="w-full"
|
||||
/>
|
||||
<div className="mt-1 flex justify-between text-xs text-gray-500">
|
||||
<div className="text-muted-foreground flex justify-between text-xs">
|
||||
<span>0px</span>
|
||||
<span>40px</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label htmlFor="padding" className="mb-2 block text-sm font-medium">
|
||||
여백: {gridSettings.padding}px
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="padding" className="text-xs font-medium">
|
||||
여백: <span className="text-primary">{gridSettings.padding}px</span>
|
||||
</Label>
|
||||
<Slider
|
||||
id="padding"
|
||||
|
|
@ -176,7 +176,7 @@ export const GridPanel: React.FC<GridPanelProps> = ({
|
|||
onValueChange={([value]) => updateSetting("padding", value)}
|
||||
className="w-full"
|
||||
/>
|
||||
<div className="mt-1 flex justify-between text-xs text-gray-500">
|
||||
<div className="text-muted-foreground flex justify-between text-xs">
|
||||
<span>0px</span>
|
||||
<span>60px</span>
|
||||
</div>
|
||||
|
|
@ -248,8 +248,8 @@ export const GridPanel: React.FC<GridPanelProps> = ({
|
|||
opacity: gridSettings.gridOpacity || 0.5,
|
||||
}}
|
||||
>
|
||||
<div className="flex h-16 items-center justify-center rounded border-2 border-dashed border-blue-300 bg-primary/20">
|
||||
<span className="text-xs text-primary">컴포넌트 예시</span>
|
||||
<div className="bg-primary/20 flex h-16 items-center justify-center rounded border-2 border-dashed border-blue-300">
|
||||
<span className="text-primary text-xs">컴포넌트 예시</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -257,7 +257,7 @@ export const GridPanel: React.FC<GridPanelProps> = ({
|
|||
|
||||
{/* 푸터 */}
|
||||
<div className="border-t border-gray-200 bg-gray-50 p-3">
|
||||
<div className="text-xs text-muted-foreground">💡 격자 설정은 실시간으로 캔버스에 반영됩니다 </div>
|
||||
<div className="text-muted-foreground text-xs">💡 격자 설정은 실시간으로 캔버스에 반영됩니다 </div>
|
||||
|
||||
{/* 해상도 및 격자 정보 */}
|
||||
{screenResolution && actualGridInfo && (
|
||||
|
|
|
|||
|
|
@ -481,9 +481,13 @@ const PropertiesPanelComponent: React.FC<PropertiesPanelProps> = ({
|
|||
if (!selectedComponent) {
|
||||
return (
|
||||
<div className="flex h-full flex-col items-center justify-center p-6 text-center">
|
||||
<Settings className="mb-4 h-12 w-12 text-gray-400" />
|
||||
<h3 className="mb-2 text-lg font-medium text-gray-900">컴포넌트를 선택하세요</h3>
|
||||
<p className="text-sm text-gray-500">캔버스에서 컴포넌트를 클릭하면 속성을 편집할 수 있습니다.</p>
|
||||
<Settings className="text-muted-foreground mb-3 h-10 w-10" />
|
||||
<h3 className="mb-2 text-sm font-semibold">컴포넌트를 선택하세요</h3>
|
||||
<p className="text-muted-foreground text-xs">
|
||||
캔버스에서 컴포넌트를 클릭하면
|
||||
<br />
|
||||
속성을 편집할 수 있습니다.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -535,58 +539,58 @@ const PropertiesPanelComponent: React.FC<PropertiesPanelProps> = ({
|
|||
<div className="flex h-full flex-col">
|
||||
{/* 헤더 */}
|
||||
<div className="border-b border-gray-200 p-4">
|
||||
<div className="mb-3 flex items-center justify-between">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="mb-4 flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<Settings className="text-muted-foreground h-4 w-4" />
|
||||
<h3 className="font-medium text-gray-900">속성 편집</h3>
|
||||
<h3 className="text-sm font-semibold">속성 편집</h3>
|
||||
</div>
|
||||
<Badge variant="secondary" className="text-xs">
|
||||
<Badge variant="secondary" className="text-xs font-medium">
|
||||
{selectedComponent.type}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
{/* 액션 버튼들 */}
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Button size="sm" variant="outline" onClick={onCopyComponent} className="flex items-center space-x-1">
|
||||
<Copy className="h-3 w-3" />
|
||||
<span>복사</span>
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
<Button size="sm" variant="outline" onClick={onCopyComponent} className="h-8 px-2.5 text-xs">
|
||||
<Copy className="mr-1 h-3 w-3" />
|
||||
복사
|
||||
</Button>
|
||||
|
||||
{canGroup && (
|
||||
<Button size="sm" variant="outline" onClick={onGroupComponents} className="flex items-center space-x-1">
|
||||
<Group className="h-3 w-3" />
|
||||
<span>그룹</span>
|
||||
<Button size="sm" variant="outline" onClick={onGroupComponents} className="h-8 px-2.5 text-xs">
|
||||
<Group className="mr-1 h-3 w-3" />
|
||||
그룹
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{canUngroup && (
|
||||
<Button size="sm" variant="outline" onClick={onUngroupComponents} className="flex items-center space-x-1">
|
||||
<Ungroup className="h-3 w-3" />
|
||||
<span>해제</span>
|
||||
<Button size="sm" variant="outline" onClick={onUngroupComponents} className="h-8 px-2.5 text-xs">
|
||||
<Ungroup className="mr-1 h-3 w-3" />
|
||||
해제
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Button size="sm" variant="destructive" onClick={onDeleteComponent} className="flex items-center space-x-1">
|
||||
<Trash2 className="h-3 w-3" />
|
||||
<span>삭제</span>
|
||||
<Button size="sm" variant="destructive" onClick={onDeleteComponent} className="h-8 px-2.5 text-xs">
|
||||
<Trash2 className="mr-1 h-3 w-3" />
|
||||
삭제
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 속성 편집 영역 */}
|
||||
<div className="flex-1 space-y-6 overflow-y-auto p-4">
|
||||
<div className="flex-1 space-y-4 overflow-y-auto p-4">
|
||||
{/* 기본 정보 */}
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Type className="text-muted-foreground h-4 w-4" />
|
||||
<h4 className="font-medium text-gray-900">기본 정보</h4>
|
||||
<h4 className="text-sm font-semibold">기본 정보</h4>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
{selectedComponent.type === "widget" && (
|
||||
<>
|
||||
<div>
|
||||
<Label htmlFor="columnName" className="text-sm font-medium">
|
||||
<div className="space-y-1.5">
|
||||
<Label htmlFor="columnName" className="text-xs font-medium">
|
||||
컬럼명 (읽기 전용)
|
||||
</Label>
|
||||
<Input
|
||||
|
|
@ -594,21 +598,20 @@ const PropertiesPanelComponent: React.FC<PropertiesPanelProps> = ({
|
|||
value={selectedComponent.columnName || ""}
|
||||
readOnly
|
||||
placeholder="데이터베이스 컬럼명"
|
||||
className="text-muted-foreground mt-1 bg-gray-50"
|
||||
className="bg-muted/50 text-muted-foreground h-8"
|
||||
title="컬럼명은 변경할 수 없습니다"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label htmlFor="inputType" className="text-sm font-medium">
|
||||
<div className="space-y-1.5">
|
||||
<Label htmlFor="inputType" className="text-xs font-medium">
|
||||
입력 타입
|
||||
</Label>
|
||||
<select
|
||||
className="focus:border-primary mt-1 w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:ring-1 focus:ring-blue-500 focus:outline-none"
|
||||
className="border-input bg-background focus-visible:ring-ring flex h-8 w-full rounded-md border px-3 py-1 text-xs shadow-sm transition-colors focus-visible:ring-1 focus-visible:outline-none"
|
||||
value={getBaseInputType(localInputs.widgetType)}
|
||||
onChange={(e) => {
|
||||
const selectedInputType = e.target.value as BaseInputType;
|
||||
// 입력 타입에 맞는 기본 세부 타입 설정
|
||||
const defaultWebType = getDefaultDetailType(selectedInputType);
|
||||
setLocalInputs((prev) => ({ ...prev, widgetType: defaultWebType }));
|
||||
onUpdateProperty("widgetType", defaultWebType);
|
||||
|
|
@ -620,11 +623,11 @@ const PropertiesPanelComponent: React.FC<PropertiesPanelProps> = ({
|
|||
</option>
|
||||
))}
|
||||
</select>
|
||||
<p className="mt-1 text-xs text-gray-500">세부 타입은 "상세 설정" 패널에서 선택하세요</p>
|
||||
<p className="text-muted-foreground text-xs">세부 타입은 "상세 설정" 패널에서 선택하세요</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label htmlFor="placeholder" className="text-sm font-medium">
|
||||
<div className="space-y-1.5">
|
||||
<Label htmlFor="placeholder" className="text-xs font-medium">
|
||||
플레이스홀더
|
||||
</Label>
|
||||
<Input
|
||||
|
|
@ -632,12 +635,11 @@ const PropertiesPanelComponent: React.FC<PropertiesPanelProps> = ({
|
|||
value={localInputs.placeholder}
|
||||
onChange={(e) => {
|
||||
const newValue = e.target.value;
|
||||
// console.log("🔄 placeholder 변경:", newValue);
|
||||
setLocalInputs((prev) => ({ ...prev, placeholder: newValue }));
|
||||
onUpdateProperty("placeholder", newValue);
|
||||
}}
|
||||
placeholder="입력 힌트 텍스트"
|
||||
className="mt-1"
|
||||
className="h-8"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ const getWidgetIcon = (widgetType: WebType) => {
|
|||
case "text":
|
||||
case "email":
|
||||
case "tel":
|
||||
return <Type className="h-3 w-3 text-primary" />;
|
||||
return <Type className="text-primary h-3 w-3" />;
|
||||
case "number":
|
||||
case "decimal":
|
||||
return <Hash className="h-3 w-3 text-green-600" />;
|
||||
|
|
@ -49,9 +49,9 @@ const getWidgetIcon = (widgetType: WebType) => {
|
|||
return <AlignLeft className="h-3 w-3 text-indigo-600" />;
|
||||
case "boolean":
|
||||
case "checkbox":
|
||||
return <CheckSquare className="h-3 w-3 text-primary" />;
|
||||
return <CheckSquare className="text-primary h-3 w-3" />;
|
||||
case "code":
|
||||
return <Code className="h-3 w-3 text-muted-foreground" />;
|
||||
return <Code className="text-muted-foreground h-3 w-3" />;
|
||||
case "entity":
|
||||
return <Building className="h-3 w-3 text-cyan-600" />;
|
||||
case "file":
|
||||
|
|
@ -89,55 +89,55 @@ export const TablesPanel: React.FC<TablesPanelProps> = ({
|
|||
return (
|
||||
<div className="flex h-full flex-col">
|
||||
{/* 헤더 */}
|
||||
<div className="border-b border-gray-200 p-4">
|
||||
<div className="border-b p-4">
|
||||
{selectedTableName && (
|
||||
<div className="mb-3 rounded-md bg-accent p-3">
|
||||
<div className="text-sm font-medium text-blue-900">선택된 테이블</div>
|
||||
<div className="mt-1 flex items-center space-x-2">
|
||||
<Database className="h-3 w-3 text-primary" />
|
||||
<span className="font-mono text-xs text-blue-800">{selectedTableName}</span>
|
||||
<div className="border-primary/20 bg-primary/5 mb-3 rounded-lg border p-3">
|
||||
<div className="text-xs font-semibold">선택된 테이블</div>
|
||||
<div className="mt-1.5 flex items-center gap-2">
|
||||
<Database className="text-primary h-3 w-3" />
|
||||
<span className="font-mono text-xs font-medium">{selectedTableName}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 검색 */}
|
||||
<div className="relative">
|
||||
<Search className="absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 transform text-gray-400" />
|
||||
<Search className="text-muted-foreground absolute top-1/2 left-2.5 h-3.5 w-3.5 -translate-y-1/2" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="테이블명, 컬럼명으로 검색..."
|
||||
placeholder="테이블명, 컬럼명 검색..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => onSearchChange(e.target.value)}
|
||||
className="w-full rounded-md border border-gray-300 py-2 pr-3 pl-10 focus:border-transparent focus:ring-2 focus:ring-blue-500"
|
||||
className="border-input bg-background focus-visible:ring-ring h-8 w-full rounded-md border px-3 pl-8 text-xs focus-visible:ring-1 focus-visible:outline-none"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mt-2 text-xs text-muted-foreground">총 {filteredTables.length}개 테이블</div>
|
||||
<div className="text-muted-foreground mt-2 text-xs">총 {filteredTables.length}개</div>
|
||||
</div>
|
||||
|
||||
{/* 테이블 목록 */}
|
||||
<div className="scrollbar-thin scrollbar-thumb-gray-300 scrollbar-track-gray-100 flex-1 overflow-y-auto">
|
||||
<div className="space-y-1 p-2">
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
<div className="space-y-1.5 p-3">
|
||||
{filteredTables.map((table) => {
|
||||
const isExpanded = expandedTables.has(table.tableName);
|
||||
|
||||
return (
|
||||
<div key={table.tableName} className="rounded-md border border-gray-200">
|
||||
<div key={table.tableName} className="bg-card rounded-lg border">
|
||||
{/* 테이블 헤더 */}
|
||||
<div
|
||||
className="flex cursor-pointer items-center justify-between p-3 hover:bg-gray-50"
|
||||
className="hover:bg-accent/50 flex cursor-pointer items-center justify-between p-2.5 transition-colors"
|
||||
onClick={() => toggleTable(table.tableName)}
|
||||
>
|
||||
<div className="flex flex-1 items-center space-x-2">
|
||||
<div className="flex flex-1 items-center gap-2">
|
||||
{isExpanded ? (
|
||||
<ChevronDown className="h-4 w-4 text-gray-500" />
|
||||
<ChevronDown className="text-muted-foreground h-3.5 w-3.5" />
|
||||
) : (
|
||||
<ChevronRight className="h-4 w-4 text-gray-500" />
|
||||
<ChevronRight className="text-muted-foreground h-3.5 w-3.5" />
|
||||
)}
|
||||
<Database className="h-4 w-4 text-primary" />
|
||||
<div className="flex-1">
|
||||
<div className="text-sm font-medium">{table.tableLabel || table.tableName}</div>
|
||||
<div className="text-xs text-gray-500">{table.columns.length}개 컬럼</div>
|
||||
<Database className="text-primary h-3.5 w-3.5" />
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="truncate text-xs font-semibold">{table.tableLabel || table.tableName}</div>
|
||||
<div className="text-muted-foreground text-xs">{table.columns.length}개</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -146,7 +146,7 @@ export const TablesPanel: React.FC<TablesPanelProps> = ({
|
|||
variant="ghost"
|
||||
draggable
|
||||
onDragStart={(e) => onDragStart(e, table)}
|
||||
className="ml-2 text-xs"
|
||||
className="h-6 px-2 text-xs"
|
||||
>
|
||||
드래그
|
||||
</Button>
|
||||
|
|
@ -154,43 +154,33 @@ export const TablesPanel: React.FC<TablesPanelProps> = ({
|
|||
|
||||
{/* 컬럼 목록 */}
|
||||
{isExpanded && (
|
||||
<div className="border-t border-gray-200 bg-gray-50">
|
||||
<div
|
||||
className={`${
|
||||
table.columns.length > 8
|
||||
? "scrollbar-thin scrollbar-thumb-gray-300 scrollbar-track-gray-100 max-h-64 overflow-y-auto"
|
||||
: ""
|
||||
}`}
|
||||
style={{
|
||||
scrollbarWidth: "thin",
|
||||
scrollbarColor: "#cbd5e1 #f1f5f9",
|
||||
}}
|
||||
>
|
||||
<div className="bg-muted/30 border-t">
|
||||
<div className={`${table.columns.length > 8 ? "max-h-64 overflow-y-auto" : ""}`}>
|
||||
{table.columns.map((column, index) => (
|
||||
<div
|
||||
key={column.columnName}
|
||||
className={`flex cursor-pointer items-center justify-between p-2 hover:bg-white ${
|
||||
index < table.columns.length - 1 ? "border-b border-gray-100" : ""
|
||||
className={`hover:bg-accent/50 flex cursor-grab items-center justify-between p-2 transition-colors ${
|
||||
index < table.columns.length - 1 ? "border-border/50 border-b" : ""
|
||||
}`}
|
||||
draggable
|
||||
onDragStart={(e) => onDragStart(e, table, column)}
|
||||
>
|
||||
<div className="flex flex-1 items-center space-x-2">
|
||||
<div className="flex min-w-0 flex-1 items-center gap-2">
|
||||
{getWidgetIcon(column.widgetType)}
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="truncate text-sm font-medium">
|
||||
<div className="truncate text-xs font-semibold">
|
||||
{column.columnLabel || column.columnName}
|
||||
</div>
|
||||
<div className="truncate text-xs text-gray-500">{column.dataType}</div>
|
||||
<div className="text-muted-foreground truncate text-xs">{column.dataType}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-shrink-0 items-center space-x-1">
|
||||
<Badge variant="secondary" className="text-xs">
|
||||
<div className="flex flex-shrink-0 items-center gap-1">
|
||||
<Badge variant="secondary" className="h-4 px-1.5 text-xs">
|
||||
{column.widgetType}
|
||||
</Badge>
|
||||
{column.required && (
|
||||
<Badge variant="destructive" className="text-xs">
|
||||
<Badge variant="destructive" className="h-4 px-1.5 text-xs">
|
||||
필수
|
||||
</Badge>
|
||||
)}
|
||||
|
|
@ -200,8 +190,8 @@ export const TablesPanel: React.FC<TablesPanelProps> = ({
|
|||
|
||||
{/* 컬럼 수가 많을 때 안내 메시지 */}
|
||||
{table.columns.length > 8 && (
|
||||
<div className="sticky bottom-0 bg-gray-100 p-2 text-center">
|
||||
<div className="text-xs text-muted-foreground">
|
||||
<div className="bg-muted sticky bottom-0 p-2 text-center">
|
||||
<div className="text-muted-foreground text-xs">
|
||||
📜 총 {table.columns.length}개 컬럼 (스크롤하여 더 보기)
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -217,7 +207,7 @@ export const TablesPanel: React.FC<TablesPanelProps> = ({
|
|||
|
||||
{/* 푸터 */}
|
||||
<div className="border-t border-gray-200 bg-gray-50 p-3">
|
||||
<div className="text-xs text-muted-foreground">💡 테이블이나 컬럼을 캔버스로 드래그하세요</div>
|
||||
<div className="text-muted-foreground text-xs">💡 테이블이나 컬럼을 캔버스로 드래그하세요</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -18,11 +18,11 @@ export default function InputWidget({ widget, value, onChange, className }: Inpu
|
|||
};
|
||||
|
||||
return (
|
||||
<div className={cn("space-y-2", className)}>
|
||||
<div className={cn("space-y-1.5", className)}>
|
||||
{widget.label && (
|
||||
<Label htmlFor={widget.id} className="text-sm font-medium">
|
||||
<Label htmlFor={widget.id} className="text-xs font-medium">
|
||||
{widget.label}
|
||||
{widget.required && <span className="ml-1 text-red-500">*</span>}
|
||||
{widget.required && <span className="text-destructive ml-0.5">*</span>}
|
||||
</Label>
|
||||
)}
|
||||
<Input
|
||||
|
|
@ -33,7 +33,7 @@ export default function InputWidget({ widget, value, onChange, className }: Inpu
|
|||
onChange={handleChange}
|
||||
required={widget.required}
|
||||
readOnly={widget.readonly}
|
||||
className={cn("w-full", widget.readonly && "cursor-not-allowed bg-gray-50")}
|
||||
className={cn("h-9 w-full text-sm", widget.readonly && "bg-muted/50 cursor-not-allowed")}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -45,20 +45,20 @@ export default function SelectWidget({ widget, value, onChange, options = [], cl
|
|||
const displayOptions = options.length > 0 ? options : getDefaultOptions();
|
||||
|
||||
return (
|
||||
<div className={cn("space-y-2", className)}>
|
||||
<div className={cn("space-y-1.5", className)}>
|
||||
{widget.label && (
|
||||
<Label htmlFor={widget.id} className="text-sm font-medium">
|
||||
<Label htmlFor={widget.id} className="text-xs font-medium">
|
||||
{widget.label}
|
||||
{widget.required && <span className="ml-1 text-red-500">*</span>}
|
||||
{widget.required && <span className="text-destructive ml-0.5">*</span>}
|
||||
</Label>
|
||||
)}
|
||||
<Select value={value} onValueChange={handleChange} disabled={widget.readonly}>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectTrigger className="h-9 w-full text-sm">
|
||||
<SelectValue placeholder={widget.placeholder || "선택해주세요"} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{displayOptions.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
<SelectItem key={option.value} value={option.value} className="text-sm">
|
||||
{option.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -18,11 +18,11 @@ export default function TextareaWidget({ widget, value, onChange, className }: T
|
|||
};
|
||||
|
||||
return (
|
||||
<div className={cn("space-y-2", className)}>
|
||||
<div className={cn("space-y-1.5", className)}>
|
||||
{widget.label && (
|
||||
<Label htmlFor={widget.id} className="text-sm font-medium">
|
||||
<Label htmlFor={widget.id} className="text-xs font-medium">
|
||||
{widget.label}
|
||||
{widget.required && <span className="ml-1 text-red-500">*</span>}
|
||||
{widget.required && <span className="text-destructive ml-0.5">*</span>}
|
||||
</Label>
|
||||
)}
|
||||
<Textarea
|
||||
|
|
@ -32,7 +32,7 @@ export default function TextareaWidget({ widget, value, onChange, className }: T
|
|||
onChange={handleChange}
|
||||
required={widget.required}
|
||||
readOnly={widget.readonly}
|
||||
className={cn("min-h-[100px] w-full", widget.readonly && "cursor-not-allowed bg-gray-50")}
|
||||
className={cn("min-h-[80px] w-full text-sm", widget.readonly && "bg-muted/50 cursor-not-allowed")}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -107,11 +107,13 @@ export const DynamicWebTypeRenderer: React.FC<DynamicComponentProps> = ({
|
|||
if (!webTypeDefinition.isActive) {
|
||||
console.warn(`웹타입 "${webType}"이 비활성화되어 있습니다.`);
|
||||
return (
|
||||
<div className="rounded-md border border-dashed border-yellow-300 bg-yellow-50 p-4">
|
||||
<div className="flex items-center gap-2 text-yellow-600">
|
||||
<span className="text-sm font-medium">⚠️ 비활성화된 웹타입</span>
|
||||
<div className="rounded-md border border-dashed border-yellow-500/30 bg-yellow-50 p-3 dark:bg-yellow-950/20">
|
||||
<div className="flex items-center gap-2 text-yellow-700 dark:text-yellow-400">
|
||||
<span className="text-xs font-medium">⚠️ 비활성화된 웹타입</span>
|
||||
</div>
|
||||
<p className="mt-1 text-xs text-yellow-500">웹타입 "{webType}"이 비활성화되어 있습니다.</p>
|
||||
<p className="mt-1 text-xs text-yellow-600 dark:text-yellow-500">
|
||||
웹타입 "{webType}"이 비활성화되어 있습니다.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -163,11 +165,11 @@ export const DynamicWebTypeRenderer: React.FC<DynamicComponentProps> = ({
|
|||
} catch (error) {
|
||||
console.error(`웹타입 "${webType}" 폴백 컴포넌트 렌더링 실패:`, error);
|
||||
return (
|
||||
<div className="rounded-md border border-dashed border-red-300 bg-red-50 p-4">
|
||||
<div className="flex items-center gap-2 text-red-600">
|
||||
<span className="text-sm font-medium">⚠️ 알 수 없는 웹타입</span>
|
||||
<div className="border-destructive/30 bg-destructive/5 rounded-md border border-dashed p-3">
|
||||
<div className="text-destructive flex items-center gap-2">
|
||||
<span className="text-xs font-medium">⚠️ 알 수 없는 웹타입</span>
|
||||
</div>
|
||||
<p className="mt-1 text-xs text-red-500">웹타입 "{webType}"을 렌더링할 수 없습니다.</p>
|
||||
<p className="text-destructive/80 mt-1 text-xs">웹타입 "{webType}"을 렌더링할 수 없습니다.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -188,8 +190,8 @@ export const WebTypePreviewRenderer: React.FC<{
|
|||
|
||||
if (!webTypeDefinition) {
|
||||
return (
|
||||
<div className="rounded border border-dashed border-gray-300 bg-gray-50 p-2 text-center">
|
||||
<span className="text-xs text-gray-500">웹타입 없음</span>
|
||||
<div className="border-border bg-muted/30 rounded-md border border-dashed p-2 text-center">
|
||||
<span className="text-muted-foreground text-xs">웹타입 없음</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -479,18 +479,19 @@ export const ButtonPrimaryComponent: React.FC<ButtonPrimaryComponentProps> = ({
|
|||
<button
|
||||
type={componentConfig.actionType || "button"}
|
||||
disabled={componentConfig.disabled || false}
|
||||
className="transition-all duration-200"
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
minHeight: "100%",
|
||||
maxHeight: "100%",
|
||||
border: "none",
|
||||
borderRadius: "8px",
|
||||
borderRadius: "0.5rem",
|
||||
background: componentConfig.disabled
|
||||
? "linear-gradient(135deg, #e5e7eb 0%, #d1d5db 100%)"
|
||||
: `linear-gradient(135deg, ${buttonColor} 0%, ${buttonDarkColor} 100%)`,
|
||||
color: componentConfig.disabled ? "#9ca3af" : "white",
|
||||
fontSize: "14px",
|
||||
fontSize: "0.875rem",
|
||||
fontWeight: "600",
|
||||
cursor: componentConfig.disabled ? "not-allowed" : "pointer",
|
||||
outline: "none",
|
||||
|
|
@ -498,11 +499,11 @@ export const ButtonPrimaryComponent: React.FC<ButtonPrimaryComponentProps> = ({
|
|||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
padding: "0 16px",
|
||||
padding: "0 1rem",
|
||||
margin: "0",
|
||||
lineHeight: "1",
|
||||
minHeight: "36px",
|
||||
boxShadow: componentConfig.disabled ? "0 1px 2px 0 rgba(0, 0, 0, 0.05)" : `0 2px 4px 0 ${buttonColor}33`, // 33은 20% 투명도
|
||||
lineHeight: "1.25",
|
||||
minHeight: "2.25rem",
|
||||
boxShadow: componentConfig.disabled ? "0 1px 2px 0 rgba(0, 0, 0, 0.05)" : `0 1px 3px 0 ${buttonColor}40`,
|
||||
// isInteractive 모드에서는 사용자 스타일 우선 적용
|
||||
...(isInteractive && component.style ? component.style : {}),
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -135,9 +135,9 @@ export const CheckboxBasicComponent: React.FC<CheckboxBasicComponentProps> = ({
|
|||
checked={checkedValues.includes(option.value)}
|
||||
onChange={(e) => handleGroupChange(option.value, e.target.checked)}
|
||||
disabled={componentConfig.disabled || isDesignMode}
|
||||
className="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-0 focus:outline-none"
|
||||
className="border-input text-primary h-4 w-4 rounded focus:ring-0 focus:outline-none"
|
||||
/>
|
||||
<span className="text-sm text-gray-900">{option.label}</span>
|
||||
<span className="text-sm">{option.label}</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
|
|
@ -153,9 +153,9 @@ export const CheckboxBasicComponent: React.FC<CheckboxBasicComponentProps> = ({
|
|||
disabled={componentConfig.disabled || isDesignMode}
|
||||
required={componentConfig.required || false}
|
||||
onChange={(e) => handleCheckboxChange(e.target.checked)}
|
||||
className="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
||||
className="border-input text-primary focus:ring-ring h-4 w-4 rounded"
|
||||
/>
|
||||
<span className="text-sm text-gray-900">{componentConfig.checkboxLabel || component.text || "체크박스"}</span>
|
||||
<span className="text-sm">{componentConfig.checkboxLabel || component.text || "체크박스"}</span>
|
||||
</label>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -5,29 +5,29 @@
|
|||
export const INPUT_CLASSES = {
|
||||
// 기본 input 스타일
|
||||
base: `
|
||||
w-full h-full px-3 py-2 text-sm
|
||||
border border-gray-300 rounded-md
|
||||
bg-white text-gray-900
|
||||
outline-none transition-all duration-200
|
||||
focus:border-orange-500 focus:ring-2 focus:ring-orange-100
|
||||
disabled:bg-gray-100 disabled:text-gray-400 disabled:cursor-not-allowed
|
||||
placeholder:text-gray-400
|
||||
w-full h-9 px-3 py-2 text-sm
|
||||
border border-input rounded-md
|
||||
bg-background text-foreground
|
||||
transition-colors
|
||||
focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring
|
||||
disabled:cursor-not-allowed disabled:opacity-50
|
||||
placeholder:text-muted-foreground
|
||||
max-w-full box-border
|
||||
`,
|
||||
|
||||
// 선택된 상태
|
||||
selected: `
|
||||
border-blue-500 ring-2 ring-blue-100
|
||||
ring-2 ring-primary/20
|
||||
`,
|
||||
|
||||
// 라벨 스타일
|
||||
label: `
|
||||
absolute -top-6 left-0 text-sm font-medium text-slate-600
|
||||
absolute -top-6 left-0 text-xs font-medium text-foreground
|
||||
`,
|
||||
|
||||
// 필수 표시
|
||||
required: `
|
||||
text-red-500
|
||||
ml-0.5 text-destructive
|
||||
`,
|
||||
|
||||
// 컨테이너
|
||||
|
|
@ -37,24 +37,24 @@ export const INPUT_CLASSES = {
|
|||
|
||||
// textarea
|
||||
textarea: `
|
||||
w-full h-full px-3 py-2 text-sm
|
||||
border border-gray-300 rounded-md
|
||||
bg-white text-gray-900
|
||||
outline-none transition-all duration-200
|
||||
focus:border-orange-500 focus:ring-2 focus:ring-orange-100
|
||||
disabled:bg-gray-100 disabled:text-gray-400 disabled:cursor-not-allowed
|
||||
min-h-[80px] w-full px-3 py-2 text-sm
|
||||
border border-input rounded-md
|
||||
bg-background text-foreground
|
||||
transition-colors
|
||||
focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring
|
||||
disabled:cursor-not-allowed disabled:opacity-50
|
||||
resize-none
|
||||
max-w-full box-border
|
||||
`,
|
||||
|
||||
// select
|
||||
select: `
|
||||
w-full h-full px-3 py-2 text-sm
|
||||
border border-gray-300 rounded-md
|
||||
bg-white text-gray-900
|
||||
outline-none transition-all duration-200
|
||||
focus:border-orange-500 focus:ring-2 focus:ring-orange-100
|
||||
disabled:bg-gray-100 disabled:text-gray-400 disabled:cursor-not-allowed
|
||||
h-9 w-full px-3 py-2 text-sm
|
||||
border border-input rounded-md
|
||||
bg-background text-foreground
|
||||
transition-colors
|
||||
focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring
|
||||
disabled:cursor-not-allowed disabled:opacity-50
|
||||
cursor-pointer
|
||||
max-w-full box-border
|
||||
`,
|
||||
|
|
@ -66,37 +66,37 @@ export const INPUT_CLASSES = {
|
|||
|
||||
// 구분자 (@ , ~ 등)
|
||||
separator: `
|
||||
text-base font-medium text-gray-500
|
||||
text-sm font-medium text-muted-foreground
|
||||
`,
|
||||
|
||||
// Currency 통화 기호
|
||||
currencySymbol: `
|
||||
text-base font-semibold text-green-600 pl-2
|
||||
text-sm font-semibold text-green-600 pl-2
|
||||
`,
|
||||
|
||||
// Currency input
|
||||
currencyInput: `
|
||||
flex-1 h-full px-3 py-2 text-base font-semibold text-right
|
||||
border border-gray-300 rounded-md
|
||||
bg-white text-green-600
|
||||
outline-none transition-all duration-200
|
||||
focus:border-orange-500 focus:ring-2 focus:ring-orange-100
|
||||
disabled:bg-gray-100 disabled:text-gray-400
|
||||
flex-1 h-9 px-3 py-2 text-sm font-semibold text-right
|
||||
border border-input rounded-md
|
||||
bg-background text-green-600
|
||||
transition-colors
|
||||
focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring
|
||||
disabled:cursor-not-allowed disabled:opacity-50
|
||||
`,
|
||||
|
||||
// Percentage 퍼센트 기호
|
||||
percentageSymbol: `
|
||||
text-base font-semibold text-blue-600 pr-2
|
||||
text-sm font-semibold text-blue-600 pr-2
|
||||
`,
|
||||
|
||||
// Percentage input
|
||||
percentageInput: `
|
||||
flex-1 h-full px-3 py-2 text-base font-semibold text-right
|
||||
border border-gray-300 rounded-md
|
||||
bg-white text-blue-600
|
||||
outline-none transition-all duration-200
|
||||
focus:border-orange-500 focus:ring-2 focus:ring-orange-100
|
||||
disabled:bg-gray-100 disabled:text-gray-400
|
||||
flex-1 h-9 px-3 py-2 text-sm font-semibold text-right
|
||||
border border-input rounded-md
|
||||
bg-background text-blue-600
|
||||
transition-colors
|
||||
focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring
|
||||
disabled:cursor-not-allowed disabled:opacity-50
|
||||
`,
|
||||
};
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -138,9 +138,9 @@ export const RadioBasicComponent: React.FC<RadioBasicComponentProps> = ({
|
|||
onChange={() => handleRadioChange(option.value)}
|
||||
disabled={componentConfig.disabled || isDesignMode}
|
||||
required={componentConfig.required || false}
|
||||
className="h-4 w-4 border-gray-300 text-blue-600 focus:ring-0"
|
||||
className="border-input text-primary h-4 w-4 focus:ring-0"
|
||||
/>
|
||||
<span className="text-sm text-gray-900">{option.label}</span>
|
||||
<span className="text-sm">{option.label}</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -323,9 +323,9 @@ const SelectBasicComponent: React.FC<SelectBasicComponentProps> = ({
|
|||
checked={selectedValue === option.value}
|
||||
onChange={() => handleOptionSelect(option.value, option.label)}
|
||||
disabled={isDesignMode}
|
||||
className="h-4 w-4 border-gray-300 text-blue-600 focus:ring-blue-500"
|
||||
className="border-input text-primary focus:ring-ring h-4 w-4"
|
||||
/>
|
||||
<span className="text-sm text-gray-900">{option.label}</span>
|
||||
<span className="text-sm">{option.label}</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -277,10 +277,10 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
|||
{/* 좌측 패널 */}
|
||||
<div
|
||||
style={{ width: `${leftWidth}%`, minWidth: isPreview ? "0" : `${minLeftWidth}px` }}
|
||||
className="flex flex-shrink-0 flex-col border-r border-gray-200"
|
||||
className="border-border flex flex-shrink-0 flex-col border-r"
|
||||
>
|
||||
<Card className="flex h-full flex-col border-0 shadow-none">
|
||||
<CardHeader className="border-b border-gray-100 pb-3">
|
||||
<CardHeader className="border-b pb-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="text-base font-semibold">
|
||||
{componentConfig.leftPanel?.title || "좌측 패널"}
|
||||
|
|
@ -312,37 +312,37 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
|||
<>
|
||||
<div
|
||||
onClick={() => handleLeftItemSelect({ id: 1, name: "항목 1" })}
|
||||
className={`cursor-pointer rounded-md p-3 transition-colors hover:bg-gray-50 ${
|
||||
selectedLeftItem?.id === 1 ? "bg-blue-50 text-blue-700" : "text-gray-700"
|
||||
className={`hover:bg-accent cursor-pointer rounded-md p-3 transition-colors ${
|
||||
selectedLeftItem?.id === 1 ? "bg-primary/10 text-primary" : ""
|
||||
}`}
|
||||
>
|
||||
<div className="font-medium">항목 1</div>
|
||||
<div className="text-xs text-gray-500">설명 텍스트</div>
|
||||
<div className="text-muted-foreground text-xs">설명 텍스트</div>
|
||||
</div>
|
||||
<div
|
||||
onClick={() => handleLeftItemSelect({ id: 2, name: "항목 2" })}
|
||||
className={`cursor-pointer rounded-md p-3 transition-colors hover:bg-gray-50 ${
|
||||
selectedLeftItem?.id === 2 ? "bg-blue-50 text-blue-700" : "text-gray-700"
|
||||
className={`hover:bg-accent cursor-pointer rounded-md p-3 transition-colors ${
|
||||
selectedLeftItem?.id === 2 ? "bg-primary/10 text-primary" : ""
|
||||
}`}
|
||||
>
|
||||
<div className="font-medium">항목 2</div>
|
||||
<div className="text-xs text-gray-500">설명 텍스트</div>
|
||||
<div className="text-muted-foreground text-xs">설명 텍스트</div>
|
||||
</div>
|
||||
<div
|
||||
onClick={() => handleLeftItemSelect({ id: 3, name: "항목 3" })}
|
||||
className={`cursor-pointer rounded-md p-3 transition-colors hover:bg-gray-50 ${
|
||||
selectedLeftItem?.id === 3 ? "bg-blue-50 text-blue-700" : "text-gray-700"
|
||||
className={`hover:bg-accent cursor-pointer rounded-md p-3 transition-colors ${
|
||||
selectedLeftItem?.id === 3 ? "bg-primary/10 text-primary" : ""
|
||||
}`}
|
||||
>
|
||||
<div className="font-medium">항목 3</div>
|
||||
<div className="text-xs text-gray-500">설명 텍스트</div>
|
||||
<div className="text-muted-foreground text-xs">설명 텍스트</div>
|
||||
</div>
|
||||
</>
|
||||
) : isLoadingLeft ? (
|
||||
// 로딩 중
|
||||
<div className="flex items-center justify-center py-8">
|
||||
<Loader2 className="h-6 w-6 animate-spin text-blue-500" />
|
||||
<span className="ml-2 text-sm text-gray-500">데이터를 불러오는 중...</span>
|
||||
<Loader2 className="text-primary h-6 w-6 animate-spin" />
|
||||
<span className="text-muted-foreground ml-2 text-sm">데이터를 불러오는 중...</span>
|
||||
</div>
|
||||
) : (
|
||||
(() => {
|
||||
|
|
@ -417,7 +417,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
|||
className="flex flex-shrink-0 flex-col"
|
||||
>
|
||||
<Card className="flex h-full flex-col border-0 shadow-none">
|
||||
<CardHeader className="border-b border-gray-100 pb-3">
|
||||
<CardHeader className="border-b pb-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="text-base font-semibold">
|
||||
{componentConfig.rightPanel?.title || "우측 패널"}
|
||||
|
|
@ -488,7 +488,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
|||
return (
|
||||
<div
|
||||
key={itemId}
|
||||
className="overflow-hidden rounded-lg border border-gray-200 bg-white shadow-sm transition-all hover:shadow-md"
|
||||
className="bg-card overflow-hidden rounded-lg border shadow-sm transition-all hover:shadow-md"
|
||||
>
|
||||
{/* 요약 정보 (클릭 가능) */}
|
||||
<div
|
||||
|
|
@ -518,9 +518,9 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
|||
|
||||
{/* 상세 정보 (확장 시 표시) */}
|
||||
{isExpanded && (
|
||||
<div className="border-t border-gray-200 bg-gray-50 px-3 py-2">
|
||||
<div className="mb-2 text-xs font-semibold text-gray-700">전체 상세 정보</div>
|
||||
<div className="overflow-auto rounded-md border border-gray-200 bg-white">
|
||||
<div className="bg-muted/50 border-t px-3 py-2">
|
||||
<div className="mb-2 text-xs font-semibold">전체 상세 정보</div>
|
||||
<div className="bg-card overflow-auto rounded-md border">
|
||||
<table className="w-full text-sm">
|
||||
<tbody className="divide-y divide-gray-200">
|
||||
{allValues.map(([key, value]) => (
|
||||
|
|
@ -561,9 +561,11 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
|||
if (value === null || value === undefined || value === "") return null;
|
||||
|
||||
return (
|
||||
<div key={key} className="rounded-lg border border-gray-200 bg-white p-4 shadow-sm">
|
||||
<div className="mb-1 text-xs font-semibold tracking-wide text-gray-500 uppercase">{key}</div>
|
||||
<div className="text-sm text-gray-900">{String(value)}</div>
|
||||
<div key={key} className="bg-card rounded-lg border p-4 shadow-sm">
|
||||
<div className="text-muted-foreground mb-1 text-xs font-semibold tracking-wide uppercase">
|
||||
{key}
|
||||
</div>
|
||||
<div className="text-sm">{String(value)}</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
|
@ -572,8 +574,8 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
|||
) : selectedLeftItem && isDesignMode ? (
|
||||
// 디자인 모드: 샘플 데이터
|
||||
<div className="space-y-4">
|
||||
<div className="rounded-lg border border-gray-200 p-4">
|
||||
<h3 className="mb-2 font-medium text-gray-900">{selectedLeftItem.name} 상세 정보</h3>
|
||||
<div className="rounded-lg border p-4">
|
||||
<h3 className="mb-2 font-medium">{selectedLeftItem.name} 상세 정보</h3>
|
||||
<div className="space-y-2 text-sm">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-600">항목 1:</span>
|
||||
|
|
|
|||
|
|
@ -1361,7 +1361,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
|||
{/* 헤더 */}
|
||||
{tableConfig.showHeader && (
|
||||
<div
|
||||
className="flex items-center justify-between border-b border-gray-200/40 bg-gradient-to-r from-slate-50/80 to-gray-50/60 px-6 py-5"
|
||||
className="bg-muted/30 flex items-center justify-between border-b px-6 py-5"
|
||||
style={{
|
||||
width: "100%",
|
||||
maxWidth: "100%",
|
||||
|
|
@ -1388,7 +1388,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
|||
size="sm"
|
||||
onClick={handleRefresh}
|
||||
disabled={loading}
|
||||
className="group relative rounded-xl border-gray-200/60 bg-white/80 shadow-sm backdrop-blur-sm transition-all duration-200 hover:bg-gray-50/80 hover:shadow-md"
|
||||
className="group relative rounded-xl shadow-sm transition-all duration-200 hover:shadow-md"
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="relative">
|
||||
|
|
@ -1699,7 +1699,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
|||
{/* showFooter와 pagination.enabled의 기본값은 true */}
|
||||
{tableConfig.showFooter !== false && tableConfig.pagination?.enabled !== false && (
|
||||
<div
|
||||
className="flex flex-col items-center justify-center space-y-4 border-t border-gray-200 bg-gray-100/80 p-6"
|
||||
className="bg-muted/30 flex flex-col items-center justify-center space-y-4 border-t p-6"
|
||||
style={{
|
||||
width: "100%",
|
||||
maxWidth: "100%",
|
||||
|
|
@ -1759,7 +1759,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
|||
|
||||
// 데이터는 useEffect에서 자동으로 다시 로드됨
|
||||
}}
|
||||
className="rounded-xl border border-gray-200/60 bg-white/80 px-4 py-2 text-sm font-medium text-gray-700 shadow-sm backdrop-blur-sm transition-all duration-200 hover:border-gray-300/60 hover:bg-white hover:shadow-md"
|
||||
className="rounded-xl border px-4 py-2 text-sm font-medium shadow-sm transition-all duration-200 hover:shadow-md"
|
||||
>
|
||||
{(tableConfig.pagination?.pageSizeOptions || [10, 20, 50, 100]).map((size) => (
|
||||
<option key={size} value={size}>
|
||||
|
|
@ -1770,7 +1770,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
|||
)}
|
||||
|
||||
{/* 페이지네이션 버튼 */}
|
||||
<div className="flex items-center space-x-2 rounded-xl border border-gray-200/60 bg-white/80 p-1 shadow-sm backdrop-blur-sm">
|
||||
<div className="flex items-center space-x-2 rounded-xl border p-1 shadow-sm">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
|
|
@ -1790,7 +1790,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
|||
<ChevronLeft className="h-4 w-4" />
|
||||
</Button>
|
||||
|
||||
<div className="flex items-center rounded-lg border border-gray-200/40 bg-gradient-to-r from-gray-50/80 to-slate-50/60 px-4 py-2 backdrop-blur-sm">
|
||||
<div className="bg-muted/30 flex items-center rounded-lg border px-4 py-2">
|
||||
<span className="text-sm font-semibold text-gray-800">{currentPage}</span>
|
||||
<span className="mx-2 font-light text-gray-400">/</span>
|
||||
<span className="text-sm font-medium text-gray-600">{totalPages}</span>
|
||||
|
|
|
|||
|
|
@ -6,11 +6,14 @@
|
|||
"": {
|
||||
"dependencies": {
|
||||
"@prisma/client": "^6.16.2",
|
||||
"@react-three/drei": "^10.7.6",
|
||||
"@react-three/fiber": "^9.4.0",
|
||||
"@types/mssql": "^9.1.8",
|
||||
"@xyflow/react": "^12.8.6",
|
||||
"axios": "^1.12.2",
|
||||
"mssql": "^11.0.1",
|
||||
"prisma": "^6.16.2"
|
||||
"prisma": "^6.16.2",
|
||||
"three": "^0.180.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/oracledb": "^6.9.1",
|
||||
|
|
@ -275,12 +278,45 @@
|
|||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/runtime": {
|
||||
"version": "7.28.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz",
|
||||
"integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@dimforge/rapier3d-compat": {
|
||||
"version": "0.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@dimforge/rapier3d-compat/-/rapier3d-compat-0.12.0.tgz",
|
||||
"integrity": "sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@js-joda/core": {
|
||||
"version": "5.6.5",
|
||||
"resolved": "https://registry.npmjs.org/@js-joda/core/-/core-5.6.5.tgz",
|
||||
"integrity": "sha512-3zwefSMwHpu8iVUW8YYz227sIv6UFqO31p1Bf1ZH/Vom7CmNyUsXjDBlnNzcuhmOL1XfxZ3nvND42kR23XlbcQ==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@mediapipe/tasks-vision": {
|
||||
"version": "0.10.17",
|
||||
"resolved": "https://registry.npmjs.org/@mediapipe/tasks-vision/-/tasks-vision-0.10.17.tgz",
|
||||
"integrity": "sha512-CZWV/q6TTe8ta61cZXjfnnHsfWIdFhms03M9T7Cnd5y2mdpylJM0rF1qRq+wsQVRMLz1OYPVEBU9ph2Bx8cxrg==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@monogrid/gainmap-js": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@monogrid/gainmap-js/-/gainmap-js-3.1.0.tgz",
|
||||
"integrity": "sha512-Obb0/gEd/HReTlg8ttaYk+0m62gQJmCblMOjHSMHRrBP2zdfKMHLCRbh/6ex9fSUJMKdjjIEiohwkbGD3wj2Nw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"promise-worker-transferable": "^1.0.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"three": ">= 0.159.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@prisma/client": {
|
||||
"version": "6.16.2",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.16.2.tgz",
|
||||
|
|
@ -360,6 +396,160 @@
|
|||
"@prisma/debug": "6.16.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-three/drei": {
|
||||
"version": "10.7.6",
|
||||
"resolved": "https://registry.npmjs.org/@react-three/drei/-/drei-10.7.6.tgz",
|
||||
"integrity": "sha512-ZSFwRlRaa4zjtB7yHO6Q9xQGuyDCzE7whXBhum92JslcMRC3aouivp0rAzszcVymIoJx6PXmibyP+xr+zKdwLg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.26.0",
|
||||
"@mediapipe/tasks-vision": "0.10.17",
|
||||
"@monogrid/gainmap-js": "^3.0.6",
|
||||
"@use-gesture/react": "^10.3.1",
|
||||
"camera-controls": "^3.1.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"detect-gpu": "^5.0.56",
|
||||
"glsl-noise": "^0.0.0",
|
||||
"hls.js": "^1.5.17",
|
||||
"maath": "^0.10.8",
|
||||
"meshline": "^3.3.1",
|
||||
"stats-gl": "^2.2.8",
|
||||
"stats.js": "^0.17.0",
|
||||
"suspend-react": "^0.1.3",
|
||||
"three-mesh-bvh": "^0.8.3",
|
||||
"three-stdlib": "^2.35.6",
|
||||
"troika-three-text": "^0.52.4",
|
||||
"tunnel-rat": "^0.1.2",
|
||||
"use-sync-external-store": "^1.4.0",
|
||||
"utility-types": "^3.11.0",
|
||||
"zustand": "^5.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@react-three/fiber": "^9.0.0",
|
||||
"react": "^19",
|
||||
"react-dom": "^19",
|
||||
"three": ">=0.159"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@react-three/drei/node_modules/zustand": {
|
||||
"version": "5.0.8",
|
||||
"resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.8.tgz",
|
||||
"integrity": "sha512-gyPKpIaxY9XcO2vSMrLbiER7QMAMGOQZVRdJ6Zi782jkbzZygq5GI9nG8g+sMgitRtndwaBSl7uiqC49o1SSiw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12.20.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": ">=18.0.0",
|
||||
"immer": ">=9.0.6",
|
||||
"react": ">=18.0.0",
|
||||
"use-sync-external-store": ">=1.2.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"immer": {
|
||||
"optional": true
|
||||
},
|
||||
"react": {
|
||||
"optional": true
|
||||
},
|
||||
"use-sync-external-store": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@react-three/fiber": {
|
||||
"version": "9.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-three/fiber/-/fiber-9.4.0.tgz",
|
||||
"integrity": "sha512-k4iu1R6e5D54918V4sqmISUkI5OgTw3v7/sDRKEC632Wd5g2WBtUS5gyG63X0GJO/HZUj1tsjSXfyzwrUHZl1g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.17.8",
|
||||
"@types/react-reconciler": "^0.32.0",
|
||||
"@types/webxr": "*",
|
||||
"base64-js": "^1.5.1",
|
||||
"buffer": "^6.0.3",
|
||||
"its-fine": "^2.0.0",
|
||||
"react-reconciler": "^0.31.0",
|
||||
"react-use-measure": "^2.1.7",
|
||||
"scheduler": "^0.25.0",
|
||||
"suspend-react": "^0.1.3",
|
||||
"use-sync-external-store": "^1.4.0",
|
||||
"zustand": "^5.0.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"expo": ">=43.0",
|
||||
"expo-asset": ">=8.4",
|
||||
"expo-file-system": ">=11.0",
|
||||
"expo-gl": ">=11.0",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-native": ">=0.78",
|
||||
"three": ">=0.156"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"expo": {
|
||||
"optional": true
|
||||
},
|
||||
"expo-asset": {
|
||||
"optional": true
|
||||
},
|
||||
"expo-file-system": {
|
||||
"optional": true
|
||||
},
|
||||
"expo-gl": {
|
||||
"optional": true
|
||||
},
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
},
|
||||
"react-native": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@react-three/fiber/node_modules/scheduler": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz",
|
||||
"integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@react-three/fiber/node_modules/zustand": {
|
||||
"version": "5.0.8",
|
||||
"resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.8.tgz",
|
||||
"integrity": "sha512-gyPKpIaxY9XcO2vSMrLbiER7QMAMGOQZVRdJ6Zi782jkbzZygq5GI9nG8g+sMgitRtndwaBSl7uiqC49o1SSiw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12.20.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": ">=18.0.0",
|
||||
"immer": ">=9.0.6",
|
||||
"react": ">=18.0.0",
|
||||
"use-sync-external-store": ">=1.2.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"immer": {
|
||||
"optional": true
|
||||
},
|
||||
"react": {
|
||||
"optional": true
|
||||
},
|
||||
"use-sync-external-store": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@standard-schema/spec": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz",
|
||||
|
|
@ -372,6 +562,12 @@
|
|||
"integrity": "sha512-7qSgZbincDDDFyRweCIEvZULFAw5iz/DeunhvuxpL31nfntX3P4Yd4HkHBRg9H8CdqY1e5WFN1PZIz/REL9MVQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@tweenjs/tween.js": {
|
||||
"version": "23.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-23.1.3.tgz",
|
||||
"integrity": "sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/d3-color": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
|
||||
|
|
@ -421,6 +617,12 @@
|
|||
"@types/d3-selection": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/draco3d": {
|
||||
"version": "1.4.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/draco3d/-/draco3d-1.4.10.tgz",
|
||||
"integrity": "sha512-AX22jp8Y7wwaBgAixaSvkoG4M/+PlAcm3Qs4OW8yT9DM4xUpWKeFhLueTAyZF39pviAdcDdeJoACapiAceqNcw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/mssql": {
|
||||
"version": "9.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/mssql/-/mssql-9.1.8.tgz",
|
||||
|
|
@ -441,6 +643,12 @@
|
|||
"undici-types": "~7.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/offscreencanvas": {
|
||||
"version": "2019.7.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.7.3.tgz",
|
||||
"integrity": "sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/oracledb": {
|
||||
"version": "6.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/oracledb/-/oracledb-6.9.1.tgz",
|
||||
|
|
@ -463,6 +671,25 @@
|
|||
"pg-types": "^2.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "19.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz",
|
||||
"integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-reconciler": {
|
||||
"version": "0.32.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.32.2.tgz",
|
||||
"integrity": "sha512-gjcm6O0aUknhYaogEl8t5pecPfiOTD8VQkbjOhgbZas/E6qGY+veW9iuJU/7p4Y1E0EuQ0mArga7VEOUWSlVRA==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/readable-stream": {
|
||||
"version": "4.0.21",
|
||||
"resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-4.0.21.tgz",
|
||||
|
|
@ -472,6 +699,33 @@
|
|||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/stats.js": {
|
||||
"version": "0.17.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.4.tgz",
|
||||
"integrity": "sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/three": {
|
||||
"version": "0.180.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/three/-/three-0.180.0.tgz",
|
||||
"integrity": "sha512-ykFtgCqNnY0IPvDro7h+9ZeLY+qjgUWv+qEvUt84grhenO60Hqd4hScHE7VTB9nOQ/3QM8lkbNE+4vKjEpUxKg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@dimforge/rapier3d-compat": "~0.12.0",
|
||||
"@tweenjs/tween.js": "~23.1.3",
|
||||
"@types/stats.js": "*",
|
||||
"@types/webxr": "*",
|
||||
"@webgpu/types": "*",
|
||||
"fflate": "~0.8.2",
|
||||
"meshoptimizer": "~0.22.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/webxr": {
|
||||
"version": "0.5.24",
|
||||
"resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.24.tgz",
|
||||
"integrity": "sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@typespec/ts-http-runtime": {
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@typespec/ts-http-runtime/-/ts-http-runtime-0.3.1.tgz",
|
||||
|
|
@ -486,6 +740,30 @@
|
|||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@use-gesture/core": {
|
||||
"version": "10.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@use-gesture/core/-/core-10.3.1.tgz",
|
||||
"integrity": "sha512-WcINiDt8WjqBdUXye25anHiNxPc0VOrlT8F6LLkU6cycrOGUDyY/yyFmsg3k8i5OLvv25llc0QC45GhR/C8llw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@use-gesture/react": {
|
||||
"version": "10.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@use-gesture/react/-/react-10.3.1.tgz",
|
||||
"integrity": "sha512-Yy19y6O2GJq8f7CHf7L0nxL8bf4PZCPaVOCgJrusOeFHY1LvHgYXnmnXg6N5iwAnbgbZCDjo60SiM6IPJi9C5g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@use-gesture/core": "10.3.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">= 16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@webgpu/types": {
|
||||
"version": "0.1.66",
|
||||
"resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.66.tgz",
|
||||
"integrity": "sha512-YA2hLrwLpDsRueNDXIMqN9NTzD6bCDkuXbOSe0heS+f8YE8usA6Gbv1prj81pzVHrbaAma7zObnIC+I6/sXJgA==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@xyflow/react": {
|
||||
"version": "12.8.6",
|
||||
"resolved": "https://registry.npmjs.org/@xyflow/react/-/react-12.8.6.tgz",
|
||||
|
|
@ -576,6 +854,15 @@
|
|||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/bidi-js": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz",
|
||||
"integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"require-from-string": "^2.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/bl": {
|
||||
"version": "6.1.3",
|
||||
"resolved": "https://registry.npmjs.org/bl/-/bl-6.1.3.tgz",
|
||||
|
|
@ -674,6 +961,19 @@
|
|||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/camera-controls": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/camera-controls/-/camera-controls-3.1.0.tgz",
|
||||
"integrity": "sha512-w5oULNpijgTRH0ARFJJ0R5ct1nUM3R3WP7/b8A6j9uTGpRfnsypc/RBMPQV8JQDPayUe37p/TZZY1PcUr4czOQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=20.11.0",
|
||||
"npm": ">=10.8.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"three": ">=0.126.1"
|
||||
}
|
||||
},
|
||||
"node_modules/chokidar": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
||||
|
|
@ -740,6 +1040,45 @@
|
|||
"node": "^14.18.0 || >=16.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-env": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz",
|
||||
"integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cross-spawn": "^7.0.1"
|
||||
},
|
||||
"bin": {
|
||||
"cross-env": "src/bin/cross-env.js",
|
||||
"cross-env-shell": "src/bin/cross-env-shell.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.14",
|
||||
"npm": ">=6",
|
||||
"yarn": ">=1"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"path-key": "^3.1.0",
|
||||
"shebang-command": "^2.0.0",
|
||||
"which": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/csstype": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/d3-color": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
|
||||
|
|
@ -932,6 +1271,15 @@
|
|||
"integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/detect-gpu": {
|
||||
"version": "5.0.70",
|
||||
"resolved": "https://registry.npmjs.org/detect-gpu/-/detect-gpu-5.0.70.tgz",
|
||||
"integrity": "sha512-bqerEP1Ese6nt3rFkwPnGbsUF9a4q+gMmpTVVOEzoCyeCc+y7/RvJnQZJx1JwhgQI5Ntg0Kgat8Uu7XpBqnz1w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"webgl-constants": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/dotenv": {
|
||||
"version": "16.6.1",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
|
||||
|
|
@ -944,6 +1292,12 @@
|
|||
"url": "https://dotenvx.com"
|
||||
}
|
||||
},
|
||||
"node_modules/draco3d": {
|
||||
"version": "1.5.7",
|
||||
"resolved": "https://registry.npmjs.org/draco3d/-/draco3d-1.5.7.tgz",
|
||||
"integrity": "sha512-m6WCKt/erDXcw+70IJXnG7M3awwQPAsZvJGX5zY7beBqpELw6RDGkYVU0W43AFxye4pDZ5i2Lbyc/NNGqwjUVQ==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
|
|
@ -1077,6 +1431,12 @@
|
|||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fflate": {
|
||||
"version": "0.8.2",
|
||||
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
|
||||
"integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.11",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
|
||||
|
|
@ -1176,6 +1536,12 @@
|
|||
"giget": "dist/cli.mjs"
|
||||
}
|
||||
},
|
||||
"node_modules/glsl-noise": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://registry.npmjs.org/glsl-noise/-/glsl-noise-0.0.0.tgz",
|
||||
"integrity": "sha512-b/ZCF6amfAUb7dJM/MxRs7AetQEahYzJ8PtgfrmEdtw6uyGOr+ZSGtgjFm6mfsBkxJ4d2W7kg+Nlqzqvn3Bc0w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||
|
|
@ -1227,6 +1593,12 @@
|
|||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/hls.js": {
|
||||
"version": "1.6.13",
|
||||
"resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.6.13.tgz",
|
||||
"integrity": "sha512-hNEzjZNHf5bFrUNvdS4/1RjIanuJ6szpWNfTaX5I6WfGynWXGT7K/YQLYtemSvFExzeMdgdE4SsyVLJbd5PcZA==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/http-proxy-agent": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
|
||||
|
|
@ -1285,6 +1657,12 @@
|
|||
],
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/immediate": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
|
||||
"integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
|
|
@ -1324,6 +1702,12 @@
|
|||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/is-promise": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz",
|
||||
"integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/is-wsl": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz",
|
||||
|
|
@ -1339,6 +1723,33 @@
|
|||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/isexe": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/its-fine": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/its-fine/-/its-fine-2.0.0.tgz",
|
||||
"integrity": "sha512-KLViCmWx94zOvpLwSlsx6yOCeMhZYaxrJV87Po5k/FoZzcPSahvK5qJ7fYhS61sZi5ikmh2S3Hz55A2l3U69ng==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/react-reconciler": "^0.28.9"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/its-fine/node_modules/@types/react-reconciler": {
|
||||
"version": "0.28.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.28.9.tgz",
|
||||
"integrity": "sha512-HHM3nxyUZ3zAylX8ZEyrDNd2XZOnQ0D5XfunJF5FLQnZbHHYq4UWvW1QfelQNXv1ICNkwYhfxjwfnqivYB6bFg==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/jiti": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz",
|
||||
|
|
@ -1397,6 +1808,15 @@
|
|||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/lie": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
|
||||
"integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"immediate": "~3.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/lodash.includes": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
|
||||
|
|
@ -1439,6 +1859,16 @@
|
|||
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/maath": {
|
||||
"version": "0.10.8",
|
||||
"resolved": "https://registry.npmjs.org/maath/-/maath-0.10.8.tgz",
|
||||
"integrity": "sha512-tRvbDF0Pgqz+9XUa4jjfgAQ8/aPKmQdWXilFu2tMy4GWj4NOsx99HlULO4IeREfbO3a0sA145DZYyvXPkybm0g==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/three": ">=0.134.0",
|
||||
"three": ">=0.134.0"
|
||||
}
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
|
|
@ -1448,6 +1878,21 @@
|
|||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/meshline": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/meshline/-/meshline-3.3.1.tgz",
|
||||
"integrity": "sha512-/TQj+JdZkeSUOl5Mk2J7eLcYTLiQm2IDzmlSvYm7ov15anEcDJ92GHqqazxTSreeNgfnYu24kiEvvv0WlbCdFQ==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"three": ">=0.137"
|
||||
}
|
||||
},
|
||||
"node_modules/meshoptimizer": {
|
||||
"version": "0.22.0",
|
||||
"resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-0.22.0.tgz",
|
||||
"integrity": "sha512-IebiK79sqIy+E4EgOr+CAw+Ke8hAspXKzBd0JdgEmPHiAwmvEj2S4h1rfvo+o/BnfEYd/jAOg5IeeIjzlzSnDg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
|
|
@ -1550,6 +1995,15 @@
|
|||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/path-key": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
||||
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/pathe": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
|
||||
|
|
@ -1650,6 +2104,12 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/potpack": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/potpack/-/potpack-1.0.2.tgz",
|
||||
"integrity": "sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/prisma": {
|
||||
"version": "6.16.2",
|
||||
"resolved": "https://registry.npmjs.org/prisma/-/prisma-6.16.2.tgz",
|
||||
|
|
@ -1684,6 +2144,16 @@
|
|||
"node": ">= 0.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/promise-worker-transferable": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/promise-worker-transferable/-/promise-worker-transferable-1.0.4.tgz",
|
||||
"integrity": "sha512-bN+0ehEnrXfxV2ZQvU2PetO0n4gqBD4ulq3MI1WOPLgr7/Mg9yRQkX5+0v1vagr74ZTsl7XtzlaYDo2EuCeYJw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"is-promise": "^2.1.0",
|
||||
"lie": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
|
|
@ -1739,6 +2209,42 @@
|
|||
"react": "^19.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-reconciler": {
|
||||
"version": "0.31.0",
|
||||
"resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.31.0.tgz",
|
||||
"integrity": "sha512-7Ob7Z+URmesIsIVRjnLoDGwBEG/tVitidU0nMsqX/eeJaLY89RISO/10ERe0MqmzuKUUB1rmY+h1itMbUHg9BQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"scheduler": "^0.25.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-reconciler/node_modules/scheduler": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz",
|
||||
"integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react-use-measure": {
|
||||
"version": "2.1.7",
|
||||
"resolved": "https://registry.npmjs.org/react-use-measure/-/react-use-measure-2.1.7.tgz",
|
||||
"integrity": "sha512-KrvcAo13I/60HpwGO5jpW7E9DfusKyLPLvuHlUyP5zqnmAPhNc6qTRjUQrdTADl0lpPpDVU2/Gg51UlOGHXbdg==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": ">=16.13",
|
||||
"react-dom": ">=16.13"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/readable-stream": {
|
||||
"version": "4.7.0",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz",
|
||||
|
|
@ -1768,6 +2274,15 @@
|
|||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/require-from-string": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
|
||||
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/rfdc": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz",
|
||||
|
|
@ -1831,12 +2346,59 @@
|
|||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/shebang-command": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"shebang-regex": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/shebang-regex": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
|
||||
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/sprintf-js": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz",
|
||||
"integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/stats-gl": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/stats-gl/-/stats-gl-2.4.2.tgz",
|
||||
"integrity": "sha512-g5O9B0hm9CvnM36+v7SFl39T7hmAlv541tU81ME8YeSb3i1CIP5/QdDeSB3A0la0bKNHpxpwxOVRo2wFTYEosQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/three": "*",
|
||||
"three": "^0.170.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/three": "*",
|
||||
"three": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/stats-gl/node_modules/three": {
|
||||
"version": "0.170.0",
|
||||
"resolved": "https://registry.npmjs.org/three/-/three-0.170.0.tgz",
|
||||
"integrity": "sha512-FQK+LEpYc0fBD+J8g6oSEyyNzjp+Q7Ks1C568WWaoMRLW+TkNNWmenWeGgJjV105Gd+p/2ql1ZcjYvNiPZBhuQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/stats.js": {
|
||||
"version": "0.17.0",
|
||||
"resolved": "https://registry.npmjs.org/stats.js/-/stats.js-0.17.0.tgz",
|
||||
"integrity": "sha512-hNKz8phvYLPEcRkeG1rsGmV5ChMjKDAWU7/OJJdDErPBNChQXxCo3WZurGpnWc6gZhAzEPFad1aVgyOANH1sMw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/string_decoder": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||
|
|
@ -1846,6 +2408,15 @@
|
|||
"safe-buffer": "~5.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/suspend-react": {
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/suspend-react/-/suspend-react-0.1.3.tgz",
|
||||
"integrity": "sha512-aqldKgX9aZqpoDp3e8/BZ8Dm7x1pJl+qI3ZKxDN0i/IQTWUwBx/ManmlVJ3wowqbno6c2bmiIfs+Um6LbsjJyQ==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": ">=17.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tarn": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/tarn/-/tarn-3.0.2.tgz",
|
||||
|
|
@ -1876,18 +2447,95 @@
|
|||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/three": {
|
||||
"version": "0.180.0",
|
||||
"resolved": "https://registry.npmjs.org/three/-/three-0.180.0.tgz",
|
||||
"integrity": "sha512-o+qycAMZrh+TsE01GqWUxUIKR1AL0S8pq7zDkYOQw8GqfX8b8VoCKYUoHbhiX5j+7hr8XsuHDVU6+gkQJQKg9w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/three-mesh-bvh": {
|
||||
"version": "0.8.3",
|
||||
"resolved": "https://registry.npmjs.org/three-mesh-bvh/-/three-mesh-bvh-0.8.3.tgz",
|
||||
"integrity": "sha512-4G5lBaF+g2auKX3P0yqx+MJC6oVt6sB5k+CchS6Ob0qvH0YIhuUk1eYr7ktsIpY+albCqE80/FVQGV190PmiAg==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"three": ">= 0.159.0"
|
||||
}
|
||||
},
|
||||
"node_modules/three-stdlib": {
|
||||
"version": "2.36.0",
|
||||
"resolved": "https://registry.npmjs.org/three-stdlib/-/three-stdlib-2.36.0.tgz",
|
||||
"integrity": "sha512-kv0Byb++AXztEGsULgMAs8U2jgUdz6HPpAB/wDJnLiLlaWQX2APHhiTJIN7rqW+Of0eRgcp7jn05U1BsCP3xBA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/draco3d": "^1.4.0",
|
||||
"@types/offscreencanvas": "^2019.6.4",
|
||||
"@types/webxr": "^0.5.2",
|
||||
"draco3d": "^1.4.1",
|
||||
"fflate": "^0.6.9",
|
||||
"potpack": "^1.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"three": ">=0.128.0"
|
||||
}
|
||||
},
|
||||
"node_modules/three-stdlib/node_modules/fflate": {
|
||||
"version": "0.6.10",
|
||||
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.6.10.tgz",
|
||||
"integrity": "sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tinyexec": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.1.tgz",
|
||||
"integrity": "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/troika-three-text": {
|
||||
"version": "0.52.4",
|
||||
"resolved": "https://registry.npmjs.org/troika-three-text/-/troika-three-text-0.52.4.tgz",
|
||||
"integrity": "sha512-V50EwcYGruV5rUZ9F4aNsrytGdKcXKALjEtQXIOBfhVoZU9VAqZNIoGQ3TMiooVqFAbR1w15T+f+8gkzoFzawg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bidi-js": "^1.0.2",
|
||||
"troika-three-utils": "^0.52.4",
|
||||
"troika-worker-utils": "^0.52.0",
|
||||
"webgl-sdf-generator": "1.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"three": ">=0.125.0"
|
||||
}
|
||||
},
|
||||
"node_modules/troika-three-utils": {
|
||||
"version": "0.52.4",
|
||||
"resolved": "https://registry.npmjs.org/troika-three-utils/-/troika-three-utils-0.52.4.tgz",
|
||||
"integrity": "sha512-NORAStSVa/BDiG52Mfudk4j1FG4jC4ILutB3foPnfGbOeIs9+G5vZLa0pnmnaftZUGm4UwSoqEpWdqvC7zms3A==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"three": ">=0.125.0"
|
||||
}
|
||||
},
|
||||
"node_modules/troika-worker-utils": {
|
||||
"version": "0.52.0",
|
||||
"resolved": "https://registry.npmjs.org/troika-worker-utils/-/troika-worker-utils-0.52.0.tgz",
|
||||
"integrity": "sha512-W1CpvTHykaPH5brv5VHLfQo9D1OYuo0cSBEUQFFT/nBUzM8iD6Lq2/tgG/f1OelbAS1WtaTPQzE5uM49egnngw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/tunnel-rat": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/tunnel-rat/-/tunnel-rat-0.1.2.tgz",
|
||||
"integrity": "sha512-lR5VHmkPhzdhrM092lI2nACsLO4QubF0/yoOhzX7c+wIpbN1GjHNzCc91QlpxBi+cnx8vVJ+Ur6vL5cEoQPFpQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"zustand": "^4.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "7.12.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.12.0.tgz",
|
||||
|
|
@ -1903,6 +2551,15 @@
|
|||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/utility-types": {
|
||||
"version": "3.11.0",
|
||||
"resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.11.0.tgz",
|
||||
"integrity": "sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 4"
|
||||
}
|
||||
},
|
||||
"node_modules/uuid": {
|
||||
"version": "8.3.2",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||
|
|
@ -1912,6 +2569,32 @@
|
|||
"uuid": "dist/bin/uuid"
|
||||
}
|
||||
},
|
||||
"node_modules/webgl-constants": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/webgl-constants/-/webgl-constants-1.1.1.tgz",
|
||||
"integrity": "sha512-LkBXKjU5r9vAW7Gcu3T5u+5cvSvh5WwINdr0C+9jpzVB41cjQAP5ePArDtk/WHYdVj0GefCgM73BA7FlIiNtdg=="
|
||||
},
|
||||
"node_modules/webgl-sdf-generator": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/webgl-sdf-generator/-/webgl-sdf-generator-1.1.1.tgz",
|
||||
"integrity": "sha512-9Z0JcMTFxeE+b2x1LJTdnaT8rT8aEp7MVxkNwoycNmJWwPdzoXzMh0BjJSh/AEFP+KPYZUli814h8bJZFIZ2jA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"isexe": "^2.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"node-which": "bin/node-which"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/wsl-utils": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz",
|
||||
|
|
|
|||
|
|
@ -1,11 +1,14 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"@prisma/client": "^6.16.2",
|
||||
"@react-three/drei": "^10.7.6",
|
||||
"@react-three/fiber": "^9.4.0",
|
||||
"@types/mssql": "^9.1.8",
|
||||
"@xyflow/react": "^12.8.6",
|
||||
"axios": "^1.12.2",
|
||||
"mssql": "^11.0.1",
|
||||
"prisma": "^6.16.2"
|
||||
"prisma": "^6.16.2",
|
||||
"three": "^0.180.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/oracledb": "^6.9.1",
|
||||
|
|
|
|||
Loading…
Reference in New Issue