fix(WEB): 견적 상세 페이지 상태 변경 및 견적완료일 표시 기능 추가
- 견적 상세 페이지에서 상태 변경 가능하도록 Select 컴포넌트 추가 - 견적완료일(completedDate) 필드를 FormData 타입에 추가 - 견적완료 상태일 때 완료일자 표시 - 저장 시 상태를 강제로 'completed'로 변경하던 로직 제거 - 목록 페이지에서 견적완료일이 표시되지 않던 문제 수정 - updated_at을 완료 상태의 completedDate로 사용 - QuantityInput 컴포넌트 controlled component 에러 수정 - defaultValue props 분리하여 spread 방지
This commit is contained in:
@@ -113,11 +113,11 @@ export default function EstimateDetailForm({
|
||||
console.log('🔍 [handleConfirmSave] formData.priceAdjustmentData:', formData.priceAdjustmentData);
|
||||
console.log('🔍 [handleConfirmSave] formData 전체:', formData);
|
||||
|
||||
// 현재 사용자 이름을 견적자로 설정하고, 상태를 견적완료로 변경하여 저장
|
||||
// 현재 사용자 이름을 견적자로 설정하고 저장 (상태는 사용자 선택값 유지)
|
||||
const result = await updateEstimate(estimateId, {
|
||||
...formData,
|
||||
estimatorName: currentUser!.name,
|
||||
status: 'completed', // 저장 시 견적완료 상태로 변경 (입찰에서 조회 가능)
|
||||
// status는 formData에 포함되어 있으므로 사용자가 선택한 값 그대로 전송
|
||||
});
|
||||
|
||||
if (result.success) {
|
||||
|
||||
@@ -247,6 +247,14 @@ interface ApiBidDocument {
|
||||
* 기존 프론트엔드 타입과 호환성 유지
|
||||
*/
|
||||
function transformQuoteToEstimate(apiData: ApiQuote): Estimate {
|
||||
const status = mapQuoteStatusToEstimateStatus(apiData.status);
|
||||
|
||||
// 완료 상태인 경우 updated_at을 완료일로 사용
|
||||
// (상태가 완료로 변경될 때 updated_at이 갱신되므로)
|
||||
const completedDate = status === 'completed' && apiData.updated_at
|
||||
? apiData.updated_at.split('T')[0] // ISO 형식에서 날짜만 추출
|
||||
: null;
|
||||
|
||||
return {
|
||||
id: String(apiData.id),
|
||||
estimateCode: apiData.quote_number || '',
|
||||
@@ -259,9 +267,9 @@ function transformQuoteToEstimate(apiData: ApiQuote): Estimate {
|
||||
estimateCompanyManagerContact: '', // API에서 제공 시 매핑 필요
|
||||
itemCount: apiData.items?.length || 0,
|
||||
estimateAmount: Number(apiData.total_amount) || 0,
|
||||
completedDate: null,
|
||||
completedDate,
|
||||
bidDate: apiData.registration_date || null,
|
||||
status: mapQuoteStatusToEstimateStatus(apiData.status),
|
||||
status,
|
||||
createdAt: apiData.created_at || '',
|
||||
updatedAt: apiData.updated_at || '',
|
||||
createdBy: apiData.created_by ? String(apiData.created_by) : '',
|
||||
|
||||
@@ -15,8 +15,8 @@ import {
|
||||
} from '@/components/ui/select';
|
||||
import { FileDropzone } from '@/components/ui/file-dropzone';
|
||||
import { FileList, type ExistingFile } from '@/components/ui/file-list';
|
||||
import type { EstimateDetailFormData } from '../types';
|
||||
import { STATUS_STYLES, STATUS_LABELS, VAT_TYPE_OPTIONS } from '../types';
|
||||
import type { EstimateDetailFormData, EstimateStatus } from '../types';
|
||||
import { STATUS_STYLES, STATUS_LABELS, VAT_TYPE_OPTIONS, ESTIMATE_STATUS_OPTIONS } from '../types';
|
||||
import { formatAmount } from '../utils';
|
||||
|
||||
interface EstimateInfoSectionProps {
|
||||
@@ -66,8 +66,8 @@ export function EstimateInfoSection({
|
||||
<Input value={formData.estimateCompanyManagerContact} disabled className="bg-gray-50" />
|
||||
</div>
|
||||
</div>
|
||||
{/* 3행: 견적금액, 상태 */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{/* 3행: 견적금액, 상태, 완료일자 */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label className="text-sm font-medium text-gray-700">견적금액</Label>
|
||||
<Input
|
||||
@@ -78,12 +78,43 @@ export function EstimateInfoSection({
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label className="text-sm font-medium text-gray-700">상태</Label>
|
||||
<div className="flex items-center h-10 px-3 border rounded-md bg-gray-50">
|
||||
<span className={STATUS_STYLES[formData.status]}>
|
||||
{STATUS_LABELS[formData.status]}
|
||||
</span>
|
||||
</div>
|
||||
{isViewMode ? (
|
||||
<div className="flex items-center h-10 px-3 border rounded-md bg-gray-50">
|
||||
<span className={STATUS_STYLES[formData.status]}>
|
||||
{STATUS_LABELS[formData.status]}
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<Select
|
||||
value={formData.status}
|
||||
onValueChange={(val) => onFormDataChange({ status: val as EstimateStatus })}
|
||||
>
|
||||
<SelectTrigger className="bg-white">
|
||||
<SelectValue placeholder="상태 선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{ESTIMATE_STATUS_OPTIONS.filter((opt) => opt.value !== 'all').map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
<span className={STATUS_STYLES[option.value as EstimateStatus]}>
|
||||
{option.label}
|
||||
</span>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)}
|
||||
</div>
|
||||
{/* 완료일자: 견적완료 상태일 때만 표시 */}
|
||||
{formData.status === 'completed' && formData.completedDate && (
|
||||
<div className="space-y-2">
|
||||
<Label className="text-sm font-medium text-gray-700">견적완료일</Label>
|
||||
<Input
|
||||
value={formData.completedDate}
|
||||
disabled
|
||||
className="bg-gray-50"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -192,6 +192,7 @@ export interface EstimateDetailFormData {
|
||||
estimateCompanyManagerContact: string; // 견적 회사 담당자 연락처
|
||||
estimateAmount: number;
|
||||
status: EstimateStatus;
|
||||
completedDate: string | null; // 견적완료일
|
||||
|
||||
// 현장설명회 정보
|
||||
siteBriefing: SiteBriefingInfo;
|
||||
@@ -273,6 +274,7 @@ export function getEmptyEstimateDetailFormData(): EstimateDetailFormData {
|
||||
estimateCompanyManagerContact: '',
|
||||
estimateAmount: 0,
|
||||
status: 'pending',
|
||||
completedDate: null,
|
||||
siteBriefing: {
|
||||
briefingCode: '',
|
||||
partnerName: '',
|
||||
@@ -314,6 +316,7 @@ export function estimateDetailToFormData(detail: EstimateDetail): EstimateDetail
|
||||
estimateCompanyManagerContact: detail.estimateCompanyManagerContact || '',
|
||||
estimateAmount: detail.estimateAmount,
|
||||
status: detail.status,
|
||||
completedDate: detail.completedDate || null,
|
||||
siteBriefing: {
|
||||
...detail.siteBriefing,
|
||||
briefingDate: normalizeDateValue(detail.siteBriefing?.briefingDate),
|
||||
|
||||
@@ -63,6 +63,8 @@ const QuantityInput = React.forwardRef<HTMLInputElement, QuantityInputProps>(
|
||||
disabled,
|
||||
onFocus,
|
||||
onBlur,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
defaultValue: _defaultValue, // controlled component이므로 defaultValue 무시
|
||||
...props
|
||||
},
|
||||
ref
|
||||
|
||||
Reference in New Issue
Block a user