ERP-node/frontend/components/screen/config-panels/CodeConfigPanel.tsx

426 lines
17 KiB
TypeScript
Raw Normal View History

2025-09-09 14:29:04 +09:00
"use client";
import React, { useState, useEffect } from "react";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Textarea } from "@/components/ui/textarea";
import { Code, Monitor, Moon, Sun } from "lucide-react";
import { WebTypeConfigPanelProps } from "@/lib/registry/types";
import { WidgetComponent, CodeTypeConfig } from "@/types/screen";
export const CodeConfigPanel: React.FC<WebTypeConfigPanelProps> = ({
component,
onUpdateComponent,
onUpdateProperty,
}) => {
const widget = component as WidgetComponent;
const config = (widget.webTypeConfig as CodeTypeConfig) || {};
// 로컬 상태
const [localConfig, setLocalConfig] = useState<CodeTypeConfig>({
language: config.language || "javascript",
theme: config.theme || "light",
showLineNumbers: config.showLineNumbers !== false, // 기본값 true
wordWrap: config.wordWrap || false,
fontSize: config.fontSize || 14,
tabSize: config.tabSize || 2,
readOnly: config.readOnly || false,
showMinimap: config.showMinimap || false,
autoComplete: config.autoComplete !== false, // 기본값 true
bracketMatching: config.bracketMatching !== false, // 기본값 true
defaultValue: config.defaultValue || "",
placeholder: config.placeholder || "코드를 입력하세요...",
height: config.height || 300,
required: config.required || false,
});
// 컴포넌트 변경 시 로컬 상태 동기화
useEffect(() => {
const currentConfig = (widget.webTypeConfig as CodeTypeConfig) || {};
setLocalConfig({
language: currentConfig.language || "javascript",
theme: currentConfig.theme || "light",
showLineNumbers: currentConfig.showLineNumbers !== false,
wordWrap: currentConfig.wordWrap || false,
fontSize: currentConfig.fontSize || 14,
tabSize: currentConfig.tabSize || 2,
readOnly: currentConfig.readOnly || false,
showMinimap: currentConfig.showMinimap || false,
autoComplete: currentConfig.autoComplete !== false,
bracketMatching: currentConfig.bracketMatching !== false,
defaultValue: currentConfig.defaultValue || "",
placeholder: currentConfig.placeholder || "코드를 입력하세요...",
height: currentConfig.height || 300,
required: currentConfig.required || false,
});
}, [widget.webTypeConfig]);
// 설정 업데이트 핸들러
const updateConfig = (field: keyof CodeTypeConfig, value: any) => {
const newConfig = { ...localConfig, [field]: value };
setLocalConfig(newConfig);
onUpdateProperty("webTypeConfig", newConfig);
};
// 지원되는 언어 목록
const supportedLanguages = [
{ value: "javascript", label: "JavaScript", sample: "console.log('Hello World');" },
{ value: "typescript", label: "TypeScript", sample: "const message: string = 'Hello World';" },
{ value: "python", label: "Python", sample: "print('Hello World')" },
{ value: "java", label: "Java", sample: "System.out.println('Hello World');" },
{ value: "html", label: "HTML", sample: "<h1>Hello World</h1>" },
{ value: "css", label: "CSS", sample: "body { color: #333; }" },
{ value: "sql", label: "SQL", sample: "SELECT * FROM users;" },
{ value: "json", label: "JSON", sample: '{"message": "Hello World"}' },
{ value: "xml", label: "XML", sample: "<message>Hello World</message>" },
{ value: "markdown", label: "Markdown", sample: "# Hello World" },
{ value: "yaml", label: "YAML", sample: "message: Hello World" },
{ value: "shell", label: "Shell", sample: "echo 'Hello World'" },
{ value: "php", label: "PHP", sample: "<?php echo 'Hello World'; ?>" },
{ value: "go", label: "Go", sample: 'fmt.Println("Hello World")' },
{ value: "rust", label: "Rust", sample: 'println!("Hello World");' },
{ value: "plaintext", label: "Plain Text", sample: "Hello World" },
];
// 테마 목록
const themes = [
{ value: "light", label: "Light", icon: Sun },
{ value: "dark", label: "Dark", icon: Moon },
{ value: "vs", label: "Visual Studio", icon: Monitor },
{ value: "github", label: "GitHub", icon: Monitor },
{ value: "monokai", label: "Monokai", icon: Monitor },
{ value: "solarized", label: "Solarized", icon: Monitor },
];
// 샘플 코드 설정
const setSampleCode = () => {
const selectedLang = supportedLanguages.find((lang) => lang.value === localConfig.language);
if (selectedLang) {
updateConfig("defaultValue", selectedLang.sample);
}
};
return (
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2 text-sm">
<Code className="h-4 w-4" />
</CardTitle>
<CardDescription className="text-xs"> , , .</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
{/* 기본 설정 */}
<div className="space-y-3">
<h4 className="text-sm font-medium"> </h4>
<div className="space-y-2">
<Label htmlFor="language" className="text-xs">
</Label>
<Select
value={localConfig.language || "javascript"}
onValueChange={(value) => updateConfig("language", value)}
>
<SelectTrigger className="text-xs">
<SelectValue placeholder="언어 선택" />
</SelectTrigger>
<SelectContent>
{supportedLanguages.map((lang) => (
<SelectItem key={lang.value} value={lang.value}>
<div className="flex flex-col">
<span>{lang.label}</span>
<span className="text-muted-foreground font-mono text-xs">{lang.sample}</span>
</div>
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label htmlFor="theme" className="text-xs">
</Label>
<Select value={localConfig.theme || "light"} onValueChange={(value) => updateConfig("theme", value)}>
<SelectTrigger className="text-xs">
<SelectValue placeholder="테마 선택" />
</SelectTrigger>
<SelectContent>
{themes.map((theme) => (
<SelectItem key={theme.value} value={theme.value}>
<div className="flex items-center gap-2">
<theme.icon className="h-3 w-3" />
{theme.label}
</div>
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label htmlFor="height" className="text-xs">
: {localConfig.height}px
</Label>
<Input
id="height"
type="range"
min={150}
max={800}
step={50}
value={localConfig.height || 300}
onChange={(e) => updateConfig("height", parseInt(e.target.value))}
className="text-xs"
/>
<div className="text-muted-foreground flex justify-between text-xs">
<span>150px</span>
<span>800px</span>
</div>
</div>
</div>
{/* 편집기 옵션 */}
<div className="space-y-3">
<h4 className="text-sm font-medium"> </h4>
<div className="grid grid-cols-2 gap-3">
<div className="space-y-2">
<Label htmlFor="fontSize" className="text-xs">
</Label>
<Input
id="fontSize"
type="number"
value={localConfig.fontSize || 14}
onChange={(e) => updateConfig("fontSize", parseInt(e.target.value))}
min={10}
max={24}
className="text-xs"
/>
</div>
<div className="space-y-2">
<Label htmlFor="tabSize" className="text-xs">
</Label>
<Input
id="tabSize"
type="number"
value={localConfig.tabSize || 2}
onChange={(e) => updateConfig("tabSize", parseInt(e.target.value))}
min={1}
max={8}
className="text-xs"
/>
</div>
</div>
<div className="flex items-center justify-between">
<div className="space-y-1">
<Label htmlFor="showLineNumbers" className="text-xs">
</Label>
<p className="text-muted-foreground text-xs"> .</p>
</div>
<Switch
id="showLineNumbers"
checked={localConfig.showLineNumbers !== false}
onCheckedChange={(checked) => updateConfig("showLineNumbers", checked)}
/>
</div>
<div className="flex items-center justify-between">
<div className="space-y-1">
<Label htmlFor="wordWrap" className="text-xs">
</Label>
<p className="text-muted-foreground text-xs"> .</p>
</div>
<Switch
id="wordWrap"
checked={localConfig.wordWrap || false}
onCheckedChange={(checked) => updateConfig("wordWrap", checked)}
/>
</div>
<div className="flex items-center justify-between">
<div className="space-y-1">
<Label htmlFor="showMinimap" className="text-xs">
</Label>
<p className="text-muted-foreground text-xs"> .</p>
</div>
<Switch
id="showMinimap"
checked={localConfig.showMinimap || false}
onCheckedChange={(checked) => updateConfig("showMinimap", checked)}
/>
</div>
</div>
{/* 고급 기능 */}
<div className="space-y-3">
<h4 className="text-sm font-medium"> </h4>
<div className="flex items-center justify-between">
<div className="space-y-1">
<Label htmlFor="autoComplete" className="text-xs">
</Label>
<p className="text-muted-foreground text-xs"> .</p>
</div>
<Switch
id="autoComplete"
checked={localConfig.autoComplete !== false}
onCheckedChange={(checked) => updateConfig("autoComplete", checked)}
/>
</div>
<div className="flex items-center justify-between">
<div className="space-y-1">
<Label htmlFor="bracketMatching" className="text-xs">
</Label>
<p className="text-muted-foreground text-xs"> .</p>
</div>
<Switch
id="bracketMatching"
checked={localConfig.bracketMatching !== false}
onCheckedChange={(checked) => updateConfig("bracketMatching", checked)}
/>
</div>
</div>
{/* 기본값 설정 */}
<div className="space-y-3">
<h4 className="text-sm font-medium"></h4>
<div className="space-y-2">
<Label htmlFor="placeholder" className="text-xs">
</Label>
<Input
id="placeholder"
value={localConfig.placeholder || ""}
onChange={(e) => updateConfig("placeholder", e.target.value)}
placeholder="코드를 입력하세요..."
className="text-xs"
/>
</div>
<div className="space-y-2">
<div className="flex items-center justify-between">
<Label htmlFor="defaultValue" className="text-xs">
</Label>
<button
type="button"
onClick={setSampleCode}
className="text-xs text-blue-600 underline hover:text-blue-800"
>
</button>
</div>
<Textarea
id="defaultValue"
value={localConfig.defaultValue || ""}
onChange={(e) => updateConfig("defaultValue", e.target.value)}
placeholder="기본 코드 내용"
className="font-mono text-xs"
rows={4}
/>
</div>
</div>
{/* 상태 설정 */}
<div className="space-y-3">
<h4 className="text-sm font-medium"> </h4>
<div className="flex items-center justify-between">
<div className="space-y-1">
<Label htmlFor="readOnly" className="text-xs">
</Label>
<p className="text-muted-foreground text-xs"> .</p>
</div>
<Switch
id="readOnly"
checked={localConfig.readOnly || false}
onCheckedChange={(checked) => updateConfig("readOnly", checked)}
/>
</div>
<div className="flex items-center justify-between">
<div className="space-y-1">
<Label htmlFor="required" className="text-xs">
</Label>
<p className="text-muted-foreground text-xs"> .</p>
</div>
<Switch
id="required"
checked={localConfig.required || false}
onCheckedChange={(checked) => updateConfig("required", checked)}
/>
</div>
</div>
{/* 미리보기 */}
<div className="space-y-3">
<h4 className="text-sm font-medium"></h4>
<div className="bg-muted/50 rounded-md border p-3">
<div
className={`rounded border font-mono text-xs ${
localConfig.theme === "dark" ? "bg-gray-900 text-gray-100" : "bg-white text-gray-900"
}`}
style={{ height: `${Math.min(localConfig.height || 300, 200)}px` }}
>
<div className="flex items-center border-b bg-gray-50 px-3 py-1 text-gray-700">
<Code className="mr-2 h-3 w-3" />
<span className="text-xs">
{supportedLanguages.find((l) => l.value === localConfig.language)?.label || "JavaScript"}
</span>
{localConfig.showLineNumbers && <span className="ml-auto text-xs text-gray-500"></span>}
</div>
<div className="overflow-auto p-3" style={{ height: "calc(100% - 32px)" }}>
{localConfig.defaultValue ? (
<pre className="text-xs">
{localConfig.showLineNumbers && <span className="mr-3 text-gray-400 select-none">1</span>}
{localConfig.defaultValue}
</pre>
) : (
<div className="text-gray-400 italic">{localConfig.placeholder}</div>
)}
</div>
</div>
<div className="text-muted-foreground mt-2 space-y-1 text-xs">
<div>
: {supportedLanguages.find((l) => l.value === localConfig.language)?.label} :{" "}
{themes.find((t) => t.value === localConfig.theme)?.label} : {localConfig.fontSize}px
</div>
<div>
{localConfig.showLineNumbers && "줄번호 • "}
{localConfig.wordWrap && "줄바꿈 • "}
{localConfig.showMinimap && "미니맵 • "}
{localConfig.autoComplete && "자동완성 • "}
{localConfig.bracketMatching && "괄호매칭 • "}
{localConfig.readOnly && "읽기전용 • "}
{localConfig.required && "필수"}
</div>
</div>
</div>
</div>
</CardContent>
</Card>
);
};
CodeConfigPanel.displayName = "CodeConfigPanel";