feat: [부서관리] 기능 보완 - 필드 확장, 검색/필터, UI 개선

- Department 타입에 code, description, isActive, sortOrder 필드 추가
- DepartmentDialog: Zod + react-hook-form 폼 검증 (5개 필드)
- DepartmentToolbar: 상태 필터(전체/활성/비활성) + 검색 기능
- DepartmentTree: 트리 필터링 (검색어 + 상태)
- DepartmentTreeItem: 코드 Badge, 부서명 볼드, 설명 표시, 체크박스 크기 조정
- convertApiToLocal에서 누락 필드 매핑 복원
This commit is contained in:
2026-03-13 00:30:09 +09:00
parent ca5a9325c6
commit 13249384e2
26 changed files with 1284 additions and 915 deletions

View File

@@ -162,12 +162,7 @@ export function PricingListClient({
// 네비게이션 핸들러
const handleRegister = (item: PricingListItem) => {
// item_type_code는 품목 정보에서 자동으로 가져오므로 URL에 포함하지 않음
const params = new URLSearchParams();
params.set('mode', 'new');
if (item.itemId) params.set('itemId', item.itemId);
if (item.itemCode) params.set('itemCode', item.itemCode);
router.push(`/sales/pricing-management?${params.toString()}`);
router.push(`/sales/pricing-management/create?itemId=${item.itemId}`);
};
const handleEdit = (item: PricingListItem) => {
@@ -221,12 +216,12 @@ export function PricingListClient({
) => {
const { isSelected, onToggle } = handlers;
// 행 클릭 핸들러: 등록되지 않은 항목은 등록, 등록된 항목은 수정
// 행 클릭 핸들러: 등록되지 않은 항목은 등록, 등록된 항목은 상세 조회
const handleRowClick = () => {
if (item.status === 'not_registered') {
handleRegister(item);
} else {
handleEdit(item);
router.push(`/sales/pricing-management/${item.id}`);
}
};
@@ -310,7 +305,7 @@ export function PricingListClient({
statusBadge={renderStatusBadge(item)}
isSelected={isSelected}
onToggleSelection={onToggle}
onCardClick={() => item.status !== 'not_registered' ? handleEdit(item) : handleRegister(item)}
onCardClick={() => item.status !== 'not_registered' ? router.push(`/sales/pricing-management/${item.id}`) : handleRegister(item)}
infoGrid={
<div className="grid grid-cols-2 gap-x-4 gap-y-3">
{item.specification && (

View File

@@ -171,7 +171,7 @@ export async function getPricingById(id: string): Promise<PricingData | null> {
}
export async function getItemInfo(itemId: string): Promise<ItemInfo | null> {
interface ItemApiItem { id: number; code: string; name: string; item_type: string; specification?: string; unit?: string }
interface ItemApiItem { id: number; item_code: string; code?: string; name: string; item_type: string; specification?: string; unit?: string }
const result = await executeServerAction<ItemApiItem>({
url: `${API_URL}/api/v1/items/${itemId}`,
errorMessage: '품목 조회에 실패했습니다.',
@@ -179,7 +179,7 @@ export async function getItemInfo(itemId: string): Promise<ItemInfo | null> {
if (!result.success || !result.data) return null;
const item = result.data;
return {
id: String(item.id), itemCode: item.code, itemName: item.name,
id: String(item.id), itemCode: item.item_code || item.code || '', itemName: item.name,
itemType: item.item_type || 'PT', specification: item.specification || undefined, unit: item.unit || 'EA',
};
}
@@ -243,10 +243,17 @@ export async function finalizePricing(id: string): Promise<{ success: boolean; d
interface ItemApiData {
id: number;
item_type: string; // FG, PT, SM, RM, CS (품목 유형)
code: string;
item_code?: string;
code?: string;
name: string;
specification?: string;
unit: string;
category_id: number | null;
attributes?: {
salesPrice?: number;
purchasePrice?: number;
[key: string]: unknown;
} | null;
created_at: string;
deleted_at: string | null;
}
@@ -330,11 +337,11 @@ export async function getPricingListData(): Promise<PricingListItem[]> {
const [itemsResult, pricingResult] = await Promise.all([
executeServerAction<ItemsPaginatedResponse>({
url: `${API_URL}/api/v1/items?group_id=1&size=100`,
url: `${API_URL}/api/v1/items?group_id=1&size=10000`,
errorMessage: '품목 목록 조회에 실패했습니다.',
}),
executeServerAction<PricingPaginatedResponse>({
url: `${API_URL}/api/v1/pricing?size=100`,
url: `${API_URL}/api/v1/pricing?size=10000`,
errorMessage: '단가 목록 조회에 실패했습니다.',
}),
]);
@@ -354,10 +361,13 @@ export async function getPricingListData(): Promise<PricingListItem[]> {
const key = `${item.item_type}_${item.id}`;
const pricing = pricingMap.get(key);
const itemCode = item.item_code || item.code || '';
const specification = item.specification || undefined;
if (pricing) {
return {
id: String(pricing.id), itemId: String(item.id), itemCode: item.code, itemName: item.name,
itemType: mapItemTypeForList(item.item_type), specification: undefined, unit: item.unit || 'EA',
id: String(pricing.id), itemId: String(item.id), itemCode, itemName: item.name,
itemType: mapItemTypeForList(item.item_type), specification, unit: item.unit || 'EA',
purchasePrice: pricing.purchase_price ? parseFloat(pricing.purchase_price) : undefined,
processingCost: pricing.processing_cost ? parseFloat(pricing.processing_cost) : undefined,
salesPrice: pricing.sales_price ? parseFloat(pricing.sales_price) : undefined,
@@ -367,10 +377,15 @@ export async function getPricingListData(): Promise<PricingListItem[]> {
currentRevision: 0, isFinal: pricing.is_final, itemTypeCode: item.item_type,
};
} else {
// prices 미등록 → items.attributes에서 참고 단가 표시
const attrSalesPrice = item.attributes?.salesPrice ? Number(item.attributes.salesPrice) : undefined;
const attrPurchasePrice = item.attributes?.purchasePrice ? Number(item.attributes.purchasePrice) : undefined;
return {
id: `item_${item.id}`, itemId: String(item.id), itemCode: item.code, itemName: item.name,
itemType: mapItemTypeForList(item.item_type), specification: undefined, unit: item.unit || 'EA',
purchasePrice: undefined, processingCost: undefined, salesPrice: undefined, marginRate: undefined,
id: `item_${item.id}`, itemId: String(item.id), itemCode, itemName: item.name,
itemType: mapItemTypeForList(item.item_type), specification, unit: item.unit || 'EA',
purchasePrice: attrPurchasePrice, processingCost: undefined,
salesPrice: attrSalesPrice, marginRate: undefined,
effectiveDate: undefined, status: 'not_registered' as const,
currentRevision: 0, isFinal: false, itemTypeCode: item.item_type,
};