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

359 lines
13 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"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 { Textarea } from "@/components/ui/textarea";
import { Slider } from "@/components/ui/slider";
import { AlignLeft } from "lucide-react";
import { WebTypeConfigPanelProps } from "@/lib/registry/types";
import { WidgetComponent, TextareaTypeConfig } from "@/types/screen";
export const TextareaConfigPanel: React.FC<WebTypeConfigPanelProps> = ({
component,
onUpdateComponent,
onUpdateProperty,
}) => {
const widget = component as WidgetComponent;
const config = (widget.webTypeConfig as TextareaTypeConfig) || {};
// 로컬 상태
const [localConfig, setLocalConfig] = useState<TextareaTypeConfig>({
rows: config.rows || 4,
cols: config.cols || undefined,
minLength: config.minLength || undefined,
maxLength: config.maxLength || undefined,
placeholder: config.placeholder || "",
defaultValue: config.defaultValue || "",
required: config.required || false,
readonly: config.readonly || false,
resizable: config.resizable !== false, // 기본값 true
autoHeight: config.autoHeight || false,
showCharCount: config.showCharCount || false,
wrap: config.wrap || "soft",
});
// 컴포넌트 변경 시 로컬 상태 동기화
useEffect(() => {
const currentConfig = (widget.webTypeConfig as TextareaTypeConfig) || {};
setLocalConfig({
rows: currentConfig.rows || 4,
cols: currentConfig.cols || undefined,
minLength: currentConfig.minLength || undefined,
maxLength: currentConfig.maxLength || undefined,
placeholder: currentConfig.placeholder || "",
defaultValue: currentConfig.defaultValue || "",
required: currentConfig.required || false,
readonly: currentConfig.readonly || false,
resizable: currentConfig.resizable !== false,
autoHeight: currentConfig.autoHeight || false,
showCharCount: currentConfig.showCharCount || false,
wrap: currentConfig.wrap || "soft",
});
}, [widget.webTypeConfig]);
// 설정 업데이트 핸들러
const updateConfig = (field: keyof TextareaTypeConfig, value: any) => {
const newConfig = { ...localConfig, [field]: value };
setLocalConfig(newConfig);
onUpdateProperty("webTypeConfig", newConfig);
};
// 현재 문자 수 계산
const currentCharCount = (localConfig.defaultValue || "").length;
const isOverLimit = localConfig.maxLength ? currentCharCount > localConfig.maxLength : false;
return (
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2 text-xs">
<AlignLeft 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="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">
<Label htmlFor="defaultValue" className="text-xs">
</Label>
<Textarea
id="defaultValue"
value={localConfig.defaultValue || ""}
onChange={(e) => updateConfig("defaultValue", e.target.value)}
placeholder="기본 텍스트 내용"
className="text-xs"
rows={3}
/>
{localConfig.showCharCount && (
<div className={`text-xs ${isOverLimit ? "text-red-500" : "text-muted-foreground"}`}>
{currentCharCount}
{localConfig.maxLength && ` / ${localConfig.maxLength}`}
</div>
)}
</div>
</div>
{/* 크기 설정 */}
<div className="space-y-3">
<h4 className="text-sm font-medium"> </h4>
<div className="space-y-2">
<Label htmlFor="rows" className="text-xs">
: {localConfig.rows}
</Label>
<Slider
id="rows"
min={1}
max={20}
step={1}
value={[localConfig.rows || 4]}
onValueChange={([value]) => updateConfig("rows", value)}
className="w-full"
/>
<div className="text-muted-foreground flex justify-between text-xs">
<span>1</span>
<span>20</span>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="cols" className="text-xs">
()
</Label>
<Input
id="cols"
type="number"
value={localConfig.cols || ""}
onChange={(e) => {
const value = e.target.value ? parseInt(e.target.value) : undefined;
updateConfig("cols", value);
}}
placeholder="자동 (CSS로 제어)"
min={10}
max={200}
className="text-xs"
/>
<p className="text-muted-foreground text-xs"> CSS width로 .</p>
</div>
<div className="flex items-center justify-between">
<div className="space-y-1">
<Label htmlFor="resizable" className="text-xs">
</Label>
<p className="text-muted-foreground text-xs"> .</p>
</div>
<Switch
id="resizable"
checked={localConfig.resizable || false}
onCheckedChange={(checked) => updateConfig("resizable", checked)}
/>
</div>
<div className="flex items-center justify-between">
<div className="space-y-1">
<Label htmlFor="autoHeight" className="text-xs">
</Label>
<p className="text-muted-foreground text-xs"> .</p>
</div>
<Switch
id="autoHeight"
checked={localConfig.autoHeight || false}
onCheckedChange={(checked) => updateConfig("autoHeight", checked)}
/>
</div>
</div>
{/* 텍스트 제한 */}
<div className="space-y-3">
<h4 className="text-sm font-medium"> </h4>
<div className="space-y-2">
<Label htmlFor="minLength" className="text-xs">
</Label>
<Input
id="minLength"
type="number"
value={localConfig.minLength || ""}
onChange={(e) => {
const value = e.target.value ? parseInt(e.target.value) : undefined;
updateConfig("minLength", value);
}}
placeholder="제한 없음"
min={0}
className="text-xs"
/>
</div>
<div className="space-y-2">
<Label htmlFor="maxLength" className="text-xs">
</Label>
<Input
id="maxLength"
type="number"
value={localConfig.maxLength || ""}
onChange={(e) => {
const value = e.target.value ? parseInt(e.target.value) : undefined;
updateConfig("maxLength", value);
}}
placeholder="제한 없음"
min={1}
className="text-xs"
/>
</div>
<div className="flex items-center justify-between">
<div className="space-y-1">
<Label htmlFor="showCharCount" className="text-xs">
</Label>
<p className="text-muted-foreground text-xs"> .</p>
</div>
<Switch
id="showCharCount"
checked={localConfig.showCharCount || false}
onCheckedChange={(checked) => updateConfig("showCharCount", checked)}
/>
</div>
</div>
{/* 텍스트 줄바꿈 설정 */}
<div className="space-y-3">
<h4 className="text-sm font-medium"> </h4>
<div className="space-y-2">
<Label className="text-xs"> </Label>
<div className="grid grid-cols-3 gap-2">
<button
type="button"
onClick={() => updateConfig("wrap", "soft")}
className={`rounded border p-2 text-xs ${
localConfig.wrap === "soft" ? "bg-primary text-primary-foreground" : "bg-background"
}`}
>
Soft
</button>
<button
type="button"
onClick={() => updateConfig("wrap", "hard")}
className={`rounded border p-2 text-xs ${
localConfig.wrap === "hard" ? "bg-primary text-primary-foreground" : "bg-background"
}`}
>
Hard
</button>
<button
type="button"
onClick={() => updateConfig("wrap", "off")}
className={`rounded border p-2 text-xs ${
localConfig.wrap === "off" ? "bg-primary text-primary-foreground" : "bg-background"
}`}
>
Off
</button>
</div>
<div className="text-muted-foreground text-xs">
{localConfig.wrap === "soft" && "화면에서만 줄바꿈 (기본값)"}
{localConfig.wrap === "hard" && "실제 텍스트에 줄바꿈 포함"}
{localConfig.wrap === "off" && "줄바꿈 없음 (스크롤)"}
</div>
</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="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 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>
{/* 미리보기 */}
<div className="space-y-3">
<h4 className="text-sm font-medium"></h4>
<div className="bg-muted/50 rounded-md border p-3">
<Textarea
placeholder={localConfig.placeholder || "텍스트 입력 미리보기"}
rows={localConfig.rows}
cols={localConfig.cols}
disabled={localConfig.readonly}
required={localConfig.required}
minLength={localConfig.minLength}
maxLength={localConfig.maxLength}
defaultValue={localConfig.defaultValue}
style={{
resize: localConfig.resizable ? "both" : "none",
minHeight: localConfig.autoHeight ? "auto" : undefined,
}}
className="text-xs"
wrap={localConfig.wrap}
/>
{localConfig.showCharCount && (
<div className="text-muted-foreground mt-1 text-right text-xs">
0{localConfig.maxLength && ` / ${localConfig.maxLength}`}
</div>
)}
<div className="text-muted-foreground mt-2 text-xs">
{localConfig.rows}{localConfig.cols && ` × ${localConfig.cols}`}
{localConfig.resizable && " • 크기조절가능"}
{localConfig.autoHeight && " • 자동높이"}
</div>
</div>
</div>
</CardContent>
</Card>
);
};
TextareaConfigPanel.displayName = "TextareaConfigPanel";