fix(WEB): 품목관리 규격 필드를 실제 스펙 텍스트로 표시하도록 수정

- Specification 타입: '인정'|'비인정' → string (실제 규격 텍스트)
- 데이터 소스: options.specification → attributes.spec으로 변경
- 상세 페이지: 규격 필드를 Select → Input(텍스트)으로 변경
- 저장 시 attributes.spec으로 저장하도록 변경
- 규격 필터 옵션에서 인정/비인정 제거
This commit is contained in:
2026-02-04 20:30:01 +09:00
parent 743467c300
commit 8902cdcfd5
5 changed files with 25 additions and 34 deletions

View File

@@ -22,10 +22,9 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { IntegratedDetailTemplate } from '@/components/templates/IntegratedDetailTemplate';
import { itemConfig } from './itemConfig';
import { toast } from 'sonner';
import type { ItemFormData, ItemType, Specification, OrderType, ItemStatus, OrderItem } from './types';
import type { ItemFormData, ItemType, OrderType, ItemStatus, OrderItem } from './types';
import {
ITEM_TYPE_OPTIONS,
SPECIFICATION_OPTIONS,
ORDER_TYPE_OPTIONS,
STATUS_OPTIONS,
UNIT_OPTIONS,
@@ -42,8 +41,7 @@ const initialFormData: ItemFormData = {
itemNumber: '',
itemType: '제품',
categoryId: '',
itemName: '',
specification: '인정',
itemName: '', specification: '',
unit: 'SET',
orderType: '경품발주',
status: '사용',
@@ -322,22 +320,13 @@ export default function ItemDetailClient({
</div>
<div className="space-y-2">
<Label htmlFor="specification"></Label>
<Select
<Input
id="specification"
value={formData.specification}
onValueChange={(v) => handleFieldChange('specification', v as Specification)}
onChange={(e) => handleFieldChange('specification', e.target.value)}
placeholder="규격을 입력하세요 (예: 1∅220V)"
disabled={isViewMode}
>
<SelectTrigger>
<SelectValue placeholder="규격 선택" />
</SelectTrigger>
<SelectContent>
{SPECIFICATION_OPTIONS.filter((o) => o.value !== 'all').map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
</SelectContent>
</Select>
/>
</div>
</div>

View File

@@ -12,7 +12,7 @@ import { UniversalListPage, type UniversalListConfig, type TableColumn, type Fil
import { MobileCard } from '@/components/organisms/MobileCard';
import { toast } from 'sonner';
import { DeleteConfirmDialog } from '@/components/ui/confirm-dialog';
import type { Item, ItemStats, ItemType, Specification, OrderType, ItemStatus } from './types';
import type { Item, ItemStats, ItemType, OrderType, ItemStatus } from './types';
import {
ITEM_TYPE_OPTIONS,
SPECIFICATION_OPTIONS,
@@ -59,7 +59,7 @@ export default function ItemManagementClient({
const [searchValue, setSearchValue] = useState('');
const [itemTypeFilter, setItemTypeFilter] = useState<ItemType | 'all'>('all');
const [categoryFilter, setCategoryFilter] = useState<string>('all');
const [specificationFilter, setSpecificationFilter] = useState<Specification | 'all'>('all');
const [specificationFilter, setSpecificationFilter] = useState<string>('all');
const [orderTypeFilter, setOrderTypeFilter] = useState<OrderType | 'all'>('all');
const [statusFilter, setStatusFilter] = useState<ItemStatus | 'all'>('all');
const [sortBy, setSortBy] = useState<'latest' | 'oldest'>('latest');
@@ -479,7 +479,7 @@ export default function ItemManagementClient({
setCategoryFilter(value as string);
break;
case 'specification':
setSpecificationFilter(value as Specification | 'all');
setSpecificationFilter(value as string);
break;
case 'orderType':
setOrderTypeFilter(value as OrderType | 'all');

View File

@@ -37,12 +37,13 @@ function transformToBackendItemType(frontendType: ItemType): string {
}
/**
* Backend options → Frontend specification 변환
* Backend attributes.spec → Frontend specification 변환
* 실제 규격 텍스트 (예: "1∅220V", "면적 18.1m²")
*/
function transformSpecification(options: Record<string, unknown> | null | undefined): Specification {
const spec = options?.specification;
if (spec === '인정' || spec === '비인정') return spec;
return '인정'; // 기본값
function transformSpecification(attributes: Record<string, unknown> | null | undefined): Specification {
const spec = attributes?.spec;
if (typeof spec === 'string' && spec.trim()) return spec.trim();
return '-';
}
/**
@@ -95,6 +96,7 @@ interface ApiItem {
category?: { name?: string } | null;
unit: string | null;
options: Record<string, unknown> | null;
attributes: Record<string, unknown> | null;
is_active: boolean;
description: string | null;
created_at: string;
@@ -110,7 +112,7 @@ function transformItem(apiItem: ApiItem): Item {
categoryId: apiItem.category_id ? String(apiItem.category_id) : '',
categoryName: apiItem.category?.name || '',
unit: apiItem.unit || 'EA',
specification: transformSpecification(apiItem.options),
specification: transformSpecification(apiItem.attributes),
orderType: transformOrderType(apiItem.options),
status: transformStatus(apiItem.is_active, apiItem.options),
createdAt: apiItem.created_at,
@@ -141,8 +143,10 @@ function transformItemToApi(data: ItemFormData): Record<string, unknown> {
unit: data.unit,
is_active: data.status === '사용' || data.status === '승인',
description: data.note || null,
attributes: {
spec: data.specification || null,
},
options: {
specification: data.specification,
orderType: data.orderType,
status: data.status,
orderItems: data.orderItems,

View File

@@ -11,11 +11,9 @@ export const ITEM_TYPE_OPTIONS: { value: ItemType | 'all'; label: string }[] = [
{ value: '공과', label: '공과' },
];
// 규격 옵션
export const SPECIFICATION_OPTIONS: { value: Specification | 'all'; label: string }[] = [
// 규격 필터 옵션 (규격 있음/없음)
export const SPECIFICATION_OPTIONS: { value: string; label: string }[] = [
{ value: 'all', label: '전체' },
{ value: '인정', label: '인정' },
{ value: '비인정', label: '비인정' },
];
// 구분(발주유형) 옵션

View File

@@ -3,8 +3,8 @@
// 물품유형
export type ItemType = '제품' | '부품' | '소모품' | '공과';
// 규격
export type Specification = '인정' | '비인정';
// 규격 (실제 스펙 텍스트, 예: "1∅220V", "면적 18.1m²")
export type Specification = string;
// 구분 (발주유형)
export type OrderType = '경품발주' | '원자재발주' | '외주발주';