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
+6
View File
@@ -0,0 +1,6 @@
projectKey=vexplor
serverUrl=http://localhost:9000
serverVersion=26.3.0.120487
dashboardUrl=http://localhost:9000/dashboard?id=vexplor
ceTaskId=f2c72369-4d50-4483-bf76-b03788385757
ceTaskUrl=http://localhost:9000/api/ce/task?id=f2c72369-4d50-4483-bf76-b03788385757
@@ -165,15 +165,16 @@ interface SettingField {
key: string; key: string;
label: string; label: string;
description: 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; defaultValue?: unknown;
options?: { value: string; label: string }[]; options?: { value: string; label: string }[];
fields?: { key: string; label: string; type: string }[]; fields?: { key: string; label: string; type: string }[];
tableFilter?: string; // numbering-rule용: inbound/outbound 등
} }
const SETTINGS_SCHEMA: Record<string, SettingField[]> = { const SETTINGS_SCHEMA: Record<string, SettingField[]> = {
inbound: [ inbound: [
{ key: "numberingRuleId", label: "📋 입고번호 채번규칙", description: "입고 확정 시 사용할 채번규칙을 선택합니다", type: "text" }, { key: "numberingRuleId", label: "📋 입고번호 채번규칙", description: "입고 확정 시 사용할 채번규칙을 선택합니다", type: "numbering-rule", tableFilter: "inbound" },
{ key: "barcodeEnabled", label: "바코드 스캔 (미구현)", description: "바코드/QR 스캔 기능을 사용합니다", type: "toggle" }, { key: "barcodeEnabled", label: "바코드 스캔 (미구현)", description: "바코드/QR 스캔 기능을 사용합니다", type: "toggle" },
{ key: "inspectionRequired", label: "검사 필수 (미구현)", description: "입고 시 검사 항목을 필수로 표시합니다", type: "toggle" }, { key: "inspectionRequired", label: "검사 필수 (미구현)", description: "입고 시 검사 항목을 필수로 표시합니다", type: "toggle" },
{ key: "photoUpload", 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" }, { key: "defectSeparation", label: "불량 분리 (미구현)", description: "양품/불량 수량을 분리 입력합니다", type: "toggle" },
], ],
outbound: [ outbound: [
{ key: "numberingRuleId", label: "📋 출고번호 채번규칙", description: "출고 확정 시 사용할 채번규칙을 선택합니다", type: "text" }, { key: "numberingRuleId", label: "📋 출고번호 채번규칙", description: "출고 확정 시 사용할 채번규칙을 선택합니다", type: "numbering-rule", tableFilter: "outbound" },
{ key: "barcodeEnabled", label: "바코드 스캔 (미구현)", description: "바코드/QR 스캔 기능을 사용합니다", type: "toggle" }, { key: "barcodeEnabled", label: "바코드 스캔 (미구현)", description: "바코드/QR 스캔 기능을 사용합니다", type: "toggle" },
{ key: "photoUpload", label: "사진 첨부 (미구현)", description: "출고 시 사진 첨부를 허용합니다", 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 // Sub-components: TagEditor, ArrayObjectEditor
// ============================================================ // ============================================================
@@ -484,6 +561,8 @@ function SettingRow({
</Select> </Select>
</div> </div>
); );
case "numbering-rule":
return <NumberingRuleSelect field={field} value={value} onChange={onChange} />;
case "color": case "color":
return ( return (
<div className="py-3 border-b last:border-0 space-y-1.5"> <div className="py-3 border-b last:border-0 space-y-1.5">
+3 -3
View File
@@ -1,7 +1,7 @@
sonar.projectKey=vexplor sonar.projectKey=vexplor
sonar.projectName=vexplor sonar.projectName=vexplor
sonar.sources=backend-node/src,frontend/src sonar.sources=backend-node/src
sonar.exclusions=**/node_modules/**,**/dist/**,**/*.test.*,**/test-scenarios/** sonar.exclusions=**/node_modules/**,**/dist/**,**/*.test.*,**/test-scenarios/**,**/build/**,**/.next/**
sonar.javascript.lcov.reportPaths=coverage/lcov.info
sonar.host.url=http://localhost:9000 sonar.host.url=http://localhost:9000
sonar.sourceEncoding=UTF-8 sonar.sourceEncoding=UTF-8
sonar.scm.disabled=true