Merge branch 'main' of http://39.117.244.52:3000/kjs/ERP-node into jskim-node

This commit is contained in:
kjs
2026-03-12 14:23:34 +09:00
99 changed files with 14205 additions and 1442 deletions

View File

@@ -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>
)}