feat: 건설 견적 상세 페이지 API 연동 완료

- 견적요약, 공과상세, 품목 단가조정 데이터 API 연동
- options JSON 필드에서 데이터 읽기/쓰기 처리
- view/edit 페이지에서 목업 데이터 제거
This commit is contained in:
2026-01-14 12:35:11 +09:00
parent 2f1946a834
commit 8f02f68390
3 changed files with 107 additions and 371 deletions

View File

@@ -9,190 +9,6 @@ interface EstimateEditPageProps {
params: Promise<{ id: string }>;
}
// 목업 데이터 - 추후 API 연동
function getEstimateDetail(id: string): EstimateDetail {
// TODO: 실제 API 연동
const mockData: EstimateDetail = {
id,
estimateCode: '123123',
partnerId: '1',
partnerName: '회사명',
projectName: '현장명',
estimatorId: 'hong',
estimatorName: '이름',
estimateCompanyManager: '홍길동',
estimateCompanyManagerContact: '01012341234',
itemCount: 21,
estimateAmount: 1420000,
completedDate: null,
bidDate: '2025-12-12',
status: 'pending',
createdAt: '2025-12-01',
updatedAt: '2025-12-01',
createdBy: 'hong',
siteBriefing: {
briefingCode: '123123',
partnerName: '회사명',
companyName: '회사명',
briefingDate: '2025-12-12',
attendee: '이름',
},
bidInfo: {
projectName: '현장명',
bidDate: '2025-12-12',
siteCount: 21,
constructionPeriod: '2026-01-01 ~ 2026-12-10',
constructionStartDate: '2026-01-01',
constructionEndDate: '2026-12-10',
vatType: 'excluded',
workReport: '업무 보고 내용',
documents: [
{
id: '1',
fileName: 'abc.zip',
fileUrl: '#',
fileSize: 1024000,
},
],
},
summaryItems: [
{
id: '1',
name: '서터 심창측공사',
quantity: 1,
unit: '식',
materialCost: 78540000,
laborCost: 15410000,
totalCost: 93950000,
remarks: '',
},
],
expenseItems: [
{
id: '1',
name: 'public_1',
amount: 10000,
},
],
priceAdjustments: [
{
id: '1',
category: '배합비',
unitPrice: 10000,
coating: 10000,
batting: 10000,
boxReinforce: 10500,
painting: 10500,
total: 51000,
},
{
id: '2',
category: '재단비',
unitPrice: 1375,
coating: 0,
batting: 0,
boxReinforce: 0,
painting: 0,
total: 1375,
},
{
id: '3',
category: '판매단가',
unitPrice: 0,
coating: 10000,
batting: 10000,
boxReinforce: 10500,
painting: 10500,
total: 41000,
},
{
id: '4',
category: '조립단가',
unitPrice: 10300,
coating: 10300,
batting: 10300,
boxReinforce: 10500,
painting: 10200,
total: 51600,
},
],
detailItems: [
{
id: '1',
no: 1,
name: 'FS530외/주차',
material: 'screen',
width: 2350,
height: 2500,
quantity: 1,
box: 1,
assembly: 0,
coating: 0,
batting: 0,
mounting: 0,
fitting: 0,
controller: 0,
widthConstruction: 0,
heightConstruction: 0,
materialCost: 1420000,
laborCost: 510000,
quantityPrice: 1930000,
expenseQuantity: 5500,
expenseTotal: 5500,
totalCost: 1930000,
otherCost: 0,
marginCost: 0,
totalPrice: 1930000,
unitPrice: 1420000,
expense: 0,
marginRate: 0,
unitQuantity: 1,
expenseResult: 0,
marginActual: 0,
},
{
id: '2',
no: 2,
name: 'FS530외/주차',
material: 'screen',
width: 7500,
height: 2500,
quantity: 1,
box: 1,
assembly: 0,
coating: 0,
batting: 0,
mounting: 0,
fitting: 0,
controller: 0,
widthConstruction: 0,
heightConstruction: 0,
materialCost: 4720000,
laborCost: 780000,
quantityPrice: 5500000,
expenseQuantity: 5500,
expenseTotal: 5500,
totalCost: 5500000,
otherCost: 0,
marginCost: 0,
totalPrice: 5500000,
unitPrice: 4720000,
expense: 0,
marginRate: 0,
unitQuantity: 1,
expenseResult: 0,
marginActual: 0,
},
],
approval: {
approvers: [],
references: [],
},
};
return mockData;
}
export default function EstimateEditPage({ params }: EstimateEditPageProps) {
const { id } = use(params);
const [data, setData] = useState<EstimateDetail | null>(null);

View File

@@ -9,190 +9,6 @@ interface EstimateDetailPageProps {
params: Promise<{ id: string }>;
}
// 목업 데이터 - 추후 API 연동
function getEstimateDetail(id: string): EstimateDetail {
// TODO: 실제 API 연동
const mockData: EstimateDetail = {
id,
estimateCode: '123123',
partnerId: '1',
partnerName: '회사명',
projectName: '현장명',
estimatorId: 'hong',
estimatorName: '이름',
estimateCompanyManager: '홍길동',
estimateCompanyManagerContact: '01012341234',
itemCount: 21,
estimateAmount: 1420000,
completedDate: null,
bidDate: '2025-12-12',
status: 'pending',
createdAt: '2025-12-01',
updatedAt: '2025-12-01',
createdBy: 'hong',
siteBriefing: {
briefingCode: '123123',
partnerName: '회사명',
companyName: '회사명',
briefingDate: '2025-12-12',
attendee: '이름',
},
bidInfo: {
projectName: '현장명',
bidDate: '2025-12-12',
siteCount: 21,
constructionPeriod: '2026-01-01 ~ 2026-12-10',
constructionStartDate: '2026-01-01',
constructionEndDate: '2026-12-10',
vatType: 'excluded',
workReport: '업무 보고 내용',
documents: [
{
id: '1',
fileName: 'abc.zip',
fileUrl: '#',
fileSize: 1024000,
},
],
},
summaryItems: [
{
id: '1',
name: '서터 심창측공사',
quantity: 1,
unit: '식',
materialCost: 78540000,
laborCost: 15410000,
totalCost: 93950000,
remarks: '',
},
],
expenseItems: [
{
id: '1',
name: 'public_1',
amount: 10000,
},
],
priceAdjustments: [
{
id: '1',
category: '배합비',
unitPrice: 10000,
coating: 10000,
batting: 10000,
boxReinforce: 10500,
painting: 10500,
total: 51000,
},
{
id: '2',
category: '재단비',
unitPrice: 1375,
coating: 0,
batting: 0,
boxReinforce: 0,
painting: 0,
total: 1375,
},
{
id: '3',
category: '판매단가',
unitPrice: 0,
coating: 10000,
batting: 10000,
boxReinforce: 10500,
painting: 10500,
total: 41000,
},
{
id: '4',
category: '조립단가',
unitPrice: 10300,
coating: 10300,
batting: 10300,
boxReinforce: 10500,
painting: 10200,
total: 51600,
},
],
detailItems: [
{
id: '1',
no: 1,
name: 'FS530외/주차',
material: 'screen',
width: 2350,
height: 2500,
quantity: 1,
box: 1,
assembly: 0,
coating: 0,
batting: 0,
mounting: 0,
fitting: 0,
controller: 0,
widthConstruction: 0,
heightConstruction: 0,
materialCost: 1420000,
laborCost: 510000,
quantityPrice: 1930000,
expenseQuantity: 5500,
expenseTotal: 5500,
totalCost: 1930000,
otherCost: 0,
marginCost: 0,
totalPrice: 1930000,
unitPrice: 1420000,
expense: 0,
marginRate: 0,
unitQuantity: 1,
expenseResult: 0,
marginActual: 0,
},
{
id: '2',
no: 2,
name: 'FS530외/주차',
material: 'screen',
width: 7500,
height: 2500,
quantity: 1,
box: 1,
assembly: 0,
coating: 0,
batting: 0,
mounting: 0,
fitting: 0,
controller: 0,
widthConstruction: 0,
heightConstruction: 0,
materialCost: 4720000,
laborCost: 780000,
quantityPrice: 5500000,
expenseQuantity: 5500,
expenseTotal: 5500,
totalCost: 5500000,
otherCost: 0,
marginCost: 0,
totalPrice: 5500000,
unitPrice: 4720000,
expense: 0,
marginRate: 0,
unitQuantity: 1,
expenseResult: 0,
marginActual: 0,
},
],
approval: {
approvers: [],
references: [],
},
};
return mockData;
}
export default function EstimateDetailPage({ params }: EstimateDetailPageProps) {
const { id } = use(params);
const [data, setData] = useState<EstimateDetail | null>(null);

View File

@@ -48,6 +48,42 @@ interface ApiQuote {
// 연관 데이터
items?: ApiQuoteItem[];
site_briefing?: ApiSiteBriefing;
// 옵션 데이터 (JSON)
options?: ApiQuoteOptions;
}
interface ApiQuoteOptions {
summary_items?: ApiSummaryItem[];
expense_items?: ApiExpenseItem[];
price_adjustments?: ApiPriceAdjustment[];
}
interface ApiSummaryItem {
id: string;
name: string;
quantity: number;
unit: string;
material_cost: number;
labor_cost: number;
total_cost: number;
remarks?: string;
}
interface ApiExpenseItem {
id: string;
name: string;
amount: number;
}
interface ApiPriceAdjustment {
id: string;
category: string;
unit_price: number;
coating: number;
batting: number;
box_reinforce: number;
painting: number;
total: number;
}
interface ApiQuoteItem {
@@ -210,9 +246,36 @@ function transformQuoteToEstimateDetail(apiData: ApiQuote): EstimateDetail {
documents: [],
};
const summaryItems: EstimateSummaryItem[] = [];
const expenseItems: ExpenseItem[] = [];
const priceAdjustments: PriceAdjustmentItem[] = [];
// options에서 데이터 변환
const opts = apiData.options;
const summaryItems: EstimateSummaryItem[] = (opts?.summary_items || []).map((item) => ({
id: item.id,
name: item.name,
quantity: item.quantity,
unit: item.unit,
materialCost: item.material_cost,
laborCost: item.labor_cost,
totalCost: item.total_cost,
remarks: item.remarks || '',
}));
const expenseItems: ExpenseItem[] = (opts?.expense_items || []).map((item) => ({
id: item.id,
name: item.name,
amount: item.amount,
}));
const priceAdjustments: PriceAdjustmentItem[] = (opts?.price_adjustments || []).map((item) => ({
id: item.id,
category: item.category,
unitPrice: item.unit_price,
coating: item.coating,
batting: item.batting,
boxReinforce: item.box_reinforce,
painting: item.painting,
total: item.total,
}));
const detailItems: EstimateDetailItem[] = (apiData.items || []).map((item, index) => ({
id: String(item.id),
@@ -286,6 +349,47 @@ function transformToApiRequest(data: Partial<EstimateDetailFormData>): Record<st
apiData.registration_date = data.bidInfo.bidDate;
}
// options 데이터 역변환
const options: Record<string, unknown> = {};
if (data.summaryItems !== undefined) {
options.summary_items = data.summaryItems.map((item) => ({
id: item.id,
name: item.name,
quantity: item.quantity,
unit: item.unit,
material_cost: item.materialCost,
labor_cost: item.laborCost,
total_cost: item.totalCost,
remarks: item.remarks,
}));
}
if (data.expenseItems !== undefined) {
options.expense_items = data.expenseItems.map((item) => ({
id: item.id,
name: item.name,
amount: item.amount,
}));
}
if (data.priceAdjustments !== undefined) {
options.price_adjustments = data.priceAdjustments.map((item) => ({
id: item.id,
category: item.category,
unit_price: item.unitPrice,
coating: item.coating,
batting: item.batting,
box_reinforce: item.boxReinforce,
painting: item.painting,
total: item.total,
}));
}
if (Object.keys(options).length > 0) {
apiData.options = options;
}
return apiData;
}