- ItemSearchModal: API 프록시 라우트 연동, 검색 유효성 검사 (영문/한글 1자 이상) - items.ts: HttpOnly 쿠키 인증을 위한 프록시 라우트 사용 - LocationDetailPanel: 수동 품목 추가 시 subtotals/grouped_items 동시 업데이트 - QuoteRegistrationV2: 견적 산출 시 수동 추가 품목(is_manual) 보존 - 상세별 합계에서 수동 추가 품목이 카테고리별로 표시되도록 개선
172 lines
5.1 KiB
TypeScript
172 lines
5.1 KiB
TypeScript
/**
|
|
* 견적 푸터 바
|
|
*
|
|
* - 예상 전체 견적금액 표시
|
|
* - 버튼: 견적서 산출, 임시저장, 최종저장
|
|
* - 뒤로가기 버튼
|
|
*/
|
|
|
|
"use client";
|
|
|
|
import { Download, Save, Check, ArrowLeft, Loader2, Calculator, Eye, Pencil, ClipboardList } from "lucide-react";
|
|
|
|
import { Button } from "../ui/button";
|
|
|
|
// =============================================================================
|
|
// Props
|
|
// =============================================================================
|
|
|
|
interface QuoteFooterBarProps {
|
|
totalLocations: number;
|
|
totalAmount: number;
|
|
status: "draft" | "temporary" | "final";
|
|
onCalculate: () => void;
|
|
onPreview: () => void;
|
|
onSaveTemporary: () => void;
|
|
onSaveFinal: () => void;
|
|
onBack: () => void;
|
|
onEdit?: () => void;
|
|
onOrderRegister?: () => void;
|
|
isCalculating?: boolean;
|
|
isSaving?: boolean;
|
|
disabled?: boolean;
|
|
/** view 모드 여부 (view: 수정+최종저장, edit: 임시저장+최종저장) */
|
|
isViewMode?: boolean;
|
|
}
|
|
|
|
// =============================================================================
|
|
// 컴포넌트
|
|
// =============================================================================
|
|
|
|
export function QuoteFooterBar({
|
|
totalLocations,
|
|
totalAmount,
|
|
status,
|
|
onCalculate,
|
|
onPreview,
|
|
onSaveTemporary,
|
|
onSaveFinal,
|
|
onBack,
|
|
onEdit,
|
|
onOrderRegister,
|
|
isCalculating = false,
|
|
isSaving = false,
|
|
disabled = false,
|
|
isViewMode = false,
|
|
}: QuoteFooterBarProps) {
|
|
return (
|
|
<div className="sticky bottom-0 bg-gradient-to-r from-blue-50 to-indigo-50 border-t border-blue-200 shadow-lg">
|
|
<div className="px-6 py-4 flex items-center justify-between">
|
|
{/* 왼쪽: 뒤로가기 + 금액 표시 */}
|
|
<div className="flex items-center gap-6">
|
|
<Button
|
|
variant="outline"
|
|
onClick={onBack}
|
|
className="gap-2"
|
|
>
|
|
<ArrowLeft className="h-4 w-4" />
|
|
목록으로
|
|
</Button>
|
|
|
|
<div>
|
|
<p className="text-sm text-gray-600">예상 전체 견적금액</p>
|
|
<p className="text-3xl font-bold text-blue-600">
|
|
{totalAmount.toLocaleString()}
|
|
<span className="text-lg font-normal text-gray-500 ml-1">원</span>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 오른쪽: 버튼들 */}
|
|
<div className="flex items-center gap-3">
|
|
{/* 견적서 산출 - edit 모드에서만 활성화 */}
|
|
{!isViewMode && (
|
|
<Button
|
|
onClick={onCalculate}
|
|
disabled={isCalculating || totalLocations === 0}
|
|
className="bg-indigo-600 hover:bg-indigo-700 text-white gap-2 px-6"
|
|
>
|
|
{isCalculating ? (
|
|
<>
|
|
<Loader2 className="h-4 w-4 animate-spin" />
|
|
산출 중...
|
|
</>
|
|
) : (
|
|
<>
|
|
<Calculator className="h-4 w-4" />
|
|
견적서 산출
|
|
</>
|
|
)}
|
|
</Button>
|
|
)}
|
|
|
|
{/* 미리보기 */}
|
|
<Button
|
|
onClick={onPreview}
|
|
disabled={totalLocations === 0}
|
|
variant="outline"
|
|
className="gap-2 px-6"
|
|
>
|
|
<Eye className="h-4 w-4" />
|
|
미리보기
|
|
</Button>
|
|
|
|
{/* 수정 - view 모드에서만 표시 */}
|
|
{isViewMode && onEdit && (
|
|
<Button
|
|
onClick={onEdit}
|
|
variant="outline"
|
|
className="gap-2 px-6"
|
|
>
|
|
<Pencil className="h-4 w-4" />
|
|
수정
|
|
</Button>
|
|
)}
|
|
|
|
{/* 임시저장 - edit 모드에서만 표시 */}
|
|
{!isViewMode && (
|
|
<Button
|
|
onClick={onSaveTemporary}
|
|
disabled={isSaving}
|
|
className="bg-slate-500 hover:bg-slate-600 text-white gap-2 px-6"
|
|
>
|
|
{isSaving ? (
|
|
<Loader2 className="h-4 w-4 animate-spin" />
|
|
) : (
|
|
<Save className="h-4 w-4" />
|
|
)}
|
|
임시저장
|
|
</Button>
|
|
)}
|
|
|
|
{/* 최종저장 - 양쪽 모드에서 표시 (final 상태가 아닐 때만) */}
|
|
{status !== "final" && (
|
|
<Button
|
|
onClick={onSaveFinal}
|
|
disabled={isSaving || totalAmount === 0}
|
|
className="bg-blue-600 hover:bg-blue-700 text-white gap-2 px-6"
|
|
>
|
|
{isSaving ? (
|
|
<Loader2 className="h-4 w-4 animate-spin" />
|
|
) : (
|
|
<Check className="h-4 w-4" />
|
|
)}
|
|
최종저장
|
|
</Button>
|
|
)}
|
|
|
|
{/* 수주등록 - final 상태일 때 표시 */}
|
|
{status === "final" && onOrderRegister && (
|
|
<Button
|
|
onClick={onOrderRegister}
|
|
className="bg-green-600 hover:bg-green-700 text-white gap-2 px-6"
|
|
>
|
|
<ClipboardList className="h-4 w-4" />
|
|
수주등록
|
|
</Button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
} |