Merge branch 'main' of http://39.117.244.52:3000/kjs/ERP-node into jskim-node
This commit is contained in:
@@ -20,6 +20,7 @@ import {
|
||||
GeneratedLocation,
|
||||
RackStructureContext,
|
||||
} from "./types";
|
||||
import { defaultFormatConfig, buildFormattedString } from "./config";
|
||||
|
||||
// 기존 위치 데이터 타입
|
||||
interface ExistingLocation {
|
||||
@@ -95,12 +96,12 @@ const ConditionCard: React.FC<ConditionCardProps> = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative rounded-lg border border-border bg-white shadow-sm">
|
||||
<div className="border-border relative rounded-lg border bg-white shadow-sm">
|
||||
{/* 헤더 */}
|
||||
<div className="flex items-center justify-between rounded-t-lg bg-primary px-4 py-2 text-white">
|
||||
<div className="bg-primary flex items-center justify-between rounded-t-lg px-4 py-2 text-white">
|
||||
<span className="font-medium">조건 {index + 1}</span>
|
||||
{!readonly && (
|
||||
<button onClick={() => onRemove(condition.id)} className="rounded p-1 transition-colors hover:bg-primary/90">
|
||||
<button onClick={() => onRemove(condition.id)} className="hover:bg-primary/90 rounded p-1 transition-colors">
|
||||
<X className="h-4 w-4" />
|
||||
</button>
|
||||
)}
|
||||
@@ -111,7 +112,7 @@ const ConditionCard: React.FC<ConditionCardProps> = ({
|
||||
{/* 열 범위 */}
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex-1">
|
||||
<label className="mb-1 block text-xs font-medium text-foreground">
|
||||
<label className="text-foreground mb-1 block text-xs font-medium">
|
||||
열 범위 <span className="text-destructive">*</span>
|
||||
</label>
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -139,7 +140,7 @@ const ConditionCard: React.FC<ConditionCardProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-20">
|
||||
<label className="mb-1 block text-xs font-medium text-foreground">
|
||||
<label className="text-foreground mb-1 block text-xs font-medium">
|
||||
단 수 <span className="text-destructive">*</span>
|
||||
</label>
|
||||
<Input
|
||||
@@ -156,7 +157,7 @@ const ConditionCard: React.FC<ConditionCardProps> = ({
|
||||
</div>
|
||||
|
||||
{/* 계산 결과 */}
|
||||
<div className="rounded-md bg-primary/10 px-3 py-2 text-center text-sm text-primary">
|
||||
<div className="bg-primary/10 text-primary rounded-md px-3 py-2 text-center text-sm">
|
||||
{locationCount > 0 ? (
|
||||
<>
|
||||
{localValues.startRow}열 ~ {localValues.endRow}열 x {localValues.levels}단 ={" "}
|
||||
@@ -288,11 +289,10 @@ export const RackStructureComponent: React.FC<RackStructureComponentProps> = ({
|
||||
return ctx;
|
||||
}, [propContext, formData, fieldMapping, getCategoryLabel]);
|
||||
|
||||
// 필수 필드 검증
|
||||
// 필수 필드 검증 (층은 선택 입력)
|
||||
const missingFields = useMemo(() => {
|
||||
const missing: string[] = [];
|
||||
if (!context.warehouseCode) missing.push("창고 코드");
|
||||
if (!context.floor) missing.push("층");
|
||||
if (!context.zone) missing.push("구역");
|
||||
return missing;
|
||||
}, [context]);
|
||||
@@ -377,9 +377,8 @@ export const RackStructureComponent: React.FC<RackStructureComponentProps> = ({
|
||||
// 기존 데이터 조회 (창고/층/구역이 변경될 때마다)
|
||||
useEffect(() => {
|
||||
const loadExistingLocations = async () => {
|
||||
// 필수 조건이 충족되지 않으면 기존 데이터 초기화
|
||||
// DB에는 라벨 값(예: "1층", "A구역")으로 저장되어 있으므로 라벨 값 사용
|
||||
if (!warehouseCodeForQuery || !floorForQuery || !zoneForQuery) {
|
||||
// 창고 코드와 구역은 필수, 층은 선택
|
||||
if (!warehouseCodeForQuery || !zoneForQuery) {
|
||||
setExistingLocations([]);
|
||||
setDuplicateErrors([]);
|
||||
return;
|
||||
@@ -387,14 +386,13 @@ export const RackStructureComponent: React.FC<RackStructureComponentProps> = ({
|
||||
|
||||
setIsCheckingDuplicates(true);
|
||||
try {
|
||||
// warehouse_location 테이블에서 해당 창고/층/구역의 기존 데이터 조회
|
||||
// DB에는 라벨 값으로 저장되어 있으므로 라벨 값으로 필터링
|
||||
// equals 연산자를 사용하여 정확한 일치 검색 (ILIKE가 아닌 = 연산자 사용)
|
||||
const searchParams = {
|
||||
const searchParams: Record<string, any> = {
|
||||
warehouse_code: { value: warehouseCodeForQuery, operator: "equals" },
|
||||
floor: { value: floorForQuery, operator: "equals" },
|
||||
zone: { value: zoneForQuery, operator: "equals" },
|
||||
};
|
||||
if (floorForQuery) {
|
||||
searchParams.floor = { value: floorForQuery, operator: "equals" };
|
||||
}
|
||||
|
||||
// 직접 apiClient 사용하여 정확한 형식으로 요청
|
||||
// 백엔드는 search를 객체로 받아서 각 필드를 WHERE 조건으로 처리
|
||||
@@ -493,23 +491,26 @@ export const RackStructureComponent: React.FC<RackStructureComponentProps> = ({
|
||||
return { totalLocations, totalRows, maxLevel };
|
||||
}, [conditions]);
|
||||
|
||||
// 위치 코드 생성
|
||||
// 포맷 설정 (ConfigPanel에서 관리자가 설정한 값, 미설정 시 기본값)
|
||||
const formatConfig = config.formatConfig || defaultFormatConfig;
|
||||
|
||||
// 위치 코드 생성 (세그먼트 기반 - 순서/구분자/라벨/자릿수 모두 formatConfig에 따름)
|
||||
const generateLocationCode = useCallback(
|
||||
(row: number, level: number): { code: string; name: string } => {
|
||||
const warehouseCode = context?.warehouseCode || "WH001";
|
||||
const floor = context?.floor || "1";
|
||||
const zone = context?.zone || "A";
|
||||
const values: Record<string, string> = {
|
||||
warehouseCode: context?.warehouseCode || "WH001",
|
||||
floor: context?.floor || "",
|
||||
zone: context?.zone || "A",
|
||||
row: row.toString(),
|
||||
level: level.toString(),
|
||||
};
|
||||
|
||||
// 코드 생성 (예: WH001-1층D구역-01-1)
|
||||
const code = `${warehouseCode}-${floor}${zone}-${row.toString().padStart(2, "0")}-${level}`;
|
||||
|
||||
// 이름 생성 - zone에 이미 "구역"이 포함되어 있으면 그대로 사용
|
||||
const zoneName = zone.includes("구역") ? zone : `${zone}구역`;
|
||||
const name = `${zoneName}-${row.toString().padStart(2, "0")}열-${level}단`;
|
||||
const code = buildFormattedString(formatConfig.codeSegments, values);
|
||||
const name = buildFormattedString(formatConfig.nameSegments, values);
|
||||
|
||||
return { code, name };
|
||||
},
|
||||
[context],
|
||||
[context, formatConfig],
|
||||
);
|
||||
|
||||
// 미리보기 생성
|
||||
@@ -626,7 +627,7 @@ export const RackStructureComponent: React.FC<RackStructureComponentProps> = ({
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between pb-2">
|
||||
<CardTitle className="flex items-center gap-2 text-base">
|
||||
<div className="h-4 w-1 rounded bg-gradient-to-b from-green-500 to-primary/50" />렉 라인 구조 설정
|
||||
<div className="to-primary/50 h-4 w-1 rounded bg-gradient-to-b from-green-500" />렉 라인 구조 설정
|
||||
</CardTitle>
|
||||
{!readonly && (
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -719,8 +720,8 @@ export const RackStructureComponent: React.FC<RackStructureComponentProps> = ({
|
||||
|
||||
{/* 기존 데이터 존재 알림 */}
|
||||
{!isCheckingDuplicates && existingLocations.length > 0 && !hasDuplicateWithExisting && (
|
||||
<Alert className="mb-4 border-primary/20 bg-primary/10">
|
||||
<AlertCircle className="h-4 w-4 text-primary" />
|
||||
<Alert className="border-primary/20 bg-primary/10 mb-4">
|
||||
<AlertCircle className="text-primary h-4 w-4" />
|
||||
<AlertDescription className="text-primary">
|
||||
해당 창고/층/구역에 <strong>{existingLocations.length}개</strong>의 위치가 이미 등록되어 있습니다.
|
||||
</AlertDescription>
|
||||
@@ -729,9 +730,9 @@ export const RackStructureComponent: React.FC<RackStructureComponentProps> = ({
|
||||
|
||||
{/* 현재 매핑된 값 표시 */}
|
||||
{(context.warehouseCode || context.warehouseName || context.floor || context.zone) && (
|
||||
<div className="mb-4 flex flex-wrap gap-2 rounded-lg bg-muted p-3">
|
||||
<div className="bg-muted mb-4 flex flex-wrap gap-2 rounded-lg p-3">
|
||||
{(context.warehouseCode || context.warehouseName) && (
|
||||
<span className="rounded bg-primary/10 px-2 py-1 text-xs text-primary">
|
||||
<span className="bg-primary/10 text-primary rounded px-2 py-1 text-xs">
|
||||
창고: {context.warehouseName || context.warehouseCode}
|
||||
{context.warehouseName && context.warehouseCode && ` (${context.warehouseCode})`}
|
||||
</span>
|
||||
@@ -748,28 +749,28 @@ export const RackStructureComponent: React.FC<RackStructureComponentProps> = ({
|
||||
</span>
|
||||
)}
|
||||
{context.status && (
|
||||
<span className="rounded bg-muted/80 px-2 py-1 text-xs text-foreground">상태: {context.status}</span>
|
||||
<span className="bg-muted/80 text-foreground rounded px-2 py-1 text-xs">상태: {context.status}</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 안내 메시지 */}
|
||||
<div className="mb-4 rounded-lg bg-primary/10 p-4">
|
||||
<ol className="space-y-1 text-sm text-primary">
|
||||
<div className="bg-primary/10 mb-4 rounded-lg p-4">
|
||||
<ol className="text-primary space-y-1 text-sm">
|
||||
<li className="flex items-start gap-2">
|
||||
<span className="flex h-5 w-5 shrink-0 items-center justify-center rounded bg-primary text-xs font-bold text-white">
|
||||
<span className="bg-primary flex h-5 w-5 shrink-0 items-center justify-center rounded text-xs font-bold text-white">
|
||||
1
|
||||
</span>
|
||||
조건 추가 버튼을 클릭하여 렉 라인 조건을 생성하세요
|
||||
</li>
|
||||
<li className="flex items-start gap-2">
|
||||
<span className="flex h-5 w-5 shrink-0 items-center justify-center rounded bg-primary text-xs font-bold text-white">
|
||||
<span className="bg-primary flex h-5 w-5 shrink-0 items-center justify-center rounded text-xs font-bold text-white">
|
||||
2
|
||||
</span>
|
||||
각 조건마다 열 범위와 단 수를 입력하세요
|
||||
</li>
|
||||
<li className="flex items-start gap-2">
|
||||
<span className="flex h-5 w-5 shrink-0 items-center justify-center rounded bg-primary text-xs font-bold text-white">
|
||||
<span className="bg-primary flex h-5 w-5 shrink-0 items-center justify-center rounded text-xs font-bold text-white">
|
||||
3
|
||||
</span>
|
||||
예시: 조건1(1~3열, 3단), 조건2(4~6열, 5단)
|
||||
@@ -779,9 +780,9 @@ export const RackStructureComponent: React.FC<RackStructureComponentProps> = ({
|
||||
|
||||
{/* 조건 목록 또는 빈 상태 */}
|
||||
{conditions.length === 0 ? (
|
||||
<div className="flex flex-col items-center justify-center rounded-lg border-2 border-dashed border-border py-12">
|
||||
<div className="mb-4 text-6xl text-muted-foreground/50">📦</div>
|
||||
<p className="mb-4 text-muted-foreground">조건을 추가하여 렉 구조를 설정하세요</p>
|
||||
<div className="border-border flex flex-col items-center justify-center rounded-lg border-2 border-dashed py-12">
|
||||
<div className="text-muted-foreground/50 mb-4 text-6xl">📦</div>
|
||||
<p className="text-muted-foreground mb-4">조건을 추가하여 렉 구조를 설정하세요</p>
|
||||
{!readonly && (
|
||||
<Button onClick={addCondition} className="gap-1">
|
||||
<Plus className="h-4 w-4" />첫 번째 조건 추가
|
||||
@@ -832,15 +833,15 @@ export const RackStructureComponent: React.FC<RackStructureComponentProps> = ({
|
||||
{config.showStatistics && (
|
||||
<div className="mb-4 grid grid-cols-3 gap-4">
|
||||
<div className="rounded-lg border bg-white p-4 text-center">
|
||||
<div className="text-sm text-muted-foreground">총 위치</div>
|
||||
<div className="text-muted-foreground text-sm">총 위치</div>
|
||||
<div className="text-2xl font-bold">{statistics.totalLocations}개</div>
|
||||
</div>
|
||||
<div className="rounded-lg border bg-white p-4 text-center">
|
||||
<div className="text-sm text-muted-foreground">열 수</div>
|
||||
<div className="text-muted-foreground text-sm">열 수</div>
|
||||
<div className="text-2xl font-bold">{statistics.totalRows}개</div>
|
||||
</div>
|
||||
<div className="rounded-lg border bg-white p-4 text-center">
|
||||
<div className="text-sm text-muted-foreground">최대 단</div>
|
||||
<div className="text-muted-foreground text-sm">최대 단</div>
|
||||
<div className="text-2xl font-bold">{statistics.maxLevel}단</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -851,7 +852,7 @@ export const RackStructureComponent: React.FC<RackStructureComponentProps> = ({
|
||||
<div className="rounded-lg border">
|
||||
<ScrollArea className="h-[400px]">
|
||||
<Table>
|
||||
<TableHeader className="sticky top-0 bg-muted">
|
||||
<TableHeader className="bg-muted sticky top-0">
|
||||
<TableRow>
|
||||
<TableHead className="w-12 text-center">No</TableHead>
|
||||
<TableHead>위치코드</TableHead>
|
||||
@@ -870,7 +871,7 @@ export const RackStructureComponent: React.FC<RackStructureComponentProps> = ({
|
||||
<TableCell className="text-center">{idx + 1}</TableCell>
|
||||
<TableCell className="font-mono">{loc.location_code}</TableCell>
|
||||
<TableCell>{loc.location_name}</TableCell>
|
||||
<TableCell className="text-center">{loc.floor || context?.floor || "1"}</TableCell>
|
||||
<TableCell className="text-center">{loc.floor || context?.floor || "-"}</TableCell>
|
||||
<TableCell className="text-center">{loc.zone || context?.zone || "A"}</TableCell>
|
||||
<TableCell className="text-center">{loc.row_num.padStart(2, "0")}</TableCell>
|
||||
<TableCell className="text-center">{loc.level_num}</TableCell>
|
||||
@@ -883,8 +884,8 @@ export const RackStructureComponent: React.FC<RackStructureComponentProps> = ({
|
||||
</ScrollArea>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col items-center justify-center rounded-lg border-2 border-dashed border-border py-8 text-muted-foreground">
|
||||
<Eye className="mb-2 h-8 w-8 text-muted-foreground/50" />
|
||||
<div className="border-border text-muted-foreground flex flex-col items-center justify-center rounded-lg border-2 border-dashed py-8">
|
||||
<Eye className="text-muted-foreground/50 mb-2 h-8 w-8" />
|
||||
<p>미리보기 생성 버튼을 클릭하여 결과를 확인하세요</p>
|
||||
</div>
|
||||
)}
|
||||
@@ -931,16 +932,16 @@ export const RackStructureComponent: React.FC<RackStructureComponentProps> = ({
|
||||
{/* 템플릿 목록 */}
|
||||
{templates.length > 0 ? (
|
||||
<div className="space-y-2">
|
||||
<div className="text-sm font-medium text-foreground">저장된 템플릿</div>
|
||||
<div className="text-foreground text-sm font-medium">저장된 템플릿</div>
|
||||
<ScrollArea className="h-[200px]">
|
||||
{templates.map((template) => (
|
||||
<div
|
||||
key={template.id}
|
||||
className="flex items-center justify-between rounded-lg border p-3 hover:bg-muted"
|
||||
className="hover:bg-muted flex items-center justify-between rounded-lg border p-3"
|
||||
>
|
||||
<div>
|
||||
<div className="font-medium">{template.name}</div>
|
||||
<div className="text-xs text-muted-foreground">{template.conditions.length}개 조건</div>
|
||||
<div className="text-muted-foreground text-xs">{template.conditions.length}개 조건</div>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button variant="outline" size="sm" onClick={() => loadTemplate(template)}>
|
||||
@@ -955,7 +956,7 @@ export const RackStructureComponent: React.FC<RackStructureComponentProps> = ({
|
||||
</ScrollArea>
|
||||
</div>
|
||||
) : (
|
||||
<div className="py-8 text-center text-muted-foreground">저장된 템플릿이 없습니다</div>
|
||||
<div className="text-muted-foreground py-8 text-center">저장된 템플릿이 없습니다</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user