ERP-node/frontend/app/(dashboard)/admin/system-settings/button-actions/new/page.tsx

467 lines
17 KiB
TypeScript

"use client";
import React, { useState } from "react";
import { useRouter } from "next/navigation";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Switch } from "@/components/ui/switch";
import { Badge } from "@/components/ui/badge";
import { toast } from "sonner";
import { ArrowLeft, Save, RotateCcw } from "lucide-react";
import { useButtonActions, type ButtonActionFormData } from "@/hooks/admin/useButtonActions";
import Link from "next/link";
// 기본 카테고리 목록
const DEFAULT_CATEGORIES = ["crud", "navigation", "utility", "custom"];
// 기본 변형 목록
const DEFAULT_VARIANTS = ["default", "destructive", "outline", "secondary", "ghost", "link"];
export default function NewButtonActionPage() {
const router = useRouter();
const { createButtonAction, isCreating, createError } = useButtonActions();
const [formData, setFormData] = useState<ButtonActionFormData>({
action_type: "",
action_name: "",
action_name_eng: "",
description: "",
category: "general",
default_text: "",
default_text_eng: "",
default_icon: "",
default_color: "",
default_variant: "default",
confirmation_required: false,
confirmation_message: "",
validation_rules: {},
action_config: {},
sort_order: 0,
is_active: "Y",
});
const [jsonErrors, setJsonErrors] = useState<{
validation_rules?: string;
action_config?: string;
}>({});
// JSON 문자열 상태 (편집용)
const [jsonStrings, setJsonStrings] = useState({
validation_rules: "{}",
action_config: "{}",
});
// 입력값 변경 핸들러
const handleInputChange = (field: keyof ButtonActionFormData, value: any) => {
setFormData((prev) => ({
...prev,
[field]: value,
}));
};
// JSON 입력 변경 핸들러
const handleJsonChange = (field: "validation_rules" | "action_config", value: string) => {
setJsonStrings((prev) => ({
...prev,
[field]: value,
}));
// JSON 파싱 시도
try {
const parsed = value.trim() ? JSON.parse(value) : {};
setFormData((prev) => ({
...prev,
[field]: parsed,
}));
setJsonErrors((prev) => ({
...prev,
[field]: undefined,
}));
} catch (error) {
setJsonErrors((prev) => ({
...prev,
[field]: "유효하지 않은 JSON 형식입니다.",
}));
}
};
// 폼 유효성 검사
const validateForm = (): boolean => {
if (!formData.action_type.trim()) {
toast.error("액션 타입을 입력해주세요.");
return false;
}
if (!formData.action_name.trim()) {
toast.error("액션명을 입력해주세요.");
return false;
}
if (!formData.category.trim()) {
toast.error("카테고리를 선택해주세요.");
return false;
}
// JSON 에러가 있는지 확인
const hasJsonErrors = Object.values(jsonErrors).some((error) => error);
if (hasJsonErrors) {
toast.error("JSON 형식 오류를 수정해주세요.");
return false;
}
return true;
};
// 저장 핸들러
const handleSave = async () => {
if (!validateForm()) return;
try {
await createButtonAction(formData);
toast.success("버튼 액션이 성공적으로 생성되었습니다.");
router.push("/admin/system-settings/button-actions");
} catch (error) {
toast.error(error instanceof Error ? error.message : "생성 중 오류가 발생했습니다.");
}
};
// 폼 초기화
const handleReset = () => {
setFormData({
action_type: "",
action_name: "",
action_name_eng: "",
description: "",
category: "general",
default_text: "",
default_text_eng: "",
default_icon: "",
default_color: "",
default_variant: "default",
confirmation_required: false,
confirmation_message: "",
validation_rules: {},
action_config: {},
sort_order: 0,
is_active: "Y",
});
setJsonStrings({
validation_rules: "{}",
action_config: "{}",
});
setJsonErrors({});
};
return (
<div className="container mx-auto px-4 py-6">
{/* 헤더 */}
<div className="mb-6 flex items-center gap-4">
<Link href="/admin/system-settings/button-actions">
<Button variant="ghost" size="sm">
<ArrowLeft className="mr-2 h-4 w-4" />
</Button>
</Link>
<div>
<h1 className="text-3xl font-bold tracking-tight"> </h1>
<p className="text-muted-foreground"> .</p>
</div>
</div>
<div className="grid grid-cols-1 gap-6 lg:grid-cols-3">
{/* 기본 정보 */}
<Card className="lg:col-span-2">
<CardHeader>
<CardTitle> </CardTitle>
<CardDescription> .</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
{/* 액션 타입 */}
<div className="space-y-2">
<Label htmlFor="action_type">
<span className="text-red-500">*</span>
</Label>
<Input
id="action_type"
value={formData.action_type}
onChange={(e) => handleInputChange("action_type", e.target.value)}
placeholder="예: save, delete, edit..."
className="font-mono"
/>
<p className="text-muted-foreground text-xs"> , , (_) .</p>
</div>
{/* 액션명 */}
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
<div className="space-y-2">
<Label htmlFor="action_name">
<span className="text-red-500">*</span>
</Label>
<Input
id="action_name"
value={formData.action_name}
onChange={(e) => handleInputChange("action_name", e.target.value)}
placeholder="예: 저장"
/>
</div>
<div className="space-y-2">
<Label htmlFor="action_name_eng"></Label>
<Input
id="action_name_eng"
value={formData.action_name_eng}
onChange={(e) => handleInputChange("action_name_eng", e.target.value)}
placeholder="예: Save"
/>
</div>
</div>
{/* 카테고리 */}
<div className="space-y-2">
<Label htmlFor="category">
<span className="text-red-500">*</span>
</Label>
<Select value={formData.category} onValueChange={(value) => handleInputChange("category", value)}>
<SelectTrigger>
<SelectValue placeholder="카테고리 선택" />
</SelectTrigger>
<SelectContent>
{DEFAULT_CATEGORIES.map((category) => (
<SelectItem key={category} value={category}>
{category}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
{/* 설명 */}
<div className="space-y-2">
<Label htmlFor="description"></Label>
<Textarea
id="description"
value={formData.description}
onChange={(e) => handleInputChange("description", e.target.value)}
placeholder="버튼 액션에 대한 설명을 입력해주세요..."
rows={3}
/>
</div>
{/* 정렬 순서 */}
<div className="space-y-2">
<Label htmlFor="sort_order"> </Label>
<Input
id="sort_order"
type="number"
value={formData.sort_order}
onChange={(e) => handleInputChange("sort_order", parseInt(e.target.value) || 0)}
placeholder="0"
min="0"
/>
<p className="text-muted-foreground text-xs"> .</p>
</div>
</CardContent>
</Card>
{/* 상태 설정 */}
<Card>
<CardHeader>
<CardTitle> </CardTitle>
<CardDescription> .</CardDescription>
</CardHeader>
<CardContent>
<div className="flex items-center justify-between">
<div className="space-y-1">
<Label htmlFor="is_active"> </Label>
<p className="text-muted-foreground text-xs"> .</p>
</div>
<Switch
id="is_active"
checked={formData.is_active === "Y"}
onCheckedChange={(checked) => handleInputChange("is_active", checked ? "Y" : "N")}
/>
</div>
<div className="mt-4">
<Badge variant={formData.is_active === "Y" ? "default" : "secondary"}>
{formData.is_active === "Y" ? "활성화" : "비활성화"}
</Badge>
</div>
</CardContent>
</Card>
{/* 기본 설정 */}
<Card className="lg:col-span-3">
<CardHeader>
<CardTitle> </CardTitle>
<CardDescription> .</CardDescription>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3">
{/* 기본 텍스트 */}
<div className="grid grid-cols-1 gap-4">
<div className="space-y-2">
<Label htmlFor="default_text"> </Label>
<Input
id="default_text"
value={formData.default_text}
onChange={(e) => handleInputChange("default_text", e.target.value)}
placeholder="예: 저장"
/>
</div>
<div className="space-y-2">
<Label htmlFor="default_text_eng"> </Label>
<Input
id="default_text_eng"
value={formData.default_text_eng}
onChange={(e) => handleInputChange("default_text_eng", e.target.value)}
placeholder="예: Save"
/>
</div>
</div>
{/* 아이콘 및 색상 */}
<div className="grid grid-cols-1 gap-4">
<div className="space-y-2">
<Label htmlFor="default_icon"> </Label>
<Input
id="default_icon"
value={formData.default_icon}
onChange={(e) => handleInputChange("default_icon", e.target.value)}
placeholder="예: Save (Lucide 아이콘명)"
/>
</div>
<div className="space-y-2">
<Label htmlFor="default_color"> </Label>
<Input
id="default_color"
value={formData.default_color}
onChange={(e) => handleInputChange("default_color", e.target.value)}
placeholder="예: blue, red, green..."
/>
</div>
</div>
{/* 변형 */}
<div className="space-y-2">
<Label htmlFor="default_variant"> </Label>
<Select
value={formData.default_variant}
onValueChange={(value) => handleInputChange("default_variant", value)}
>
<SelectTrigger>
<SelectValue placeholder="변형 선택" />
</SelectTrigger>
<SelectContent>
{DEFAULT_VARIANTS.map((variant) => (
<SelectItem key={variant} value={variant}>
{variant}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
</CardContent>
</Card>
{/* 확인 설정 */}
<Card className="lg:col-span-3">
<CardHeader>
<CardTitle> </CardTitle>
<CardDescription> .</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex items-center justify-between">
<div className="space-y-1">
<Label htmlFor="confirmation_required"> </Label>
<p className="text-muted-foreground text-xs"> .</p>
</div>
<Switch
id="confirmation_required"
checked={formData.confirmation_required}
onCheckedChange={(checked) => handleInputChange("confirmation_required", checked)}
/>
</div>
{formData.confirmation_required && (
<div className="space-y-2">
<Label htmlFor="confirmation_message"> </Label>
<Textarea
id="confirmation_message"
value={formData.confirmation_message}
onChange={(e) => handleInputChange("confirmation_message", e.target.value)}
placeholder="예: 정말로 삭제하시겠습니까?"
rows={2}
/>
</div>
)}
</CardContent>
</Card>
{/* JSON 설정 */}
<Card className="lg:col-span-3">
<CardHeader>
<CardTitle> (JSON)</CardTitle>
<CardDescription> JSON .</CardDescription>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 gap-6 md:grid-cols-2">
{/* 유효성 검사 규칙 */}
<div className="space-y-2">
<Label htmlFor="validation_rules"> </Label>
<Textarea
id="validation_rules"
value={jsonStrings.validation_rules}
onChange={(e) => handleJsonChange("validation_rules", e.target.value)}
placeholder='{"requiresData": true, "minItems": 1}'
rows={4}
className="font-mono text-xs"
/>
{jsonErrors.validation_rules && <p className="text-xs text-red-500">{jsonErrors.validation_rules}</p>}
</div>
{/* 액션 설정 */}
<div className="space-y-2">
<Label htmlFor="action_config"> </Label>
<Textarea
id="action_config"
value={jsonStrings.action_config}
onChange={(e) => handleJsonChange("action_config", e.target.value)}
placeholder='{"apiEndpoint": "/api/save", "redirectUrl": "/list"}'
rows={4}
className="font-mono text-xs"
/>
{jsonErrors.action_config && <p className="text-xs text-red-500">{jsonErrors.action_config}</p>}
</div>
</div>
</CardContent>
</Card>
</div>
{/* 액션 버튼 */}
<div className="mt-6 flex justify-end gap-4">
<Button variant="outline" onClick={handleReset}>
<RotateCcw className="mr-2 h-4 w-4" />
</Button>
<Button onClick={handleSave} disabled={isCreating}>
<Save className="mr-2 h-4 w-4" />
{isCreating ? "생성 중..." : "저장"}
</Button>
</div>
{/* 에러 메시지 */}
{createError && (
<div className="mt-4 rounded-md border border-red-200 bg-red-50 p-4">
<p className="text-red-600">
: {createError instanceof Error ? createError.message : "알 수 없는 오류"}
</p>
</div>
)}
</div>
);
}