206 lines
6.5 KiB
TypeScript
206 lines
6.5 KiB
TypeScript
|
|
'use client';
|
||
|
|
|
||
|
|
import { useState } from 'react';
|
||
|
|
import {
|
||
|
|
Dialog,
|
||
|
|
DialogContent,
|
||
|
|
DialogHeader,
|
||
|
|
DialogTitle,
|
||
|
|
DialogFooter,
|
||
|
|
} from '@/components/ui/dialog';
|
||
|
|
import { Button } from '@/components/ui/button';
|
||
|
|
import { Input } from '@/components/ui/input';
|
||
|
|
import { Label } from '@/components/ui/label';
|
||
|
|
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
|
||
|
|
import { Switch } from '@/components/ui/switch';
|
||
|
|
import {
|
||
|
|
Select,
|
||
|
|
SelectContent,
|
||
|
|
SelectItem,
|
||
|
|
SelectTrigger,
|
||
|
|
SelectValue,
|
||
|
|
} from '@/components/ui/select';
|
||
|
|
import { Search } from 'lucide-react';
|
||
|
|
import type {
|
||
|
|
ClassificationRule,
|
||
|
|
RuleRegistrationType,
|
||
|
|
RuleType,
|
||
|
|
MatchingType,
|
||
|
|
} from '@/types/process';
|
||
|
|
import { RULE_TYPE_OPTIONS, MATCHING_TYPE_OPTIONS } from '@/types/process';
|
||
|
|
|
||
|
|
interface RuleModalProps {
|
||
|
|
open: boolean;
|
||
|
|
onOpenChange: (open: boolean) => void;
|
||
|
|
onAdd: (rule: Omit<ClassificationRule, 'id' | 'createdAt'>) => void;
|
||
|
|
editRule?: ClassificationRule;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function RuleModal({ open, onOpenChange, onAdd, editRule }: RuleModalProps) {
|
||
|
|
const [registrationType, setRegistrationType] = useState<RuleRegistrationType>(
|
||
|
|
editRule?.registrationType || 'pattern'
|
||
|
|
);
|
||
|
|
const [ruleType, setRuleType] = useState<RuleType>(editRule?.ruleType || '품목코드');
|
||
|
|
const [matchingType, setMatchingType] = useState<MatchingType>(
|
||
|
|
editRule?.matchingType || 'startsWith'
|
||
|
|
);
|
||
|
|
const [conditionValue, setConditionValue] = useState(editRule?.conditionValue || '');
|
||
|
|
const [priority, setPriority] = useState(editRule?.priority || 10);
|
||
|
|
const [description, setDescription] = useState(editRule?.description || '');
|
||
|
|
const [isActive, setIsActive] = useState(editRule?.isActive ?? true);
|
||
|
|
|
||
|
|
const handleSubmit = () => {
|
||
|
|
if (!conditionValue.trim()) {
|
||
|
|
alert('조건 값을 입력해주세요.');
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
onAdd({
|
||
|
|
registrationType,
|
||
|
|
ruleType,
|
||
|
|
matchingType,
|
||
|
|
conditionValue: conditionValue.trim(),
|
||
|
|
priority,
|
||
|
|
description: description.trim() || undefined,
|
||
|
|
isActive,
|
||
|
|
});
|
||
|
|
|
||
|
|
// Reset form
|
||
|
|
setRegistrationType('pattern');
|
||
|
|
setRuleType('품목코드');
|
||
|
|
setMatchingType('startsWith');
|
||
|
|
setConditionValue('');
|
||
|
|
setPriority(10);
|
||
|
|
setDescription('');
|
||
|
|
setIsActive(true);
|
||
|
|
|
||
|
|
onOpenChange(false);
|
||
|
|
};
|
||
|
|
|
||
|
|
return (
|
||
|
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||
|
|
<DialogContent className="max-w-md">
|
||
|
|
<DialogHeader>
|
||
|
|
<DialogTitle>규칙 추가</DialogTitle>
|
||
|
|
</DialogHeader>
|
||
|
|
|
||
|
|
<div className="space-y-6 py-4">
|
||
|
|
{/* 등록 방식 */}
|
||
|
|
<div className="space-y-3">
|
||
|
|
<Label>등록 방식 *</Label>
|
||
|
|
<RadioGroup
|
||
|
|
value={registrationType}
|
||
|
|
onValueChange={(v) => setRegistrationType(v as RuleRegistrationType)}
|
||
|
|
>
|
||
|
|
<div className="flex items-center space-x-2">
|
||
|
|
<RadioGroupItem value="pattern" id="pattern" />
|
||
|
|
<Label htmlFor="pattern" className="font-normal">
|
||
|
|
패턴 규칙 (코드/명칭 기반 자동 분류)
|
||
|
|
</Label>
|
||
|
|
</div>
|
||
|
|
<div className="flex items-center space-x-2">
|
||
|
|
<RadioGroupItem value="individual" id="individual" />
|
||
|
|
<Label htmlFor="individual" className="font-normal">
|
||
|
|
개별 품목 (특정 품목 직접 지정)
|
||
|
|
</Label>
|
||
|
|
</div>
|
||
|
|
</RadioGroup>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* 규칙 유형 */}
|
||
|
|
<div className="space-y-2">
|
||
|
|
<Label>규칙 유형 *</Label>
|
||
|
|
<Select value={ruleType} onValueChange={(v) => setRuleType(v as RuleType)}>
|
||
|
|
<SelectTrigger>
|
||
|
|
<SelectValue />
|
||
|
|
</SelectTrigger>
|
||
|
|
<SelectContent>
|
||
|
|
{RULE_TYPE_OPTIONS.map((opt) => (
|
||
|
|
<SelectItem key={opt.value} value={opt.value}>
|
||
|
|
{opt.label}
|
||
|
|
</SelectItem>
|
||
|
|
))}
|
||
|
|
</SelectContent>
|
||
|
|
</Select>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* 매칭 방식 */}
|
||
|
|
<div className="space-y-2">
|
||
|
|
<Label>매칭 방식 *</Label>
|
||
|
|
<Select
|
||
|
|
value={matchingType}
|
||
|
|
onValueChange={(v) => setMatchingType(v as MatchingType)}
|
||
|
|
>
|
||
|
|
<SelectTrigger>
|
||
|
|
<SelectValue />
|
||
|
|
</SelectTrigger>
|
||
|
|
<SelectContent>
|
||
|
|
{MATCHING_TYPE_OPTIONS.map((opt) => (
|
||
|
|
<SelectItem key={opt.value} value={opt.value}>
|
||
|
|
{opt.label}
|
||
|
|
</SelectItem>
|
||
|
|
))}
|
||
|
|
</SelectContent>
|
||
|
|
</Select>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* 조건 값 */}
|
||
|
|
<div className="space-y-2">
|
||
|
|
<Label>조건 값 *</Label>
|
||
|
|
<div className="flex gap-2">
|
||
|
|
<Input
|
||
|
|
value={conditionValue}
|
||
|
|
onChange={(e) => setConditionValue(e.target.value)}
|
||
|
|
placeholder="예: SCR-, E-, STEEL-"
|
||
|
|
className="flex-1"
|
||
|
|
/>
|
||
|
|
<Button variant="outline" size="icon">
|
||
|
|
<Search className="h-4 w-4" />
|
||
|
|
</Button>
|
||
|
|
</div>
|
||
|
|
<p className="text-xs text-muted-foreground">
|
||
|
|
Enter 키를 누르거나 검색 버튼을 클릭하세요
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* 우선순위 */}
|
||
|
|
<div className="space-y-2">
|
||
|
|
<Label>우선순위</Label>
|
||
|
|
<Input
|
||
|
|
type="number"
|
||
|
|
value={priority}
|
||
|
|
onChange={(e) => setPriority(Number(e.target.value))}
|
||
|
|
min={1}
|
||
|
|
max={100}
|
||
|
|
className="w-24"
|
||
|
|
/>
|
||
|
|
<p className="text-xs text-muted-foreground">낮을수록 먼저 적용됩니다</p>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* 설명 */}
|
||
|
|
<div className="space-y-2">
|
||
|
|
<Label>설명</Label>
|
||
|
|
<Input
|
||
|
|
value={description}
|
||
|
|
onChange={(e) => setDescription(e.target.value)}
|
||
|
|
placeholder="규칙에 대한 설명"
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* 활성 상태 */}
|
||
|
|
<div className="flex items-center justify-between">
|
||
|
|
<Label>활성 상태</Label>
|
||
|
|
<Switch checked={isActive} onCheckedChange={setIsActive} />
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<DialogFooter>
|
||
|
|
<Button variant="outline" onClick={() => onOpenChange(false)}>
|
||
|
|
취소
|
||
|
|
</Button>
|
||
|
|
<Button onClick={handleSubmit}>추가</Button>
|
||
|
|
</DialogFooter>
|
||
|
|
</DialogContent>
|
||
|
|
</Dialog>
|
||
|
|
);
|
||
|
|
}
|