feat(WEB): 절곡 작업일지 bending_info를 work_orders.options으로 이동 + 텍스트 크기 통일

- bending_info 저장 위치: work_order_items → work_orders.options으로 변경
- types.ts: WorkOrderApi에 options 필드 추가, 변환 함수에서 order 레벨 bendingInfo 매핑
- BendingWorkLogContent: items 탐색 대신 order.bendingInfo 직접 참조
- utils.ts: getMaterialMapping에 KSS01/KSS02 SUS 제품 지원 추가
- 4개 섹션 컴포넌트: text-[10px]/text-[8px] → text-xs로 통일 (가독성 개선)
This commit is contained in:
2026-02-19 23:46:30 +09:00
parent 37215a7758
commit 6d934b4418
7 changed files with 32 additions and 29 deletions

View File

@@ -56,20 +56,20 @@ export function BendingWorkLogContent({ data: order }: BendingWorkLogContentProp
}).replace(/\. /g, '-').replace('.', '')
: '-';
// 첫 번째 아이템의 bendingInfo에서 절곡 데이터 추출
const firstBendingInfo = items.find(item => item.bendingInfo)?.bendingInfo as BendingInfoExtended | undefined;
// 작업지시 레벨의 bendingInfo에서 절곡 데이터 추출
const bendingInfo = order.bendingInfo as BendingInfoExtended | undefined;
// bendingInfo가 없으면 빈 상태 표시
const hasBendingData = !!firstBendingInfo?.productCode;
const hasBendingData = !!bendingInfo?.productCode;
// 재질 매핑
const mapping = hasBendingData
? getMaterialMapping(firstBendingInfo!.productCode, firstBendingInfo!.finishMaterial)
? getMaterialMapping(bendingInfo!.productCode, bendingInfo!.finishMaterial)
: null;
// 생산량 합계
const summary = hasBendingData && mapping
? calculateProductionSummary(firstBendingInfo!, mapping)
? calculateProductionSummary(bendingInfo!, mapping)
: { susTotal: 0, egiTotal: 0, grandTotal: 0 };
return (
@@ -142,16 +142,16 @@ export function BendingWorkLogContent({ data: order }: BendingWorkLogContentProp
<tbody>
<tr>
<td className="border border-gray-400 p-2">
{hasBendingData ? firstBendingInfo!.productCode : '-'}
{hasBendingData ? bendingInfo!.productCode : '-'}
</td>
<td className="border border-gray-400 p-2">
{mapping?.bodyMaterial || '-'}
</td>
<td className="border border-gray-400 p-2">
{hasBendingData ? firstBendingInfo!.finishMaterial : '-'}
{hasBendingData ? bendingInfo!.finishMaterial : '-'}
</td>
<td className="border border-gray-400 p-2">
{hasBendingData ? firstBendingInfo!.common.type : '-'}
{hasBendingData ? bendingInfo!.common.type : '-'}
</td>
</tr>
</tbody>
@@ -161,19 +161,19 @@ export function BendingWorkLogContent({ data: order }: BendingWorkLogContentProp
{hasBendingData && mapping ? (
<>
<GuideRailSection
bendingInfo={firstBendingInfo!}
bendingInfo={bendingInfo!}
mapping={mapping}
lotNo={order.lotNo}
/>
<BottomBarSection
bendingInfo={firstBendingInfo!}
bendingInfo={bendingInfo!}
mapping={mapping}
/>
<ShutterBoxSection
bendingInfo={firstBendingInfo!}
bendingInfo={bendingInfo!}
/>
<SmokeBarrierSection
bendingInfo={firstBendingInfo!}
bendingInfo={bendingInfo!}
/>
</>
) : (

View File

@@ -37,7 +37,7 @@ export function BottomBarSection({ bendingInfo, mapping }: BottomBarSectionProps
{/* 우측: 테이블 */}
<div className="flex-1">
<table className="w-full border-collapse text-[10px]">
<table className="w-full border-collapse text-xs">
<thead>
<tr className="bg-gray-100">
<th className="border border-gray-400 px-1 py-0.5"></th>

View File

@@ -32,7 +32,7 @@ function PartTable({ title, rows, imageUrl, lotNo, baseSize }: {
{/* 좌측: 이미지 + LOT */}
<div className="flex-shrink-0 w-48">
<img src={imageUrl} alt={title} className="w-full border border-gray-300" />
<div className="text-[10px] text-gray-500 mt-1">
<div className="text-xs text-gray-500 mt-1">
&amp; LOT NO:<br />
<span className="text-gray-400">_______________</span>
</div>
@@ -40,7 +40,7 @@ function PartTable({ title, rows, imageUrl, lotNo, baseSize }: {
{/* 우측: 테이블 */}
<div className="flex-1">
<table className="w-full border-collapse text-[10px]">
<table className="w-full border-collapse text-xs">
<thead>
<tr className="bg-gray-100">
<th className="border border-gray-400 px-1 py-0.5"></th>

View File

@@ -41,18 +41,18 @@ function ShutterBoxSubSection({ box, index }: { box: ShutterBoxData; index: numb
className="w-full border border-gray-300"
/>
{/* 치수 텍스트 오버레이 */}
<div className="absolute bottom-1 left-1 text-[8px] bg-white/80 px-1 rounded">
<div className="absolute bottom-1 left-1 text-xs bg-white/80 px-1 rounded">
{box.size} ({box.direction})
</div>
</div>
<div className="text-[10px] text-gray-500 mt-1">
<div className="text-xs text-gray-500 mt-1">
: {box.railWidth}mm
</div>
</div>
{/* 우측: 테이블 */}
<div className="flex-1">
<table className="w-full border-collapse text-[10px]">
<table className="w-full border-collapse text-xs">
<thead>
<tr className="bg-gray-100">
<th className="border border-gray-400 px-1 py-0.5"></th>

View File

@@ -37,7 +37,7 @@ export function SmokeBarrierSection({ bendingInfo }: SmokeBarrierSectionProps) {
{/* 우측: 테이블 */}
<div className="flex-1">
<table className="w-full border-collapse text-[10px]">
<table className="w-full border-collapse text-xs">
<thead>
<tr className="bg-gray-100">
<th className="border border-gray-400 px-1 py-0.5"></th>

View File

@@ -75,8 +75,8 @@ export function calcWeight(material: string, width: number, height: number): Wei
// ============================================================
export function getMaterialMapping(productCode: string, finishMaterial: string): MaterialMapping {
// Group 1: KQTS01 - SUS 마감
if (productCode === 'KQTS01') {
// Group 1: KQTS01, KSS01, KSS02 - SUS 전용 제품
if (['KQTS01', 'KSS01', 'KSS02'].includes(productCode)) {
return {
guideRailFinish: 'SUS 1.2T',
bodyMaterial: 'EGI 1.55T',
@@ -96,13 +96,14 @@ export function getMaterialMapping(productCode: string, finishMaterial: string):
bottomBarExtraFinish: isSUS ? 'SUS 1.2T' : '없음',
};
}
// 기타 제품코드 (KSE01, KSS01, KSS02, KWE01 등) - EGI 기본
// 기타 제품코드 (KSE01, KWE01 등) - EGI 기본, 마감유형에 따라 SUS 추가
const isSUS = finishMaterial?.includes('SUS');
return {
guideRailFinish: 'EGI 1.55T',
bodyMaterial: 'EGI 1.55T',
guideRailExtraFinish: '',
guideRailExtraFinish: isSUS ? 'SUS 1.2T' : '',
bottomBarFinish: 'EGI 1.55T',
bottomBarExtraFinish: '없음',
bottomBarExtraFinish: isSUS ? 'SUS 1.2T' : '없음',
};
}
@@ -119,7 +120,8 @@ export function getBendingImageUrl(
): string {
switch (category) {
case 'guiderail': {
const size = ['KQTS01', 'KTE01'].includes(productCode)
const isLargeProfile = ['KQTS01', 'KTE01'].includes(productCode);
const size = isLargeProfile
? (type === 'wall' ? '130x75' : '130x125')
: (type === 'wall' ? '120x70' : '120x120');
return `${API_BASE}/images/bending/guiderail/guiderail_${productCode}_${type}_${size}.jpg`;

View File

@@ -206,7 +206,8 @@ export interface WorkOrder {
// 공정 진행 상태 (현재 단계)
currentStep: number;
// 절곡 전용 - 전개도 상세
// 절곡 전용
bendingInfo?: Record<string, unknown>; // 작업지시 레벨 bending_info (work_orders.options)
bendingDetails?: BendingDetail[];
// 이슈
@@ -339,6 +340,7 @@ export interface WorkOrderApi {
team_id: number | null;
scheduled_date: string | null;
memo: string | null;
options?: { bending_info?: Record<string, unknown>; [key: string]: unknown } | null;
is_active: boolean;
created_by: number | null;
updated_by: number | null;
@@ -482,9 +484,7 @@ export function transformApiToFrontend(api: WorkOrderApi): WorkOrder {
const si = item.options.slat_info as { length?: number; slat_count?: number; joint_bar?: number; glass_qty?: number };
return { length: si.length || 0, slatCount: si.slat_count || 0, jointBar: si.joint_bar || 0, glassQty: si.glass_qty || 0 };
})() : undefined,
bendingInfo: item.options?.bending_info
? (item.options.bending_info as Record<string, unknown>)
: undefined,
bendingInfo: undefined, // deprecated: work order 레벨로 이동 (order.bendingInfo)
materialInputLots: (() => {
const inputs = item.material_inputs as Array<{ stock_lot?: { lot_no?: string } }> | undefined;
if (!inputs || inputs.length === 0) return undefined;
@@ -492,6 +492,7 @@ export function transformApiToFrontend(api: WorkOrderApi): WorkOrder {
return lots.length > 0 ? [...new Set(lots)] : undefined;
})(),
})),
bendingInfo: api.options?.bending_info || undefined,
bendingDetails: api.bending_detail ? transformBendingDetail(api.bending_detail) : undefined,
issues: (api.issues || []).map(issue => ({
id: String(issue.id),