feat: POP 화면설정 채번규칙 셀렉트 박스 구현
- 새 type "numbering-rule" 추가 - NumberingRuleSelect 컴포넌트: 회사별 채번규칙 목록 자동 로드 - 입고/출고 설정에서 inbound/outbound 키워드로 필터링 - 등록된 채번규칙이 없으면 안내 메시지 표시
This commit is contained in:
@@ -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">
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user