# 품목관리 검색 상태 보존 — React UX 개선 요청서

> **작성일**: 2026-03-19
> **요청자**: R&D실
> **대상**: React 프론트엔드 개발자
> **우선순위**: 중간 (UX 개선)
> **영향 범위**: React만 수정 (API 변경 없음)

---

## 1. 문제 설명

### 1.1 현상

품목관리 목록(`/production/screen-production` 또는 `/items`)에서:

1. 검색어 "실리카" 입력 → 4건 필터링
2. 품목 클릭 → 상세 페이지 이동
3. 수정 후 저장 → 목록으로 복귀
4. **검색어가 초기화되어 전체 1,175건이 다시 표시됨**

```
[목록] 검색: "실리카" (4건)
    ↓ 클릭
[상세] 품목 조회
    ↓ 수정 → 저장
[목록] 검색: "" (초기화됨, 1,175건) ← 문제!
```

### 1.2 기대 동작

수정/조회 후 목록으로 돌아오면 **이전 검색어와 탭 필터가 그대로 유지**되어야 한다.

---

## 2. 원인 분석

### 2.1 근본 원인: 검색 상태가 컴포넌트 state에만 존재

`ItemListClient.tsx:76`에서 검색어가 `useState('')`로 초기화된다. URL에 반영되지 않으므로 페이지 이동 후 복귀하면 상태가 소멸된다.

```typescript
// ItemListClient.tsx:76 — 항상 빈 문자열로 시작
const [searchTerm, setSearchTerm] = useState('');
const [selectedType, setSelectedType] = useState<string>('all');
```

### 2.2 직접 원인: 목록 복귀 시 `router.push` 사용

수정/등록 완료 후 검색 파라미터 없이 하드코딩된 경로로 이동한다:

| 파일 | 라인 | 현재 코드 |
|------|------|----------|
| `DynamicItemForm/index.tsx` | 408 | `router.push('/production/screen-production')` |
| `ItemDetailEdit.tsx` | 353, 368 | `router.push('/production/screen-production')` |
| `ItemForm/index.tsx` | 178 | `router.push('/production/screen-production')` |

> 참고: `ItemDetailClient.tsx:568`의 "목록으로" 버튼은 이미 `router.back()`을 사용 중 (정상).

---

## 3. 수정 방안

**2단계 조합**으로 해결한다:

### 3.1 Step 1: 목록 복귀 시 `router.back()` 사용

수정/등록 완료 후 `router.push(...)` 대신 `router.back()`을 사용한다.
브라우저 히스토리의 이전 페이지로 복귀하므로 검색 상태가 자연스럽게 보존된다.

**수정 대상 3개 파일:**

#### (1) `src/components/items/DynamicItemForm/index.tsx` (라인 408)

```typescript
// Before
router.push('/production/screen-production');
router.refresh();

// After
router.back();
```

#### (2) `src/components/items/ItemDetailEdit.tsx` (라인 353, 368)

```typescript
// Before (2곳 모두)
onClick={() => router.push('/production/screen-production')}

// After (2곳 모두)
onClick={() => router.back()}
```

#### (3) `src/components/items/ItemForm/index.tsx` (라인 178)

```typescript
// Before
router.push('/production/screen-production');
router.refresh();

// After
router.back();
```

### 3.2 Step 2: URL searchParams 동기화 (선택사항 — 권장)

> Step 1만으로도 기본 동작은 해결된다. Step 2는 **새로고침/URL 공유 시에도 검색 상태를 보존**하기 위한 추가 개선이다.

`ItemListClient.tsx`에서 검색어와 탭 필터를 URL 쿼리 파라미터에 동기화한다.

#### 변경 1: URL에서 초기값 읽기

```typescript
// Before (ItemListClient.tsx:76)
const [searchTerm, setSearchTerm] = useState('');
const [selectedType, setSelectedType] = useState<string>('all');

// After
import { useSearchParams } from 'next/navigation';

const searchParams = useSearchParams();
const [searchTerm, setSearchTerm] = useState(searchParams.get('search') || '');
const [selectedType, setSelectedType] = useState<string>(searchParams.get('type') || 'all');
```

#### 변경 2: 검색/탭 변경 시 URL 업데이트

```typescript
// ItemListClient.tsx — 새로 추가할 useEffect
import { useRouter, useSearchParams, usePathname } from 'next/navigation';

const pathname = usePathname();

useEffect(() => {
  const params = new URLSearchParams();
  if (debouncedSearchTerm) params.set('search', debouncedSearchTerm);
  if (selectedType !== 'all') params.set('type', selectedType);

  const queryString = params.toString();
  const newUrl = queryString ? `${pathname}?${queryString}` : pathname;

  // 히스토리 교체 (뒤로가기 스택 오염 방지)
  window.history.replaceState(null, '', newUrl);
}, [debouncedSearchTerm, selectedType, pathname]);
```

#### 변경 3: handleView에서 검색 파라미터 전달 (선택)

```typescript
// ItemListClient.tsx:133 — 상세 페이지 이동 시 returnUrl 전달
const handleView = (itemCode: string, itemType: string, itemId: string) => {
  const returnParams = new URLSearchParams();
  if (searchTerm) returnParams.set('search', searchTerm);
  if (selectedType !== 'all') returnParams.set('type', selectedType);

  router.push(
    `/production/screen-production/${encodeURIComponent(itemCode)}?mode=view&type=${itemType}&id=${itemId}`
  );
};
```

> Step 2를 적용하면 URL이 `/items?search=실리카&type=all` 형태가 되어 새로고침/공유에도 상태가 유지된다.

---

## 4. 수정 파일 요약

| 파일 | 수정 내용 | 필수/선택 |
|------|----------|:--------:|
| `src/components/items/DynamicItemForm/index.tsx` | `router.push(...)` → `router.back()` | 필수 |
| `src/components/items/ItemDetailEdit.tsx` | `router.push(...)` → `router.back()` (2곳) | 필수 |
| `src/components/items/ItemForm/index.tsx` | `router.push(...)` → `router.back()` | 필수 |
| `src/components/items/ItemListClient.tsx` | URL searchParams 동기화 | 권장 |

---

## 5. 주의사항

### 5.1 `router.back()` 사용 시 고려점

- 사용자가 **URL 직접 입력**으로 수정 페이지에 진입한 경우, `router.back()`은 품목 목록이 아닌 이전 페이지(브라우저 탭의 히스토리)로 이동할 수 있다
- 이 경우를 대비하려면 `router.back()` 대신 **referrer 체크 후 fallback** 패턴을 사용할 수 있다:

```typescript
// 안전한 뒤로가기 패턴 (선택)
const handleGoBack = () => {
  if (window.history.length > 1) {
    router.back();
  } else {
    router.push('/production/screen-production');
  }
};
```

### 5.2 `router.refresh()` 제거 여부

`DynamicItemForm/index.tsx:409`와 `ItemForm/index.tsx:179`에서 `router.refresh()`를 호출 중이다. `router.back()`으로 전환하면 `router.refresh()`는 불필요하다 (이전 페이지의 캐시된 상태로 복귀하므로). 단, 저장 후 목록 데이터 갱신이 필요하면 `ItemListClient`의 데이터 fetching이 마운트 시 자동 실행되는지 확인 필요.

### 5.3 `UniversalListPage`의 `externalSearch` prop

`UniversalListPage`에 이미 `externalSearch` prop이 존재한다. Step 2 구현 시 이 prop을 활용하면 `UniversalListPage` 내부의 검색 input 값도 URL과 동기화할 수 있다.

---

## 6. 테스트 체크리스트

- [ ] 검색어 입력 후 품목 클릭 → 상세 조회 → "목록으로" → 검색어 유지 확인
- [ ] 검색어 입력 후 품목 클릭 → 수정 → 저장 → 검색어 유지 확인
- [ ] 검색어 입력 후 품목 등록(`?mode=new`) → 저장 → 검색어 유지 확인
- [ ] 탭 필터(완제품/부품 등) 선택 후 상세 → 복귀 → 탭 유지 확인
- [ ] (Step 2) 검색 후 페이지 새로고침 → 검색어 유지 확인
- [ ] (Step 2) 검색 상태 URL 복사 → 새 탭에서 열기 → 동일 결과 확인
- [ ] 에러 상태에서 "품목 목록으로 돌아가기" 클릭 → 목록 정상 표시 확인

---

## 관련 문서

- 품목 정책: `rules/item-policy.md`

---

**최종 업데이트**: 2026-03-19
