- ItemMasterDataManagement 컴포넌트 구조화 (tabs, dialogs, components 분리) - HierarchyTab 타입 에러 수정 (BOMItem section_id, updated_at 추가) - API 클라이언트 구현 (item-master.ts, 13개 엔드포인트) - ItemMasterContext 구현 (상태 관리 및 데이터 흐름) - 백엔드 요구사항 문서 작성 (CORS 설정, API 스펙 등) - SSR 호환성 수정 (navigator API typeof window 체크) - 미사용 변수 ESLint 에러 해결 - Context 리팩토링 (AuthContext, RootProvider 추가) - API 유틸리티 추가 (error-handler, logger, transformers) 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
26 KiB
26 KiB
품목기준관리 API 설계 문서
작성일: 2025-11-18 목적: 품목기준관리 페이지의 설정 데이터를 서버와 동기화하기 위한 API 구조 설계
📋 목차
개요
테넌트 정보 구조
본 시스템은 로그인 시 받는 실제 테넌트 정보 구조를 기반으로 설계되었습니다.
// 로그인 성공 시 받는 실제 사용자 정보
{
userId: "TestUser3",
name: "드미트리",
tenant: {
id: 282, // ✅ 테넌트 고유 ID (number 타입)
company_name: "(주)테크컴퍼니", // 테넌트 회사명
business_num: "123-45-67890", // 사업자 번호
tenant_st_code: "trial" // 테넌트 상태 코드
}
}
중요: API 엔드포인트의 {tenantId}는 위 구조의 tenant.id 값(number 타입, 예: 282)을 의미합니다.
시스템 흐름
┌──────────────────────────────┐
│ 로그인 (Login) │
│ tenant.id: 282 (number) │
└────────┬─────────────────────┘
│
▼
┌──────────────────────────────┐
│ 테넌트 (Tenant) │
│ 고유 필드 구성 │
│ tenant.id 기반 격리 │
└────────┬─────────────────────┘
│
▼
┌────────────────────────────────┐
│ 품목기준관리 페이지 │
│ (Item Master Config Page) │
│ │
│ - 페이지 구조 설정 │
│ - 섹션 구성 │
│ - 필드 정의 │
│ - 마스터 데이터 관리 │
│ - 버전 관리 │
└────────┬───────────────────────┘
│ Save (with tenant.id)
▼
┌────────────────────────────────┐
│ API Server │
│ (Backend) │
│ │
│ - 테넌트별 데이터 저장 │
│ - tenant.id 검증 │
│ - 버전 관리 │
│ - 유효성 검증 │
└────────┬───────────────────────┘
│ Load (filtered by tenant.id)
▼
┌────────────────────────────────┐
│ 품목관리 페이지 │
│ (Item Management Page) │
│ │
│ - 설정 기반 동적 폼 생성 │
│ - 실제 품목 데이터 입력 │
└────────────────────────────────┘
핵심 요구사항
- 테넌트 격리: 각 테넌트별로 독립적인 설정 (
tenant.id기반 완전 격리) - 계층 구조: Page → Section → Field 3단계 계층
- 버전 관리: 설정 변경 이력 추적
- 재사용성: 템플릿 기반 섹션/필드 재사용
- 동적 생성: 설정 기반 품목관리 페이지 동적 렌더링
- 서버 검증: JWT의 tenant.id와 API 요청의 tenantId 일치 검증
데이터 구조 분석
1. 계층 구조 (Hierarchical Structure)
ItemMasterConfig (전체 설정)
│
├─ ItemPage[] (페이지 배열)
│ ├─ id
│ ├─ pageName
│ ├─ itemType (FG/PT/SM/RM/CS)
│ └─ sections[]
│ │
│ ├─ ItemSection (섹션)
│ │ ├─ id
│ │ ├─ title
│ │ ├─ type ('fields' | 'bom')
│ │ ├─ order
│ │ └─ fields[]
│ │ │
│ │ └─ ItemField (필드)
│ │ ├─ id
│ │ ├─ name
│ │ ├─ fieldKey
│ │ ├─ property (ItemFieldProperty)
│ │ └─ displayCondition
│
├─ SectionTemplate[] (재사용 섹션 템플릿)
│
├─ ItemMasterField[] (재사용 필드 템플릿)
│
└─ MasterData (마스터 데이터들)
├─ SpecificationMaster[]
├─ MaterialItemName[]
├─ ItemCategory[]
├─ ItemUnit[]
├─ ItemMaterial[]
├─ SurfaceTreatment[]
├─ PartTypeOption[]
├─ PartUsageOption[]
└─ GuideRailOption[]
2. 저장해야 할 데이터 범위
✅ 저장 필수 데이터
- 페이지 구조 (
itemPages) - 섹션 템플릿 (
sectionTemplates) - 항목 마스터 (
itemMasterFields) - 마스터 데이터 (9가지):
- 규격 마스터 (
specificationMasters) - 품목명 마스터 (
materialItemNames) - 품목 분류 (
itemCategories) - 단위 (
itemUnits) - 재질 (
itemMaterials) - 표면처리 (
surfaceTreatments) - 부품 유형 옵션 (
partTypeOptions) - 부품 용도 옵션 (
partUsageOptions) - 가이드레일 옵션 (
guideRailOptions)
- 규격 마스터 (
❌ 저장 불필요 데이터
- 실제 품목 데이터 (
itemMasters) - 별도 API로 관리
API 엔드포인트 설계
Base URL
/api/tenants/{tenantId}/item-master-config
참고: {tenantId}는 로그인 응답의 tenant.id 값(number 타입)입니다. 예: /api/tenants/282/item-master-config
서버 검증 (Server-side Validation)
모든 API 요청에서 다음 검증을 수행해야 합니다:
// Middleware 예시
async function validateTenantAccess(req, res, next) {
// 1. JWT에서 사용자의 tenant.id 추출
const userTenantId = req.user.tenant.id; // number (예: 282)
// 2. URL 파라미터의 tenantId 추출 및 타입 변환
const requestedTenantId = parseInt(req.params.tenantId, 10);
// 3. 일치 검증
if (userTenantId !== requestedTenantId) {
return res.status(403).json({
success: false,
error: {
code: "FORBIDDEN",
message: "접근 권한이 없습니다.",
details: {
userTenantId,
requestedTenantId,
reason: "테넌트 ID 불일치"
}
}
});
}
next();
}
1. 전체 설정 조회 (GET)
엔드포인트
GET /api/tenants/{tenantId}/item-master-config
예시: GET /api/tenants/282/item-master-config
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| version | string | No | 버전 (기본값: latest) |
| includeInactive | boolean | No | 비활성 항목 포함 여부 (기본값: false) |
Response
{
"success": true,
"data": {
"tenantId": 282, // ✅ number 타입
"version": "1.0",
"lastUpdated": "2025-11-18T10:30:00Z",
"updatedBy": "TestUser3",
"config": {
// 페이지 구조
"pages": ItemPage[],
// 재사용 템플릿
"sectionTemplates": SectionTemplate[],
"itemMasterFields": ItemMasterField[],
// 마스터 데이터
"masters": {
"specifications": SpecificationMaster[],
"materialNames": MaterialItemName[],
"categories": ItemCategory[],
"units": ItemUnit[],
"materials": ItemMaterial[],
"surfaceTreatments": SurfaceTreatment[],
"partTypes": PartTypeOption[],
"partUsages": PartUsageOption[],
"guideRailOptions": GuideRailOption[]
}
}
}
}
2. 전체 설정 저장 (POST/PUT)
엔드포인트
POST /api/tenants/{tenantId}/item-master-config
PUT /api/tenants/{tenantId}/item-master-config/{version}
Request Body
{
"version": "1.0", // 버전 명시 (PUT의 경우 URL의 version과 일치해야 함)
"comment": "초기 설정 저장", // 변경 사유 (선택)
"config": {
"pages": ItemPage[],
"sectionTemplates": SectionTemplate[],
"itemMasterFields": ItemMasterField[],
"masters": {
"specifications": SpecificationMaster[],
"materialNames": MaterialItemName[],
"categories": ItemCategory[],
"units": ItemUnit[],
"materials": ItemMaterial[],
"surfaceTreatments": SurfaceTreatment[],
"partTypes": PartTypeOption[],
"partUsages": PartUsageOption[],
"guideRailOptions": GuideRailOption[]
}
}
}
Response
{
"success": true,
"data": {
"tenantId": 282, // ✅ number 타입
"version": "1.0",
"savedAt": "2025-11-18T10:30:00Z",
"savedBy": "TestUser3"
},
"message": "설정이 성공적으로 저장되었습니다."
}
3. 특정 페이지 조회 (GET)
엔드포인트
GET /api/tenants/{tenantId}/item-master-config/pages/{pageId}
예시: GET /api/tenants/282/item-master-config/pages/PAGE-001
Response
{
"success": true,
"data": {
"page": ItemPage,
"metadata": {
"tenantId": 282, // ✅ number 타입
"version": "1.0",
"lastUpdated": "2025-11-18T10:30:00Z"
}
}
}
4. 특정 페이지 업데이트 (PUT)
엔드포인트
PUT /api/tenants/{tenantId}/item-master-config/pages/{pageId}
Request Body
{
"page": ItemPage,
"comment": "페이지 구조 변경"
}
5. 페이지 추가 (POST)
엔드포인트
POST /api/tenants/{tenantId}/item-master-config/pages
Request Body
{
"page": {
"id": "PAGE-001",
"pageName": "제품 등록",
"itemType": "FG",
"sections": [],
"isActive": true,
"createdAt": "2025-11-18T10:30:00Z"
}
}
6. 섹션 템플릿 관리
엔드포인트
GET /api/tenants/{tenantId}/item-master-config/section-templates
POST /api/tenants/{tenantId}/item-master-config/section-templates
PUT /api/tenants/{tenantId}/item-master-config/section-templates/{templateId}
DELETE /api/tenants/{tenantId}/item-master-config/section-templates/{templateId}
7. 항목 마스터 관리
엔드포인트
GET /api/tenants/{tenantId}/item-master-config/item-master-fields
POST /api/tenants/{tenantId}/item-master-config/item-master-fields
PUT /api/tenants/{tenantId}/item-master-config/item-master-fields/{fieldId}
DELETE /api/tenants/{tenantId}/item-master-config/item-master-fields/{fieldId}
8. 마스터 데이터 관리
각 마스터 데이터별 CRUD API
# 규격 마스터
GET /api/tenants/{tenantId}/item-master-config/masters/specifications
POST /api/tenants/{tenantId}/item-master-config/masters/specifications
PUT /api/tenants/{tenantId}/item-master-config/masters/specifications/{id}
DELETE /api/tenants/{tenantId}/item-master-config/masters/specifications/{id}
# 품목명 마스터
GET /api/tenants/{tenantId}/item-master-config/masters/material-names
POST /api/tenants/{tenantId}/item-master-config/masters/material-names
PUT /api/tenants/{tenantId}/item-master-config/masters/material-names/{id}
DELETE /api/tenants/{tenantId}/item-master-config/masters/material-names/{id}
# ... (나머지 마스터 데이터도 동일 패턴)
데이터 모델
1. ItemMasterConfig (전체 설정)
interface ItemMasterConfig {
tenantId: number; // ✅ 테넌트 ID (number 타입, 예: 282)
version: string; // 버전 (1.0, 1.1, 2.0...)
lastUpdated: string; // 마지막 업데이트 시간 (ISO 8601)
updatedBy: string; // 업데이트한 사용자 ID
comment?: string; // 변경 사유
config: {
pages: ItemPage[];
sectionTemplates: SectionTemplate[];
itemMasterFields: ItemMasterField[];
masters: {
specifications: SpecificationMaster[];
materialNames: MaterialItemName[];
categories: ItemCategory[];
units: ItemUnit[];
materials: ItemMaterial[];
surfaceTreatments: SurfaceTreatment[];
partTypes: PartTypeOption[];
partUsages: PartUsageOption[];
guideRailOptions: GuideRailOption[];
};
};
}
2. API Response 공통 형식
성공 응답
interface ApiSuccessResponse<T> {
success: true;
data: T;
message?: string;
metadata?: {
total?: number;
page?: number;
pageSize?: number;
};
}
에러 응답
interface ApiErrorResponse {
success: false;
error: {
code: string; // 에러 코드 (VALIDATION_ERROR, NOT_FOUND 등)
message: string; // 사용자용 에러 메시지
details?: any; // 상세 에러 정보
timestamp: string; // 에러 발생 시간
};
}
저장/불러오기 시나리오
시나리오 1: 초기 설정 저장
상황: 품목기준관리 페이지에서 처음으로 설정을 저장
// 1. 사용자가 Save 버튼 클릭
// 2. Frontend에서 전체 설정 데이터 준비
const configData = {
version: "1.0",
comment: "초기 설정",
config: {
pages: itemPages,
sectionTemplates: sectionTemplates,
itemMasterFields: itemMasterFields,
masters: {
specifications: specificationMasters,
materialNames: materialItemNames,
// ... 나머지 마스터 데이터
}
}
};
// 3. API 호출
const response = await fetch(`/api/tenants/${tenantId}/item-master-config`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(configData)
});
// 4. 성공 시 localStorage 업데이트
if (response.ok) {
localStorage.setItem('mes-itemMasterConfig-version', '1.0');
localStorage.setItem('mes-itemMasterConfig-lastSync', new Date().toISOString());
}
시나리오 2: 설정 불러오기 (페이지 로드)
상황: 품목기준관리 페이지 접속 시
// 1. 컴포넌트 마운트 시 useEffect
useEffect(() => {
const loadConfig = async () => {
try {
// 2. 서버에서 최신 설정 조회
const response = await fetch(
`/api/tenants/${tenantId}/item-master-config?version=latest`
);
const { data } = await response.json();
// 3. Context 상태 업데이트
setItemPages(data.config.pages);
setSectionTemplates(data.config.sectionTemplates);
setItemMasterFields(data.config.itemMasterFields);
setSpecificationMasters(data.config.masters.specifications);
// ... 나머지 데이터 설정
// 4. localStorage에 캐시
localStorage.setItem('mes-itemMasterConfig', JSON.stringify(data));
localStorage.setItem('mes-itemMasterConfig-version', data.version);
localStorage.setItem('mes-itemMasterConfig-lastSync', new Date().toISOString());
} catch (error) {
// 5. 에러 시 localStorage 폴백
const cachedConfig = localStorage.getItem('mes-itemMasterConfig');
if (cachedConfig) {
const data = JSON.parse(cachedConfig);
// ... 캐시된 데이터로 설정
}
}
};
loadConfig();
}, [tenantId]);
시나리오 3: 특정 항목만 업데이트
상황: 규격 마스터 1개만 추가
// 1. 새 규격 마스터 추가
const newSpec = {
id: "SPEC-NEW-001",
specificationCode: "2.0T x 1219 x 2438",
itemType: "RM",
// ... 나머지 필드
};
// 2. 부분 업데이트 API 호출
const response = await fetch(
`/api/tenants/${tenantId}/item-master-config/masters/specifications`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(newSpec)
}
);
// 3. Context 상태 업데이트
if (response.ok) {
addSpecificationMaster(newSpec);
}
시나리오 4: 버전 업그레이드
상황: 기존 설정을 기반으로 새 버전 생성
// 1. 현재 버전 조회
const currentConfig = await fetch(
`/api/tenants/${tenantId}/item-master-config?version=1.0`
).then(res => res.json());
// 2. 수정사항 반영
const updatedConfig = {
...currentConfig.data.config,
pages: [...currentConfig.data.config.pages, newPage]
};
// 3. 새 버전으로 저장
const response = await fetch(
`/api/tenants/${tenantId}/item-master-config`,
{
method: 'POST',
body: JSON.stringify({
version: "1.1",
comment: "신규 페이지 추가",
config: updatedConfig
})
}
);
버전 관리 전략
1. 버전 네이밍 규칙
{MAJOR}.{MINOR}
MAJOR: 구조적 변경 (페이지 추가/삭제, 필드 타입 변경)
MINOR: 데이터 추가 (마스터 데이터 추가, 섹션 추가)
예시:
1.0 - 초기 버전
1.1 - 마스터 데이터 추가
1.2 - 섹션 추가
2.0 - 페이지 구조 변경
2. 버전 관리 테이블 구조
CREATE TABLE item_master_config_versions (
id VARCHAR(50) PRIMARY KEY,
tenant_id BIGINT NOT NULL, -- ✅ number 타입 (tenant.id와 일치)
version VARCHAR(10) NOT NULL,
config JSON NOT NULL,
comment TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
created_by VARCHAR(50),
is_active BOOLEAN DEFAULT TRUE,
UNIQUE KEY unique_tenant_version (tenant_id, version),
INDEX idx_tenant_active (tenant_id, is_active),
FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE
);
참고: tenant_id는 BIGINT 타입으로 정의하여 로그인 응답의 tenant.id(number) 값과 정확히 일치하도록 합니다.
3. 버전 조회 전략
// Latest 버전 조회
GET /api/tenants/{tenantId}/item-master-config?version=latest
// 특정 버전 조회
GET /api/tenants/{tenantId}/item-master-config?version=1.0
// 버전 목록 조회
GET /api/tenants/{tenantId}/item-master-config/versions
// Response:
{
"versions": [
{ "version": "1.0", "createdAt": "2025-11-01", "comment": "초기 버전" },
{ "version": "1.1", "createdAt": "2025-11-10", "comment": "마스터 데이터 추가" },
{ "version": "2.0", "createdAt": "2025-11-18", "comment": "페이지 구조 변경" }
],
"current": "2.0"
}
에러 처리
1. 에러 코드 정의
| Code | HTTP Status | Description |
|---|---|---|
VALIDATION_ERROR |
400 | 데이터 유효성 검증 실패 |
UNAUTHORIZED |
401 | 인증 실패 |
FORBIDDEN |
403 | 권한 없음 (테넌트 접근 권한 없음) |
NOT_FOUND |
404 | 설정 또는 버전을 찾을 수 없음 |
CONFLICT |
409 | 버전 충돌 (이미 존재하는 버전) |
VERSION_MISMATCH |
409 | 버전 불일치 (동시 수정 충돌) |
SERVER_ERROR |
500 | 서버 내부 오류 |
2. 에러 응답 예시
Validation Error
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "입력 데이터가 올바르지 않습니다.",
"details": {
"field": "config.pages[0].sections[0].fields[0].property.inputType",
"message": "inputType은 필수입니다.",
"value": null
},
"timestamp": "2025-11-18T10:30:00Z"
}
}
Version Conflict
{
"success": false,
"error": {
"code": "CONFLICT",
"message": "버전 1.0이 이미 존재합니다.",
"details": {
"existingVersion": "1.0",
"suggestedVersion": "1.1"
},
"timestamp": "2025-11-18T10:30:00Z"
}
}
프론트엔드 구현 가이드
1. API 클라이언트 생성
// src/lib/api/itemMasterConfigApi.ts
export const ItemMasterConfigAPI = {
// 전체 설정 조회
async getConfig(tenantId: number, version = 'latest') { // ✅ number 타입
const response = await fetch(
`/api/tenants/${tenantId}/item-master-config?version=${version}`
);
if (!response.ok) throw new Error('설정 조회 실패');
return response.json();
},
// 전체 설정 저장
async saveConfig(tenantId: number, config: ItemMasterConfig) { // ✅ number 타입
const response = await fetch(
`/api/tenants/${tenantId}/item-master-config`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(config)
}
);
if (!response.ok) throw new Error('설정 저장 실패');
return response.json();
},
// 페이지 조회
async getPage(tenantId: number, pageId: string) { // ✅ number 타입
const response = await fetch(
`/api/tenants/${tenantId}/item-master-config/pages/${pageId}`
);
if (!response.ok) throw new Error('페이지 조회 실패');
return response.json();
},
// 규격 마스터 추가
async addSpecification(tenantId: number, spec: SpecificationMaster) { // ✅ number 타입
const response = await fetch(
`/api/tenants/${tenantId}/item-master-config/masters/specifications`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(spec)
}
);
if (!response.ok) throw new Error('규격 추가 실패');
return response.json();
}
};
사용 예시:
// AuthContext에서 tenant.id를 추출하여 사용
const { user } = useAuth();
const tenantId = user.tenant.id; // number 타입 (예: 282)
// API 호출
const config = await ItemMasterConfigAPI.getConfig(tenantId);
2. Context 통합
// ItemMasterContext.tsx
// 서버 동기화 함수 추가
const syncWithServer = async () => {
try {
const { data } = await ItemMasterConfigAPI.getConfig(tenantId);
// 모든 상태 업데이트
setItemPages(data.config.pages);
setSectionTemplates(data.config.sectionTemplates);
// ... 나머지 데이터
// localStorage 캐시
localStorage.setItem('mes-itemMasterConfig', JSON.stringify(data));
localStorage.setItem('mes-itemMasterConfig-lastSync', new Date().toISOString());
} catch (error) {
console.error('서버 동기화 실패:', error);
// localStorage 폴백
}
};
// 저장 함수 추가
const saveToServer = async () => {
try {
const configData = {
version: currentVersion,
comment: saveComment,
config: {
pages: itemPages,
sectionTemplates: sectionTemplates,
itemMasterFields: itemMasterFields,
masters: {
specifications: specificationMasters,
materialNames: materialItemNames,
// ... 나머지
}
}
};
await ItemMasterConfigAPI.saveConfig(tenantId, configData);
} catch (error) {
console.error('저장 실패:', error);
throw error;
}
};
다음 단계
Phase 1: API 모킹 (현재)
- ✅ API 구조 설계 완료
- ⏳ Mock API 서버 구현 (MSW 또는 json-server)
- ⏳ 프론트엔드 API 클라이언트 구현
- ⏳ Context와 API 통합
Phase 2: 백엔드 구현
- ⏳ 데이터베이스 스키마 설계
- ⏳ API 엔드포인트 구현
- ⏳ 인증/권한 처리
- ⏳ 버전 관리 로직 구현
Phase 3: 품목관리 페이지 동적 생성
- ⏳ 설정 기반 폼 렌더러 구현
- ⏳ 조건부 표시 로직 구현
- ⏳ 유효성 검증 구현
- ⏳ 실제 품목 데이터 저장 API 연동
부록
A. localStorage 키 규칙
❌ 기존 (tenant.id 없음 - 데이터 오염 위험):
// 테넌트 ID가 없어서 테넌트 전환 시 데이터 오염 발생!
'mes-itemMasterConfig'
'mes-specificationMasters'
✅ 권장 (tenant.id 포함 - 완전한 격리):
// 설정 데이터 (tenant.id 포함)
`mes-${tenantId}-itemMasterConfig` // 예: 'mes-282-itemMasterConfig'
`mes-${tenantId}-itemMasterConfig-version`
`mes-${tenantId}-itemMasterConfig-lastSync`
// 개별 마스터 데이터 (tenant.id + 버전 포함)
`mes-${tenantId}-specificationMasters` // 예: 'mes-282-specificationMasters'
`mes-${tenantId}-specificationMasters-version`
`mes-${tenantId}-materialItemNames`
`mes-${tenantId}-materialItemNames-version`
`mes-${tenantId}-itemCategories`
`mes-${tenantId}-itemUnits`
`mes-${tenantId}-itemMaterials`
`mes-${tenantId}-surfaceTreatments`
`mes-${tenantId}-partTypeOptions`
`mes-${tenantId}-partUsageOptions`
`mes-${tenantId}-guideRailOptions`
구현 예시:
// TenantAwareCache 클래스 사용 (권장)
// 자세한 구현은 [REF-2025-11-19] multi-tenancy-implementation.md 참조
const cache = new TenantAwareCache(user.tenant.id);
cache.set('itemMasterConfig', configData);
// 또는 직접 구현
const key = `mes-${user.tenant.id}-itemMasterConfig`; // 'mes-282-itemMasterConfig'
localStorage.setItem(key, JSON.stringify(configData));
테넌트 전환 시 캐시 삭제:
// 로그아웃 또는 테넌트 전환 시
function clearTenantCache(tenantId: number) {
const keys = Object.keys(localStorage);
const prefix = `mes-${tenantId}-`;
keys.forEach(key => {
if (key.startsWith(prefix)) {
localStorage.removeItem(key);
}
});
}
B. 타입 정의 파일 위치
src/
├─ types/
│ ├─ itemMaster.ts # 품목 관련 타입
│ ├─ itemMasterConfig.ts # 설정 관련 타입
│ └─ api.ts # API 응답 타입
├─ lib/
│ └─ api/
│ └─ itemMasterConfigApi.ts # API 클라이언트
└─ contexts/
└─ ItemMasterContext.tsx # Context (기존)
문서 버전: 1.0 마지막 업데이트: 2025-11-18