feat: POP 화면설정 채번규칙 셀렉트 박스 구현

- 새 type "numbering-rule" 추가
- NumberingRuleSelect 컴포넌트: 회사별 채번규칙 목록 자동 로드
- 입고/출고 설정에서 inbound/outbound 키워드로 필터링
- 등록된 채번규칙이 없으면 안내 메시지 표시
This commit is contained in:
SeongHyun Kim
2026-04-07 16:59:25 +09:00
parent 2f675660b4
commit 31e225f6d3
4 changed files with 91 additions and 6 deletions

View File

@@ -165,15 +165,16 @@ interface SettingField {
key: string;
label: string;
description: string;
type: "toggle" | "text" | "number" | "select" | "color" | "tags" | "array-object";
type: "toggle" | "text" | "number" | "select" | "color" | "tags" | "array-object" | "numbering-rule";
defaultValue?: unknown;
options?: { value: string; label: string }[];
fields?: { key: string; label: string; type: string }[];
tableFilter?: string; // numbering-rule용: inbound/outbound 등
}
const SETTINGS_SCHEMA: Record<string, SettingField[]> = {
inbound: [
{ key: "numberingRuleId", label: "📋 입고번호 채번규칙", description: "입고 확정 시 사용할 채번규칙을 선택합니다", type: "text" },
{ key: "numberingRuleId", label: "📋 입고번호 채번규칙", description: "입고 확정 시 사용할 채번규칙을 선택합니다", type: "numbering-rule", tableFilter: "inbound" },
{ key: "barcodeEnabled", label: "바코드 스캔 (미구현)", description: "바코드/QR 스캔 기능을 사용합니다", type: "toggle" },
{ key: "inspectionRequired", label: "검사 필수 (미구현)", description: "입고 시 검사 항목을 필수로 표시합니다", type: "toggle" },
{ key: "photoUpload", label: "사진 첨부 (미구현)", description: "입고 확정 시 사진 첨부를 허용합니다", type: "toggle" },
@@ -181,7 +182,7 @@ const SETTINGS_SCHEMA: Record<string, SettingField[]> = {
{ key: "defectSeparation", label: "불량 분리 (미구현)", description: "양품/불량 수량을 분리 입력합니다", type: "toggle" },
],
outbound: [
{ key: "numberingRuleId", label: "📋 출고번호 채번규칙", description: "출고 확정 시 사용할 채번규칙을 선택합니다", type: "text" },
{ key: "numberingRuleId", label: "📋 출고번호 채번규칙", description: "출고 확정 시 사용할 채번규칙을 선택합니다", type: "numbering-rule", tableFilter: "outbound" },
{ key: "barcodeEnabled", label: "바코드 스캔 (미구현)", description: "바코드/QR 스캔 기능을 사용합니다", type: "toggle" },
{ key: "photoUpload", label: "사진 첨부 (미구현)", description: "출고 시 사진 첨부를 허용합니다", type: "toggle" },
],
@@ -256,6 +257,82 @@ const SETTINGS_SCHEMA: Record<string, SettingField[]> = {
],
};
// ============================================================
// 채번규칙 셀렉트 컴포넌트
// ============================================================
function NumberingRuleSelect({
field,
value,
onChange,
}: {
field: SettingField;
value: unknown;
onChange: (value: unknown) => void;
}) {
const { user } = useAuth();
const [rules, setRules] = useState<{ value: string; label: string }[]>([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
const loadRules = async () => {
setLoading(true);
try {
const companyCode = user?.companyCode || "COMPANY_7";
const res = await apiClient.get(`/numbering-rules?company_code=${companyCode}`);
const data = res.data?.data || res.data || [];
const allRules = Array.isArray(data) ? data : (data.rules || []);
// tableFilter로 필터링 (inbound/outbound 등)
const filtered = field.tableFilter
? allRules.filter((r: any) =>
(r.table_name || "").toLowerCase().includes(field.tableFilter!) ||
(r.rule_name || r.column_name || "").toLowerCase().includes(field.tableFilter!)
)
: allRules;
setRules(
filtered.length > 0
? filtered.map((r: any) => ({
value: r.id || r.rule_id,
label: `${r.rule_name || r.table_name + "." + r.column_name} (${r.prefix || ""}${r.separator || ""}...)`,
}))
: allRules.map((r: any) => ({
value: r.id || r.rule_id,
label: `${r.rule_name || r.table_name + "." + r.column_name}`,
}))
);
} catch {
setRules([]);
}
setLoading(false);
};
loadRules();
}, [user?.companyCode, field.tableFilter]);
return (
<div className="py-3 border-b last:border-0 space-y-1.5">
<Label className="text-sm font-medium">{field.label}</Label>
<p className="text-xs text-muted-foreground">{field.description}</p>
{loading ? (
<p className="text-xs text-muted-foreground"> ...</p>
) : rules.length === 0 ? (
<p className="text-xs text-amber-600"> . PC에서 .</p>
) : (
<Select value={(value as string) || ""} onValueChange={onChange}>
<SelectTrigger className="w-full h-9">
<SelectValue placeholder="채번규칙을 선택하세요" />
</SelectTrigger>
<SelectContent>
<SelectItem value="__none__"> ()</SelectItem>
{rules.map((r) => (
<SelectItem key={r.value} value={r.value}>{r.label}</SelectItem>
))}
</SelectContent>
</Select>
)}
</div>
);
}
// ============================================================
// Sub-components: TagEditor, ArrayObjectEditor
// ============================================================
@@ -484,6 +561,8 @@ function SettingRow({
</Select>
</div>
);
case "numbering-rule":
return <NumberingRuleSelect field={field} value={value} onChange={onChange} />;
case "color":
return (
<div className="py-3 border-b last:border-0 space-y-1.5">